diff options
| author | botato <63275405+botatooo@users.noreply.github.com> | 2022-07-01 23:43:17 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-07-01 16:43:17 -0700 |
| commit | 4daf028e7acb49932e274ad5c32cd98045c7474a (patch) | |
| tree | fa75d035d90d30d3cb7e2ecd17cb3f8e8f67b168 | |
| parent | 934a50f683f579695292f88437d91d4fce2f4edc (diff) | |
| download | decky-loader-4daf028e7acb49932e274ad5c32cd98045c7474a.tar.gz decky-loader-4daf028e7acb49932e274ad5c32cd98045c7474a.zip | |
Uninstall functionality (#97)
* feat: POC uninstallation feature
* Fixes, placeholder
* bugfix: wrong function call
* add oncancel and change function called
* clean up plugin uninstall code
* bugfix, uninstall in store
* Limit scope of feature branch
* feat: PluginLoader.unloadPlugin
* problematic logs
| -rw-r--r-- | backend/browser.py | 34 | ||||
| -rw-r--r-- | frontend/src/components/settings/index.tsx | 6 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/PluginList.tsx | 32 | ||||
| -rw-r--r-- | frontend/src/plugin-loader.tsx | 41 |
4 files changed, 102 insertions, 11 deletions
diff --git a/backend/browser.py b/backend/browser.py index 37099e19..316d57a8 100644 --- a/backend/browser.py +++ b/backend/browser.py @@ -1,6 +1,6 @@ from injector import get_tab from logging import getLogger -from os import path, rename +from os import path, rename, listdir from shutil import rmtree from aiohttp import ClientSession, web from io import BytesIO @@ -11,6 +11,8 @@ from time import time from hashlib import sha256 from subprocess import Popen +import json + class PluginInstallContext: def __init__(self, artifact, name, version, hash) -> None: self.artifact = artifact @@ -25,7 +27,8 @@ class PluginBrowser: self.install_requests = {} server_instance.add_routes([ - web.post("/browser/install_plugin", self.install_plugin) + web.post("/browser/install_plugin", self.install_plugin), + web.post("/browser/uninstall_plugin", self.uninstall_plugin) ]) def _unzip_to_plugin_dir(self, zip, name, hash): @@ -39,8 +42,31 @@ class PluginBrowser: Popen(["chmod", "-R", "555", self.plugin_path]) return True + def find_plugin_folder(self, name): + for folder in listdir(self.plugin_path): + with open(path.join(self.plugin_path, folder, 'plugin.json'), 'r') as f: + plugin = json.load(f) + + if plugin['name'] == name: + return path.join(self.plugin_path, folder) + + async def uninstall_plugin(self, name): + tab = await get_tab("SP") + await tab.open_websocket() + + try: + if type(name) != str: + data = await name.post() + name = data.get("name") + await tab.evaluate_js(f"DeckyPluginLoader.unloadPlugin('{name}')") + rmtree(self.find_plugin_folder(name)) + except FileNotFoundError: + self.log.warning(f"Plugin {name} not installed, skipping uninstallation") + + return web.Response(text="Requested plugin uninstall") + async def _install(self, artifact, name, version, hash): - rmtree(path.join(self.plugin_path, name), ignore_errors=True) + self.uninstall_plugin(name) self.log.info(f"Installing {name} (Version: {version})") async with ClientSession() as client: self.log.debug(f"Fetching {artifact}") @@ -83,4 +109,4 @@ class PluginBrowser: await self._install(request.artifact, request.name, request.version, request.hash) def cancel_plugin_install(self, request_id): - self.install_requests.pop(request_id)
\ No newline at end of file + self.install_requests.pop(request_id) diff --git a/frontend/src/components/settings/index.tsx b/frontend/src/components/settings/index.tsx index d4799fa9..f9c84c7b 100644 --- a/frontend/src/components/settings/index.tsx +++ b/frontend/src/components/settings/index.tsx @@ -1,6 +1,7 @@ import { SidebarNavigation } from 'decky-frontend-lib'; import GeneralSettings from './pages/GeneralSettings'; +import PluginList from './pages/PluginList'; export default function SettingsPage() { return ( @@ -13,6 +14,11 @@ export default function SettingsPage() { content: <GeneralSettings />, route: '/decky/settings/general', }, + { + title: 'Plugins', + content: <PluginList />, + route: '/decky/settings/plugins', + }, ]} /> ); diff --git a/frontend/src/components/settings/pages/PluginList.tsx b/frontend/src/components/settings/pages/PluginList.tsx new file mode 100644 index 00000000..4fd2c063 --- /dev/null +++ b/frontend/src/components/settings/pages/PluginList.tsx @@ -0,0 +1,32 @@ +import { DialogButton, staticClasses } from 'decky-frontend-lib'; +import { FaTrash } from 'react-icons/fa'; + +export default function PluginList() { + const plugins = window.DeckyPluginLoader?.getPlugins(); + + if (plugins.length === 0) { + return ( + <div> + <p>No plugins installed</p> + </div> + ); + } + + return ( + <ul style={{ listStyleType: 'none' }}> + {window.DeckyPluginLoader?.getPlugins().map(({ name }) => ( + <li style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}> + <span>{name}</span> + <div className={staticClasses.Title} style={{ marginLeft: 'auto', boxShadow: 'none' }}> + <DialogButton + style={{ height: '40px', width: '40px', padding: '10px 12px' }} + onClick={() => window.DeckyPluginLoader.uninstall_plugin(name)} + > + <FaTrash /> + </DialogButton> + </div> + </li> + ))} + </ul> + ); +} diff --git a/frontend/src/plugin-loader.tsx b/frontend/src/plugin-loader.tsx index b3df0f28..fc48fdd9 100644 --- a/frontend/src/plugin-loader.tsx +++ b/frontend/src/plugin-loader.tsx @@ -47,6 +47,10 @@ class PluginLoader extends Logger { this.routerHook.addRoute('/decky/settings', () => <SettingsPage />); } + public getPlugins() { + return this.plugins; + } + public addPluginInstallPrompt(artifact: string, version: string, request_id: string, hash: string) { showModal( <ModalRoot @@ -66,6 +70,28 @@ class PluginLoader extends Logger { ); } + public uninstall_plugin(name: string) { + showModal( + <ModalRoot + onOK={async () => { + const formData = new FormData(); + formData.append('name', name); + await fetch('http://localhost:1337/browser/uninstall_plugin', { + method: 'POST', + body: formData, + }); + }} + onCancel={() => { + // do nothing + }} + > + <div className={staticClasses.Title} style={{ flexDirection: 'column' }}> + Uninstall {name}? + </div> + </ModalRoot>, + ); + } + public dismountAll() { for (const plugin of this.plugins) { this.log(`Dismounting ${plugin.name}`); @@ -78,6 +104,13 @@ class PluginLoader extends Logger { this.routerHook.removeRoute('/decky/settings'); } + public unloadPlugin(name: string) { + const plugin = this.plugins.find((plugin) => plugin.name === name || plugin.name === name.replace('$LEGACY_', '')); + plugin?.onDismount?.(); + this.plugins = this.plugins.filter((p) => p !== plugin); + this.deckyState.setPlugins(this.plugins); + } + public async importPlugin(name: string) { if (this.reloadLock) { this.log('Reload currently in progress, adding to queue', name); @@ -89,13 +122,7 @@ class PluginLoader extends Logger { this.reloadLock = true; this.log(`Trying to load ${name}`); - const oldPlugin = this.plugins.find( - (plugin) => plugin.name === name || plugin.name === name.replace('$LEGACY_', ''), - ); - if (oldPlugin) { - oldPlugin.onDismount?.(); - this.plugins = this.plugins.filter((plugin) => plugin !== oldPlugin); - } + this.unloadPlugin(name); if (name.startsWith('$LEGACY_')) { await this.importLegacyPlugin(name.replace('$LEGACY_', '')); |
