Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions backend/decky_loader/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# Local modules
from .localplatform.localplatform import chown, chmod, get_chown_plugin_path
from .loader import Loader, Plugins
from .helpers import get_ssl_context, download_remote_binary_to_path
from .helpers import get_session, get_ssl_context, download_remote_binary_to_path
from .enums import UserType
from .settings import SettingsManager

Expand Down Expand Up @@ -116,7 +116,7 @@ def find_plugin_folder(self, name: str) -> str | None:
return folder
except:
logger.debug(f"skipping {folder}")

def set_plugin_dir_permissions(self, plugin_dir: str) -> bool:
plugin_json_path = path.join(plugin_dir, 'plugin.json')
logger.debug(f"Checking plugin.json at {plugin_json_path}")
Expand Down Expand Up @@ -150,7 +150,7 @@ async def uninstall_plugin(self, name: str):
# plugins_snapshot = self.plugins.copy()
# snapshot_string = pformat(plugins_snapshot)
# logger.debug("current plugins: %s", snapshot_string)

if name in self.plugins:
logger.debug("Plugin %s was found", name)
await self.plugins[name].stop(uninstall=True)
Expand Down Expand Up @@ -199,7 +199,7 @@ async def _install(self, artifact: str, name: str, version: str, hash: str):
logger.info(f"Installing {name} from URL (Version: {version})")
await self.loader.ws.emit("loader/plugin_download_info", 10, "Store.download_progress_info.download_zip")

async with ClientSession() as client:
async with get_session() as client:
logger.debug(f"Fetching {artifact}")
res = await client.get(artifact, ssl=get_ssl_context())
#TODO track progress of this download in chunks like with decky updates
Expand All @@ -220,7 +220,7 @@ async def _install(self, artifact: str, name: str, version: str, hash: str):
case 2: storeUrl = self.settings.getSetting("store-url", "https://plugins.deckbrew.xyz/plugins") # custom
case _: storeUrl = "https://plugins.deckbrew.xyz/plugins"
logger.info(f"Incrementing installs for {name} from URL {storeUrl} (version {version})")
async with ClientSession() as client:
async with get_session() as client:
res = await client.post(storeUrl+f"/{name}/versions/{version}/increment?isUpdate={isInstalled}", ssl=get_ssl_context())
if res.status != 200:
logger.error(f"Server did not accept install count increment request. code: {res.status}")
Expand Down Expand Up @@ -346,7 +346,7 @@ def cleanup_plugin_settings(self, name: str):
if name in plugin_order:
plugin_order.remove(name)
self.settings.setSetting("pluginOrder", plugin_order)

