mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-06-13 04:05:04 +03:00
fix plugin uninstalls
This commit is contained in:
@@ -147,8 +147,9 @@ class PluginBrowser:
|
||||
except Exception as e:
|
||||
logger.error(f"Plugin {name} in {plugin_dir} was not uninstalled")
|
||||
logger.error(f"Error at {str(e)}", exc_info=e)
|
||||
if self.loader.watcher:
|
||||
self.loader.watcher.disabled = False
|
||||
finally:
|
||||
if self.loader.watcher:
|
||||
self.loader.watcher.disabled = False
|
||||
|
||||
async def _install(self, artifact: str, name: str, version: str, hash: str):
|
||||
await self.loader.ws.emit("loader/plugin_download_start", name)
|
||||
|
||||
@@ -88,6 +88,8 @@ class UnixSocket:
|
||||
except asyncio.IncompleteReadError as err:
|
||||
line.extend(err.partial)
|
||||
break
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ class PluginWrapper:
|
||||
def start(self):
|
||||
if self.passive:
|
||||
return self
|
||||
self.proc = Process(target=self.sandboxed_plugin.initialize, args=[self._socket], daemon=True)
|
||||
self.proc = Process(target=self.sandboxed_plugin.initialize, args=[self._socket])
|
||||
self.proc.start()
|
||||
self._listener_task = create_task(self._response_listener())
|
||||
return self
|
||||
@@ -121,9 +121,11 @@ class PluginWrapper:
|
||||
if hasattr(self, "_socket"):
|
||||
await self._socket.write_single_line(dumps({ "stop": True, "uninstall": uninstall }, ensure_ascii=False))
|
||||
await self._socket.close_socket_connection()
|
||||
if self.proc:
|
||||
self.proc.join()
|
||||
await self.kill_if_still_running()
|
||||
if hasattr(self, "_listener_task"):
|
||||
self._listener_task.cancel()
|
||||
await self.kill_if_still_running()
|
||||
|
||||
async def kill_if_still_running(self):
|
||||
time = 0
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from os import path, environ
|
||||
from signal import SIG_IGN, SIGINT, SIGTERM, signal
|
||||
from signal import SIG_IGN, SIGINT, SIGTERM, getsignal, signal
|
||||
from importlib.util import module_from_spec, spec_from_file_location
|
||||
from json import dumps, loads
|
||||
from logging import getLogger
|
||||
from sys import exit, path as syspath, modules as sysmodules
|
||||
from traceback import format_exc
|
||||
from asyncio import (get_event_loop, new_event_loop,
|
||||
set_event_loop, sleep)
|
||||
set_event_loop)
|
||||
from setproctitle import setproctitle, setthreadtitle
|
||||
|
||||
from .messages import SocketResponseDict, SocketMessageType
|
||||
@@ -19,6 +19,8 @@ from typing import List, TypeVar, Any
|
||||
|
||||
DataType = TypeVar("DataType")
|
||||
|
||||
original_term_handler = getsignal(SIGTERM)
|
||||
|
||||
class SandboxedPlugin:
|
||||
def __init__(self,
|
||||
name: str,
|
||||
@@ -46,6 +48,8 @@ class SandboxedPlugin:
|
||||
self._socket = socket
|
||||
|
||||
try:
|
||||
# Ignore signals meant for parent Process
|
||||
# TODO SURELY there's a better way to do this.
|
||||
signal(SIGINT, SIG_IGN)
|
||||
signal(SIGTERM, SIG_IGN)
|
||||
|
||||
@@ -120,7 +124,14 @@ class SandboxedPlugin:
|
||||
except:
|
||||
self.log.error("Failed to start " + self.name + "!\n" + format_exc())
|
||||
exit(0)
|
||||
get_event_loop().run_forever()
|
||||
try:
|
||||
get_event_loop().run_forever()
|
||||
except SystemExit:
|
||||
pass
|
||||
except:
|
||||
self.log.error("Loop exited for " + self.name + "!\n" + format_exc())
|
||||
finally:
|
||||
get_event_loop().close()
|
||||
|
||||
async def _unload(self):
|
||||
try:
|
||||
@@ -156,6 +167,8 @@ class SandboxedPlugin:
|
||||
data = loads(message)
|
||||
|
||||
if "stop" in data:
|
||||
# Incase the loader needs to terminate our process soon
|
||||
signal(SIGTERM, original_term_handler)
|
||||
self.log.info(f"Calling Loader unload function for {self.name}.")
|
||||
await self._unload()
|
||||
|
||||
@@ -163,10 +176,10 @@ class SandboxedPlugin:
|
||||
self.log.info("Calling Loader uninstall function.")
|
||||
await self._uninstall()
|
||||
|
||||
get_event_loop().stop()
|
||||
while get_event_loop().is_running():
|
||||
await sleep(0.1)
|
||||
get_event_loop().close()
|
||||
self.log.debug("Stopping event loop")
|
||||
|
||||
loop = get_event_loop()
|
||||
loop.call_soon_threadsafe(loop.stop)
|
||||
exit(0)
|
||||
|
||||
d: SocketResponseDict = {"type": SocketMessageType.RESPONSE, "res": None, "success": True, "id": data["id"]}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from logging import getLogger
|
||||
|
||||
from asyncio import AbstractEventLoop, create_task
|
||||
from asyncio import AbstractEventLoop
|
||||
|
||||
from aiohttp import WSCloseCode, WSMsgType, WSMessage
|
||||
from aiohttp.web import Application, WebSocketResponse, Request, Response, get
|
||||
@@ -114,10 +114,10 @@ class WSRouter:
|
||||
# do stuff with the message
|
||||
if data["route"] in self.routes:
|
||||
self.logger.debug(f'Started PY call {data["route"]} ID {data["id"]}')
|
||||
create_task(self._call_route(data["route"], data["args"], data["id"]))
|
||||
self.loop.create_task(self._call_route(data["route"], data["args"], data["id"]))
|
||||
else:
|
||||
error = {"error":f'Route {data["route"]} does not exist.', "name": "RouteNotFoundError", "traceback": None}
|
||||
create_task(self.write({"type": MessageType.ERROR.value, "id": data["id"], "error": error}))
|
||||
self.loop.create_task(self.write({"type": MessageType.ERROR.value, "id": data["id"], "error": error}))
|
||||
case _:
|
||||
self.logger.error("Unknown message type", data)
|
||||
finally:
|
||||
|
||||
@@ -58,13 +58,24 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
|
||||
await onCancel();
|
||||
}}
|
||||
strTitle={
|
||||
<div>
|
||||
<div style={{display: "flex", flexDirection: "row", alignItems: "center", width: "100%"}}>
|
||||
<TranslationHelper
|
||||
transClass={TranslationClass.PLUGIN_INSTALL_MODAL}
|
||||
transText="title"
|
||||
i18nArgs={{ artifact: artifact }}
|
||||
installType={installType}
|
||||
/>
|
||||
{loading && (
|
||||
<div style={{marginLeft: "auto"}}>
|
||||
<ProgressBarWithInfo
|
||||
layout="inline"
|
||||
|
||||
bottomSeparator="none"
|
||||
nProgress={percentage}
|
||||
sOperationText={downloadInfo}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
strOKButtonText={
|
||||
@@ -98,14 +109,6 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({
|
||||
installType={installType}
|
||||
/>
|
||||
</div>
|
||||
{loading && (
|
||||
<ProgressBarWithInfo
|
||||
layout="inline"
|
||||
bottomSeparator="none"
|
||||
nProgress={percentage}
|
||||
sOperationText={downloadInfo}
|
||||
/>
|
||||
)}
|
||||
{hash == 'False' && <span style={{ color: 'red' }}>{t('PluginInstallModal.no_hash')}</span>}
|
||||
</ConfirmModal>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ConfirmModal } from '@decky/ui';
|
||||
import { FC } from 'react';
|
||||
import { ConfirmModal, Spinner } from '@decky/ui';
|
||||
import { FC, useState } from 'react';
|
||||
|
||||
import { uninstallPlugin } from '../../plugin';
|
||||
|
||||
@@ -12,10 +12,12 @@ interface PluginUninstallModalProps {
|
||||
}
|
||||
|
||||
const PluginUninstallModal: FC<PluginUninstallModalProps> = ({ name, title, buttonText, description, closeModal }) => {
|
||||
const [uninstalling, setUninstalling] = useState<boolean>(false);
|
||||
return (
|
||||
<ConfirmModal
|
||||
closeModal={closeModal}
|
||||
onOK={async () => {
|
||||
setUninstalling(true);
|
||||
await uninstallPlugin(name);
|
||||
// uninstalling a plugin resets the hidden setting for it server-side
|
||||
// we invalidate here so if you re-install it, you won't have an out-of-date hidden filter
|
||||
@@ -23,7 +25,12 @@ const PluginUninstallModal: FC<PluginUninstallModalProps> = ({ name, title, butt
|
||||
await DeckyPluginLoader.hiddenPluginsService.invalidate();
|
||||
closeModal?.();
|
||||
}}
|
||||
strTitle={title}
|
||||
bOKDisabled={uninstalling}
|
||||
bCancelDisabled={uninstalling}
|
||||
strTitle={<div style={{display: "flex", flexDirection: "row", alignItems: "center", width: "100%"}}>
|
||||
{title}
|
||||
{uninstalling && <Spinner width="24px" height="24px" style={{marginLeft: "auto"}}/>}
|
||||
</div>}
|
||||
strOKButtonText={buttonText}
|
||||
>
|
||||
{description}
|
||||
|
||||
Reference in New Issue
Block a user