// 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 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 json) : key = json['key'] ?? '', text = json['text'] ?? '', tooltip = json['tooltip'] ?? '', action = json['action'] ?? ''; static UiType? create(Map 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 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 json) : super.fromJson(json); } class Location { // location key: // host|main|settings|plugin // client|remote|toolbar|display HashMap ui; Location(this.ui); Location.fromJson(Map json) : ui = HashMap() { (json['ui'] as Map).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 _plugins = []; PluginManager._(); static final PluginManager _instance = PluginManager._(); static PluginManager get instance => _instance; List get plugins => _plugins; PluginInfo? getPlugin(String id) { for (var p in _plugins) { if (p.meta.id == id) { return p; } } return null; } void handleEvent(Map 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) { 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 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;