summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbotato <63275405+botatooo@users.noreply.github.com>2022-07-01 23:43:17 +0000
committerGitHub <noreply@github.com>2022-07-01 16:43:17 -0700
commit4daf028e7acb49932e274ad5c32cd98045c7474a (patch)
treefa75d035d90d30d3cb7e2ecd17cb3f8e8f67b168
parent934a50f683f579695292f88437d91d4fce2f4edc (diff)
downloaddecky-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.py34
-rw-r--r--frontend/src/components/settings/index.tsx6
-rw-r--r--frontend/src/components/settings/pages/PluginList.tsx32
-rw-r--r--frontend/src/plugin-loader.tsx41
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_', ''));