disabled_plugins: List[str] = self.settings.getSetting("disabled_plugins", [])
if name in disabled_plugins:
disabled_plugins.remove(name)
Expand Down
8 changes: 6 additions & 2 deletions backend/decky_loader/helpers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import re
import ssl
from typing import Any
import uuid
import os
import subprocess
Expand Down Expand Up @@ -97,7 +98,7 @@ def get_system_pythonpaths() -> list[str]:
proc = subprocess.run(["python3" if localplatform.ON_LINUX else "python", "-c", "import sys; print('\\n'.join(x for x in sys.path if x))"],
# 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) # pyright: ignore [reportPrivateUsage]

proc.check_returncode()

versions = [x.strip() for x in proc.stdout.decode().strip().split("\n")]
Expand All @@ -111,7 +112,7 @@ async def download_remote_binary_to_path(url: str, binHash: str, path: str) -> b
rv = False
try:
if os.access(os.path.dirname(path), os.W_OK):
async with ClientSession() as client:
async with get_session() as client:
res = await client.get(url, ssl=get_ssl_context())
if res.status == 200:
logger.debug("Download attempt of URL: " + url)
Expand Down Expand Up @@ -192,3 +193,6 @@ async def stop_systemd_unit(unit_name: str) -> bool:

async def start_systemd_unit(unit_name: str) -> bool:
return await localplatform.service_start(unit_name)

def get_session(**kwargs: Any) -> ClientSession:
return ClientSession(trust_env=True, **kwargs)
41 changes: 19 additions & 22 deletions backend/decky_loader/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
from logging import getLogger
import os
from os import getcwd, path, remove
from typing import TYPE_CHECKING, List, TypedDict
from typing import TYPE_CHECKING, TypedDict
if TYPE_CHECKING:
from .main import PluginManager
from .localplatform.localplatform import chmod, service_restart, service_stop, ON_LINUX, ON_WINDOWS, get_keep_systemd_service, get_selinux
import shutil
from typing import List, TYPE_CHECKING, TypedDict
import zipfile

from aiohttp import ClientSession

from .helpers import get_session
from . import helpers
from .settings import SettingsManager
if TYPE_CHECKING:
Expand All @@ -28,7 +25,7 @@ class RemoteVerAsset(TypedDict):
class RemoteVer(TypedDict):
tag_name: str
prerelease: bool
assets: List[RemoteVerAsset]
assets: list[RemoteVerAsset]
class TestingVersion(TypedDict):
id: int
name: str
Expand All @@ -40,7 +37,7 @@ def __init__(self, context: PluginManager) -> None:
self.context = context
self.settings = self.context.settings
self.remoteVer: RemoteVer | None = None
self.allRemoteVers: List[RemoteVer] = []
self.allRemoteVers: list[RemoteVer] = []
self.localVer = helpers.get_loader_version()

try:
Expand Down Expand Up @@ -102,9 +99,9 @@ async def get_version_info(self):
async def check_for_updates(self):
logger.debug("checking for updates")
selectedBranch = self.get_branch(self.context.settings)
async with ClientSession() as web:
async with get_session as web:
async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases", headers={'X-GitHub-Api-Version': '2022-11-28'}, ssl=helpers.get_ssl_context()) as res:
remoteVersions: List[RemoteVer] = 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))
Expand Down Expand Up @@ -145,7 +142,7 @@ async def download_decky_binary(self, download_url: str, version: str, is_zip: b
if size_in_bytes == None:
size_in_bytes = 26214400 # 25MiB, a reasonable overestimate (19.6MiB as of 2024/02/25)

async with ClientSession() as web:
async with get_session() as web:
logger.debug("Downloading binary")
async with web.request("GET", download_url, ssl=helpers.get_ssl_context(), allow_redirects=True) as res:
total = int(res.headers.get('content-length', size_in_bytes))
Expand Down Expand Up @@ -174,7 +171,7 @@ async def download_decky_binary(self, download_url: str, version: str, is_zip: b
shutil.move(path.join(getcwd(), download_filename + ".unzipped"), path.join(getcwd(), download_filename))
else:
shutil.move(path.join(getcwd(), download_temp_filename), path.join(getcwd(), download_filename))

chmod(path.join(getcwd(), download_filename), 777, False)
if get_selinux():
from asyncio.subprocess import create_subprocess_exec
Expand Down Expand Up @@ -203,14 +200,14 @@ async def do_update(self):
download_url = x["browser_download_url"]
size_in_bytes = x["size"]
break

if download_url == None:
raise Exception("Download url not found")

service_url = self.get_service_url()
logger.debug("Retrieved service URL")

async with ClientSession() as web:
async with get_session() as web:
if ON_LINUX and not get_keep_systemd_service():
logger.debug("Downloading systemd service")
# download the relevant systemd service depending upon branch
Expand All @@ -229,14 +226,14 @@ async def do_update(self):
service_data = service_data.replace("${HOMEBREW_FOLDER}", helpers.get_homebrew_path())
with open(path.join(getcwd(), "plugin_loader.service"), "w", encoding="utf-8") as service_file:
service_file.write(service_data)

logger.debug("Saved service file")
logger.debug("Copying service file over current file.")
shutil.copy(service_file_path, "/etc/systemd/system/plugin_loader.service")
if not os.path.exists(path.join(getcwd(), ".systemd")):
os.mkdir(path.join(getcwd(), ".systemd"))
shutil.move(service_file_path, path.join(getcwd(), ".systemd")+"/plugin_loader.service")

await self.download_decky_binary(download_url, version, size_in_bytes=size_in_bytes)

async def do_restart(self):
Expand All @@ -246,10 +243,10 @@ async def do_restart(self):
async def do_shutdown(self):
await service_stop("plugin_loader")

async def get_testing_versions(self) -> List[TestingVersion]:
result: List[TestingVersion] = []
async with ClientSession() as web:
async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/pulls",
async def get_testing_versions(self) -> list[TestingVersion]:
result: list[TestingVersion] = []
async with get_session() as web:
async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/pulls",
headers={'X-GitHub-Api-Version': '2022-11-28'}, params={'state':'open'}, ssl=helpers.get_ssl_context()) as res:
open_prs = await res.json()
for pr in open_prs:
Expand All @@ -264,8 +261,8 @@ async def get_testing_versions(self) -> List[TestingVersion]:
async def download_testing_version(self, pr_id: int, sha_id: str):
down_id = ''
#Get all the associated workflow run for the given sha_id code hash
async with ClientSession() as web:
async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/actions/runs",
async with get_session() as web:
async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/actions/runs",
headers={'X-GitHub-Api-Version': '2022-11-28'}, params={'head_sha': sha_id}, ssl=helpers.get_ssl_context()) as res:
works = await res.json()
#Iterate over the workflow_run to get the two builds if they exists
Expand All @@ -277,7 +274,7 @@ async def download_testing_version(self, pr_id: int, sha_id: str):
down_id=work['id']
break
if down_id != '':
async with ClientSession() as web:
async with get_session() as web:
async with web.request("GET", f"https://api.github.com/repos/SteamDeckHomebrew/decky-loader/actions/runs/{down_id}/artifacts",
headers={'X-GitHub-Api-Version': '2022-11-28'}, ssl=helpers.get_ssl_context()) as res:
jresp = await res.json()
Expand Down
36 changes: 18 additions & 18 deletions backend/decky_loader/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,14 @@ async def http_request(self, req: Request) -> StreamResponse:
# JS engine for it to do the decompression. Otherwise we need need to clear
# the Content-Encoding header in the response headers, however that would
# defeat the point of this proxy.
async with ClientSession(auto_decompress=False) as web:
async with ClientSession(auto_decompress=False, trust_env=True) as web:
async with web.request(req.method, url, headers=headers, data=body, ssl=helpers.get_ssl_context()) as web_res:
# Whenever the aiohttp_cors is used, it expects a near complete control over whatever headers are needed
# for `aiohttp_cors.ResourceOptions`. As a server, if you delegate CORS handling to aiohttp_cors,
# the headers below must NOT be set. Otherwise they would be overwritten by aiohttp_cors and there would be
# the headers below must NOT be set. Otherwise they would be overwritten by aiohttp_cors and there would be
# logic bugs, so it was probably a smart choice to assert if the headers are present.
#
# However, this request handler method does not act like our own local server, it always acts like a proxy
# However, this request handler method does not act like our own local server, it always acts like a proxy
# where we do not have control over the response headers. For responses that do not allow CORS, we add the support
# via aiohttp_cors. For responses that allow CORS, we have to remove the conflicting headers to allow
# aiohttp_cors handle it for us as if there was no CORS support.
Expand All @@ -215,7 +215,7 @@ async def http_request(self, req: Request) -> StreamResponse:
return res

