From ae399b8c0eac95f048c31e223d9f25a451864f0e Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 5 Aug 2023 17:10:54 -0400 Subject: [PATCH 01/25] remove useless main.py imports --- backend/main.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/backend/main.py b/backend/main.py index b2e3e74a7..ae11b0668 100644 --- a/backend/main.py +++ b/backend/main.py @@ -3,21 +3,19 @@ from localplatform import (chmod, chown, service_stop, service_start, ON_WINDOWS, get_log_level, get_live_reload, get_server_port, get_server_host, get_chown_plugin_path, - get_unprivileged_user, get_unprivileged_path, get_privileged_path) if hasattr(sys, '_MEIPASS'): - chmod(sys._MEIPASS, 755) + chmod(sys._MEIPASS, 755) # type: ignore # Full imports from asyncio import new_event_loop, set_event_loop, sleep -from json import dumps, loads -from logging import DEBUG, INFO, basicConfig, getLogger -from os import getenv, path +from logging import basicConfig, getLogger +from os import path from traceback import format_exc import multiprocessing import aiohttp_cors # Partial imports -from aiohttp import client_exceptions, WSMsgType +from aiohttp import client_exceptions from aiohttp.web import Application, Response, get, run_app, static from aiohttp_jinja2 import setup as jinja_setup @@ -26,7 +24,7 @@ from helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token, mkdir_as_user, get_system_pythonpaths, get_effective_user_id) -from injector import get_gamepadui_tab, Tab, get_tabs, close_old_tabs +from injector import get_gamepadui_tab, Tab, close_old_tabs from loader import Loader from settings import SettingsManager from updater import Updater From ecc5f5c2fa0318057291a0eba2ab03da81e4e02d Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 26 Aug 2023 22:06:01 -0400 Subject: [PATCH 02/25] begin adding static types to backend code --- backend/browser.py | 62 +++++++++++++++++--------- backend/helpers.py | 55 ++++++++++------------- backend/injector.py | 84 +++++++++++++++++++++-------------- backend/loader.py | 69 +++++++++++++++------------- backend/localplatformlinux.py | 16 +++---- backend/main.py | 10 ++--- backend/plugin.py | 20 +++++---- backend/settings.py | 11 ++--- backend/utilities.py | 15 ++++--- 9 files changed, 189 insertions(+), 153 deletions(-) diff --git a/backend/browser.py b/backend/browser.py index ce9b3dd78..358c05f9e 100644 --- a/backend/browser.py +++ b/backend/browser.py @@ -4,53 +4,70 @@ # from pprint import pformat # Partial imports -from aiohttp import ClientSession, web -from asyncio import get_event_loop, sleep -from concurrent.futures import ProcessPoolExecutor +from aiohttp import ClientSession +from asyncio import sleep from hashlib import sha256 from io import BytesIO from logging import getLogger -from os import R_OK, W_OK, path, rename, listdir, access, mkdir +from os import R_OK, W_OK, path, listdir, access, mkdir from shutil import rmtree from time import time from zipfile import ZipFile from localplatform import chown, chmod +from enum import IntEnum +from typing import Dict, List, TypedDict # Local modules -from helpers import get_ssl_context, download_remote_binary_to_path -from injector import get_gamepadui_tab +from .loader import Loader, Plugins +from .helpers import get_ssl_context, download_remote_binary_to_path +from .settings import SettingsManager +from .injector import get_gamepadui_tab logger = getLogger("Browser") +class PluginInstallType(IntEnum): + INSTALL = 0 + REINSTALL = 1 + UPDATE = 2 + +class PluginInstallRequest(TypedDict): + name: str + artifact: str + version: str + hash: str + install_type: PluginInstallType + class PluginInstallContext: - def __init__(self, artifact, name, version, hash) -> None: + def __init__(self, artifact: str, name: str, version: str, hash: str) -> None: self.artifact = artifact self.name = name self.version = version self.hash = hash class PluginBrowser: - def __init__(self, plugin_path, plugins, loader, settings) -> None: + def __init__(self, plugin_path: str, plugins: Plugins, loader: Loader, settings: SettingsManager) -> None: self.plugin_path = plugin_path self.plugins = plugins self.loader = loader self.settings = settings - self.install_requests = {} + self.install_requests: Dict[str, PluginInstallContext | List[PluginInstallContext]] = {} - def _unzip_to_plugin_dir(self, zip, name, hash): + def _unzip_to_plugin_dir(self, zip: BytesIO, name: str, hash: str): zip_hash = sha256(zip.getbuffer()).hexdigest() if hash and (zip_hash != hash): return False zip_file = ZipFile(zip) zip_file.extractall(self.plugin_path) - plugin_dir = path.join(self.plugin_path, self.find_plugin_folder(name)) + plugin_folder = self.find_plugin_folder(name) + assert plugin_folder is not None + plugin_dir = path.join(self.plugin_path, plugin_folder) if not chown(plugin_dir) or not chmod(plugin_dir, 555): logger.error(f"chown/chmod exited with a non-zero exit code") return False return True - async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath): + async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath: str): rv = False try: packageJsonPath = path.join(pluginBasePath, 'package.json') @@ -91,7 +108,7 @@ async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath): return rv """Return the filename (only) for the specified plugin""" - def find_plugin_folder(self, name): + def find_plugin_folder(self, name: str) -> str | None: for folder in listdir(self.plugin_path): try: with open(path.join(self.plugin_path, folder, 'plugin.json'), "r", encoding="utf-8") as f: @@ -102,11 +119,13 @@ def find_plugin_folder(self, name): except: logger.debug(f"skipping {folder}") - async def uninstall_plugin(self, name): + async def uninstall_plugin(self, name: str): if self.loader.watcher: self.loader.watcher.disabled = True tab = await get_gamepadui_tab() - plugin_dir = path.join(self.plugin_path, self.find_plugin_folder(name)) + plugin_folder = self.find_plugin_folder(name) + assert plugin_folder is not None + plugin_dir = path.join(self.plugin_path, ) try: logger.info("uninstalling " + name) logger.info(" at dir " + plugin_dir) @@ -133,7 +152,7 @@ async def uninstall_plugin(self, name): if self.loader.watcher: self.loader.watcher.disabled = False - async def _install(self, artifact, name, version, hash): + async def _install(self, artifact: str, name: str, version: str, hash: str): # Will be set later in code res_zip = None @@ -185,6 +204,7 @@ async def _install(self, artifact, name, version, hash): ret = self._unzip_to_plugin_dir(res_zip, name, hash) if ret: plugin_folder = self.find_plugin_folder(name) + assert plugin_folder is not None plugin_dir = path.join(self.plugin_path, plugin_folder) ret = await self._download_remote_binaries_for_plugin_with_name(plugin_dir) if ret: @@ -206,14 +226,14 @@ async def _install(self, artifact, name, version, hash): if self.loader.watcher: self.loader.watcher.disabled = False - async def request_plugin_install(self, artifact, name, version, hash, install_type): + async def request_plugin_install(self, artifact: str, name: str, version: str, hash: str, install_type: PluginInstallType): request_id = str(time()) self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash) tab = await get_gamepadui_tab() await tab.open_websocket() await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}', {install_type})") - async def request_multiple_plugin_installs(self, requests): + async def request_multiple_plugin_installs(self, requests: List[PluginInstallRequest]): request_id = str(time()) self.install_requests[request_id] = [PluginInstallContext(req['artifact'], req['name'], req['version'], req['hash']) for req in requests] js_requests_parameter = ','.join([ @@ -224,17 +244,17 @@ async def request_multiple_plugin_installs(self, requests): await tab.open_websocket() await tab.evaluate_js(f"DeckyPluginLoader.addMultiplePluginsInstallPrompt('{request_id}', [{js_requests_parameter}])") - async def confirm_plugin_install(self, request_id): + async def confirm_plugin_install(self, request_id: str): requestOrRequests = self.install_requests.pop(request_id) if isinstance(requestOrRequests, list): [await self._install(req.artifact, req.name, req.version, req.hash) for req in requestOrRequests] else: await self._install(requestOrRequests.artifact, requestOrRequests.name, requestOrRequests.version, requestOrRequests.hash) - def cancel_plugin_install(self, request_id): + def cancel_plugin_install(self, request_id: str): self.install_requests.pop(request_id) - def cleanup_plugin_settings(self, name): + def cleanup_plugin_settings(self, name: str): """Removes any settings related to a plugin. Propably called when a plugin is uninstalled. Args: diff --git a/backend/helpers.py b/backend/helpers.py index a1877fb85..4036db857 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -2,13 +2,13 @@ import ssl import uuid import os -import sys import subprocess from hashlib import sha256 from io import BytesIO import certifi -from aiohttp.web import Response, middleware +from aiohttp.web import Request, Response, middleware +from aiohttp.typedefs import Handler from aiohttp import ClientSession import localplatform from customtypes import UserType @@ -31,17 +31,17 @@ def get_csrf_token(): return csrf_token @middleware -async def csrf_middleware(request, handler): +async def csrf_middleware(request: Request, handler: Handler): if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/legacy/") or str(request.rel_url).startswith("/steam_resource/") or str(request.rel_url).startswith("/frontend/") or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)): return await handler(request) - return Response(text='Forbidden', status='403') + return Response(text='Forbidden', status=403) # Get the default homebrew path unless a home_path is specified. home_path argument is deprecated -def get_homebrew_path(home_path = None) -> str: +def get_homebrew_path() -> str: return localplatform.get_unprivileged_path() # Recursively create path and chown as user -def mkdir_as_user(path): +def mkdir_as_user(path: str): path = os.path.realpath(path) os.makedirs(path, exist_ok=True) localplatform.chown(path) @@ -57,23 +57,18 @@ def get_loader_version() -> str: # returns the appropriate system python paths def get_system_pythonpaths() -> list[str]: - extra_args = {} - - if localplatform.ON_LINUX: - # run as normal normal user to also include user python paths - extra_args["user"] = localplatform.localplatform._get_user_id() - extra_args["env"] = {} - try: + # run as normal normal user if on linux to also include user python paths proc = subprocess.run(["python3" if localplatform.ON_LINUX else "python", "-c", "import sys; print('\\n'.join(x for x in sys.path if x))"], - capture_output=True, **extra_args) + # TODO make this less insane + capture_output=True, user=localplatform.localplatform._get_user_id() if localplatform.ON_LINUX else None, env={} if localplatform.ON_LINUX else None) # type: ignore return [x.strip() for x in proc.stdout.decode().strip().split("\n")] except Exception as e: logger.warn(f"Failed to execute get_system_pythonpaths(): {str(e)}") return [] # Download Remote Binaries to local Plugin -async def download_remote_binary_to_path(url, binHash, path) -> bool: +async def download_remote_binary_to_path(url: str, binHash: str, path: str) -> bool: rv = False try: if os.access(os.path.dirname(path), os.W_OK): @@ -110,46 +105,42 @@ def set_user_group() -> str: # Get the user id hosting the plugin loader def get_user_id() -> int: - return localplatform.localplatform._get_user_id() + return localplatform.localplatform._get_user_id() # pyright: ignore [reportPrivateUsage] # Get the user hosting the plugin loader def get_user() -> str: - return localplatform.localplatform._get_user() + return localplatform.localplatform._get_user() # pyright: ignore [reportPrivateUsage] # Get the effective user id of the running process def get_effective_user_id() -> int: - return localplatform.localplatform._get_effective_user_id() + return localplatform.localplatform._get_effective_user_id() # pyright: ignore [reportPrivateUsage] # Get the effective user of the running process def get_effective_user() -> str: - return localplatform.localplatform._get_effective_user() + return localplatform.localplatform._get_effective_user() # pyright: ignore [reportPrivateUsage] # Get the effective user group id of the running process def get_effective_user_group_id() -> int: - return localplatform.localplatform._get_effective_user_group_id() + return localplatform.localplatform._get_effective_user_group_id() # pyright: ignore [reportPrivateUsage] # Get the effective user group of the running process def get_effective_user_group() -> str: - return localplatform.localplatform._get_effective_user_group() + return localplatform.localplatform._get_effective_user_group() # pyright: ignore [reportPrivateUsage] # Get the user owner of the given file path. -def get_user_owner(file_path) -> str: - return localplatform.localplatform._get_user_owner(file_path) +def get_user_owner(file_path: str) -> str: + return localplatform.localplatform._get_user_owner(file_path) # pyright: ignore [reportPrivateUsage] -# Get the user group of the given file path. -def get_user_group(file_path) -> str: - return localplatform.localplatform._get_user_group(file_path) +# Get the user group of the given file path, or the user group hosting the plugin loader +def get_user_group(file_path: str | None = None) -> str: + return localplatform.localplatform._get_user_group(file_path) # pyright: ignore [reportPrivateUsage] # Get the group id of the user hosting the plugin loader def get_user_group_id() -> int: - return localplatform.localplatform._get_user_group_id() - -# Get the group of the user hosting the plugin loader -def get_user_group() -> str: - return localplatform.localplatform._get_user_group() + return localplatform.localplatform._get_user_group_id() # pyright: ignore [reportPrivateUsage] # Get the default home path unless a user is specified -def get_home_path(username = None) -> str: +def get_home_path(username: str | None = None) -> str: return localplatform.get_home_path(UserType.ROOT if username == "root" else UserType.HOST_USER) async def is_systemd_unit_active(unit_name: str) -> bool: diff --git a/backend/injector.py b/backend/injector.py index e3414fee1..a217f6891 100644 --- a/backend/injector.py +++ b/backend/injector.py @@ -2,10 +2,9 @@ from asyncio import sleep from logging import getLogger -from traceback import format_exc -from typing import List +from typing import Any, Callable, List, TypedDict, Dict -from aiohttp import ClientSession, WSMsgType +from aiohttp import ClientSession from aiohttp.client_exceptions import ClientConnectorError, ClientOSError from asyncio.exceptions import TimeoutError import uuid @@ -14,35 +13,43 @@ logger = getLogger("Injector") +class _TabResponse(TypedDict): + title: str + id: str + url: str + webSocketDebuggerUrl: str class Tab: cmd_id = 0 - def __init__(self, res) -> None: - self.title = res["title"] - self.id = res["id"] - self.url = res["url"] - self.ws_url = res["webSocketDebuggerUrl"] + def __init__(self, res: _TabResponse) -> None: + self.title: str = res["title"] + self.id: str = res["id"] + self.url: str = res["url"] + self.ws_url: str = res["webSocketDebuggerUrl"] self.websocket = None self.client = None async def open_websocket(self): self.client = ClientSession() - self.websocket = await self.client.ws_connect(self.ws_url) + self.websocket = await self.client.ws_connect(self.ws_url) # type: ignore async def close_websocket(self): - await self.websocket.close() - await self.client.close() + if self.websocket: + await self.websocket.close() + if self.client: + await self.client.close() async def listen_for_message(self): - async for message in self.websocket: - data = message.json() - yield data - logger.warn(f"The Tab {self.title} socket has been disconnected while listening for messages.") - await self.close_websocket() + if self.websocket: + async for message in self.websocket: + data = message.json() + yield data + logger.warn(f"The Tab {self.title} socket has been disconnected while listening for messages.") + await self.close_websocket() - async def _send_devtools_cmd(self, dc, receive=True): + async def _send_devtools_cmd(self, dc: Dict[str, Any], receive: bool = True): if self.websocket: self.cmd_id += 1 dc["id"] = self.cmd_id @@ -54,7 +61,7 @@ async def _send_devtools_cmd(self, dc, receive=True): return None raise RuntimeError("Websocket not opened") - async def evaluate_js(self, js, run_async=False, manage_socket=True, get_result=True): + async def evaluate_js(self, js: str, run_async: bool | None = False, manage_socket: bool | None = True, get_result: bool = True): try: if manage_socket: await self.open_websocket() @@ -73,15 +80,16 @@ async def evaluate_js(self, js, run_async=False, manage_socket=True, get_result= await self.close_websocket() return res - async def has_global_var(self, var_name, manage_socket=True): + async def has_global_var(self, var_name: str, manage_socket: bool = True): res = await self.evaluate_js(f"window['{var_name}'] !== null && window['{var_name}'] !== undefined", False, manage_socket) + assert res is not None if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: return False return res["result"]["result"]["value"] - async def close(self, manage_socket=True): + async def close(self, manage_socket: bool = True): try: if manage_socket: await self.open_websocket() @@ -111,7 +119,7 @@ async def disable(self): "method": "Page.disable", }, False) - async def refresh(self, manage_socket=True): + async def refresh(self, manage_socket: bool = True): try: if manage_socket: await self.open_websocket() @@ -125,7 +133,7 @@ async def refresh(self, manage_socket=True): await self.close_websocket() return - async def reload_and_evaluate(self, js, manage_socket=True): + async def reload_and_evaluate(self, js: str, manage_socket: bool = True): """ Reloads the current tab, with JS to run on load via debugger """ @@ -153,11 +161,13 @@ async def reload_and_evaluate(self, js, manage_socket=True): } }, True) + assert breakpoint_res is not None + logger.info(breakpoint_res) # Page finishes loading when breakpoint hits - for x in range(20): + for _ in range(20): # this works around 1/5 of the time, so just send it 8 times. # the js accounts for being injected multiple times allowing only one instance to run at a time anyway await self._send_devtools_cmd({ @@ -176,7 +186,7 @@ async def reload_and_evaluate(self, js, manage_socket=True): } }, False) - for x in range(4): + for _ in range(4): await self._send_devtools_cmd({ "method": "Debugger.resume" }, False) @@ -190,7 +200,7 @@ async def reload_and_evaluate(self, js, manage_socket=True): await self.close_websocket() return - async def add_script_to_evaluate_on_new_document(self, js, add_dom_wrapper=True, manage_socket=True, get_result=True): + async def add_script_to_evaluate_on_new_document(self, js: str, add_dom_wrapper: bool = True, manage_socket: bool = True, get_result: bool = True): """ How the underlying call functions is not particularly clear from the devtools docs, so stealing puppeteer's description: @@ -253,7 +263,7 @@ async def add_script_to_evaluate_on_new_document(self, js, add_dom_wrapper=True, await self.close_websocket() return res - async def remove_script_to_evaluate_on_new_document(self, script_id, manage_socket=True): + async def remove_script_to_evaluate_on_new_document(self, script_id: str, manage_socket: bool = True): """ Removes a script from a page that was added with `add_script_to_evaluate_on_new_document` @@ -267,7 +277,7 @@ async def remove_script_to_evaluate_on_new_document(self, script_id, manage_sock if manage_socket: await self.open_websocket() - res = await self._send_devtools_cmd({ + await self._send_devtools_cmd({ "method": "Page.removeScriptToEvaluateOnNewDocument", "params": { "identifier": script_id @@ -278,15 +288,16 @@ async def remove_script_to_evaluate_on_new_document(self, script_id, manage_sock if manage_socket: await self.close_websocket() - async def has_element(self, element_name, manage_socket=True): + async def has_element(self, element_name: str, manage_socket: bool = True): res = await self.evaluate_js(f"document.getElementById('{element_name}') != null", False, manage_socket) + assert res is not None if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]: return False return res["result"]["result"]["value"] - async def inject_css(self, style, manage_socket=True): + async def inject_css(self, style: str, manage_socket: bool = True): try: css_id = str(uuid.uuid4()) @@ -300,6 +311,8 @@ async def inject_css(self, style, manage_socket=True): }})() """, False, manage_socket) + assert result is not None + if "exceptionDetails" in result["result"]: return { "success": False, @@ -316,7 +329,7 @@ async def inject_css(self, style, manage_socket=True): "result": e } - async def remove_css(self, css_id, manage_socket=True): + async def remove_css(self, css_id: str, manage_socket: bool = True): try: result = await self.evaluate_js( f""" @@ -328,6 +341,8 @@ async def remove_css(self, css_id, manage_socket=True): }})() """, False, manage_socket) + assert result is not None + if "exceptionDetails" in result["result"]: return { "success": False, @@ -343,8 +358,9 @@ async def remove_css(self, css_id, manage_socket=True): "result": e } - async def get_steam_resource(self, url): + async def get_steam_resource(self, url: str): res = await self.evaluate_js(f'(async function test() {{ return await (await fetch("{url}")).text() }})()', True) + assert res is not None return res["result"]["result"]["value"] def __repr__(self): @@ -380,14 +396,14 @@ async def get_tabs() -> List[Tab]: raise Exception(f"/json did not return 200. {await res.text()}") -async def get_tab(tab_name) -> Tab: +async def get_tab(tab_name: str) -> Tab: tabs = await get_tabs() tab = next((i for i in tabs if i.title == tab_name), None) if not tab: raise ValueError(f"Tab {tab_name} not found") return tab -async def get_tab_lambda(test) -> Tab: +async def get_tab_lambda(test: Callable[[Tab], bool]) -> Tab: tabs = await get_tabs() tab = next((i for i in tabs if test(i)), None) if not tab: @@ -408,7 +424,7 @@ async def get_gamepadui_tab() -> Tab: raise ValueError(f"GamepadUI Tab not found") return tab -async def inject_to_tab(tab_name, js, run_async=False): +async def inject_to_tab(tab_name: str, js: str, run_async: bool = False): tab = await get_tab(tab_name) return await tab.evaluate_js(js, run_async) diff --git a/backend/loader.py b/backend/loader.py index d07b1c088..89bb04572 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -1,34 +1,40 @@ -from asyncio import Queue, sleep +from asyncio import AbstractEventLoop, Queue, sleep from json.decoder import JSONDecodeError from logging import getLogger from os import listdir, path from pathlib import Path from traceback import print_exc +from typing import Any, Tuple from aiohttp import web from os.path import exists -from watchdog.events import RegexMatchingEventHandler -from watchdog.observers import Observer +from watchdog.events import RegexMatchingEventHandler, DirCreatedEvent, DirModifiedEvent, FileCreatedEvent, FileModifiedEvent # type: ignore +from watchdog.observers import Observer # type: ignore -from injector import get_tab, get_gamepadui_tab -from plugin import PluginWrapper +from backend.main import PluginManager # type: ignore + +from .injector import get_tab, get_gamepadui_tab +from .plugin import PluginWrapper + +Plugins = dict[str, PluginWrapper] +ReloadQueue = Queue[Tuple[str, str, bool | None] | Tuple[str, str]] class FileChangeHandler(RegexMatchingEventHandler): - def __init__(self, queue, plugin_path) -> None: - super().__init__(regexes=[r'^.*?dist\/index\.js$', r'^.*?main\.py$']) + def __init__(self, queue: ReloadQueue, plugin_path: str) -> None: + super().__init__(regexes=[r'^.*?dist\/index\.js$', r'^.*?main\.py$']) # type: ignore self.logger = getLogger("file-watcher") self.plugin_path = plugin_path self.queue = queue self.disabled = True - def maybe_reload(self, src_path): + def maybe_reload(self, src_path: str): if self.disabled: return plugin_dir = Path(path.relpath(src_path, self.plugin_path)).parts[0] if exists(path.join(self.plugin_path, plugin_dir, "plugin.json")): self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True)) - def on_created(self, event): + def on_created(self, event: DirCreatedEvent | FileCreatedEvent): src_path = event.src_path if "__pycache__" in src_path: return @@ -42,7 +48,7 @@ def on_created(self, event): self.logger.debug(f"file created: {src_path}") self.maybe_reload(src_path) - def on_modified(self, event): + def on_modified(self, event: DirModifiedEvent | FileModifiedEvent): src_path = event.src_path if "__pycache__" in src_path: return @@ -57,25 +63,25 @@ def on_modified(self, event): self.maybe_reload(src_path) class Loader: - def __init__(self, server_instance, plugin_path, loop, live_reload=False) -> None: + def __init__(self, server_instance: PluginManager, plugin_path: str, loop: AbstractEventLoop, live_reload: bool =False) -> None: self.loop = loop self.logger = getLogger("Loader") self.plugin_path = plugin_path self.logger.info(f"plugin_path: {self.plugin_path}") - self.plugins : dict[str, PluginWrapper] = {} + self.plugins: Plugins = {} self.watcher = None self.live_reload = live_reload - self.reload_queue = Queue() + self.reload_queue: ReloadQueue = Queue() self.loop.create_task(self.handle_reloads()) if live_reload: self.observer = Observer() self.watcher = FileChangeHandler(self.reload_queue, plugin_path) - self.observer.schedule(self.watcher, self.plugin_path, recursive=True) + self.observer.schedule(self.watcher, self.plugin_path, recursive=True) # type: ignore self.observer.start() self.loop.create_task(self.enable_reload_wait()) - server_instance.add_routes([ + server_instance.web_app.add_routes([ web.get("/frontend/{path:.*}", self.handle_frontend_assets), web.get("/locales/{path:.*}", self.handle_frontend_locales), web.get("/plugins", self.get_plugins), @@ -93,15 +99,16 @@ def __init__(self, server_instance, plugin_path, loop, live_reload=False) -> Non async def enable_reload_wait(self): if self.live_reload: await sleep(10) - self.logger.info("Hot reload enabled") - self.watcher.disabled = False + if self.watcher: + self.logger.info("Hot reload enabled") + self.watcher.disabled = False - async def handle_frontend_assets(self, request): + async def handle_frontend_assets(self, request: web.Request): file = path.join(path.dirname(__file__), "static", request.match_info["path"]) return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) - async def handle_frontend_locales(self, request): + async def handle_frontend_locales(self, request: web.Request): req_lang = request.match_info["path"] file = path.join(path.dirname(__file__), "locales", req_lang) if exists(file): @@ -110,23 +117,23 @@ async def handle_frontend_locales(self, request): self.logger.info(f"Language {req_lang} not available, returning an empty dictionary") return web.json_response(data={}, headers={"Cache-Control": "no-cache"}) - async def get_plugins(self, request): + async def get_plugins(self, request: web.Request): plugins = list(self.plugins.values()) return web.json_response([{"name": str(i) if not i.legacy else "$LEGACY_"+str(i), "version": i.version} for i in plugins]) - def handle_plugin_frontend_assets(self, request): + async def handle_plugin_frontend_assets(self, request: web.Request): plugin = self.plugins[request.match_info["plugin_name"]] file = path.join(self.plugin_path, plugin.plugin_directory, "dist/assets", request.match_info["path"]) return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) - def handle_frontend_bundle(self, request): + async def handle_frontend_bundle(self, request: web.Request): plugin = self.plugins[request.match_info["plugin_name"]] with open(path.join(self.plugin_path, plugin.plugin_directory, "dist/index.js"), "r", encoding="utf-8") as bundle: return web.Response(text=bundle.read(), content_type="application/javascript") - def import_plugin(self, file, plugin_directory, refresh=False, batch=False): + def import_plugin(self, file: str, plugin_directory: str, refresh: bool | None = False, batch: bool | None = False): try: plugin = PluginWrapper(file, plugin_directory, self.plugin_path) if plugin.name in self.plugins: @@ -146,7 +153,7 @@ def import_plugin(self, file, plugin_directory, refresh=False, batch=False): self.logger.error(f"Could not load {file}. {e}") print_exc() - async def dispatch_plugin(self, name, version): + async def dispatch_plugin(self, name: str, version: str | None): gpui_tab = await get_gamepadui_tab() await gpui_tab.evaluate_js(f"window.importDeckyPlugin('{name}', '{version}')") @@ -161,15 +168,15 @@ def import_plugins(self): async def handle_reloads(self): while True: args = await self.reload_queue.get() - self.import_plugin(*args) + self.import_plugin(*args) # type: ignore - async def handle_plugin_method_call(self, request): + async def handle_plugin_method_call(self, request: web.Request): res = {} plugin = self.plugins[request.match_info["plugin_name"]] method_name = request.match_info["method_name"] try: method_info = await request.json() - args = method_info["args"] + args: Any = method_info["args"] except JSONDecodeError: args = {} try: @@ -189,7 +196,7 @@ async def handle_plugin_method_call(self, request): can introduce it more smoothly and give people the chance to sample the new features even without plugin support. They will be removed once legacy plugins are no longer relevant. """ - async def load_plugin_main_view(self, request): + async def load_plugin_main_view(self, request: web.Request): plugin = self.plugins[request.match_info["name"]] with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.main_view_html), "r", encoding="utf-8") as template: template_data = template.read() @@ -201,7 +208,7 @@ async def load_plugin_main_view(self, request): """ return web.Response(text=ret, content_type="text/html") - async def handle_sub_route(self, request): + async def handle_sub_route(self, request: web.Request): plugin = self.plugins[request.match_info["name"]] route_path = request.match_info["path"] self.logger.info(path) @@ -212,14 +219,14 @@ async def handle_sub_route(self, request): return web.Response(text=ret) - async def get_steam_resource(self, request): + async def get_steam_resource(self, request: web.Request): tab = await get_tab("SP") try: return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html") except Exception as e: return web.Response(text=str(e), status=400) - async def handle_backend_reload_request(self, request): + async def handle_backend_reload_request(self, request: web.Request): plugin_name : str = request.match_info["plugin_name"] plugin = self.plugins[plugin_name] diff --git a/backend/localplatformlinux.py b/backend/localplatformlinux.py index 811db8a62..58b9dbc2c 100644 --- a/backend/localplatformlinux.py +++ b/backend/localplatformlinux.py @@ -29,21 +29,17 @@ def _get_effective_user_group() -> str: return grp.getgrgid(_get_effective_user_group_id()).gr_name # Get the user owner of the given file path. -def _get_user_owner(file_path) -> str: +def _get_user_owner(file_path: str) -> str: return pwd.getpwuid(os.stat(file_path).st_uid).pw_name -# Get the user group of the given file path. -def _get_user_group(file_path) -> str: - return grp.getgrgid(os.stat(file_path).st_gid).gr_name +# Get the user group of the given file path, or the user group hosting the plugin loader +def _get_user_group(file_path: str | None = None) -> str: + return grp.getgrgid(os.stat(file_path).st_gid if file_path is not None else _get_user_group_id()).gr_name # Get the group id of the user hosting the plugin loader def _get_user_group_id() -> int: return pwd.getpwuid(_get_user_id()).pw_gid -# Get the group of the user hosting the plugin loader -def _get_user_group() -> str: - return grp.getgrgid(_get_user_group_id()).gr_name - def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: user_str = "" @@ -146,7 +142,7 @@ def get_privileged_path() -> str: return path -def _parent_dir(path : str) -> str: +def _parent_dir(path : str | None) -> str | None: if path == None: return None @@ -166,7 +162,7 @@ def get_unprivileged_path() -> str: # Expected path of loader binary is /home/deck/homebrew/service/PluginLoader path = _parent_dir(_parent_dir(os.path.realpath(sys.argv[0]))) - if not os.path.exists(path): + if path != None and not os.path.exists(path): path = None if path == None: diff --git a/backend/main.py b/backend/main.py index ae11b0668..433b202fa 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,16 +7,16 @@ if hasattr(sys, '_MEIPASS'): chmod(sys._MEIPASS, 755) # type: ignore # Full imports -from asyncio import new_event_loop, set_event_loop, sleep +from asyncio import AbstractEventLoop, new_event_loop, set_event_loop, sleep from logging import basicConfig, getLogger from os import path from traceback import format_exc import multiprocessing -import aiohttp_cors +import aiohttp_cors # type: ignore # Partial imports from aiohttp import client_exceptions -from aiohttp.web import Application, Response, get, run_app, static +from aiohttp.web import Application, Response, get, run_app, static # type: ignore from aiohttp_jinja2 import setup as jinja_setup # local modules @@ -51,7 +51,7 @@ def chown_plugin_dir(): chown_plugin_dir() class PluginManager: - def __init__(self, loop) -> None: + def __init__(self, loop: AbstractEventLoop) -> None: self.loop = loop self.web_app = Application() self.web_app.middlewares.append(csrf_middleware) @@ -62,7 +62,7 @@ def __init__(self, loop) -> None: allow_credentials=True ) }) - self.plugin_loader = Loader(self.web_app, plugin_path, self.loop, get_live_reload()) + self.plugin_loader = Loader(self, plugin_path, self.loop, get_live_reload()) self.settings = SettingsManager("loader", path.join(get_privileged_path(), "settings")) self.plugin_browser = PluginBrowser(plugin_path, self.plugin_loader.plugins, self.plugin_loader, self.settings) self.utilities = Utilities(self) diff --git a/backend/plugin.py b/backend/plugin.py index 026a6b09f..781d9f7b3 100644 --- a/backend/plugin.py +++ b/backend/plugin.py @@ -1,7 +1,6 @@ import multiprocessing from asyncio import (Lock, get_event_loop, new_event_loop, set_event_loop, sleep) -from concurrent.futures import ProcessPoolExecutor from importlib.util import module_from_spec, spec_from_file_location from json import dumps, load, loads from logging import getLogger @@ -9,14 +8,14 @@ from os import path, environ from signal import SIGINT, signal from sys import exit, path as syspath -from time import time +from typing import Any, Dict from localsocket import LocalSocket from localplatform import setgid, setuid, get_username, get_home_path from customtypes import UserType import helpers class PluginWrapper: - def __init__(self, file, plugin_directory, plugin_path) -> None: + def __init__(self, file: str, plugin_directory: str, plugin_path: str) -> None: self.file = file self.plugin_path = plugin_path self.plugin_directory = plugin_directory @@ -73,14 +72,17 @@ def _init(self): helpers.mkdir_as_user(environ["DECKY_PLUGIN_LOG_DIR"]) environ["DECKY_PLUGIN_DIR"] = path.join(self.plugin_path, self.plugin_directory) environ["DECKY_PLUGIN_NAME"] = self.name - environ["DECKY_PLUGIN_VERSION"] = self.version + if self.version: + environ["DECKY_PLUGIN_VERSION"] = self.version environ["DECKY_PLUGIN_AUTHOR"] = self.author # append the plugin's `py_modules` to the recognized python paths syspath.append(path.join(environ["DECKY_PLUGIN_DIR"], "py_modules")) spec = spec_from_file_location("_", self.file) + assert spec is not None module = module_from_spec(spec) + assert spec.loader is not None spec.loader.exec_module(module) self.Plugin = module.Plugin @@ -118,7 +120,8 @@ async def _on_new_message(self, message : str) -> str|None: get_event_loop().close() raise Exception("Closing message listener") - d = {"res": None, "success": True} + # TODO there is definitely a better way to type this + d: Dict[str, Any] = {"res": None, "success": True} try: d["res"] = await getattr(self.Plugin, data["method"])(self.Plugin, **data["args"]) except Exception as e: @@ -137,17 +140,18 @@ def stop(self): if self.passive: return - async def _(self): + async def _(self: PluginWrapper): await self.socket.write_single_line(dumps({ "stop": True }, ensure_ascii=False)) await self.socket.close_socket_connection() get_event_loop().create_task(_(self)) - async def execute_method(self, method_name, kwargs): + async def execute_method(self, method_name: str, kwargs: Dict[Any, Any]): if self.passive: raise RuntimeError("This plugin is passive (aka does not implement main.py)") async with self.method_call_lock: - reader, writer = await self.socket.get_socket_connection() + # reader, writer = + await self.socket.get_socket_connection() await self.socket.write_single_line(dumps({ "method": method_name, "args": kwargs }, ensure_ascii=False)) diff --git a/backend/settings.py b/backend/settings.py index c00e6a823..26dfc97f7 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -1,5 +1,6 @@ from json import dump, load from os import mkdir, path, listdir, rename +from typing import Any, Dict from localplatform import chown, folder_owner, get_chown_plugin_path from customtypes import UserType @@ -7,7 +8,7 @@ class SettingsManager: - def __init__(self, name, settings_directory = None) -> None: + def __init__(self, name: str, settings_directory: str | None = None) -> None: wrong_dir = get_homebrew_path() if settings_directory == None: settings_directory = path.join(wrong_dir, "settings") @@ -31,11 +32,11 @@ def __init__(self, name, settings_directory = None) -> None: if folder_owner(settings_directory) != expected_user: chown(settings_directory, expected_user, False) - self.settings = {} + self.settings: Dict[str, Any] = {} try: open(self.path, "x", encoding="utf-8") - except FileExistsError as e: + except FileExistsError as _: self.read() pass @@ -51,9 +52,9 @@ def commit(self): with open(self.path, "w+", encoding="utf-8") as file: dump(self.settings, file, indent=4, ensure_ascii=False) - def getSetting(self, key, default=None): + def getSetting(self, key: str, default: Any = None) -> Any: return self.settings.get(key, default) - def setSetting(self, key, value): + def setSetting(self, key: str, value: Any) -> Any: self.settings[key] = value self.commit() diff --git a/backend/utilities.py b/backend/utilities.py index bcb355785..72b6f008a 100644 --- a/backend/utilities.py +++ b/backend/utilities.py @@ -1,26 +1,27 @@ import uuid -import os from json.decoder import JSONDecodeError from os.path import splitext import re from traceback import format_exc -from stat import FILE_ATTRIBUTE_HIDDEN +from stat import FILE_ATTRIBUTE_HIDDEN # type: ignore -from asyncio import sleep, start_server, gather, open_connection +from asyncio import start_server, gather, open_connection from aiohttp import ClientSession, web +from typing import Dict from logging import getLogger +from backend.browser import PluginInstallType +from backend.main import PluginManager from injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab from pathlib import Path from localplatform import ON_WINDOWS import helpers -import subprocess from localplatform import service_stop, service_start, get_home_path, get_username class Utilities: - def __init__(self, context) -> None: + def __init__(self, context: PluginManager) -> None: self.context = context - self.util_methods = { + self.util_methods: Dict[] = { "ping": self.ping, "http_request": self.http_request, "install_plugin": self.install_plugin, @@ -69,7 +70,7 @@ async def _handle_server_method_call(self, request): res["success"] = False return web.json_response(res) - async def install_plugin(self, artifact="", name="No name", version="dev", hash=False, install_type=0): + async def install_plugin(self, artifact="", name="No name", version="dev", hash=False, install_type=PluginInstallType.INSTALL): return await self.context.plugin_browser.request_plugin_install( artifact=artifact, name=name, From 75fbc7524f33dd7c5bd9aea93c2907d2738de930 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Mon, 18 Sep 2023 00:31:54 +0300 Subject: [PATCH 03/25] type hints on main,plugin,updater,utilites.localsocket --- backend/localsocket.py | 37 ++++++++++++--------- backend/main.py | 15 +++++---- backend/plugin.py | 2 +- backend/updater.py | 27 ++++++++++----- backend/utilities.py | 74 ++++++++++++++++++++++++------------------ 5 files changed, 92 insertions(+), 63 deletions(-) diff --git a/backend/localsocket.py b/backend/localsocket.py index ef0e3933a..3659da038 100644 --- a/backend/localsocket.py +++ b/backend/localsocket.py @@ -1,10 +1,13 @@ -import asyncio, time, random +import asyncio, time +from typing import Awaitable, Callable +import random + from localplatform import ON_WINDOWS BUFFER_LIMIT = 2 ** 20 # 1 MiB class UnixSocket: - def __init__(self, on_new_message): + def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): ''' on_new_message takes 1 string argument. It's return value gets used, if not None, to write data to the socket. @@ -46,28 +49,32 @@ async def close_socket_connection(self): self.reader = None async def read_single_line(self) -> str|None: - reader, writer = await self.get_socket_connection() + reader, _ = await self.get_socket_connection() - if self.reader == None: - return None + try: + assert reader + except AssertionError: + return return await self._read_single_line(reader) async def write_single_line(self, message : str): - reader, writer = await self.get_socket_connection() + _, writer = await self.get_socket_connection() - if self.writer == None: - return; + try: + assert writer + except AssertionError: + return await self._write_single_line(writer, message) - async def _read_single_line(self, reader) -> str: + async def _read_single_line(self, reader: asyncio.StreamReader) -> str: line = bytearray() while True: try: line.extend(await reader.readuntil()) except asyncio.LimitOverrunError: - line.extend(await reader.read(reader._limit)) + line.extend(await reader.read(reader._limit)) # type: ignore continue except asyncio.IncompleteReadError as err: line.extend(err.partial) @@ -77,27 +84,27 @@ async def _read_single_line(self, reader) -> str: return line.decode("utf-8") - async def _write_single_line(self, writer, message : str): + async def _write_single_line(self, writer: asyncio.StreamWriter, message : str): if not message.endswith("\n"): message += "\n" writer.write(message.encode("utf-8")) await writer.drain() - async def _listen_for_method_call(self, reader, writer): + async def _listen_for_method_call(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): while True: line = await self._read_single_line(reader) try: res = await self.on_new_message(line) - except Exception as e: + except Exception: return if res != None: await self._write_single_line(writer, res) class PortSocket (UnixSocket): - def __init__(self, on_new_message): + def __init__(self, on_new_message: Callable[[str], Awaitable[str|None]]): ''' on_new_message takes 1 string argument. It's return value gets used, if not None, to write data to the socket. @@ -125,7 +132,7 @@ async def _open_socket_if_not_exists(self): return True if ON_WINDOWS: - class LocalSocket (PortSocket): + class LocalSocket (PortSocket): # type: ignore pass else: class LocalSocket (UnixSocket): diff --git a/backend/main.py b/backend/main.py index 433b202fa..8857fb225 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,5 +1,6 @@ # Change PyInstaller files permissions import sys +from typing import Dict from localplatform import (chmod, chown, service_stop, service_start, ON_WINDOWS, get_log_level, get_live_reload, get_server_port, get_server_host, get_chown_plugin_path, @@ -16,7 +17,7 @@ import aiohttp_cors # type: ignore # Partial imports from aiohttp import client_exceptions -from aiohttp.web import Application, Response, get, run_app, static # type: ignore +from aiohttp.web import Application, Response, Request, get, run_app, static # type: ignore from aiohttp_jinja2 import setup as jinja_setup # local modules @@ -70,7 +71,7 @@ def __init__(self, loop: AbstractEventLoop) -> None: jinja_setup(self.web_app) - async def startup(_): + async def startup(_: Application): if self.settings.getSetting("cef_forward", False): self.loop.create_task(service_start(REMOTE_DEBUGGER_UNIT)) else: @@ -84,16 +85,16 @@ async def startup(_): 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) # type: ignore 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'))]) - def exception_handler(self, loop, context): + def exception_handler(self, loop: AbstractEventLoop, context: Dict[str, str]): if context["message"] == "Unclosed connection": return loop.default_exception_handler(context) - async def get_auth_token(self, request): + async def get_auth_token(self, request: Request): return Response(text=get_csrf_token()) async def load_plugins(self): @@ -144,7 +145,7 @@ async def loader_reinjector(self): # This is because of https://github.com/aio-libs/aiohttp/blob/3ee7091b40a1bc58a8d7846e7878a77640e96996/aiohttp/client_ws.py#L321 logger.info("CEF has disconnected...") # At this point the loop starts again and we connect to the freshly started Steam client once it is ready. - except Exception as e: + except Exception: logger.error("Exception while reading page events " + format_exc()) await tab.close_websocket() pass @@ -154,7 +155,7 @@ async def loader_reinjector(self): # logger.info("Plugin loader isn't present in Steam anymore, reinjecting...") # await self.inject_javascript(tab) - async def inject_javascript(self, tab: Tab, first=False, request=None): + async def inject_javascript(self, tab: Tab, first: bool=False, request: Request|None=None): logger.info("Loading Decky frontend!") try: if first: diff --git a/backend/plugin.py b/backend/plugin.py index 781d9f7b3..5c1e099fa 100644 --- a/backend/plugin.py +++ b/backend/plugin.py @@ -20,7 +20,7 @@ def __init__(self, file: str, plugin_directory: str, plugin_path: str) -> None: self.plugin_path = plugin_path self.plugin_directory = plugin_directory self.method_call_lock = Lock() - self.socket = LocalSocket(self._on_new_message) + self.socket: LocalSocket = LocalSocket(self._on_new_message) self.version = None diff --git a/backend/updater.py b/backend/updater.py index 6b38dd25d..d7a3d7128 100644 --- a/backend/updater.py +++ b/backend/updater.py @@ -1,23 +1,31 @@ import os import shutil -import uuid from asyncio import sleep -from ensurepip import version from json.decoder import JSONDecodeError from logging import getLogger from os import getcwd, path, remove +from typing import List, TypedDict +from backend.main import PluginManager from localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux from aiohttp import ClientSession, web import helpers -from injector import get_gamepadui_tab, inject_to_tab +from injector import get_gamepadui_tab from settings import SettingsManager logger = getLogger("Updater") +class RemoteVerAsset(TypedDict): + name: str + browser_download_url: str +class RemoteVer(TypedDict): + tag_name: str + prerelease: bool + assets: List[RemoteVerAsset] + class Updater: - def __init__(self, context) -> None: + def __init__(self, context: PluginManager) -> None: self.context = context self.settings = self.context.settings # Exposes updater methods to frontend @@ -28,8 +36,8 @@ def __init__(self, context) -> None: "do_restart": self.do_restart, "check_for_updates": self.check_for_updates } - self.remoteVer = None - self.allRemoteVers = None + self.remoteVer: RemoteVer | None = None + self.allRemoteVers: List[RemoteVer] = [] self.localVer = helpers.get_loader_version() try: @@ -44,7 +52,7 @@ def __init__(self, context) -> None: ]) context.loop.create_task(self.version_reloader()) - async def _handle_server_method_call(self, request): + async def _handle_server_method_call(self, request: web.Request): method_name = request.match_info["method_name"] try: args = await request.json() @@ -52,7 +60,7 @@ async def _handle_server_method_call(self, request): args = {} res = {} try: - r = await self.updater_methods[method_name](**args) + r = await self.updater_methods[method_name](**args) # type: ignore res["result"] = r res["success"] = True except Exception as e: @@ -105,7 +113,7 @@ async def check_for_updates(self): selectedBranch = self.get_branch(self.context.settings) async with ClientSession() as web: async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases", ssl=helpers.get_ssl_context()) as res: - remoteVersions = await res.json() + remoteVersions: List[RemoteVer] = await res.json() if selectedBranch == 0: logger.debug("release type: release") remoteVersions = list(filter(lambda ver: ver["tag_name"].startswith("v") and not ver["prerelease"] and not ver["tag_name"].find("-pre") > 0 and ver["tag_name"], remoteVersions)) @@ -142,6 +150,7 @@ async def version_reloader(self): async def do_update(self): logger.debug("Starting update.") + assert self.remoteVer version = self.remoteVer["tag_name"] download_url = None download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe" diff --git a/backend/utilities.py b/backend/utilities.py index 72b6f008a..d45bec9bb 100644 --- a/backend/utilities.py +++ b/backend/utilities.py @@ -1,3 +1,4 @@ +from os import stat_result import uuid from json.decoder import JSONDecodeError from os.path import splitext @@ -5,12 +6,12 @@ from traceback import format_exc from stat import FILE_ATTRIBUTE_HIDDEN # type: ignore -from asyncio import start_server, gather, open_connection +from asyncio import StreamReader, StreamWriter, start_server, gather, open_connection from aiohttp import ClientSession, web -from typing import Dict +from typing import Callable, Coroutine, Dict, Any, List, TypedDict from logging import getLogger -from backend.browser import PluginInstallType +from backend.browser import PluginInstallRequest, PluginInstallType from backend.main import PluginManager from injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab from pathlib import Path @@ -18,10 +19,15 @@ import helpers from localplatform import service_stop, service_start, get_home_path, get_username +class FilePickerObj(TypedDict): + file: Path + filest: stat_result + is_dir: bool + class Utilities: def __init__(self, context: PluginManager) -> None: self.context = context - self.util_methods: Dict[] = { + self.util_methods: Dict[str, Callable[..., Coroutine[Any, Any, Any]]] = { "ping": self.ping, "http_request": self.http_request, "install_plugin": self.install_plugin, @@ -54,7 +60,7 @@ def __init__(self, context: PluginManager) -> None: web.post("/methods/{method_name}", self._handle_server_method_call) ]) - async def _handle_server_method_call(self, request): + async def _handle_server_method_call(self, request: web.Request): method_name = request.match_info["method_name"] try: args = await request.json() @@ -70,7 +76,7 @@ async def _handle_server_method_call(self, request): res["success"] = False return web.json_response(res) - async def install_plugin(self, artifact="", name="No name", version="dev", hash=False, install_type=PluginInstallType.INSTALL): + async def install_plugin(self, artifact: str="", name: str="No name", version: str="dev", hash: str="", install_type: PluginInstallType=PluginInstallType.INSTALL): return await self.context.plugin_browser.request_plugin_install( artifact=artifact, name=name, @@ -79,21 +85,21 @@ async def install_plugin(self, artifact="", name="No name", version="dev", hash= install_type=install_type ) - async def install_plugins(self, requests): + async def install_plugins(self, requests: List[PluginInstallRequest]): return await self.context.plugin_browser.request_multiple_plugin_installs( requests=requests ) - async def confirm_plugin_install(self, request_id): + async def confirm_plugin_install(self, request_id: str): return await self.context.plugin_browser.confirm_plugin_install(request_id) - def cancel_plugin_install(self, request_id): + async def cancel_plugin_install(self, request_id: str): return self.context.plugin_browser.cancel_plugin_install(request_id) - async def uninstall_plugin(self, name): + async def uninstall_plugin(self, name: str): return await self.context.plugin_browser.uninstall_plugin(name) - async def http_request(self, method="", url="", **kwargs): + async def http_request(self, method: str="", url: str="", **kwargs: Any): async with ClientSession() as web: res = await web.request(method, url, ssl=helpers.get_ssl_context(), **kwargs) text = await res.text() @@ -103,12 +109,13 @@ async def http_request(self, method="", url="", **kwargs): "body": text } - async def ping(self, **kwargs): + async def ping(self, **kwargs: Any): return "pong" - async def execute_in_tab(self, tab, run_async, code): + async def execute_in_tab(self, tab: str, run_async: bool, code: str): try: result = await inject_to_tab(tab, code, run_async) + assert result if "exceptionDetails" in result["result"]: return { "success": False, @@ -125,7 +132,7 @@ async def execute_in_tab(self, tab, run_async, code): "result": e } - async def inject_css_into_tab(self, tab, style): + async def inject_css_into_tab(self, tab: str, style: str): try: css_id = str(uuid.uuid4()) @@ -139,7 +146,7 @@ async def inject_css_into_tab(self, tab, style): }})() """, False) - if "exceptionDetails" in result["result"]: + if result and "exceptionDetails" in result["result"]: return { "success": False, "result": result["result"] @@ -155,7 +162,7 @@ async def inject_css_into_tab(self, tab, style): "result": e } - async def remove_css_from_tab(self, tab, css_id): + async def remove_css_from_tab(self, tab: str, css_id: str): try: result = await inject_to_tab(tab, f""" @@ -167,7 +174,7 @@ async def remove_css_from_tab(self, tab, css_id): }})() """, False) - if "exceptionDetails" in result["result"]: + if result and "exceptionDetails" in result["result"]: return { "success": False, "result": result @@ -182,10 +189,10 @@ async def remove_css_from_tab(self, tab, css_id): "result": e } - async def get_setting(self, key, default): + async def get_setting(self, key: str, default: Any): return self.context.settings.getSetting(key, default) - async def set_setting(self, key, value): + async def set_setting(self, key: str, value: Any): return self.context.settings.setSetting(key, value) async def allow_remote_debugging(self): @@ -210,17 +217,18 @@ async def filepicker_ls(self, if path == None: path = get_home_path() - path = Path(path).resolve() + path_obj = Path(path).resolve() - files, folders = [], [] + files: List[FilePickerObj] = [] + folders: List[FilePickerObj] = [] #Resolving all files/folders in the requested directory - for file in path.iterdir(): + for file in path_obj.iterdir(): if file.exists(): filest = file.stat() is_hidden = file.name.startswith('.') if ON_WINDOWS and not is_hidden: - is_hidden = bool(filest.st_file_attributes & FILE_ATTRIBUTE_HIDDEN) + is_hidden = bool(filest.st_file_attributes & FILE_ATTRIBUTE_HIDDEN) # type: ignore if include_folders and file.is_dir(): if (is_hidden and include_hidden) or not is_hidden: folders.append({"file": file, "filest": filest, "is_dir": True}) @@ -234,9 +242,9 @@ async def filepicker_ls(self, if filter_for is not None: try: if re.compile(filter_for): - files = filter(lambda file: re.search(filter_for, file.name) != None, files) + files = list(filter(lambda file: re.search(filter_for, file["file"].name) != None, files)) except re.error: - files = filter(lambda file: file.name.find(filter_for) != -1, files) + files = list(filter(lambda file: file["file"].name.find(filter_for) != -1, files)) # Ordering logic ord_arg = order_by.split("_") @@ -256,6 +264,9 @@ async def filepicker_ls(self, files.sort(key=lambda x: x['filest'].st_size, reverse = not rev) # Folders has no file size, order by name instead folders.sort(key=lambda x: x['file'].name.casefold()) + case _: + files.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) + folders.sort(key=lambda x: x['file'].name.casefold(), reverse = rev) #Constructing the final file list, folders first all = [{ @@ -275,14 +286,14 @@ async def filepicker_ls(self, # Based on https://stackoverflow.com/a/46422554/13174603 - def start_rdt_proxy(self, ip, port): - async def pipe(reader, writer): + def start_rdt_proxy(self, ip: str, port: int): + async def pipe(reader: StreamReader, writer: StreamWriter): try: while not reader.at_eof(): writer.write(await reader.read(2048)) finally: writer.close() - async def handle_client(local_reader, local_writer): + async def handle_client(local_reader: StreamReader, local_writer: StreamWriter): try: remote_reader, remote_writer = await open_connection( ip, port) @@ -298,7 +309,8 @@ async def handle_client(local_reader, local_writer): def stop_rdt_proxy(self): if self.rdt_proxy_server: self.rdt_proxy_server.close() - self.rdt_proxy_task.cancel() + if self.rdt_proxy_task: + self.rdt_proxy_task.cancel() async def _enable_rdt(self): # TODO un-hardcode port @@ -348,11 +360,11 @@ async def disable_rdt(self): await tab.evaluate_js("location.reload();", False, True, False) self.logger.info("React DevTools disabled") - async def get_user_info(self) -> dict: + async def get_user_info(self) -> Dict[str, str]: return { "username": get_username(), "path": get_home_path() } - async def get_tab_id(self, name): + async def get_tab_id(self, name: str): return (await get_tab(name)).id From f6401f4995cfe19a9891ddc372ee2b44d9bafbbe Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 25 Sep 2023 13:06:46 -0400 Subject: [PATCH 04/25] move to module imports --- .github/workflows/build-win.yml | 4 ++-- .github/workflows/build.yml | 2 +- backend/browser.py | 2 +- backend/helpers.py | 4 ++-- backend/loader.py | 6 ++++-- backend/localplatform.py | 8 ++++---- backend/localplatformlinux.py | 2 +- backend/localplatformwin.py | 2 +- backend/localsocket.py | 2 +- backend/main.py | 20 ++++++++++---------- backend/plugin.py | 8 ++++---- backend/settings.py | 6 +++--- backend/updater.py | 22 ++++++++++++++-------- backend/utilities.py | 20 +++++++++++--------- main.py | 4 ++++ 15 files changed, 63 insertions(+), 49 deletions(-) create mode 100644 main.py diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml index 4b54bc12a..16577da36 100644 --- a/.github/workflows/build-win.yml +++ b/.github/workflows/build-win.yml @@ -43,10 +43,10 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/legacy;/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/static;/backend/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/legacy;/backend/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./main.py - name: Build Python Backend (noconsole) 🛠️ - run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/legacy;/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/static;/backend/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/legacy;/backend/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./main.py - name: Upload package artifact ⬆️ uses: actions/upload-artifact@v3 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe58eecb1..04e932cd9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,7 +86,7 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/static --add-data ./backend/locales:/locales --add-data ./backend/legacy:/legacy --add-data ./plugin:/plugin --hidden-import=sqlite3 ./backend/*.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/backend/static --add-data ./backend/locales:/backend/locales --add-data ./backend/legacy:/backend/legacy --add-data ./plugin:/plugin --hidden-import=sqlite3 ./main.py - name: Upload package artifact ⬆️ if: ${{ !env.ACT }} diff --git a/backend/browser.py b/backend/browser.py index 358c05f9e..085607499 100644 --- a/backend/browser.py +++ b/backend/browser.py @@ -13,11 +13,11 @@ from shutil import rmtree from time import time from zipfile import ZipFile -from localplatform import chown, chmod from enum import IntEnum from typing import Dict, List, TypedDict # Local modules +from .localplatform import chown, chmod from .loader import Loader, Plugins from .helpers import get_ssl_context, download_remote_binary_to_path from .settings import SettingsManager diff --git a/backend/helpers.py b/backend/helpers.py index 4036db857..f8796bd81 100644 --- a/backend/helpers.py +++ b/backend/helpers.py @@ -10,8 +10,8 @@ from aiohttp.web import Request, Response, middleware from aiohttp.typedefs import Handler from aiohttp import ClientSession -import localplatform -from customtypes import UserType +from . import localplatform +from .customtypes import UserType from logging import getLogger REMOTE_DEBUGGER_UNIT = "steam-web-debug-portforward.service" diff --git a/backend/loader.py b/backend/loader.py index 89bb04572..dc7a56337 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -11,7 +11,9 @@ from watchdog.events import RegexMatchingEventHandler, DirCreatedEvent, DirModifiedEvent, FileCreatedEvent, FileModifiedEvent # type: ignore from watchdog.observers import Observer # type: ignore -from backend.main import PluginManager # type: ignore +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .main import PluginManager from .injector import get_tab, get_gamepadui_tab from .plugin import PluginWrapper @@ -63,7 +65,7 @@ def on_modified(self, event: DirModifiedEvent | FileModifiedEvent): self.maybe_reload(src_path) class Loader: - def __init__(self, server_instance: PluginManager, plugin_path: str, loop: AbstractEventLoop, live_reload: bool =False) -> None: + def __init__(self, server_instance: 'PluginManager', plugin_path: str, loop: AbstractEventLoop, live_reload: bool = False) -> None: self.loop = loop self.logger = getLogger("Loader") self.plugin_path = plugin_path diff --git a/backend/localplatform.py b/backend/localplatform.py index 43043ad03..028eff8fc 100644 --- a/backend/localplatform.py +++ b/backend/localplatform.py @@ -4,11 +4,11 @@ ON_LINUX = not ON_WINDOWS if ON_WINDOWS: - from localplatformwin import * - import localplatformwin as localplatform + from .localplatformwin import * + from . import localplatformwin as localplatform else: - from localplatformlinux import * - import localplatformlinux as localplatform + from .localplatformlinux import * + from . import localplatformlinux as localplatform def get_privileged_path() -> str: '''Get path accessible by elevated user. Holds plugins, decky loader and decky loader configs''' diff --git a/backend/localplatformlinux.py b/backend/localplatformlinux.py index 58b9dbc2c..bde2caac1 100644 --- a/backend/localplatformlinux.py +++ b/backend/localplatformlinux.py @@ -1,6 +1,6 @@ import os, pwd, grp, sys, logging from subprocess import call, run, DEVNULL, PIPE, STDOUT -from customtypes import UserType +from .customtypes import UserType logger = logging.getLogger("localplatform") diff --git a/backend/localplatformwin.py b/backend/localplatformwin.py index b6bee330b..4c4e94395 100644 --- a/backend/localplatformwin.py +++ b/backend/localplatformwin.py @@ -1,4 +1,4 @@ -from customtypes import UserType +from .customtypes import UserType import os, sys def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool = True) -> bool: diff --git a/backend/localsocket.py b/backend/localsocket.py index 3659da038..f38fe5e7e 100644 --- a/backend/localsocket.py +++ b/backend/localsocket.py @@ -2,7 +2,7 @@ from typing import Awaitable, Callable import random -from localplatform import ON_WINDOWS +from .localplatform import ON_WINDOWS BUFFER_LIMIT = 2 ** 20 # 1 MiB diff --git a/backend/main.py b/backend/main.py index 8857fb225..793d000c2 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,7 +1,7 @@ # Change PyInstaller files permissions import sys from typing import Dict -from localplatform import (chmod, chown, service_stop, service_start, +from .localplatform import (chmod, chown, service_stop, service_start, ON_WINDOWS, get_log_level, get_live_reload, get_server_port, get_server_host, get_chown_plugin_path, get_privileged_path) @@ -21,16 +21,16 @@ from aiohttp_jinja2 import setup as jinja_setup # local modules -from browser import PluginBrowser -from helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token, +from .browser import PluginBrowser +from .helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token, mkdir_as_user, get_system_pythonpaths, get_effective_user_id) -from injector import get_gamepadui_tab, Tab, close_old_tabs -from loader import Loader -from settings import SettingsManager -from updater import Updater -from utilities import Utilities -from customtypes import UserType +from .injector import get_gamepadui_tab, Tab, close_old_tabs +from .loader import Loader +from .settings import SettingsManager +from .updater import Updater +from .utilities import Utilities +from .customtypes import UserType basicConfig( @@ -169,7 +169,7 @@ async def inject_javascript(self, tab: Tab, first: bool=False, request: Request| def run(self): return run_app(self.web_app, host=get_server_host(), port=get_server_port(), loop=self.loop, access_log=None) -if __name__ == "__main__": +def main(): if ON_WINDOWS: # Fix windows/flask not recognising that .js means 'application/javascript' import mimetypes diff --git a/backend/plugin.py b/backend/plugin.py index 5c1e099fa..163bb9b64 100644 --- a/backend/plugin.py +++ b/backend/plugin.py @@ -9,10 +9,10 @@ from signal import SIGINT, signal from sys import exit, path as syspath from typing import Any, Dict -from localsocket import LocalSocket -from localplatform import setgid, setuid, get_username, get_home_path -from customtypes import UserType -import helpers +from .localsocket import LocalSocket +from .localplatform import setgid, setuid, get_username, get_home_path +from .customtypes import UserType +from . import helpers class PluginWrapper: def __init__(self, file: str, plugin_directory: str, plugin_path: str) -> None: diff --git a/backend/settings.py b/backend/settings.py index 26dfc97f7..a9ab3daac 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -1,10 +1,10 @@ from json import dump, load from os import mkdir, path, listdir, rename from typing import Any, Dict -from localplatform import chown, folder_owner, get_chown_plugin_path -from customtypes import UserType +from .localplatform import chown, folder_owner, get_chown_plugin_path +from .customtypes import UserType -from helpers import get_homebrew_path +from .helpers import get_homebrew_path class SettingsManager: diff --git a/backend/updater.py b/backend/updater.py index d7a3d7128..0bd7218ae 100644 --- a/backend/updater.py +++ b/backend/updater.py @@ -4,15 +4,16 @@ from json.decoder import JSONDecodeError from logging import getLogger from os import getcwd, path, remove -from typing import List, TypedDict -from backend.main import PluginManager -from localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux +from typing import TYPE_CHECKING, List, TypedDict +if TYPE_CHECKING: + from .main import PluginManager +from .localplatform import chmod, service_restart, ON_LINUX, get_keep_systemd_service, get_selinux from aiohttp import ClientSession, web -import helpers -from injector import get_gamepadui_tab -from settings import SettingsManager +from .import helpers +from .injector import get_gamepadui_tab +from .settings import SettingsManager logger = getLogger("Updater") @@ -25,7 +26,7 @@ class RemoteVer(TypedDict): assets: List[RemoteVerAsset] class Updater: - def __init__(self, context: PluginManager) -> None: + def __init__(self, context: 'PluginManager') -> None: self.context = context self.settings = self.context.settings # Exposes updater methods to frontend @@ -150,7 +151,12 @@ async def version_reloader(self): async def do_update(self): logger.debug("Starting update.") - assert self.remoteVer + try: + assert self.remoteVer + except AssertionError: + logger.error("Unable to update as remoteVer is missing") + return + version = self.remoteVer["tag_name"] download_url = None download_filename = "PluginLoader" if ON_LINUX else "PluginLoader.exe" diff --git a/backend/utilities.py b/backend/utilities.py index d45bec9bb..1e4110cff 100644 --- a/backend/utilities.py +++ b/backend/utilities.py @@ -8,16 +8,18 @@ from asyncio import StreamReader, StreamWriter, start_server, gather, open_connection from aiohttp import ClientSession, web -from typing import Callable, Coroutine, Dict, Any, List, TypedDict +from typing import TYPE_CHECKING, Callable, Coroutine, Dict, Any, List, TypedDict from logging import getLogger -from backend.browser import PluginInstallRequest, PluginInstallType -from backend.main import PluginManager -from injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab from pathlib import Path -from localplatform import ON_WINDOWS -import helpers -from localplatform import service_stop, service_start, get_home_path, get_username + +from .browser import PluginInstallRequest, PluginInstallType +if TYPE_CHECKING: + from .main import PluginManager +from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab +from .localplatform import ON_WINDOWS +from .import helpers +from .localplatform import service_stop, service_start, get_home_path, get_username class FilePickerObj(TypedDict): file: Path @@ -25,7 +27,7 @@ class FilePickerObj(TypedDict): is_dir: bool class Utilities: - def __init__(self, context: PluginManager) -> None: + def __init__(self, context: 'PluginManager') -> None: self.context = context self.util_methods: Dict[str, Callable[..., Coroutine[Any, Any, Any]]] = { "ping": self.ping, @@ -307,7 +309,7 @@ async def handle_client(local_reader: StreamReader, local_writer: StreamWriter): self.rdt_proxy_task = self.context.loop.create_task(self.rdt_proxy_server) def stop_rdt_proxy(self): - if self.rdt_proxy_server: + if self.rdt_proxy_server != None: self.rdt_proxy_server.close() if self.rdt_proxy_task: self.rdt_proxy_task.cancel() diff --git a/main.py b/main.py new file mode 100644 index 000000000..c2b990893 --- /dev/null +++ b/main.py @@ -0,0 +1,4 @@ +# This file is needed to make the relative imports in backend/ work properly. +if __name__ == "__main__": + from backend.main import main + main() From 5838ddca56fb9683ecf327200c522253b9e66d53 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 25 Sep 2023 13:09:33 -0400 Subject: [PATCH 05/25] add pyright ci --- .github/workflows/lint.yml | 24 ++++++++++++++++++++---- pyrightconfig.json | 3 +++ 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 pyrightconfig.json diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1d9444f65..6fb35586f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,8 +10,24 @@ jobs: steps: - uses: actions/checkout@v2 # Check out the repository first. - - name: Run prettier (JavaScript & TypeScript) + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + [ -f requirements.txt ] && pip install -r requirements.txt + + - name: Install JavaScript dependencies + working-directory: frontend run: | - pushd frontend - npm install - npm run lint + npm i -g pnpm + pnpm i --frozen-lockfile + + - name: Run pyright (Python) + uses: jakebailey/pyright-action@v1 + with: + python-version: "3.10.6" + no-comments: true + + - name: Run prettier (JavaScript & TypeScript) + working-directory: frontend + run: pnpm run lint \ No newline at end of file diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 000000000..9937f2277 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,3 @@ +{ + "strict": ["*"] +} \ No newline at end of file From 300885f7242d4acc9a3578d9d90cbd5880f5864f Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 25 Sep 2023 13:23:38 -0400 Subject: [PATCH 06/25] move type checking to other workflow, fix TS errors, add TSC checking --- .github/workflows/lint.yml | 15 ++------- .github/workflows/typecheck.yml | 33 +++++++++++++++++++ .../components/modals/filepicker/index.tsx | 9 +++-- .../settings/pages/general/BranchSelect.tsx | 6 ++-- .../settings/pages/general/StoreSelect.tsx | 6 ++-- frontend/tsconfig.json | 1 - 6 files changed, 48 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/typecheck.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6fb35586f..24e756bf3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -11,23 +11,12 @@ jobs: steps: - uses: actions/checkout@v2 # Check out the repository first. - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - [ -f requirements.txt ] && pip install -r requirements.txt - - - name: Install JavaScript dependencies + - name: Install TypeScript dependencies working-directory: frontend run: | npm i -g pnpm pnpm i --frozen-lockfile - - name: Run pyright (Python) - uses: jakebailey/pyright-action@v1 - with: - python-version: "3.10.6" - no-comments: true - - - name: Run prettier (JavaScript & TypeScript) + - name: Run prettier (TypeScript) working-directory: frontend run: pnpm run lint \ No newline at end of file diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml new file mode 100644 index 000000000..d54c40483 --- /dev/null +++ b/.github/workflows/typecheck.yml @@ -0,0 +1,33 @@ +name: Type check + +on: + push: + +jobs: + typecheck: + name: Run type checkers + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 # Check out the repository first. + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + [ -f requirements.txt ] && pip install -r requirements.txt + + - name: Install TypeScript dependencies + working-directory: frontend + run: | + npm i -g pnpm + pnpm i --frozen-lockfile + + - name: Run pyright (Python) + uses: jakebailey/pyright-action@v1 + with: + python-version: "3.10.6" + no-comments: true + + - name: Run tsc (TypeScript) + working-directory: frontend + run: $(pnpm bin)/tsc --noEmit \ No newline at end of file diff --git a/frontend/src/components/modals/filepicker/index.tsx b/frontend/src/components/modals/filepicker/index.tsx index ae745c9ce..c4e72d95b 100644 --- a/frontend/src/components/modals/filepicker/index.tsx +++ b/frontend/src/components/modals/filepicker/index.tsx @@ -13,7 +13,7 @@ import { } from 'decky-frontend-lib'; import { filesize } from 'filesize'; import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'; -import { FileIcon, defaultStyles } from 'react-file-icon'; +import { DefaultExtensionType, FileIcon, defaultStyles } from 'react-file-icon'; import { useTranslation } from 'react-i18next'; import { FaArrowUp, FaFolder } from 'react-icons/fa'; @@ -316,7 +316,12 @@ const FilePicker: FunctionComponent = ({ ) : (
{file.realpath.includes('.') ? ( - + ) : ( )} diff --git a/frontend/src/components/settings/pages/general/BranchSelect.tsx b/frontend/src/components/settings/pages/general/BranchSelect.tsx index 9b304f3a8..1af068232 100644 --- a/frontend/src/components/settings/pages/general/BranchSelect.tsx +++ b/frontend/src/components/settings/pages/general/BranchSelect.tsx @@ -29,10 +29,10 @@ const BranchSelect: FunctionComponent<{}> = () => { typeof branch == 'string') + .filter((branch) => typeof branch == 'number') .map((branch) => ({ - label: tBranches[UpdateBranch[branch]], - data: UpdateBranch[branch], + label: tBranches[branch as number], + data: branch, }))} selectedOption={selectedBranch} onChange={async (newVal) => { diff --git a/frontend/src/components/settings/pages/general/StoreSelect.tsx b/frontend/src/components/settings/pages/general/StoreSelect.tsx index ebf1bd819..3cb80303e 100644 --- a/frontend/src/components/settings/pages/general/StoreSelect.tsx +++ b/frontend/src/components/settings/pages/general/StoreSelect.tsx @@ -26,10 +26,10 @@ const StoreSelect: FunctionComponent<{}> = () => { typeof store == 'string') + .filter((store) => typeof store == 'number') .map((store) => ({ - label: tStores[Store[store]], - data: Store[store], + label: tStores[store as number], + data: store, }))} selectedOption={selectedStore} onChange={async (newVal) => { diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index e693d3e92..6b18e4e43 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -14,7 +14,6 @@ "noImplicitThis": true, "noImplicitAny": true, "strict": true, - "suppressImplicitAnyIndexErrors": true, "allowSyntheticDefaultImports": true, "skipLibCheck": true, "resolveJsonModule": true From e22cc6269d04cc9d034cd0e8cffebf9146b7df61 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 25 Sep 2023 13:24:31 -0400 Subject: [PATCH 07/25] make ci title consistent --- .github/workflows/typecheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index d54c40483..d55a8f986 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -1,4 +1,4 @@ -name: Type check +name: Type Check on: push: From b81c41f667faba8d59a7f4c9705ce3375cb7966d Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 25 Sep 2023 13:28:15 -0400 Subject: [PATCH 08/25] remove quotes on some types --- backend/loader.py | 3 ++- backend/updater.py | 2 +- backend/utilities.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/loader.py b/backend/loader.py index dc7a56337..684570f7d 100644 --- a/backend/loader.py +++ b/backend/loader.py @@ -1,3 +1,4 @@ +from __future__ import annotations from asyncio import AbstractEventLoop, Queue, sleep from json.decoder import JSONDecodeError from logging import getLogger @@ -65,7 +66,7 @@ def on_modified(self, event: DirModifiedEvent | FileModifiedEvent): self.maybe_reload(src_path) class Loader: - def __init__(self, server_instance: 'PluginManager', plugin_path: str, loop: AbstractEventLoop, live_reload: bool = False) -> None: + def __init__(self, server_instance: PluginManager, plugin_path: str, loop: AbstractEventLoop, live_reload: bool = False) -> None: self.loop = loop self.logger = getLogger("Loader") self.plugin_path = plugin_path diff --git a/backend/updater.py b/backend/updater.py index 0bd7218ae..ac7c78d82 100644 --- a/backend/updater.py +++ b/backend/updater.py @@ -26,7 +26,7 @@ class RemoteVer(TypedDict): assets: List[RemoteVerAsset] class Updater: - def __init__(self, context: 'PluginManager') -> None: + def __init__(self, context: PluginManager) -> None: self.context = context self.settings = self.context.settings # Exposes updater methods to frontend diff --git a/backend/utilities.py b/backend/utilities.py index 1e4110cff..3c7c8c2e1 100644 --- a/backend/utilities.py +++ b/backend/utilities.py @@ -1,3 +1,4 @@ +from __future__ import annotations from os import stat_result import uuid from json.decoder import JSONDecodeError @@ -27,7 +28,7 @@ class FilePickerObj(TypedDict): is_dir: bool class Utilities: - def __init__(self, context: 'PluginManager') -> None: + def __init__(self, context: PluginManager) -> None: self.context = context self.util_methods: Dict[str, Callable[..., Coroutine[Any, Any, Any]]] = { "ping": self.ping, From 2d68809c1be8e469a623e8e28ffefbf819160dae Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 25 Sep 2023 13:37:15 -0400 Subject: [PATCH 09/25] run lint and typecheck on PRs --- .github/workflows/lint.yml | 1 + .github/workflows/typecheck.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 24e756bf3..b2a930573 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -2,6 +2,7 @@ name: Lint on: push: + pull_request: jobs: lint: diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index d55a8f986..056b4f9f5 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -2,6 +2,7 @@ name: Type Check on: push: + pull_request: jobs: typecheck: From 3960d28b066805109fbf7a3b02e91fa2dbeafb84 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Mon, 25 Sep 2023 13:37:28 -0400 Subject: [PATCH 10/25] with, not env --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 04e932cd9..3c09beb94 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,7 +127,7 @@ jobs: - name: Get latest release uses: rez0n/actions-github-release@main id: latest_release - env: + with: token: ${{ secrets.GITHUB_TOKEN }} repository: "SteamDeckHomebrew/decky-loader" type: "nodraft" @@ -206,7 +206,7 @@ jobs: - name: Get latest release uses: rez0n/actions-github-release@main id: latest_release - env: + with: token: ${{ secrets.GITHUB_TOKEN }} repository: "SteamDeckHomebrew/decky-loader" type: "nodraft" From 0c2079fa85b0576fefed78a749b615088bf9d798 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Tue, 26 Sep 2023 14:54:52 +0200 Subject: [PATCH 11/25] Moved backend entirely into the backend folder --- .github/workflows/build-win.yml | 4 ++-- .github/workflows/build.yml | 2 +- backend/{ => src}/browser.py | 0 backend/{ => src}/customtypes.py | 0 backend/{ => src}/helpers.py | 0 backend/{ => src}/injector.py | 0 backend/{ => src}/legacy/library.js | 0 backend/{ => src}/loader.py | 0 backend/{ => src}/locales/bg-BG.json | 0 backend/{ => src}/locales/cs-CZ.json | 0 backend/{ => src}/locales/de-DE.json | 0 backend/{ => src}/locales/el-GR.json | 0 backend/{ => src}/locales/en-US.json | 0 backend/{ => src}/locales/es-ES.json | 0 backend/{ => src}/locales/fi-FI.json | 0 backend/{ => src}/locales/fr-FR.json | 0 backend/{ => src}/locales/it-IT.json | 0 backend/{ => src}/locales/ko-KR.json | 0 backend/{ => src}/locales/nl-NL.json | 0 backend/{ => src}/locales/pl-PL.json | 0 backend/{ => src}/locales/pt-BR.json | 0 backend/{ => src}/locales/pt-PT.json | 0 backend/{ => src}/locales/ru-RU.json | 0 backend/{ => src}/locales/sq-AL.json | 0 backend/{ => src}/locales/uk-UA.json | 0 backend/{ => src}/locales/zh-CN.json | 0 backend/{ => src}/locales/zh-TW.json | 0 backend/{ => src}/localplatform.py | 0 backend/{ => src}/localplatformlinux.py | 0 backend/{ => src}/localplatformwin.py | 0 backend/{ => src}/localsocket.py | 0 backend/{ => src}/main.py | 0 backend/{ => src}/plugin.py | 0 backend/{ => src}/settings.py | 0 backend/{ => src}/updater.py | 0 backend/{ => src}/utilities.py | 0 36 files changed, 3 insertions(+), 3 deletions(-) rename backend/{ => src}/browser.py (100%) rename backend/{ => src}/customtypes.py (100%) rename backend/{ => src}/helpers.py (100%) rename backend/{ => src}/injector.py (100%) rename backend/{ => src}/legacy/library.js (100%) rename backend/{ => src}/loader.py (100%) rename backend/{ => src}/locales/bg-BG.json (100%) rename backend/{ => src}/locales/cs-CZ.json (100%) rename backend/{ => src}/locales/de-DE.json (100%) rename backend/{ => src}/locales/el-GR.json (100%) rename backend/{ => src}/locales/en-US.json (100%) rename backend/{ => src}/locales/es-ES.json (100%) rename backend/{ => src}/locales/fi-FI.json (100%) rename backend/{ => src}/locales/fr-FR.json (100%) rename backend/{ => src}/locales/it-IT.json (100%) rename backend/{ => src}/locales/ko-KR.json (100%) rename backend/{ => src}/locales/nl-NL.json (100%) rename backend/{ => src}/locales/pl-PL.json (100%) rename backend/{ => src}/locales/pt-BR.json (100%) rename backend/{ => src}/locales/pt-PT.json (100%) rename backend/{ => src}/locales/ru-RU.json (100%) rename backend/{ => src}/locales/sq-AL.json (100%) rename backend/{ => src}/locales/uk-UA.json (100%) rename backend/{ => src}/locales/zh-CN.json (100%) rename backend/{ => src}/locales/zh-TW.json (100%) rename backend/{ => src}/localplatform.py (100%) rename backend/{ => src}/localplatformlinux.py (100%) rename backend/{ => src}/localplatformwin.py (100%) rename backend/{ => src}/localsocket.py (100%) rename backend/{ => src}/main.py (100%) rename backend/{ => src}/plugin.py (100%) rename backend/{ => src}/settings.py (100%) rename backend/{ => src}/updater.py (100%) rename backend/{ => src}/utilities.py (100%) diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml index 16577da36..a0fd13b98 100644 --- a/.github/workflows/build-win.yml +++ b/.github/workflows/build-win.yml @@ -43,10 +43,10 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/static;/backend/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/legacy;/backend/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./main.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/src/static;/backend/src/static" --add-data "./backend/src/locales;/backend/src/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py - name: Build Python Backend (noconsole) 🛠️ - run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/static;/backend/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/legacy;/backend/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./main.py + run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/src/static;/backend/src/static" --add-data "./backend/src/locales;/backend/src/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py - name: Upload package artifact ⬆️ uses: actions/upload-artifact@v3 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3c09beb94..70b68f964 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,7 +86,7 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/backend/static --add-data ./backend/locales:/backend/locales --add-data ./backend/legacy:/backend/legacy --add-data ./plugin:/plugin --hidden-import=sqlite3 ./main.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/src/static:/backend/src/static --add-data ./backend/src/locales:/backend/src/locales --add-data ./backend/src/legacy:/backend/src/legacy --add-data ./plugin:/plugin --hidden-import=sqlite3 ./backend/main.py - name: Upload package artifact ⬆️ if: ${{ !env.ACT }} diff --git a/backend/browser.py b/backend/src/browser.py similarity index 100% rename from backend/browser.py rename to backend/src/browser.py diff --git a/backend/customtypes.py b/backend/src/customtypes.py similarity index 100% rename from backend/customtypes.py rename to backend/src/customtypes.py diff --git a/backend/helpers.py b/backend/src/helpers.py similarity index 100% rename from backend/helpers.py rename to backend/src/helpers.py diff --git a/backend/injector.py b/backend/src/injector.py similarity index 100% rename from backend/injector.py rename to backend/src/injector.py diff --git a/backend/legacy/library.js b/backend/src/legacy/library.js similarity index 100% rename from backend/legacy/library.js rename to backend/src/legacy/library.js diff --git a/backend/loader.py b/backend/src/loader.py similarity index 100% rename from backend/loader.py rename to backend/src/loader.py diff --git a/backend/locales/bg-BG.json b/backend/src/locales/bg-BG.json similarity index 100% rename from backend/locales/bg-BG.json rename to backend/src/locales/bg-BG.json diff --git a/backend/locales/cs-CZ.json b/backend/src/locales/cs-CZ.json similarity index 100% rename from backend/locales/cs-CZ.json rename to backend/src/locales/cs-CZ.json diff --git a/backend/locales/de-DE.json b/backend/src/locales/de-DE.json similarity index 100% rename from backend/locales/de-DE.json rename to backend/src/locales/de-DE.json diff --git a/backend/locales/el-GR.json b/backend/src/locales/el-GR.json similarity index 100% rename from backend/locales/el-GR.json rename to backend/src/locales/el-GR.json diff --git a/backend/locales/en-US.json b/backend/src/locales/en-US.json similarity index 100% rename from backend/locales/en-US.json rename to backend/src/locales/en-US.json diff --git a/backend/locales/es-ES.json b/backend/src/locales/es-ES.json similarity index 100% rename from backend/locales/es-ES.json rename to backend/src/locales/es-ES.json diff --git a/backend/locales/fi-FI.json b/backend/src/locales/fi-FI.json similarity index 100% rename from backend/locales/fi-FI.json rename to backend/src/locales/fi-FI.json diff --git a/backend/locales/fr-FR.json b/backend/src/locales/fr-FR.json similarity index 100% rename from backend/locales/fr-FR.json rename to backend/src/locales/fr-FR.json diff --git a/backend/locales/it-IT.json b/backend/src/locales/it-IT.json similarity index 100% rename from backend/locales/it-IT.json rename to backend/src/locales/it-IT.json diff --git a/backend/locales/ko-KR.json b/backend/src/locales/ko-KR.json similarity index 100% rename from backend/locales/ko-KR.json rename to backend/src/locales/ko-KR.json diff --git a/backend/locales/nl-NL.json b/backend/src/locales/nl-NL.json similarity index 100% rename from backend/locales/nl-NL.json rename to backend/src/locales/nl-NL.json diff --git a/backend/locales/pl-PL.json b/backend/src/locales/pl-PL.json similarity index 100% rename from backend/locales/pl-PL.json rename to backend/src/locales/pl-PL.json diff --git a/backend/locales/pt-BR.json b/backend/src/locales/pt-BR.json similarity index 100% rename from backend/locales/pt-BR.json rename to backend/src/locales/pt-BR.json diff --git a/backend/locales/pt-PT.json b/backend/src/locales/pt-PT.json similarity index 100% rename from backend/locales/pt-PT.json rename to backend/src/locales/pt-PT.json diff --git a/backend/locales/ru-RU.json b/backend/src/locales/ru-RU.json similarity index 100% rename from backend/locales/ru-RU.json rename to backend/src/locales/ru-RU.json diff --git a/backend/locales/sq-AL.json b/backend/src/locales/sq-AL.json similarity index 100% rename from backend/locales/sq-AL.json rename to backend/src/locales/sq-AL.json diff --git a/backend/locales/uk-UA.json b/backend/src/locales/uk-UA.json similarity index 100% rename from backend/locales/uk-UA.json rename to backend/src/locales/uk-UA.json diff --git a/backend/locales/zh-CN.json b/backend/src/locales/zh-CN.json similarity index 100% rename from backend/locales/zh-CN.json rename to backend/src/locales/zh-CN.json diff --git a/backend/locales/zh-TW.json b/backend/src/locales/zh-TW.json similarity index 100% rename from backend/locales/zh-TW.json rename to backend/src/locales/zh-TW.json diff --git a/backend/localplatform.py b/backend/src/localplatform.py similarity index 100% rename from backend/localplatform.py rename to backend/src/localplatform.py diff --git a/backend/localplatformlinux.py b/backend/src/localplatformlinux.py similarity index 100% rename from backend/localplatformlinux.py rename to backend/src/localplatformlinux.py diff --git a/backend/localplatformwin.py b/backend/src/localplatformwin.py similarity index 100% rename from backend/localplatformwin.py rename to backend/src/localplatformwin.py diff --git a/backend/localsocket.py b/backend/src/localsocket.py similarity index 100% rename from backend/localsocket.py rename to backend/src/localsocket.py diff --git a/backend/main.py b/backend/src/main.py similarity index 100% rename from backend/main.py rename to backend/src/main.py diff --git a/backend/plugin.py b/backend/src/plugin.py similarity index 100% rename from backend/plugin.py rename to backend/src/plugin.py diff --git a/backend/settings.py b/backend/src/settings.py similarity index 100% rename from backend/settings.py rename to backend/src/settings.py diff --git a/backend/updater.py b/backend/src/updater.py similarity index 100% rename from backend/updater.py rename to backend/src/updater.py diff --git a/backend/utilities.py b/backend/src/utilities.py similarity index 100% rename from backend/utilities.py rename to backend/src/utilities.py From a0d50baacaee5c721973ffccc44792e4fb3ba379 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Tue, 26 Sep 2023 14:55:09 +0200 Subject: [PATCH 12/25] Moved main.py --- main.py => backend/main.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename main.py => backend/main.py (100%) diff --git a/main.py b/backend/main.py similarity index 100% rename from main.py rename to backend/main.py From 75ae7dfe6965fc4820f0a057ae9e44519f962da5 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Tue, 26 Sep 2023 14:58:27 +0200 Subject: [PATCH 13/25] Moved locales folder and requirements.txt --- .github/workflows/build-win.yml | 6 +++--- .github/workflows/build.yml | 4 ++-- backend/{src => }/locales/bg-BG.json | 0 backend/{src => }/locales/cs-CZ.json | 0 backend/{src => }/locales/de-DE.json | 0 backend/{src => }/locales/el-GR.json | 0 backend/{src => }/locales/en-US.json | 0 backend/{src => }/locales/es-ES.json | 0 backend/{src => }/locales/fi-FI.json | 0 backend/{src => }/locales/fr-FR.json | 0 backend/{src => }/locales/it-IT.json | 0 backend/{src => }/locales/ko-KR.json | 0 backend/{src => }/locales/nl-NL.json | 0 backend/{src => }/locales/pl-PL.json | 0 backend/{src => }/locales/pt-BR.json | 0 backend/{src => }/locales/pt-PT.json | 0 backend/{src => }/locales/ru-RU.json | 0 backend/{src => }/locales/sq-AL.json | 0 backend/{src => }/locales/uk-UA.json | 0 backend/{src => }/locales/zh-CN.json | 0 backend/{src => }/locales/zh-TW.json | 0 requirements.txt => backend/requirements.txt | 0 22 files changed, 5 insertions(+), 5 deletions(-) rename backend/{src => }/locales/bg-BG.json (100%) rename backend/{src => }/locales/cs-CZ.json (100%) rename backend/{src => }/locales/de-DE.json (100%) rename backend/{src => }/locales/el-GR.json (100%) rename backend/{src => }/locales/en-US.json (100%) rename backend/{src => }/locales/es-ES.json (100%) rename backend/{src => }/locales/fi-FI.json (100%) rename backend/{src => }/locales/fr-FR.json (100%) rename backend/{src => }/locales/it-IT.json (100%) rename backend/{src => }/locales/ko-KR.json (100%) rename backend/{src => }/locales/nl-NL.json (100%) rename backend/{src => }/locales/pl-PL.json (100%) rename backend/{src => }/locales/pt-BR.json (100%) rename backend/{src => }/locales/pt-PT.json (100%) rename backend/{src => }/locales/ru-RU.json (100%) rename backend/{src => }/locales/sq-AL.json (100%) rename backend/{src => }/locales/uk-UA.json (100%) rename backend/{src => }/locales/zh-CN.json (100%) rename backend/{src => }/locales/zh-TW.json (100%) rename requirements.txt => backend/requirements.txt (100%) diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml index a0fd13b98..b1eee0dd5 100644 --- a/.github/workflows/build-win.yml +++ b/.github/workflows/build-win.yml @@ -30,7 +30,7 @@ jobs: run: | python -m pip install --upgrade pip pip install pyinstaller==5.13.0 - pip install -r requirements.txt + pip install -r backend/requirements.txt - name: Install JS dependencies ⬇️ working-directory: ./frontend @@ -43,10 +43,10 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/src/static;/backend/src/static" --add-data "./backend/src/locales;/backend/src/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/src/static;/backend/src/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py - name: Build Python Backend (noconsole) 🛠️ - run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/src/static;/backend/src/static" --add-data "./backend/src/locales;/backend/src/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/src/static;/backend/src/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py - name: Upload package artifact ⬆️ uses: actions/upload-artifact@v3 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 70b68f964..31d01bef5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -73,7 +73,7 @@ jobs: run: | python -m pip install --upgrade pip pip install pyinstaller==5.13.0 - [ -f requirements.txt ] && pip install -r requirements.txt + [ -f backend/requirements.txt ] && pip install -r backend/requirements.txt - name: Install JS dependencies ⬇️ working-directory: ./frontend @@ -86,7 +86,7 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/src/static:/backend/src/static --add-data ./backend/src/locales:/backend/src/locales --add-data ./backend/src/legacy:/backend/src/legacy --add-data ./plugin:/plugin --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/src/static:/backend/src/static --add-data ./backend/locales:/backend/locales --add-data ./backend/src/legacy:/backend/src/legacy --add-data ./plugin:/plugin --hidden-import=sqlite3 ./backend/main.py - name: Upload package artifact ⬆️ if: ${{ !env.ACT }} diff --git a/backend/src/locales/bg-BG.json b/backend/locales/bg-BG.json similarity index 100% rename from backend/src/locales/bg-BG.json rename to backend/locales/bg-BG.json diff --git a/backend/src/locales/cs-CZ.json b/backend/locales/cs-CZ.json similarity index 100% rename from backend/src/locales/cs-CZ.json rename to backend/locales/cs-CZ.json diff --git a/backend/src/locales/de-DE.json b/backend/locales/de-DE.json similarity index 100% rename from backend/src/locales/de-DE.json rename to backend/locales/de-DE.json diff --git a/backend/src/locales/el-GR.json b/backend/locales/el-GR.json similarity index 100% rename from backend/src/locales/el-GR.json rename to backend/locales/el-GR.json diff --git a/backend/src/locales/en-US.json b/backend/locales/en-US.json similarity index 100% rename from backend/src/locales/en-US.json rename to backend/locales/en-US.json diff --git a/backend/src/locales/es-ES.json b/backend/locales/es-ES.json similarity index 100% rename from backend/src/locales/es-ES.json rename to backend/locales/es-ES.json diff --git a/backend/src/locales/fi-FI.json b/backend/locales/fi-FI.json similarity index 100% rename from backend/src/locales/fi-FI.json rename to backend/locales/fi-FI.json diff --git a/backend/src/locales/fr-FR.json b/backend/locales/fr-FR.json similarity index 100% rename from backend/src/locales/fr-FR.json rename to backend/locales/fr-FR.json diff --git a/backend/src/locales/it-IT.json b/backend/locales/it-IT.json similarity index 100% rename from backend/src/locales/it-IT.json rename to backend/locales/it-IT.json diff --git a/backend/src/locales/ko-KR.json b/backend/locales/ko-KR.json similarity index 100% rename from backend/src/locales/ko-KR.json rename to backend/locales/ko-KR.json diff --git a/backend/src/locales/nl-NL.json b/backend/locales/nl-NL.json similarity index 100% rename from backend/src/locales/nl-NL.json rename to backend/locales/nl-NL.json diff --git a/backend/src/locales/pl-PL.json b/backend/locales/pl-PL.json similarity index 100% rename from backend/src/locales/pl-PL.json rename to backend/locales/pl-PL.json diff --git a/backend/src/locales/pt-BR.json b/backend/locales/pt-BR.json similarity index 100% rename from backend/src/locales/pt-BR.json rename to backend/locales/pt-BR.json diff --git a/backend/src/locales/pt-PT.json b/backend/locales/pt-PT.json similarity index 100% rename from backend/src/locales/pt-PT.json rename to backend/locales/pt-PT.json diff --git a/backend/src/locales/ru-RU.json b/backend/locales/ru-RU.json similarity index 100% rename from backend/src/locales/ru-RU.json rename to backend/locales/ru-RU.json diff --git a/backend/src/locales/sq-AL.json b/backend/locales/sq-AL.json similarity index 100% rename from backend/src/locales/sq-AL.json rename to backend/locales/sq-AL.json diff --git a/backend/src/locales/uk-UA.json b/backend/locales/uk-UA.json similarity index 100% rename from backend/src/locales/uk-UA.json rename to backend/locales/uk-UA.json diff --git a/backend/src/locales/zh-CN.json b/backend/locales/zh-CN.json similarity index 100% rename from backend/src/locales/zh-CN.json rename to backend/locales/zh-CN.json diff --git a/backend/src/locales/zh-TW.json b/backend/locales/zh-TW.json similarity index 100% rename from backend/src/locales/zh-TW.json rename to backend/locales/zh-TW.json diff --git a/requirements.txt b/backend/requirements.txt similarity index 100% rename from requirements.txt rename to backend/requirements.txt From b7043655b3cef8acf3274c0d9ef6d92f6b0e3f68 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 30 Sep 2023 12:36:17 -0400 Subject: [PATCH 14/25] speed up stupid make --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31d01bef5..07b5b8043 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ jobs: -DSQLITE_ENABLE_UNLOCK_NOTIFY -DSQLITE_ENABLE_DBSTAT_VTAB=1 -DSQLITE_ENABLE_FTS3_TOKENIZER=1 \ -DSQLITE_ENABLE_FTS3_PARENTHESIS -DSQLITE_SECURE_DELETE -DSQLITE_ENABLE_STMTVTAB -DSQLITE_MAX_VARIABLE_NUMBER=250000 \ -DSQLITE_MAX_EXPR_DEPTH=10000 -DSQLITE_ENABLE_MATH_FUNCTIONS" && - make && + make -j$(nproc) && sudo make install && sudo cp /usr/lib/libsqlite3.so /usr/lib/x86_64-linux-gnu/ && sudo cp /usr/lib/libsqlite3.so.0 /usr/lib/x86_64-linux-gnu/ && From 00e10be93f8231aa296aea2ef9b4a6defebe62b4 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 30 Sep 2023 12:42:02 -0400 Subject: [PATCH 15/25] fix ci (hopefully, because act wont work) --- .github/workflows/build-win.yml | 5 +++-- .github/workflows/build.yml | 3 ++- .github/workflows/typecheck.yml | 2 ++ pyrightconfig.json => backend/pyrightconfig.json | 0 4 files changed, 7 insertions(+), 3 deletions(-) rename pyrightconfig.json => backend/pyrightconfig.json (100%) diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml index b1eee0dd5..6b4051daa 100644 --- a/.github/workflows/build-win.yml +++ b/.github/workflows/build-win.yml @@ -27,6 +27,7 @@ jobs: python-version: "3.11.4" - name: Install Python dependencies ⬇️ + working-directory: ./backend run: | python -m pip install --upgrade pip pip install pyinstaller==5.13.0 @@ -43,10 +44,10 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/src/static;/backend/src/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/static;/backend/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py - name: Build Python Backend (noconsole) 🛠️ - run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/src/static;/backend/src/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/static;/backend/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py - name: Upload package artifact ⬆️ uses: actions/upload-artifact@v3 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 07b5b8043..3b37cc10b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,6 +70,7 @@ jobs: rm -r /tmp/sqlite-autoconf-3420000 - name: Install Python dependencies ⬇️ + working-directory: ./backend run: | python -m pip install --upgrade pip pip install pyinstaller==5.13.0 @@ -86,7 +87,7 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/src/static:/backend/src/static --add-data ./backend/locales:/backend/locales --add-data ./backend/src/legacy:/backend/src/legacy --add-data ./plugin:/plugin --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/backend/static --add-data ./backend/locales:/backend/locales --add-data ./backend/src/legacy:/backend/src/legacy --add-data ./plugin:/plugin --hidden-import=sqlite3 ./backend/main.py - name: Upload package artifact ⬆️ if: ${{ !env.ACT }} diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 056b4f9f5..01a4bdff9 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -13,6 +13,7 @@ jobs: - uses: actions/checkout@v2 # Check out the repository first. - name: Install Python dependencies + working-directory: backend run: | python -m pip install --upgrade pip [ -f requirements.txt ] && pip install -r requirements.txt @@ -28,6 +29,7 @@ jobs: with: python-version: "3.10.6" no-comments: true + working-directory: backend - name: Run tsc (TypeScript) working-directory: frontend diff --git a/pyrightconfig.json b/backend/pyrightconfig.json similarity index 100% rename from pyrightconfig.json rename to backend/pyrightconfig.json From 4b89fc1f9d7b8884d5cda6e9c1fae112025c2a80 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 30 Sep 2023 12:43:35 -0400 Subject: [PATCH 16/25] fix broken import --- backend/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/main.py b/backend/main.py index c2b990893..46a0671a9 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,4 +1,4 @@ -# This file is needed to make the relative imports in backend/ work properly. +# This file is needed to make the relative imports in src/ work properly. if __name__ == "__main__": - from backend.main import main + from src.main import main main() From e8cbeb18054907f2f87382e423d4a5e911d9672f Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 30 Sep 2023 12:46:48 -0400 Subject: [PATCH 17/25] oops --- .github/workflows/build-win.yml | 2 +- .github/workflows/build.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml index 6b4051daa..53d1e964e 100644 --- a/.github/workflows/build-win.yml +++ b/.github/workflows/build-win.yml @@ -31,7 +31,7 @@ jobs: run: | python -m pip install --upgrade pip pip install pyinstaller==5.13.0 - pip install -r backend/requirements.txt + pip install -r requirements.txt - name: Install JS dependencies ⬇️ working-directory: ./frontend diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b37cc10b..978acc4c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -74,7 +74,7 @@ jobs: run: | python -m pip install --upgrade pip pip install pyinstaller==5.13.0 - [ -f backend/requirements.txt ] && pip install -r backend/requirements.txt + pip install -r requirements.txt - name: Install JS dependencies ⬇️ working-directory: ./frontend From ade7cb7640484a73fc04924c1b3fed8ef482ca24 Mon Sep 17 00:00:00 2001 From: AAGaming Date: Sat, 30 Sep 2023 13:15:35 -0400 Subject: [PATCH 18/25] fix paths --- .github/workflows/build-win.yml | 4 ++-- .github/workflows/build.yml | 2 +- backend/src/loader.py | 4 ++-- backend/src/main.py | 2 +- backend/src/updater.py | 3 ++- backend/src/utilities.py | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml index 53d1e964e..1ce47d4a9 100644 --- a/.github/workflows/build-win.yml +++ b/.github/workflows/build-win.yml @@ -44,10 +44,10 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/static;/backend/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/src/legacy;/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py - name: Build Python Backend (noconsole) 🛠️ - run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/static;/backend/static" --add-data "./backend/locales;/backend/locales" --add-data "./backend/src/legacy;/backend/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/src/legacy;/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py - name: Upload package artifact ⬆️ uses: actions/upload-artifact@v3 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 978acc4c6..2d617db39 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,7 +87,7 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/backend/static --add-data ./backend/locales:/backend/locales --add-data ./backend/src/legacy:/backend/src/legacy --add-data ./plugin:/plugin --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/static --add-data ./backend/locales:/locales --add-data ./backend/src/legacy:/src/legacy --add-data ./plugin:/plugin --hidden-import=sqlite3 ./backend/main.py - name: Upload package artifact ⬆️ if: ${{ !env.ACT }} diff --git a/backend/src/loader.py b/backend/src/loader.py index 684570f7d..e59cbcaff 100644 --- a/backend/src/loader.py +++ b/backend/src/loader.py @@ -107,13 +107,13 @@ async def enable_reload_wait(self): self.watcher.disabled = False async def handle_frontend_assets(self, request: web.Request): - file = path.join(path.dirname(__file__), "static", request.match_info["path"]) + file = path.join(path.dirname(__file__), "..", "static", request.match_info["path"]) return web.FileResponse(file, headers={"Cache-Control": "no-cache"}) async def handle_frontend_locales(self, request: web.Request): req_lang = request.match_info["path"] - file = path.join(path.dirname(__file__), "locales", req_lang) + file = path.join(path.dirname(__file__), "..", "locales", req_lang) if exists(file): return web.FileResponse(file, headers={"Cache-Control": "no-cache", "Content-Type": "application/json"}) else: diff --git a/backend/src/main.py b/backend/src/main.py index 793d000c2..83a4b9974 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -86,7 +86,7 @@ async def startup(_: Application): for route in list(self.web_app.router.routes()): self.cors.add(route) # type: ignore - self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))]) + 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'))]) def exception_handler(self, loop: AbstractEventLoop, context: Dict[str, str]): diff --git a/backend/src/updater.py b/backend/src/updater.py index ac7c78d82..d28e67b0d 100644 --- a/backend/src/updater.py +++ b/backend/src/updater.py @@ -1,3 +1,4 @@ +from __future__ import annotations import os import shutil from asyncio import sleep @@ -11,7 +12,7 @@ from aiohttp import ClientSession, web -from .import helpers +from . import helpers from .injector import get_gamepadui_tab from .settings import SettingsManager diff --git a/backend/src/utilities.py b/backend/src/utilities.py index 3c7c8c2e1..b0e23b88d 100644 --- a/backend/src/utilities.py +++ b/backend/src/utilities.py @@ -19,7 +19,7 @@ from .main import PluginManager from .injector import inject_to_tab, get_gamepadui_tab, close_old_tabs, get_tab from .localplatform import ON_WINDOWS -from .import helpers +from . import helpers from .localplatform import service_stop, service_start, get_home_path, get_username class FilePickerObj(TypedDict): From 4a174741337a8f5b1394d4389e909c404a9fc49d Mon Sep 17 00:00:00 2001 From: marios8543 Date: Wed, 11 Oct 2023 23:46:26 +0300 Subject: [PATCH 19/25] fix decky_plugin path in pyinstaller --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d617db39..a2b1e29fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,7 +87,7 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/static --add-data ./backend/locales:/locales --add-data ./backend/src/legacy:/src/legacy --add-data ./plugin:/plugin --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/static --add-data ./backend/locales:/locales --add-data ./backend/src/legacy:/src/legacy --add-data ./plugin/*:/ --hidden-import=sqlite3 ./backend/main.py - name: Upload package artifact ⬆️ if: ${{ !env.ACT }} From 944e0e6e07d1252a8aeda0c324cf541a3ab73508 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Tue, 17 Oct 2023 16:39:45 +0300 Subject: [PATCH 20/25] Fix decky_plugin on windows CI --- .github/workflows/build-win.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-win.yml b/.github/workflows/build-win.yml index 1ce47d4a9..118553bae 100644 --- a/.github/workflows/build-win.yml +++ b/.github/workflows/build-win.yml @@ -44,10 +44,10 @@ jobs: run: pnpm run build - name: Build Python Backend 🛠️ - run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/src/legacy;/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/src/legacy;/src/legacy" --add-data "./plugin/*;/" --hidden-import=sqlite3 ./backend/main.py - name: Build Python Backend (noconsole) 🛠️ - run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/src/legacy;/src/legacy" --add-data "./plugin;/plugin" --hidden-import=sqlite3 ./backend/main.py + run: pyinstaller --noconfirm --noconsole --onefile --name "PluginLoader_noconsole" --add-data "./backend/static;/static" --add-data "./backend/locales;/locales" --add-data "./backend/src/legacy;/src/legacy" --add-data "./plugin/*;/" --hidden-import=sqlite3 ./backend/main.py - name: Upload package artifact ⬆️ uses: actions/upload-artifact@v3 From 2cdb49168ca2925d2d82eec100b62291a144a60d Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:44:44 +0100 Subject: [PATCH 21/25] fix logical error when no store was set --- frontend/src/store.tsx | 58 +++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/frontend/src/store.tsx b/frontend/src/store.tsx index 55d1e731a..fd194469e 100644 --- a/frontend/src/store.tsx +++ b/frontend/src/store.tsx @@ -38,40 +38,34 @@ export async function getStore(): Promise { export async function getPluginList(): Promise { let version = await window.DeckyPluginLoader.updateVersion(); - let store = await getSetting('store', Store.Default); + let store = await getSetting('store', null); let customURL = await getSetting('store-url', 'https://plugins.deckbrew.xyz/plugins'); let storeURL; - if (!store) { - console.log('Could not get a default store, using Default.'); - await setSetting('store-url', Store.Default); - return fetch('https://plugins.deckbrew.xyz/plugins', { - method: 'GET', - headers: { - 'X-Decky-Version': version.current, - }, - }).then((r) => r.json()); - } else { - switch (+store) { - case Store.Default: - storeURL = 'https://plugins.deckbrew.xyz/plugins'; - break; - case Store.Testing: - storeURL = 'https://testing.deckbrew.xyz/plugins'; - break; - case Store.Custom: - storeURL = customURL; - break; - default: - console.error('Somehow you ended up without a standard URL, using the default URL.'); - storeURL = 'https://plugins.deckbrew.xyz/plugins'; - break; - } - return fetch(storeURL, { - method: 'GET', - headers: { - 'X-Decky-Version': version.current, - }, - }).then((r) => r.json()); + if (store === null) { + console.log('Could not get store, using Default.'); + await setSetting('store', Store.Default); + store = Store.Default + } + switch (+store) { + case Store.Default: + storeURL = 'https://plugins.deckbrew.xyz/plugins'; + break; + case Store.Testing: + storeURL = 'https://testing.deckbrew.xyz/plugins'; + break; + case Store.Custom: + storeURL = customURL; + break; + default: + console.error('Somehow you ended up without a standard URL, using the default URL.'); + storeURL = 'https://plugins.deckbrew.xyz/plugins'; + break; + return fetch(storeURL, { + method: 'GET', + headers: { + 'X-Decky-Version': version.current, + }, + }).then((r) => r.json()); } } From a2312256b34d3f8de52876de45ba189142d7db1f Mon Sep 17 00:00:00 2001 From: Party Wumpus <48649272+PartyWumpus@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:52:11 +0100 Subject: [PATCH 22/25] fix typo this is what i get for commiting to main :pensive: --- frontend/src/store.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/store.tsx b/frontend/src/store.tsx index fd194469e..846c4766a 100644 --- a/frontend/src/store.tsx +++ b/frontend/src/store.tsx @@ -44,7 +44,7 @@ export async function getPluginList(): Promise { if (store === null) { console.log('Could not get store, using Default.'); await setSetting('store', Store.Default); - store = Store.Default + store = Store.Default; } switch (+store) { case Store.Default: @@ -60,13 +60,13 @@ export async function getPluginList(): Promise { console.error('Somehow you ended up without a standard URL, using the default URL.'); storeURL = 'https://plugins.deckbrew.xyz/plugins'; break; + } return fetch(storeURL, { method: 'GET', headers: { 'X-Decky-Version': version.current, }, }).then((r) => r.json()); - } } export async function installFromURL(url: string) { From ffbc79d9190300dc606da609f45f3f3890cb8143 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Tue, 17 Oct 2023 16:46:53 +0300 Subject: [PATCH 23/25] fix bad type on store.tsx --- frontend/src/store.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/store.tsx b/frontend/src/store.tsx index 846c4766a..2cc1495d4 100644 --- a/frontend/src/store.tsx +++ b/frontend/src/store.tsx @@ -38,7 +38,7 @@ export async function getStore(): Promise { export async function getPluginList(): Promise { let version = await window.DeckyPluginLoader.updateVersion(); - let store = await getSetting('store', null); + let store = await getSetting('store', null); let customURL = await getSetting('store-url', 'https://plugins.deckbrew.xyz/plugins'); let storeURL; if (store === null) { From 0736a6f9952b6271e103f23963e0206829c1cb3f Mon Sep 17 00:00:00 2001 From: marios8543 Date: Tue, 17 Oct 2023 16:53:13 +0300 Subject: [PATCH 24/25] fix bad type on store.tsx --- frontend/src/store.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/store.tsx b/frontend/src/store.tsx index 40809c074..fd458bef3 100644 --- a/frontend/src/store.tsx +++ b/frontend/src/store.tsx @@ -38,7 +38,7 @@ export async function getStore(): Promise { export async function getPluginList(): Promise { let version = await window.DeckyPluginLoader.updateVersion(); - let store = await getSetting('store', null); + let store = await getSetting('store', null); let customURL = await getSetting('store-url', 'https://plugins.deckbrew.xyz/plugins'); let storeURL; From e0592f09592988206667ea5f3a652fa6afdd8010 Mon Sep 17 00:00:00 2001 From: marios8543 Date: Tue, 17 Oct 2023 18:59:13 +0300 Subject: [PATCH 25/25] fix uninstall bug --- backend/src/browser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/browser.py b/backend/src/browser.py index 085607499..da8569bee 100644 --- a/backend/src/browser.py +++ b/backend/src/browser.py @@ -125,7 +125,7 @@ async def uninstall_plugin(self, name: str): tab = await get_gamepadui_tab() plugin_folder = self.find_plugin_folder(name) assert plugin_folder is not None - plugin_dir = path.join(self.plugin_path, ) + plugin_dir = path.join(self.plugin_path, plugin_folder) try: logger.info("uninstalling " + name) logger.info(" at dir " + plugin_dir)