320 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			320 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
// The plugin manager is a singleton class that manages the plugins.
 | 
						|
// 1. It merge metadata and the desc of plugins.
 | 
						|
 | 
						|
import 'dart:convert';
 | 
						|
import 'dart:collection';
 | 
						|
import 'package:flutter/material.dart';
 | 
						|
 | 
						|
const String kValueTrue = '1';
 | 
						|
const String kValueFalse = '0';
 | 
						|
 | 
						|
class ConfigItem {
 | 
						|
  String key;
 | 
						|
  String description;
 | 
						|
  String defaultValue;
 | 
						|
 | 
						|
  ConfigItem(this.key, this.defaultValue, this.description);
 | 
						|
  ConfigItem.fromJson(Map<String, dynamic> json)
 | 
						|
      : key = json['key'] ?? '',
 | 
						|
        description = json['description'] ?? '',
 | 
						|
        defaultValue = json['default'] ?? '';
 | 
						|
 | 
						|
  static String get trueValue => kValueTrue;
 | 
						|
  static String get falseValue => kValueFalse;
 | 
						|
  static bool isTrue(String value) => value == kValueTrue;
 | 
						|
  static bool isFalse(String value) => value == kValueFalse;
 | 
						|
}
 | 
						|
 | 
						|
class UiType {
 | 
						|
  String key;
 | 
						|
  String text;
 | 
						|
  String tooltip;
 | 
						|
  String action;
 | 
						|
 | 
						|
  UiType(this.key, this.text, this.tooltip, this.action);
 | 
						|
 | 
						|
  UiType.fromJson(Map<String, dynamic> json)
 | 
						|
      : key = json['key'] ?? '',
 | 
						|
        text = json['text'] ?? '',
 | 
						|
        tooltip = json['tooltip'] ?? '',
 | 
						|
        action = json['action'] ?? '';
 | 
						|
 | 
						|
  static UiType? create(Map<String, dynamic> json) {
 | 
						|
    if (json['t'] == 'Button') {
 | 
						|
      return UiButton.fromJson(json['c']);
 | 
						|
    } else if (json['t'] == 'Checkbox') {
 | 
						|
      return UiCheckbox.fromJson(json['c']);
 | 
						|
    } else {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class UiButton extends UiType {
 | 
						|
  String icon;
 | 
						|
 | 
						|
  UiButton(
 | 
						|
      {required String key,
 | 
						|
      required String text,
 | 
						|
      required this.icon,
 | 
						|
      required String tooltip,
 | 
						|
      required String action})
 | 
						|
      : super(key, text, tooltip, action);
 | 
						|
 | 
						|
  UiButton.fromJson(Map<String, dynamic> json)
 | 
						|
      : icon = json['icon'] ?? '',
 | 
						|
        super.fromJson(json);
 | 
						|
}
 | 
						|
 | 
						|
class UiCheckbox extends UiType {
 | 
						|
  UiCheckbox(
 | 
						|
      {required String key,
 | 
						|
      required String text,
 | 
						|
      required String tooltip,
 | 
						|
      required String action})
 | 
						|
      : super(key, text, tooltip, action);
 | 
						|
 | 
						|
  UiCheckbox.fromJson(Map<String, dynamic> json) : super.fromJson(json);
 | 
						|
}
 | 
						|
 | 
						|
class Location {
 | 
						|
  // location key:
 | 
						|
  //  host|main|settings|plugin
 | 
						|
  //  client|remote|toolbar|display
 | 
						|
  HashMap<String, UiType> ui;
 | 
						|
 | 
						|
  Location(this.ui);
 | 
						|
  Location.fromJson(Map<String, dynamic> json) : ui = HashMap() {
 | 
						|
    (json['ui'] as Map<String, dynamic>).forEach((key, value) {
 | 
						|
      var ui = UiType.create(value);
 | 
						|
      if (ui != null) {
 | 
						|
        this.ui[ui.key] = ui;
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class PublishInfo {
 | 
						|
  PublishInfo({
 | 
						|
    required this.lastReleased,
 | 
						|
    required this.published,
 | 
						|
  });
 | 
						|
 | 
						|
  final DateTime lastReleased;
 | 
						|
  final DateTime published;
 | 
						|
}
 | 
						|
 | 
						|
class Meta {
 | 
						|
  Meta({
 | 
						|
    required this.id,
 | 
						|
    required this.name,
 | 
						|
    required this.version,
 | 
						|
    required this.description,
 | 
						|
    required this.author,
 | 
						|
    required this.home,
 | 
						|
    required this.license,
 | 
						|
    required this.publishInfo,
 | 
						|
    required this.source,
 | 
						|
  });
 | 
						|
 | 
						|
  final String id;
 | 
						|
  final String name;
 | 
						|
  final String version;
 | 
						|
  final String description;
 | 
						|
  final String author;
 | 
						|
  final String home;
 | 
						|
  final String license;
 | 
						|
  final PublishInfo publishInfo;
 | 
						|
  final String source;
 | 
						|
}
 | 
						|
 | 
						|
class SourceInfo {
 | 
						|
  String name; // 1. RustDesk github 2. Local
 | 
						|
  String url;
 | 
						|
  String description;
 | 
						|
 | 
						|
  SourceInfo({
 | 
						|
    required this.name,
 | 
						|
    required this.url,
 | 
						|
    required this.description,
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
class PluginInfo with ChangeNotifier {
 | 
						|
  SourceInfo sourceInfo;
 | 
						|
  Meta meta;
 | 
						|
  String installedVersion; // It is empty if not installed.
 | 
						|
  String failedMsg;
 | 
						|
  String invalidReason; // It is empty if valid.
 | 
						|
 | 
						|
  PluginInfo({
 | 
						|
    required this.sourceInfo,
 | 
						|
    required this.meta,
 | 
						|
    required this.installedVersion,
 | 
						|
    required this.invalidReason,
 | 
						|
    this.failedMsg = '',
 | 
						|
  });
 | 
						|
 | 
						|
  bool get installed => installedVersion.isNotEmpty;
 | 
						|
  bool get needUpdate => installed && installedVersion != meta.version;
 | 
						|
 | 
						|
  void setInstall(String msg) {
 | 
						|
    if (msg == "finished") {
 | 
						|
      msg = '';
 | 
						|
    }
 | 
						|
    failedMsg = msg;
 | 
						|
    if (msg.isEmpty) {
 | 
						|
      installedVersion = meta.version;
 | 
						|
    }
 | 
						|
    notifyListeners();
 | 
						|
  }
 | 
						|
 | 
						|
  void setUninstall(String msg) {
 | 
						|
    failedMsg = msg;
 | 
						|
    if (msg.isEmpty) {
 | 
						|
      installedVersion = '';
 | 
						|
    }
 | 
						|
    notifyListeners();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class PluginManager with ChangeNotifier {
 | 
						|
  String failedReason = ''; // The reason of failed to load plugins.
 | 
						|
  final List<PluginInfo> _plugins = [];
 | 
						|
 | 
						|
  PluginManager._();
 | 
						|
  static final PluginManager _instance = PluginManager._();
 | 
						|
  static PluginManager get instance => _instance;
 | 
						|
 | 
						|
  List<PluginInfo> get plugins => _plugins;
 | 
						|
 | 
						|
  PluginInfo? getPlugin(String id) {
 | 
						|
    for (var p in _plugins) {
 | 
						|
      if (p.meta.id == id) {
 | 
						|
        return p;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
  }
 | 
						|
 | 
						|
  void handleEvent(Map<String, dynamic> evt) {
 | 
						|
    if (evt['plugin_list'] != null) {
 | 
						|
      _handlePluginList(evt['plugin_list']);
 | 
						|
    } else if (evt['plugin_install'] != null && evt['id'] != null) {
 | 
						|
      _handlePluginInstall(evt['id'], evt['plugin_install']);
 | 
						|
    } else if (evt['plugin_uninstall'] != null && evt['id'] != null) {
 | 
						|
      _handlePluginUninstall(evt['id'], evt['plugin_uninstall']);
 | 
						|
    } else {
 | 
						|
      debugPrint('Failed to handle manager event: $evt');
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  void _sortPlugins() {
 | 
						|
    plugins.sort((a, b) {
 | 
						|
      if (a.installed) {
 | 
						|
        return -1;
 | 
						|
      } else if (b.installed) {
 | 
						|
        return 1;
 | 
						|
      } else {
 | 
						|
        return 0;
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  void _handlePluginList(String pluginList) {
 | 
						|
    _plugins.clear();
 | 
						|
    try {
 | 
						|
      for (var p in json.decode(pluginList) as List<dynamic>) {
 | 
						|
        final plugin = _getPluginFromEvent(p);
 | 
						|
        if (plugin == null) {
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
        _plugins.add(plugin);
 | 
						|
      }
 | 
						|
    } catch (e) {
 | 
						|
      debugPrint('Failed to decode $e,  plugin list \'$pluginList\'');
 | 
						|
    }
 | 
						|
    _sortPlugins();
 | 
						|
    notifyListeners();
 | 
						|
  }
 | 
						|
 | 
						|
  void _handlePluginInstall(String id, String msg) {
 | 
						|
    debugPrint('Plugin \'$id\' install msg $msg');
 | 
						|
    for (var i = 0; i < _plugins.length; i++) {
 | 
						|
      if (_plugins[i].meta.id == id) {
 | 
						|
        _plugins[i].setInstall(msg);
 | 
						|
        _sortPlugins();
 | 
						|
        notifyListeners();
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  void _handlePluginUninstall(String id, String msg) {
 | 
						|
    debugPrint('Plugin \'$id\' uninstall msg $msg');
 | 
						|
    for (var i = 0; i < _plugins.length; i++) {
 | 
						|
      if (_plugins[i].meta.id == id) {
 | 
						|
        _plugins[i].setUninstall(msg);
 | 
						|
        _sortPlugins();
 | 
						|
        notifyListeners();
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  PluginInfo? _getPluginFromEvent(Map<String, dynamic> evt) {
 | 
						|
    final s = evt['source'];
 | 
						|
    assert(s != null, 'Source is null');
 | 
						|
    if (s == null) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
    final source = SourceInfo(
 | 
						|
      name: s['name'],
 | 
						|
      url: s['url'] ?? '',
 | 
						|
      description: s['description'] ?? '',
 | 
						|
    );
 | 
						|
 | 
						|
    final m = evt['meta'];
 | 
						|
    assert(m != null, 'Meta is null');
 | 
						|
    if (m == null) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    late DateTime lastReleased;
 | 
						|
    late DateTime published;
 | 
						|
    try {
 | 
						|
      lastReleased = DateTime.parse(
 | 
						|
          m['publish_info']?['last_released'] ?? '1970-01-01T00+00:00');
 | 
						|
    } catch (e) {
 | 
						|
      lastReleased = DateTime.utc(1970);
 | 
						|
    }
 | 
						|
    try {
 | 
						|
      published = DateTime.parse(
 | 
						|
          m['publish_info']?['published'] ?? '1970-01-01T00+00:00');
 | 
						|
    } catch (e) {
 | 
						|
      published = DateTime.utc(1970);
 | 
						|
    }
 | 
						|
 | 
						|
    final meta = Meta(
 | 
						|
      id: m['id'],
 | 
						|
      name: m['name'],
 | 
						|
      version: m['version'],
 | 
						|
      description: m['description'] ?? '',
 | 
						|
      author: m['author'],
 | 
						|
      home: m['home'] ?? '',
 | 
						|
      license: m['license'] ?? '',
 | 
						|
      source: m['source'] ?? '',
 | 
						|
      publishInfo:
 | 
						|
          PublishInfo(lastReleased: lastReleased, published: published),
 | 
						|
    );
 | 
						|
    return PluginInfo(
 | 
						|
      sourceInfo: source,
 | 
						|
      meta: meta,
 | 
						|
      installedVersion: evt['installed_version'],
 | 
						|
      invalidReason: evt['invalid_reason'] ?? '',
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
PluginManager get pluginManager => PluginManager.instance;
 |