async def http_request_legacy(self, method: str, url: str, extra_opts: Any = {}, timeout: int | None = None):
async with ClientSession() as web:
async with ClientSession(trust_env=True) as web:
res = await web.request(method, url, ssl=helpers.get_ssl_context(), timeout=timeout, **extra_opts) # type: ignore
text = await res.text()
return {
Expand Down Expand Up @@ -275,7 +275,7 @@ async def remove_css_from_tab(self, tab: str, css_id: str):
style.parentNode.removeChild(style);
}})()
""", False)

assert result
if "exceptionDetails" in result["result"]:
raise result["result"]["exceptionDetails"]
Expand Down Expand Up @@ -311,8 +311,8 @@ async def close_cef_socket(self):
async def restart_webhelper(self):
await restart_webhelper()

async def filepicker_ls(self,
path: str | None = None,
async def filepicker_ls(self,
path: str | None = None,
include_files: bool = True,
include_folders: bool = True,
include_ext: list[str] | None = None,
Expand All @@ -321,7 +321,7 @@ async def filepicker_ls(self,
filter_for: str | None = None,
page: int = 1,
max: int = 1000):

if path == None:
path = get_home_path()

Expand Down Expand Up @@ -353,7 +353,7 @@ async def filepicker_ls(self,
files = list(filter(lambda file: re.search(filter_for, file["file"].name) != None, files))
except re.error:
files = list(filter(lambda file: file["file"].name.find(filter_for) != -1, files))

# Ordering logic
ord_arg = order_by.split("_")
ord = ord_arg[0]
Expand All @@ -375,7 +375,7 @@ async def filepicker_ls(self,
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 = [{
"isdir": x['is_dir'],
Expand All @@ -391,7 +391,7 @@ async def filepicker_ls(self,
"files": all[(page-1)*max:(page)*max],
"total": len(all),
}

# Based on https://stackoverflow.com/a/46422554/13174603
def start_rdt_proxy(self, ip: str, port: int):
async def pipe(reader: StreamReader, writer: StreamWriter):
Expand Down Expand Up @@ -472,7 +472,7 @@ async def get_user_info(self) -> Dict[str, str]:
"username": get_username(),
"path": get_home_path()
}

async def get_tab_id(self, name: str):
return (await get_tab(name)).id

Expand All @@ -484,23 +484,23 @@ async def disable_plugin(self, name: str):

await self.context.plugin_loader.plugins[name].stop()
await self.context.ws.emit("loader/disable_plugin", name)

async def enable_plugin(self, name: str):
plugin_folder = self.context.plugin_browser.find_plugin_folder(name)
assert plugin_folder is not None
plugin_dir = path.join(self.context.plugin_browser.plugin_path, plugin_folder)

if name in self.context.plugin_loader.plugins:
plugin = self.context.plugin_loader.plugins[name]
if plugin.proc and plugin.proc.is_alive():
await plugin.stop()
self.context.plugin_loader.plugins.pop(name, None)
await sleep(1)

disabled_plugins: List[str] = await self.get_setting("disabled_plugins", [])

if name in disabled_plugins:
disabled_plugins.remove(name)
await self.set_setting("disabled_plugins", disabled_plugins)
await self.context.plugin_loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder)

await self.context.plugin_loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder)