mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
PluginSystem: first pass on auto update
This commit is contained in:
@@ -458,6 +458,7 @@
|
||||
"add": "Add",
|
||||
"apply": "Apply",
|
||||
"cancel": "Cancel",
|
||||
"check-settings": "Check Settings for details",
|
||||
"close": "Close",
|
||||
"save": "Save"
|
||||
},
|
||||
@@ -1808,7 +1809,16 @@
|
||||
"uninstall-error": "Failed to uninstall: {error}",
|
||||
"uninstall-success": "Successfully uninstalled {plugin}",
|
||||
"uninstall.tooltip": "Uninstall plugin",
|
||||
"uninstalling": "Uninstalling {plugin}..."
|
||||
"uninstalling": "Uninstalling {plugin}...",
|
||||
"update": "Update",
|
||||
"update-all": "Update All ({count})",
|
||||
"update-all-success": "All plugins updated successfully",
|
||||
"update-available": "{count} update(s) available",
|
||||
"update-error": "Failed to update {plugin}: {error}",
|
||||
"update-incompatible": "Requires Noctalia v{version} or higher",
|
||||
"update-success": "Updated {plugin} to v{version}",
|
||||
"update-version": "v{current} → v{new}",
|
||||
"updating": "Updating {plugin}..."
|
||||
},
|
||||
"screen-recorder": {
|
||||
"audio": {
|
||||
|
||||
@@ -17,12 +17,23 @@ Singleton {
|
||||
signal availablePluginsUpdated
|
||||
signal allPluginsLoaded
|
||||
|
||||
// When available plugins are updated, check if we should perform update check
|
||||
onAvailablePluginsUpdated: {
|
||||
if (shouldCheckUpdatesAfterFetch && Object.keys(activeFetches).length === 0) {
|
||||
Logger.d("PluginService", "All registry fetches complete, performing update check");
|
||||
performUpdateCheck();
|
||||
}
|
||||
}
|
||||
|
||||
// Loaded plugin instances
|
||||
property var loadedPlugins: ({}) // { pluginId: { component, instance, api } }
|
||||
|
||||
// Available plugins from all sources (fetched from registries)
|
||||
property var availablePlugins: ([]) // Array of plugin metadata from all sources
|
||||
|
||||
// Plugin updates available: { pluginId: { currentVersion, availableVersion } }
|
||||
property var pluginUpdates: ({})
|
||||
|
||||
// Track active fetches
|
||||
property var activeFetches: ({})
|
||||
|
||||
@@ -131,6 +142,12 @@ Singleton {
|
||||
|
||||
// Refresh available plugins from all sources
|
||||
function refreshAvailablePlugins() {
|
||||
// If fetches are already in progress, don't start new ones
|
||||
if (Object.keys(activeFetches).length > 0) {
|
||||
Logger.d("PluginService", "Refresh already in progress, skipping duplicate refresh");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.i("PluginService", "Refreshing available plugins");
|
||||
root.availablePlugins = [];
|
||||
|
||||
@@ -837,6 +854,176 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
// Find available plugin by ID
|
||||
function findAvailablePlugin(pluginId) {
|
||||
for (var i = 0; i < root.availablePlugins.length; i++) {
|
||||
if (root.availablePlugins[i].id === pluginId) {
|
||||
return root.availablePlugins[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Internal flag to track if we should check for updates after registry fetch
|
||||
property bool shouldCheckUpdatesAfterFetch: false
|
||||
|
||||
// Check for plugin updates (call this after availablePlugins are loaded)
|
||||
function checkForUpdates() {
|
||||
Logger.i("PluginService", "Checking for plugin updates");
|
||||
|
||||
// If we have available plugins, check immediately regardless of active fetches
|
||||
if (root.availablePlugins.length > 0) {
|
||||
Logger.d("PluginService", "Available plugins already loaded, checking now");
|
||||
performUpdateCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
// No plugins yet - check if fetch is in progress
|
||||
if (Object.keys(activeFetches).length > 0) {
|
||||
Logger.d("PluginService", "Registry fetch in progress, will check after fetch completes");
|
||||
shouldCheckUpdatesAfterFetch = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// No plugins and no fetches - trigger refresh
|
||||
Logger.d("PluginService", "No available plugins yet, triggering refresh");
|
||||
shouldCheckUpdatesAfterFetch = true;
|
||||
refreshAvailablePlugins();
|
||||
}
|
||||
|
||||
// Perform the actual update check
|
||||
function performUpdateCheck() {
|
||||
var updates = {};
|
||||
var installedIds = PluginRegistry.getAllInstalledPluginIds();
|
||||
|
||||
Logger.d("PluginService", "Checking", installedIds.length, "installed plugins against", root.availablePlugins.length, "available plugins");
|
||||
|
||||
for (var i = 0; i < installedIds.length; i++) {
|
||||
var pluginId = installedIds[i];
|
||||
var installedManifest = PluginRegistry.getPluginManifest(pluginId);
|
||||
var availablePlugin = findAvailablePlugin(pluginId);
|
||||
|
||||
if (installedManifest && availablePlugin) {
|
||||
var currentVersion = installedManifest.version;
|
||||
var availableVersion = availablePlugin.version;
|
||||
|
||||
Logger.d("PluginService", "Comparing", pluginId + ":", currentVersion, "vs", availableVersion);
|
||||
|
||||
// Compare versions
|
||||
if (compareVersions(availableVersion, currentVersion) > 0) {
|
||||
updates[pluginId] = {
|
||||
currentVersion: currentVersion,
|
||||
availableVersion: availableVersion
|
||||
};
|
||||
Logger.i("PluginService", "Update available for", pluginId + ":", currentVersion, "→", availableVersion);
|
||||
}
|
||||
} else if (installedManifest && !availablePlugin) {
|
||||
Logger.d("PluginService", "Plugin", pluginId, "not found in available plugins (might be from disabled source)");
|
||||
}
|
||||
}
|
||||
|
||||
root.pluginUpdates = updates;
|
||||
var updateCount = Object.keys(updates).length;
|
||||
|
||||
if (updateCount > 0) {
|
||||
Logger.i("PluginService", updateCount, "plugin update(s) available");
|
||||
ToastService.showNotice(I18n.tr("settings.plugins.update-available", {
|
||||
"count": updateCount
|
||||
}), I18n.tr("common.check-settings"));
|
||||
} else {
|
||||
Logger.i("PluginService", "All plugins are up to date");
|
||||
}
|
||||
|
||||
shouldCheckUpdatesAfterFetch = false;
|
||||
}
|
||||
|
||||
// Simple version comparison (semantic versioning x.y.z)
|
||||
function compareVersions(a, b) {
|
||||
var aParts = a.split('.').map(function (x) {
|
||||
return parseInt(x) || 0;
|
||||
});
|
||||
var bParts = b.split('.').map(function (x) {
|
||||
return parseInt(x) || 0;
|
||||
});
|
||||
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var aNum = aParts[i] || 0;
|
||||
var bNum = bParts[i] || 0;
|
||||
if (aNum > bNum)
|
||||
return 1;
|
||||
if (aNum < bNum)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Update a plugin to the latest version
|
||||
function updatePlugin(pluginId, callback) {
|
||||
Logger.i("PluginService", "Updating plugin:", pluginId);
|
||||
|
||||
// Find available plugin metadata
|
||||
var availablePlugin = findAvailablePlugin(pluginId);
|
||||
if (!availablePlugin) {
|
||||
Logger.e("PluginService", "Plugin not found in available plugins:", pluginId);
|
||||
if (callback)
|
||||
callback(false, "Plugin not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check Noctalia compatibility
|
||||
if (availablePlugin.minNoctaliaVersion) {
|
||||
// Simple check: just warn, don't block (UpdateService would have more sophisticated logic)
|
||||
Logger.d("PluginService", "Plugin requires Noctalia v" + availablePlugin.minNoctaliaVersion);
|
||||
}
|
||||
|
||||
// Backup entire bar layout
|
||||
var barBackup = {
|
||||
left: JSON.parse(JSON.stringify(Settings.data.bar.widgets.left || [])),
|
||||
center: JSON.parse(JSON.stringify(Settings.data.bar.widgets.center || [])),
|
||||
right: JSON.parse(JSON.stringify(Settings.data.bar.widgets.right || []))
|
||||
};
|
||||
Logger.d("PluginService", "Backed up bar layout");
|
||||
|
||||
// Disable plugin (this removes widgets and unloads code)
|
||||
if (PluginRegistry.isPluginEnabled(pluginId)) {
|
||||
disablePlugin(pluginId);
|
||||
}
|
||||
|
||||
// Now install the new version (reuse installPlugin logic)
|
||||
installPlugin(availablePlugin, function (success, error) {
|
||||
if (success) {
|
||||
Logger.i("PluginService", "Plugin updated successfully:", pluginId);
|
||||
|
||||
// Restore bar layout
|
||||
Settings.data.bar.widgets.left = barBackup.left;
|
||||
Settings.data.bar.widgets.center = barBackup.center;
|
||||
Settings.data.bar.widgets.right = barBackup.right;
|
||||
Logger.d("PluginService", "Restored bar layout");
|
||||
|
||||
// Re-enable the plugin
|
||||
enablePlugin(pluginId);
|
||||
|
||||
// Remove from updates list
|
||||
var updates = Object.assign({}, root.pluginUpdates);
|
||||
delete updates[pluginId];
|
||||
root.pluginUpdates = updates;
|
||||
|
||||
if (callback)
|
||||
callback(true, null);
|
||||
} else {
|
||||
Logger.e("PluginService", "Failed to update plugin:", pluginId, error);
|
||||
|
||||
// Restore bar layout even on failure
|
||||
Settings.data.bar.widgets.left = barBackup.left;
|
||||
Settings.data.bar.widgets.center = barBackup.center;
|
||||
Settings.data.bar.widgets.right = barBackup.right;
|
||||
|
||||
if (callback)
|
||||
callback(false, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Get plugin API for a loaded plugin
|
||||
function getPluginAPI(pluginId) {
|
||||
return root.loadedPlugins[pluginId]?.api || null;
|
||||
|
||||
@@ -128,6 +128,20 @@ ShellRoot {
|
||||
PluginService.pluginContainer = pluginContainer;
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for when available plugins are fetched, then check for updates
|
||||
Connections {
|
||||
target: PluginService
|
||||
property bool hasCheckedOnStartup: false
|
||||
|
||||
function onAvailablePluginsUpdated() {
|
||||
// Only check once on startup, after first plugin list is fetched
|
||||
if (!hasCheckedOnStartup && Object.keys(PluginService.activeFetches).length === 0) {
|
||||
hasCheckedOnStartup = true;
|
||||
PluginService.checkForUpdates();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user