Refractor plugin backend (#111)

* refractor uninstall plugin backend

* refractor plugin installation method

* Change formatting in browser.py

* Manually format main.py

* Manually format utilities.py

* remove inconsistency

* remove unnecessary linebreaks

* lol what

* last minute pythoning

* Fix async missing

* lint

* more refractor

* await forgotten

* fix: menu not disappearing after first click

* lint

* bug: fix double click on uninstall

* depricate request installs

* basic patch notes viewer, lazy-load settings and store, build frontend as esmodule, add lazy-loaded react-markdown, backend changes to accomodate ESModule frontend

* refractor uninstall plugin backend

* Change formatting in browser.py

* Manually format main.py

* Manually format utilities.py

* remove unnecessary linebreaks

* lol what

* last minute pythoning

* Fix async missing

* rebase onto main

* fix error, fix React crash if patch notes are opened before remote version info is loaded

Co-authored-by: TrainDoctor <traindoctor@protonmail.com>
Co-authored-by: AAGaming <aa@mail.catvibers.me>
This commit is contained in:
botato
2022-08-27 00:01:23 -04:00
committed by GitHub
parent d4d1c2bbab
commit b7d7ca04e1
6 changed files with 86 additions and 119 deletions
+11 -28
View File
@@ -28,16 +28,11 @@ class PluginInstallContext:
self.hash = hash
class PluginBrowser:
def __init__(self, plugin_path, server_instance, plugins) -> None:
def __init__(self, plugin_path, plugins) -> None:
self.plugin_path = plugin_path
self.plugins = plugins
self.install_requests = {}
server_instance.add_routes([
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):
zip_hash = sha256(zip.getbuffer()).hexdigest()
if hash and (zip_hash != hash):
@@ -64,25 +59,19 @@ class PluginBrowser:
async def uninstall_plugin(self, name):
tab = await get_tab("SP")
try:
if type(name) != str:
data = await name.post()
name = data.get("name", "undefined")
logger.info("uninstalling " + name)
logger.info(" at dir " + self.find_plugin_folder(name))
await tab.evaluate_js(f"DeckyPluginLoader.unloadPlugin('{name}')")
if self.plugins[name]:
self.plugins[name].stop()
self.plugins.pop(name, None)
self.plugins.remove(name)
rmtree(self.find_plugin_folder(name))
except FileNotFoundError:
logger.warning(f"Plugin {name} not installed, skipping uninstallation")
return web.Response(text="Requested plugin uninstall")
async def _install(self, artifact, name, version, hash):
try:
try:
await self.uninstall_plugin(name)
except:
logger.error(f"Plugin {name} not installed, skipping uninstallation")
@@ -95,29 +84,23 @@ class PluginBrowser:
data = await res.read()
logger.debug(f"Read {len(data)} bytes")
res_zip = BytesIO(data)
with ProcessPoolExecutor() as executor:
logger.debug("Unzipping...")
ret = self._unzip_to_plugin_dir(res_zip, name, hash)
if ret:
logger.info(f"Installed {name} (Version: {version})")
await inject_to_tab("SP", "window.syncDeckyPlugins()")
else:
logger.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})")
logger.debug("Unzipping...")
ret = self._unzip_to_plugin_dir(res_zip, name, hash)
if ret:
logger.info(f"Installed {name} (Version: {version})")
await inject_to_tab("SP", "window.syncDeckyPlugins()")
else:
self.log.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})")
else:
logger.fatal(f"Could not fetch from URL. {await res.text()}")
async def install_plugin(self, request):
data = await request.post()
get_event_loop().create_task(self.request_plugin_install(data.get("artifact", ""), data.get("name", "No name"), data.get("version", "dev"), data.get("hash", False)))
return web.Response(text="Requested plugin install")
async def request_plugin_install(self, artifact, name, version, hash):
request_id = str(time())
self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash)
tab = await get_tab("SP")
await tab.open_websocket()
await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}')")
async def confirm_plugin_install(self, request_id):
request = self.install_requests.pop(request_id)
await self._install(request.artifact, request.name, request.version, request.hash)
+15 -7
View File
@@ -36,10 +36,15 @@ CONFIG = {
"server_host": getenv("SERVER_HOST", "127.0.0.1"),
"server_port": int(getenv("SERVER_PORT", "1337")),
"live_reload": getenv("LIVE_RELOAD", "1") == "1",
"log_level": {"CRITICAL": 50, "ERROR": 40, "WARNING":30, "INFO": 20, "DEBUG": 10}[getenv("LOG_LEVEL", "INFO")]
"log_level": {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[
getenv("LOG_LEVEL", "INFO")
],
}
basicConfig(level=CONFIG["log_level"], format="[%(module)s][%(levelname)s]: %(message)s")
basicConfig(
level=CONFIG["log_level"],
format="[%(module)s][%(levelname)s]: %(message)s"
)
logger = getLogger("Main")
@@ -55,11 +60,14 @@ class PluginManager:
self.web_app = Application()
self.web_app.middlewares.append(csrf_middleware)
self.cors = aiohttp_cors.setup(self.web_app, defaults={
"https://steamloopback.host": aiohttp_cors.ResourceOptions(expose_headers="*",
allow_headers="*", allow_credentials=True)
"https://steamloopback.host": aiohttp_cors.ResourceOptions(
expose_headers="*",
allow_headers="*",
allow_credentials=True
)
})
self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"])
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.web_app, self.plugin_loader.plugins)
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.plugin_loader.plugins)
self.settings = SettingsManager("loader", path.join(HOMEBREW_PATH, "settings"))
self.utilities = Utilities(self)
self.updater = Updater(self)
@@ -75,7 +83,7 @@ class PluginManager:
self.web_app.add_routes([get("/auth/token", self.get_auth_token)])
for route in list(self.web_app.router.routes()):
self.cors.add(route)
self.cors.add(route)
self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))])
self.web_app.add_routes([static("/legacy", path.join(path.dirname(__file__), 'legacy'))])
@@ -99,7 +107,7 @@ class PluginManager:
async def load_plugins(self):
await self.wait_for_server()
self.plugin_loader.import_plugins()
#await inject_to_tab("SP", "window.syncDeckyPlugins();")
# await inject_to_tab("SP", "window.syncDeckyPlugins();")
async def loader_reinjector(self):
await sleep(2)
+18 -4
View File
@@ -7,14 +7,17 @@ from injector import inject_to_tab
import helpers
import subprocess
class Utilities:
def __init__(self, context) -> None:
self.context = context
self.util_methods = {
"ping": self.ping,
"http_request": self.http_request,
"install_plugin": self.install_plugin,
"cancel_plugin_install": self.cancel_plugin_install,
"confirm_plugin_install": self.confirm_plugin_install,
"uninstall_plugin": self.uninstall_plugin,
"execute_in_tab": self.execute_in_tab,
"inject_css_into_tab": self.inject_css_into_tab,
"remove_css_from_tab": self.remove_css_from_tab,
@@ -45,12 +48,23 @@ class Utilities:
res["success"] = False
return web.json_response(res)
async def install_plugin(self, artifact="", name="No name", version="dev", hash=False):
return await self.context.plugin_browser.request_plugin_install(
artifact=artifact,
name=name,
version=version,
hash=hash
)
async def confirm_plugin_install(self, request_id):
return await self.context.plugin_browser.confirm_plugin_install(request_id)
def cancel_plugin_install(self, request_id):
return self.context.plugin_browser.cancel_plugin_install(request_id)
async def uninstall_plugin(self, name):
return await self.context.plugin_browser.uninstall_plugin(name)
async def http_request(self, method="", url="", **kwargs):
async with ClientSession() as web:
async with web.request(method, url, ssl=helpers.get_ssl_context(), **kwargs) as res:
@@ -74,12 +88,12 @@ class Utilities:
return {
"success": True,
"result" : result["result"]["result"].get("value")
"result": result["result"]["result"].get("value")
}
except Exception as e:
return {
"success": False,
"result": e
"success": False,
"result": e
}
async def inject_css_into_tab(self, tab, style):
@@ -104,7 +118,7 @@ class Utilities:
return {
"success": True,
"result" : css_id
"result": css_id
}
except Exception as e:
return {
@@ -17,46 +17,38 @@ function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | n
return (
<Focusable onCancelButton={closeModal}>
<Carousel
fnItemRenderer={(id: number, ...args: any[]) => {
console.log(args, versionInfo);
return (
<Focusable
onActivate={() => {}}
style={{
marginTop: '40px',
height: 'calc( 100% - 40px )',
overflowY: 'scroll',
display: 'flex',
justifyContent: 'center',
margin: '40px',
}}
>
<div>
<h1>{versionInfo?.all?.[id]?.name}</h1>
{versionInfo?.all?.[id]?.body ? (
<Suspense fallback={<Spinner style={{ width: '24', height: '24' }} />}>
<MarkdownRenderer>{versionInfo.all[id].body}</MarkdownRenderer>
</Suspense>
) : (
'no patch notes for this version'
)}
</div>
</Focusable>
);
}}
fnGetId={(id) => {
return id;
}}
fnItemRenderer={(id: number) => (
<Focusable
onActivate={() => {}}
style={{
marginTop: '40px',
height: 'calc( 100% - 40px )',
overflowY: 'scroll',
display: 'flex',
justifyContent: 'center',
margin: '40px',
}}
>
<div>
<h1>{versionInfo?.all?.[id]?.name}</h1>
{versionInfo?.all?.[id]?.body ? (
<Suspense fallback={<Spinner style={{ width: '24', height: '24' }} />}>
<MarkdownRenderer>{versionInfo.all[id].body}</MarkdownRenderer>
</Suspense>
) : (
'no patch notes for this version'
)}
</div>
</Focusable>
)}
fnGetId={(id) => id}
nNumItems={versionInfo?.all?.length}
nHeight={window.innerHeight - 150}
nItemHeight={window.innerHeight - 200}
nItemMarginX={0}
initialColumn={0}
autoFocus={true}
fnGetColumnWidth={(...args: any[]) => {
console.log('cw', args);
return window.innerWidth;
}}
fnGetColumnWidth={() => window.innerWidth}
/>
</Focusable>
);
@@ -98,8 +90,8 @@ export default function UpdaterSettings() {
return (
<>
<Field
onOptionsActionDescription="Patch Notes"
onOptionsButton={showPatchNotes}
onOptionsActionDescription={versionInfo?.all ? 'Patch Notes' : undefined}
onOptionsButton={versionInfo?.all ? showPatchNotes : undefined}
label="Updates"
description={
versionInfo && (
+1 -10
View File
@@ -150,16 +150,7 @@ class PluginLoader extends Logger {
showModal(
<ModalRoot
onOK={async () => {
const formData = new FormData();
formData.append('name', name);
await fetch('http://localhost:1337/browser/uninstall_plugin', {
method: 'POST',
body: formData,
credentials: 'include',
headers: {
Authentication: window.deckyAuthToken,
},
});
await this.callServerMethod('uninstall_plugin', { name });
}}
onCancel={() => {
// do nothing
+13 -34
View File
@@ -42,17 +42,10 @@ export function getLegacyPluginList(): Promise<LegacyStorePlugin[]> {
}
export async function installFromURL(url: string) {
const formData = new FormData();
const splitURL = url.split('/');
formData.append('name', splitURL[splitURL.length - 1].replace('.zip', ''));
formData.append('artifact', url);
await fetch('http://localhost:1337/browser/install_plugin', {
method: 'POST',
body: formData,
credentials: 'include',
headers: {
Authentication: window.deckyAuthToken,
},
await window.DeckyPluginLoader.callServerMethod('install_plugin', {
name: splitURL[splitURL.length - 1].replace('.zip', ''),
artifact: url,
});
}
@@ -60,18 +53,11 @@ export function requestLegacyPluginInstall(plugin: LegacyStorePlugin, selectedVe
showModal(
<ModalRoot
onOK={() => {
const formData = new FormData();
formData.append('name', plugin.artifact);
formData.append('artifact', `https://github.com/${plugin.artifact}/archive/refs/tags/${selectedVer}.zip`);
formData.append('version', selectedVer);
formData.append('hash', plugin.versions[selectedVer]);
fetch('http://localhost:1337/browser/install_plugin', {
method: 'POST',
body: formData,
credentials: 'include',
headers: {
Authentication: window.deckyAuthToken,
},
window.DeckyPluginLoader.callServerMethod('install_plugin', {
name: plugin.artifact,
artifact: `https://github.com/${plugin.artifact}/archive/refs/tags/${selectedVer}.zip`,
version: selectedVer,
hash: plugin.versions[selectedVer],
});
}}
onCancel={() => {
@@ -89,18 +75,11 @@ export function requestLegacyPluginInstall(plugin: LegacyStorePlugin, selectedVe
}
export async function requestPluginInstall(plugin: string, selectedVer: StorePluginVersion) {
const formData = new FormData();
formData.append('name', plugin);
formData.append('artifact', `https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/${selectedVer.hash}.zip`);
formData.append('version', selectedVer.name);
formData.append('hash', selectedVer.hash);
await fetch('http://localhost:1337/browser/install_plugin', {
method: 'POST',
body: formData,
credentials: 'include',
headers: {
Authentication: window.deckyAuthToken,
},
await window.DeckyPluginLoader.callServerMethod('install_plugin', {
name: plugin,
artifact: `https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/${selectedVer.hash}.zip`,
version: selectedVer.name,
hash: selectedVer.hash,
});
}