Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions backend/decky_loader/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,12 @@ async def uninstall_plugin(self, name: str):
# logger.debug("current plugins: %s", snapshot_string)

if name in self.plugins:
logger.debug("Plugin %s was found", name)
plugin_display = self.plugins[name].get_display_name()
logger.debug(f"Plugin {plugin_display} was found")
await self.plugins[name].stop(uninstall=True)
logger.debug("Plugin %s was stopped", name)
logger.debug(f"Plugin {plugin_display} was stopped")
del self.plugins[name]
logger.debug("Plugin %s was removed from the dictionary", name)
logger.debug(f"Plugin {plugin_display} was removed from the dictionary")
self.cleanup_plugin_settings(name)
logger.debug("removing files %s" % str(name))
rmtree(plugin_dir)
Expand Down
12 changes: 6 additions & 6 deletions backend/decky_loader/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,16 @@ async def plugin_emitted_event(event: str, args: Any):
return
if plugin.name in self.plugins:
if not "debug" in plugin.flags and refresh:
self.logger.info(f"Plugin {plugin.name} is already loaded and has requested to not be re-loaded")
self.logger.info(f"Plugin {plugin.get_display_name()} is already loaded and has requested to not be re-loaded")
return
else:
await self.plugins[plugin.name].stop()
self.plugins.pop(plugin.name, None)
if plugin.passive:
self.logger.info(f"Plugin {plugin.name} is passive")
self.logger.info(f"Plugin {plugin.get_display_name()} is passive")

self.plugins[plugin.name] = plugin.start()
self.logger.info(f"Loaded {plugin.name}")
self.logger.info(f"Loaded {plugin.get_display_name()}")
if not batch:
self.loop.create_task(self.dispatch_plugin(plugin.name, plugin.version, plugin.load_type))
except Exception as e:
Expand Down Expand Up @@ -208,7 +208,7 @@ async def handle_plugin_method_call_legacy(self, plugin_name: str, method_name:
plugin = self.plugins[plugin_name]
try:
if method_name.startswith("_"):
raise RuntimeError(f"Plugin {plugin.name} tried to call private method {method_name}")
raise RuntimeError(f"Plugin {plugin.get_display_name()} tried to call private method {method_name}")
res["result"] = await plugin.execute_legacy_method(method_name, kwargs)
res["success"] = True
except Exception as e:
Expand All @@ -220,10 +220,10 @@ async def handle_plugin_method_call(self, plugin_name: str, method_name: str, *a
plugin = self.plugins[plugin_name]
try:
if method_name.startswith("_"):
raise RuntimeError(f"Plugin {plugin.name} tried to call private method {method_name}")
raise RuntimeError(f"Plugin {plugin.get_display_name()} tried to call private method {method_name}")
result = await plugin.execute_method(method_name, *args)
except Exception as e:
self.logger.error(f"Method {method_name} of plugin {plugin.name} failed with the following exception:\n{format_exc()}")
self.logger.error(f"Method {method_name} of plugin {plugin.get_display_name()} failed with the following exception:\n{format_exc()}")
raise e # throw again to pass the error to the frontend
return result

Expand Down
19 changes: 13 additions & 6 deletions backend/decky_loader/plugin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ def __init__(self, file: str, plugin_directory: str, plugin_path: str, emit_call
def __str__(self) -> str:
return self.name

def get_display_name(self) -> str:
"""Returns plugin name with version if available, formatted for logging."""
if self.version:
return f"{self.name} (v{self.version})"

return self.name

async def _response_listener(self):
while self._socket.active:
try:
Expand All @@ -92,7 +99,7 @@ async def _response_listener(self):
elif res["type"] == SocketMessageType.RESPONSE.value:
self._method_call_requests.pop(res["id"]).set_result(res)
except CancelledError:
self.log.info(f"Stopping response listener for {self.name}")
self.log.info(f"Stopping response listener for {self.get_display_name()}")
await self._socket.close_socket_connection()
raise
except:
Expand All @@ -101,7 +108,7 @@ async def _response_listener(self):
async def execute_legacy_method(self, method_name: str, kwargs: Dict[Any, Any]):
if not self.legacy_method_warning:
self.legacy_method_warning = True
self.log.warning(f"Plugin {self.name} is using legacy method calls. This will be removed in a future release.")
self.log.warning(f"Plugin {self.get_display_name()} is using legacy method calls. This will be removed in a future release.")
if self.passive:
raise RuntimeError("This plugin is passive (aka does not implement main.py)")

Expand Down Expand Up @@ -136,7 +143,7 @@ async def stop(self, uninstall: bool = False):
start_time = time()
if self.passive:
return
self.log.info(f"Shutting down {self.name}")
self.log.info(f"Shutting down {self.get_display_name()}")

pending: set[Task[None]] | None = None;

Expand All @@ -156,16 +163,16 @@ async def stop(self, uninstall: bool = False):
for pending_task in pending:
pending_task.cancel()

self.log.info(f"Plugin {self.name} has been stopped in {time() - start_time:.1f}s")
self.log.info(f"Plugin {self.get_display_name()} has been stopped in {time() - start_time:.1f}s")
except Exception as e:
self.log.error(f"Error during shutdown for plugin {self.name}: {str(e)}\n{format_exc()}")
self.log.error(f"Error during shutdown for plugin {self.get_display_name()}: {str(e)}\n{format_exc()}")

async def kill_if_still_running(self):
start_time = time()
while self.proc and self.proc.is_alive():
elapsed_time = time() - start_time
if elapsed_time >= 5:
self.log.warning(f"Plugin {self.name} still alive 5 seconds after stop request! Sending SIGKILL!")
self.log.warning(f"Plugin {self.get_display_name()} still alive 5 seconds after stop request! Sending SIGKILL!")
self.terminate(True)
await sleep(0.1)

Expand Down
29 changes: 18 additions & 11 deletions backend/decky_loader/plugin/sandboxed_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ def __init__(self,

self.log = getLogger("sandboxed_plugin")

def get_display_name(self) -> str:
"""Returns plugin name with version if available, formatted for logging."""
if self.version:
return f"{self.name} (v{self.version})"

return self.name

def initialize(self, socket: LocalSocket):
self._socket = socket

Expand Down Expand Up @@ -125,51 +132,51 @@ async def emit(event: str, *args: Any) -> None:
get_event_loop().create_task(self.Plugin._main(self.Plugin))
get_event_loop().create_task(socket.setup_server(self.on_new_message))
except:
self.log.error("Failed to start " + self.name + "!\n" + format_exc())
self.log.error(f"Failed to start {self.get_display_name()}!\n{format_exc()}")
sys.exit(0)
try:
get_event_loop().run_forever()
except SystemExit:
pass
except:
self.log.error("Loop exited for " + self.name + "!\n" + format_exc())
self.log.error(f"Loop exited for {self.get_display_name()}!\n{format_exc()}")
finally:
get_event_loop().close()

async def _unload(self):
try:
self.log.info("Attempting to unload with plugin " + self.name + "'s \"_unload\" function.\n")
self.log.info(f"Attempting to unload with plugin {self.get_display_name()}'s \"_unload\" function.\n")
if hasattr(self.Plugin, "_unload"):
if self.api_version > 0:
await self.Plugin._unload()
else:
await self.Plugin._unload(self.Plugin)
self.log.info("Unloaded " + self.name + "\n")
self.log.info(f"Unloaded {self.get_display_name()}\n")
else:
self.log.info("Could not find \"_unload\" in " + self.name + "'s main.py" + "\n")
self.log.info(f"Could not find \"_unload\" in {self.get_display_name()}'s main.py\n")
except:
self.log.error("Failed to unload " + self.name + "!\n" + format_exc())
self.log.error(f"Failed to unload {self.get_display_name()}!\n{format_exc()}")
pass

async def _uninstall(self):
try:
self.log.info("Attempting to uninstall with plugin " + self.name + "'s \"_uninstall\" function.\n")
self.log.info(f"Attempting to uninstall with plugin {self.get_display_name()}'s \"_uninstall\" function.\n")
if hasattr(self.Plugin, "_uninstall"):
if self.api_version > 0:
await self.Plugin._uninstall()
else:
await self.Plugin._uninstall(self.Plugin)
self.log.info("Uninstalled " + self.name + "\n")
self.log.info(f"Uninstalled {self.get_display_name()}\n")
else:
self.log.info("Could not find \"_uninstall\" in " + self.name + "'s main.py" + "\n")
self.log.info(f"Could not find \"_uninstall\" in {self.get_display_name()}'s main.py\n")
except:
self.log.error("Failed to uninstall " + self.name + "!\n" + format_exc())
self.log.error(f"Failed to uninstall {self.get_display_name()}!\n{format_exc()}")
pass

async def shutdown(self):
if not self.shutdown_running:
self.shutdown_running = True
self.log.info(f"Calling Loader unload function for {self.name}.")
self.log.info(f"Calling Loader unload function for {self.get_display_name()}.")
await self._unload()

if self.uninstalling:
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/settings/pages/plugin_list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
requestPluginInstall,
} from '../../../../store';
import { useSetting } from '../../../../utils/hooks/useSetting';
import { getPluginDisplayName } from '../../../../utils/pluginHelpers';
import { useDeckyState } from '../../../DeckyState';
import PluginListLabel from './PluginListLabel';

Expand Down Expand Up @@ -69,7 +70,7 @@ function PluginInteractables(props: { entry: ReorderableEntry<PluginTableData> }
try {
await reloadPluginBackend(name);
} catch (err) {
console.error('Error Reloading Plugin Backend', err);
console.error(`Error Reloading Plugin Backend for ${getPluginDisplayName(name, version)}`, err);
}
}}
>
Expand Down
19 changes: 11 additions & 8 deletions frontend/src/plugin-loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { checkForPluginUpdates } from './store';
import TabsHook from './tabs-hook';
import Toaster from './toaster';
import { getVersionInfo } from './updater';
import { getPluginDisplayName } from './utils/pluginHelpers';
import { getSetting, setSetting } from './utils/settings';
import TranslationHelper, { TranslationClass } from './utils/TranslationHelper';

Expand Down Expand Up @@ -343,7 +344,7 @@ class PluginLoader extends Logger {

public dismountAll() {
for (const plugin of this.plugins) {
this.log(`Dismounting ${plugin.name}`);
this.log(`Dismounting ${getPluginDisplayName(plugin.name, plugin.version)}`);
plugin.onDismount?.();
}
}
Expand Down Expand Up @@ -403,14 +404,14 @@ class PluginLoader extends Logger {
timeoutMS?: number,
) {
if (useQueue && this.reloadLock) {
this.log('Reload currently in progress, adding to queue', name);
this.log(`Reload currently in progress, adding ${getPluginDisplayName(name, version)} to queue`);
this.pluginReloadQueue.push({ name, version: version, loadType });
return;
}

try {
if (useQueue) this.reloadLock = true;
this.log(`Trying to load ${name}`);
this.log(`Trying to load ${getPluginDisplayName(name, version)}`);

this.unloadPlugin(name, true);
const startTime = performance.now();
Expand All @@ -420,7 +421,7 @@ class PluginLoader extends Logger {

this.deckyState.setDisabledPlugins(this.deckyState.publicState().disabledPlugins.filter((d) => d.name !== name));
this.deckyState.setPlugins(this.plugins);
this.log(`Loaded ${name} in ${endTime - startTime}ms`);
this.log(`Loaded ${getPluginDisplayName(name, version)} in ${endTime - startTime}ms`);
} catch (e) {
throw e;
} finally {
Expand Down Expand Up @@ -502,16 +503,17 @@ class PluginLoader extends Logger {
version: version,
loadType,
});
} else throw new Error(`${name} frontend_bundle not OK`);
} else throw new Error(`${getPluginDisplayName(name, version)} frontend_bundle not OK`);
break;

default:
throw new Error(`${name} has no defined loadType.`);
throw new Error(`${getPluginDisplayName(name, version)} has no defined loadType.`);
}
} catch (e) {
if (e === timeoutException) throw timeoutException;

this.error('Error loading plugin ' + name, e);
this.error(`Error loading plugin ${getPluginDisplayName(name, version)}`, e);

const TheError: FC<{}> = () => (
<PanelSection>
<PanelSectionRow>
Expand Down Expand Up @@ -731,7 +733,8 @@ class PluginLoader extends Logger {
backendAPI.useQuickAccessVisible = useQuickAccessVisible;
}

this.debug(`${pluginName} connected to loader API.`);
// Note: version info not available at connection time
this.debug(`Plugin ${pluginName} connected to loader API.`);
return backendAPI;
},
};
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/utils/pluginHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Returns a formatted display name for a plugin with version if available
* @param name - The plugin name
* @param version - The optional plugin version
*
* @returns Formatted string like "PluginName (v1.2.3)" or just "PluginName" if version is undefined
*/
export function getPluginDisplayName(name: string, version?: string): string {
if (version) {
return `${name} (v${version})`;
}

return name;
}
Loading