diff options
| author | AAGaming <aa@mail.catvibers.me> | 2022-07-14 22:51:55 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-07-14 22:51:55 -0400 |
| commit | 8c8cf180fad2ad6951ad7ce6b74e6c163fa01d18 (patch) | |
| tree | dd051aaa7928867657c955cf2cad5143ca72f658 | |
| parent | 05d11cfff037734f7aa6b6ac8e701eacd9f38d60 (diff) | |
| download | decky-loader-8c8cf180fad2ad6951ad7ce6b74e6c163fa01d18.tar.gz decky-loader-8c8cf180fad2ad6951ad7ce6b74e6c163fa01d18.zip | |
Updater for decky-loader (#117)
* Add an updater in settings for decky-loader
* add chmod
* remove junk comments
| -rw-r--r-- | backend/main.py | 2 | ||||
| -rw-r--r-- | backend/updater.py | 113 | ||||
| -rwxr-xr-x | dist/install_prerelease.sh | 12 | ||||
| -rw-r--r-- | frontend/package.json | 4 | ||||
| -rw-r--r-- | frontend/pnpm-lock.yaml | 200 | ||||
| -rw-r--r-- | frontend/src/components/settings/index.tsx | 4 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/general/Updater.tsx | 86 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/general/index.tsx (renamed from frontend/src/components/settings/pages/GeneralSettings.tsx) | 8 | ||||
| -rw-r--r-- | frontend/src/components/settings/pages/plugin_list/index.tsx (renamed from frontend/src/components/settings/pages/PluginList.tsx) | 2 | ||||
| -rw-r--r-- | frontend/src/index.tsx | 2 | ||||
| -rw-r--r-- | frontend/src/updater.ts | 30 | ||||
| -rw-r--r-- | frontend/tsconfig.json | 1 |
12 files changed, 353 insertions, 111 deletions
diff --git a/backend/main.py b/backend/main.py index 99e6c618..52e0ce06 100644 --- a/backend/main.py +++ b/backend/main.py @@ -27,6 +27,7 @@ from browser import PluginBrowser from injector import inject_to_tab, tab_has_global_var from loader import Loader from utilities import Utilities +from updater import Updater logger = getLogger("Main") @@ -47,6 +48,7 @@ class PluginManager: self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"]) self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.web_app) self.utilities = Utilities(self) + self.updater = Updater(self) jinja_setup(self.web_app) self.web_app.on_startup.append(self.inject_javascript) diff --git a/backend/updater.py b/backend/updater.py new file mode 100644 index 00000000..d7aed0d7 --- /dev/null +++ b/backend/updater.py @@ -0,0 +1,113 @@ +import uuid +from logging import getLogger +from json.decoder import JSONDecodeError + +from asyncio import sleep + +from aiohttp import ClientSession, web + +from injector import inject_to_tab, get_tab + +from os import getcwd, path + +from subprocess import call + +import helpers + +logger = getLogger("Updater") + +class Updater: + def __init__(self, context) -> None: + self.context = context + self.updater_methods = { + "get_version": self.get_version, + "do_update": self.do_update, + "do_restart": self.do_restart + } + self.remoteVer = None + try: + with open(path.join(getcwd(), ".loader.version"), 'r') as version_file: + self.localVer = version_file.readline().replace("\n", "") + except: + self.localVer = False + + if context: + context.web_app.add_routes([ + web.post("/updater/{method_name}", self._handle_server_method_call) + ]) + context.loop.create_task(self.version_reloader()) + + async def _handle_server_method_call(self, request): + method_name = request.match_info["method_name"] + try: + args = await request.json() + except JSONDecodeError: + args = {} + res = {} + try: + r = await self.updater_methods[method_name](**args) + res["result"] = r + res["success"] = True + except Exception as e: + res["result"] = str(e) + res["success"] = False + return web.json_response(res) + + async def get_version(self): + if self.localVer: + return { + "current": self.localVer, + "remote": self.remoteVer, + "updatable": self.remoteVer != None + } + else: + return {"current": "unknown", "updatable": False} + + async def version_reloader(self): + while True: + try: + 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() + self.remoteVer = next(filter(lambda ver: ver["prerelease"] and ver["tag_name"].startswith("v") and ver["tag_name"].endswith("-pre"), remoteVersions), None) + logger.info("Updated remote version information") + except: + pass + await sleep(60 * 60) # 1 hour + + async def do_update(self): + version = self.remoteVer["tag_name"] + #TODO don't hardcode this + download_url = self.remoteVer["assets"][0]["browser_download_url"] + + tab = await get_tab("SP") + await tab.open_websocket() + async with ClientSession() as web: + async with web.request("GET", download_url, ssl=helpers.get_ssl_context(), allow_redirects=True) as res: + total = int(res.headers.get('content-length', 0)) + + with open(path.join(getcwd(), "PluginLoader"), "wb") as out: + progress = 0 + raw = 0 + async for c in res.content.iter_chunked(512): + out.write(c) + raw += len(c) + new_progress = round((raw / total) * 100) + if progress != new_progress: + if new_progress - progress>= 2: + self.context.loop.create_task(tab.evaluate_js(f"window.DeckyUpdater.updateProgress({progress})", False, False)) + progress = new_progress + + with open(path.join(getcwd(), ".loader.version"), "w") as out: + out.write(version) + + call(['chmod', '+x', path.join(getcwd(), "PluginLoader")]) + + logger.info("Updated loader installation.") + await tab.evaluate_js("window.DeckyUpdater.finish()", False, False) + await tab.client.close() + + async def do_restart(self): + call(["systemctl", "daemon-reload"]) + call(["systemctl", "restart", "plugin_loader"]) + exit(0) diff --git a/dist/install_prerelease.sh b/dist/install_prerelease.sh index 276f3587..384383d2 100755 --- a/dist/install_prerelease.sh +++ b/dist/install_prerelease.sh @@ -12,10 +12,14 @@ sudo -u deck mkdir -p ${HOMEBREW_FOLDER}/services sudo -u deck mkdir -p ${HOMEBREW_FOLDER}/plugins # Download latest release and install it -DOWNLOADURL="$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases' | jq -r "first(.[] | select(.prerelease == "true"))" | jq -r ".assets[].browser_download_url")" +RELEASES="$(curl -s 'https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases')" +RELEASE="$($RELEASES | jq -r "first(.[] | select(.prerelease == "true"))")" +VERSION="$($RELEASE | jq -r ".tag_name")" +DOWNLOADURL="$($RELEASE | jq -r ".assets[].browser_download_url")" # printf "DOWNLOADURL=$DOWNLOADURL\n" curl -L $DOWNLOADURL --output ${HOMEBREW_FOLDER}/services/PluginLoader chmod +x ${HOMEBREW_FOLDER}/services/PluginLoader +echo $VERSION > ${HOMEBREW_FOLDER}/services/.loader.version systemctl --user stop plugin_loader 2> /dev/null systemctl --user disable plugin_loader 2> /dev/null @@ -30,9 +34,9 @@ Description=SteamDeck Plugin Loader Type=simple User=root Restart=always -ExecStart=/home/deck/homebrew/services/PluginLoader -WorkingDirectory=/home/deck/homebrew/services -Environment=PLUGIN_PATH=/home/deck/homebrew/plugins +ExecStart=${HOMEBREW_FOLDER}/services/PluginLoader +WorkingDirectory=${HOMEBREW_FOLDER}/services +Environment=PLUGIN_PATH=${HOMEBREW_FOLDER}/plugins Environment=LOG_LEVEL=DEBUG [Install] WantedBy=multi-user.target diff --git a/frontend/package.json b/frontend/package.json index 07cdeddc..b16452e1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,7 +26,7 @@ "prettier-plugin-import-sort": "^0.0.7", "react": "16.14.0", "react-dom": "16.14.0", - "rollup": "^2.75.7", + "rollup": "^2.76.0", "tslib": "^2.4.0", "typescript": "^4.7.4" }, @@ -37,7 +37,7 @@ } }, "dependencies": { - "decky-frontend-lib": "^1.0.2", + "decky-frontend-lib": "^1.2.1", "react-icons": "^4.4.0" } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 89d21963..21bddef3 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -9,7 +9,7 @@ specifiers: '@types/react': 16.14.0 '@types/react-router': 5.1.18 '@types/webpack': ^5.28.0 - decky-frontend-lib: ^1.0.2 + decky-frontend-lib: ^1.2.1 husky: ^8.0.1 import-sort-style-module: ^6.0.0 inquirer: ^8.2.4 @@ -18,20 +18,20 @@ specifiers: react: 16.14.0 react-dom: 16.14.0 react-icons: ^4.4.0 - rollup: ^2.75.7 + rollup: ^2.76.0 tslib: ^2.4.0 typescript: ^4.7.4 dependencies: - decky-frontend-lib: 1.0.2 + decky-frontend-lib: 1.2.1 react-icons: 4.4.0_react@16.14.0 devDependencies: - '@rollup/plugin-commonjs': 21.1.0_rollup@2.75.7 - '@rollup/plugin-json': 4.1.0_rollup@2.75.7 - '@rollup/plugin-node-resolve': 13.3.0_rollup@2.75.7 - '@rollup/plugin-replace': 4.0.0_rollup@2.75.7 - '@rollup/plugin-typescript': 8.3.3_g4qkabtmybowem44p7ts7jnbqm + '@rollup/plugin-commonjs': 21.1.0_rollup@2.76.0 + '@rollup/plugin-json': 4.1.0_rollup@2.76.0 + '@rollup/plugin-node-resolve': 13.3.0_rollup@2.76.0 + '@rollup/plugin-replace': 4.0.0_rollup@2.76.0 + '@rollup/plugin-typescript': 8.3.3_mrkdcqv53wzt2ybukxlrvz47fu '@types/react': 16.14.0 '@types/react-router': 5.1.18 '@types/webpack': 5.28.0 @@ -42,7 +42,7 @@ devDependencies: prettier-plugin-import-sort: 0.0.7_prettier@2.7.1 react: 16.14.0 react-dom: 16.14.0_react@16.14.0 - rollup: 2.75.7 + rollup: 2.76.0 tslib: 2.4.0 typescript: 4.7.4 @@ -63,8 +63,8 @@ packages: '@babel/highlight': 7.18.6 dev: true - /@babel/compat-data/7.18.6: - resolution: {integrity: sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ==} + /@babel/compat-data/7.18.8: + resolution: {integrity: sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==} engines: {node: '>=6.9.0'} dev: true @@ -76,12 +76,12 @@ packages: '@babel/code-frame': 7.18.6 '@babel/generator': 7.18.7 '@babel/helper-compilation-targets': 7.18.6_@babel+core@7.18.6 - '@babel/helper-module-transforms': 7.18.6 + '@babel/helper-module-transforms': 7.18.8 '@babel/helpers': 7.18.6 - '@babel/parser': 7.18.6 + '@babel/parser': 7.18.8 '@babel/template': 7.18.6 - '@babel/traverse': 7.18.6 - '@babel/types': 7.18.7 + '@babel/traverse': 7.18.8 + '@babel/types': 7.18.8 convert-source-map: 1.8.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -95,7 +95,7 @@ packages: resolution: {integrity: sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.18.7 + '@babel/types': 7.18.8 '@jridgewell/gen-mapping': 0.3.2 jsesc: 2.5.2 dev: true @@ -106,10 +106,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0 dependencies: - '@babel/compat-data': 7.18.6 + '@babel/compat-data': 7.18.8 '@babel/core': 7.18.6 '@babel/helper-validator-option': 7.18.6 - browserslist: 4.21.1 + browserslist: 4.21.2 semver: 6.3.0 dev: true @@ -123,25 +123,25 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.18.6 - '@babel/types': 7.18.7 + '@babel/types': 7.18.8 dev: true /@babel/helper-hoist-variables/7.18.6: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.18.7 + '@babel/types': 7.18.8 dev: true /@babel/helper-module-imports/7.18.6: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.18.7 + '@babel/types': 7.18.8 dev: true - /@babel/helper-module-transforms/7.18.6: - resolution: {integrity: sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw==} + /@babel/helper-module-transforms/7.18.8: + resolution: {integrity: sha512-che3jvZwIcZxrwh63VfnFTUzcAM9v/lznYkkRxIBGMPt1SudOKHAEec0SIRCfiuIzTcF7VGj/CaTT6gY4eWxvA==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-environment-visitor': 7.18.6 @@ -150,8 +150,8 @@ packages: '@babel/helper-split-export-declaration': 7.18.6 '@babel/helper-validator-identifier': 7.18.6 '@babel/template': 7.18.6 - '@babel/traverse': 7.18.6 - '@babel/types': 7.18.7 + '@babel/traverse': 7.18.8 + '@babel/types': 7.18.8 transitivePeerDependencies: - supports-color dev: true @@ -160,14 +160,14 @@ packages: resolution: {integrity: sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.18.7 + '@babel/types': 7.18.8 dev: true /@babel/helper-split-export-declaration/7.18.6: resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.18.7 + '@babel/types': 7.18.8 dev: true /@babel/helper-validator-identifier/7.18.6: @@ -185,8 +185,8 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.18.6 - '@babel/traverse': 7.18.6 - '@babel/types': 7.18.7 + '@babel/traverse': 7.18.8 + '@babel/types': 7.18.8 transitivePeerDependencies: - supports-color dev: true @@ -200,12 +200,12 @@ packages: js-tokens: 4.0.0 dev: true - /@babel/parser/7.18.6: - resolution: {integrity: sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw==} + /@babel/parser/7.18.8: + resolution: {integrity: sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.18.7 + '@babel/types': 7.18.8 dev: true /@babel/template/7.18.6: @@ -213,12 +213,12 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.18.6 - '@babel/parser': 7.18.6 - '@babel/types': 7.18.7 + '@babel/parser': 7.18.8 + '@babel/types': 7.18.8 dev: true - /@babel/traverse/7.18.6: - resolution: {integrity: sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw==} + /@babel/traverse/7.18.8: + resolution: {integrity: sha512-UNg/AcSySJYR/+mIcJQDCv00T+AqRO7j/ZEJLzpaYtgM48rMg5MnkJgyNqkzo88+p4tfRvZJCEiwwfG6h4jkRg==} engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.18.6 @@ -227,16 +227,16 @@ packages: '@babel/helper-function-name': 7.18.6 '@babel/helper-hoist-variables': 7.18.6 '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.18.6 - '@babel/types': 7.18.7 + '@babel/parser': 7.18.8 + '@babel/types': 7.18.8 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: - supports-color dev: true - /@babel/types/7.18.7: - resolution: {integrity: sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ==} + /@babel/types/7.18.8: + resolution: {integrity: sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-validator-identifier': 7.18.6 @@ -260,8 +260,8 @@ packages: '@jridgewell/trace-mapping': 0.3.14 dev: true - /@jridgewell/resolve-uri/3.0.8: - resolution: {integrity: sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==} + /@jridgewell/resolve-uri/3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} dev: true @@ -284,61 +284,61 @@ packages: /@jridgewell/trace-mapping/0.3.14: resolution: {integrity: sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==} dependencies: - '@jridgewell/resolve-uri': 3.0.8 + '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 dev: true - /@rollup/plugin-commonjs/21.1.0_rollup@2.75.7: + /@rollup/plugin-commonjs/21.1.0_rollup@2.76.0: resolution: {integrity: sha512-6ZtHx3VHIp2ReNNDxHjuUml6ur+WcQ28N1yHgCQwsbNkQg2suhxGMDQGJOn/KuDxKtd1xuZP5xSTwBA4GQ8hbA==} engines: {node: '>= 8.0.0'} peerDependencies: rollup: ^2.38.3 dependencies: - '@rollup/pluginutils': 3.1.0_rollup@2.75.7 + '@rollup/pluginutils': 3.1.0_rollup@2.76.0 commondir: 1.0.1 estree-walker: 2.0.2 glob: 7.2.3 is-reference: 1.2.1 magic-string: 0.25.9 resolve: 1.22.1 - rollup: 2.75.7 + rollup: 2.76.0 dev: true - /@rollup/plugin-json/4.1.0_rollup@2.75.7: + /@rollup/plugin-json/4.1.0_rollup@2.76.0: resolution: {integrity: sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==} peerDependencies: rollup: ^1.20.0 || ^2.0.0 dependencies: - '@rollup/pluginutils': 3.1.0_rollup@2.75.7 - rollup: 2.75.7 + '@rollup/pluginutils': 3.1.0_rollup@2.76.0 + rollup: 2.76.0 dev: true - /@rollup/plugin-node-resolve/13.3.0_rollup@2.75.7: + /@rollup/plugin-node-resolve/13.3.0_rollup@2.76.0: resolution: {integrity: sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==} engines: {node: '>= 10.0.0'} peerDependencies: rollup: ^2.42.0 dependencies: - '@rollup/pluginutils': 3.1.0_rollup@2.75.7 + '@rollup/pluginutils': 3.1.0_rollup@2.76.0 '@types/resolve': 1.17.1 deepmerge: 4.2.2 is-builtin-module: 3.1.0 is-module: 1.0.0 resolve: 1.22.1 - rollup: 2.75.7 + rollup: 2.76.0 dev: true - /@rollup/plugin-replace/4.0.0_rollup@2.75.7: + /@rollup/plugin-replace/4.0.0_rollup@2.76.0: resolution: {integrity: sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g==} peerDependencies: rollup: ^1.20.0 || ^2.0.0 dependencies: - '@rollup/pluginutils': 3.1.0_rollup@2.75.7 + '@rollup/pluginutils': 3.1.0_rollup@2.76.0 magic-string: 0.25.9 - rollup: 2.75.7 + rollup: 2.76.0 dev: true - /@rollup/plugin-typescript/8.3.3_g4qkabtmybowem44p7ts7jnbqm: + /@rollup/plugin-typescript/8.3.3_mrkdcqv53wzt2ybukxlrvz47fu: resolution: {integrity: sha512-55L9SyiYu3r/JtqdjhwcwaECXP7JeJ9h1Sg1VWRJKIutla2MdZQodTgcCNybXLMCnqpNLEhS2vGENww98L1npg==} engines: {node: '>=8.0.0'} peerDependencies: @@ -349,14 +349,14 @@ packages: tslib: optional: true dependencies: - '@rollup/pluginutils': 3.1.0_rollup@2.75.7 + '@rollup/pluginutils': 3.1.0_rollup@2.76.0 resolve: 1.22.1 - rollup: 2.75.7 + rollup: 2.76.0 tslib: 2.4.0 typescript: 4.7.4 dev: true - /@rollup/pluginutils/3.1.0_rollup@2.75.7: + /@rollup/pluginutils/3.1.0_rollup@2.76.0: resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} engines: {node: '>= 8.0.0'} peerDependencies: @@ -365,18 +365,18 @@ packages: '@types/estree': 0.0.39 estree-walker: 1.0.1 picomatch: 2.3.1 - rollup: 2.75.7 + rollup: 2.76.0 dev: true /@types/eslint-scope/3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: - '@types/eslint': 8.4.3 + '@types/eslint': 8.4.5 '@types/estree': 0.0.51 dev: true - /@types/eslint/8.4.3: - resolution: {integrity: sha512-YP1S7YJRMPs+7KZKDb9G63n8YejIwW9BALq7a5j2+H4yl6iOv9CB29edho+cuFRrvmJbbaH2yiVChKLJVysDGw==} + /@types/eslint/8.4.5: + resolution: {integrity: sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==} dependencies: '@types/estree': 0.0.51 '@types/json-schema': 7.0.11 @@ -390,8 +390,8 @@ packages: resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} dev: true - /@types/estree/0.0.52: - resolution: {integrity: sha512-BZWrtCU0bMVAIliIV+HJO1f1PR41M7NKjfxrFJwwhKI1KwhwOxYw1SXg9ao+CIMt774nFuGiG6eU+udtbEI9oQ==} + /@types/estree/1.0.0: + resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} dev: true /@types/history/4.7.11: @@ -402,8 +402,8 @@ packages: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true - /@types/node/18.0.0: - resolution: {integrity: sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==} + /@types/node/18.0.4: + resolution: {integrity: sha512-M0+G6V0Y4YV8cqzHssZpaNCqvYwlCiulmm0PwpNLF55r/+cT8Ol42CHRU1SEaYFH2rTwiiE1aYg/2g2rrtGdPA==} dev: true /@types/prop-types/15.7.5: @@ -427,13 +427,13 @@ packages: /@types/resolve/1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.0.4 dev: true /@types/webpack/5.28.0: resolution: {integrity: sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w==} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.0.4 tapable: 2.2.1 webpack: 5.73.0 transitivePeerDependencies: @@ -643,15 +643,15 @@ packages: concat-map: 0.0.1 dev: true - /browserslist/4.21.1: - resolution: {integrity: sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ==} + /browserslist/4.21.2: + resolution: {integrity: sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001361 - electron-to-chromium: 1.4.174 - node-releases: 2.0.5 - update-browserslist-db: 1.0.4_browserslist@4.21.1 + caniuse-lite: 1.0.30001366 + electron-to-chromium: 1.4.189 + node-releases: 2.0.6 + update-browserslist-db: 1.0.4_browserslist@4.21.2 dev: true /buffer-from/1.1.2: @@ -689,8 +689,8 @@ packages: engines: {node: '>=4'} dev: true - /caniuse-lite/1.0.30001361: - resolution: {integrity: sha512-ybhCrjNtkFji1/Wto6SSJKkWk6kZgVQsDq5QI83SafsF6FXv2JB4df9eEdH6g8sdGgqTXrFLjAxqBGgYoU3azQ==} + /caniuse-lite/1.0.30001366: + resolution: {integrity: sha512-yy7XLWCubDobokgzudpkKux8e0UOOnLHE6mlNJBzT3lZJz6s5atSEzjoL+fsCPkI0G8MP5uVdDx1ur/fXEWkZA==} dev: true /chalk/2.4.2: @@ -771,7 +771,7 @@ packages: dev: true /concat-map/0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} dev: true /convert-source-map/1.8.0: @@ -806,8 +806,8 @@ packages: ms: 2.1.2 dev: true - /decky-frontend-lib/1.0.2: - resolution: {integrity: sha512-l2Fq1oKkZdi2W4Qq+EXWgwOynWgANLTSFHcHrbJwCimTJ75uPlvrtsulnh5PlIovydM6iC+NyjAbW/qsVXWeLg==} + /decky-frontend-lib/1.2.1: + resolution: {integrity: sha512-aJmjOSMwQN9LTquYaMhSqW+FhmKLRgLb75JkcGRWKuIe8rjDfwwbAB/ckJseIC8UMzPKhspvcznfxyp+c72B5Q==} dev: false /deepmerge/4.2.2: @@ -826,8 +826,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /electron-to-chromium/1.4.174: - resolution: {integrity: sha512-JER+w+9MV2MBVFOXxP036bLlNOnzbYAWrWU8sNUwoOO69T3w4564WhM5H5atd8VVS8U4vpi0i0kdoYzm1NPQgQ==} + /electron-to-chromium/1.4.189: + resolution: {integrity: sha512-dQ6Zn4ll2NofGtxPXaDfY2laIa6NyCQdqXYHdwH90GJQW0LpJJib0ZU/ERtbb0XkBEmUD2eJtagbOie3pdMiPg==} dev: true /emoji-regex/8.0.0: @@ -1038,9 +1038,9 @@ packages: resolution: {integrity: sha512-NyShTiNhTh4Vy7kJUVe6CuvOaQAzzfSIT72wtp3CzGjz8bHjNj59DCAjncuviicmDOgVAgmLuSh1WMcLYAMWGg==} dependencies: '@babel/core': 7.18.6 - '@babel/parser': 7.18.6 - '@babel/traverse': 7.18.6 - '@babel/types': 7.18.7 + '@babel/parser': 7.18.8 + '@babel/traverse': 7.18.8 + '@babel/types': 7.18.8 find-line-column: 0.5.2 transitivePeerDependencies: - supports-color @@ -1099,7 +1099,7 @@ packages: mute-stream: 0.0.8 ora: 5.4.1 run-async: 2.4.1 - rxjs: 7.5.5 + rxjs: 7.5.6 string-width: 4.2.3 strip-ansi: 6.0.1 through: 2.3.8 @@ -1145,7 +1145,7 @@ packages: /is-reference/1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} dependencies: - '@types/estree': 0.0.52 + '@types/estree': 1.0.0 dev: true /is-unicode-supported/0.1.0: @@ -1157,7 +1157,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.0.0 + '@types/node': 18.0.4 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true @@ -1265,8 +1265,8 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /node-releases/2.0.5: - resolution: {integrity: sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==} + /node-releases/2.0.6: + resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} dev: true /object-assign/4.1.1: @@ -1437,8 +1437,8 @@ packages: signal-exit: 3.0.7 dev: true - /rollup/2.75.7: - resolution: {integrity: sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==} + /rollup/2.76.0: + resolution: {integrity: sha512-9jwRIEY1jOzKLj3nsY/yot41r19ITdQrhs+q3ggNWhr9TQgduHqANvPpS32RNpzGklJu3G1AJfvlZLi/6wFgWA==} engines: {node: '>=10.0.0'} hasBin: true optionalDependencies: @@ -1450,8 +1450,8 @@ packages: engines: {node: '>=0.12.0'} dev: true - /rxjs/7.5.5: - resolution: {integrity: sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==} + /rxjs/7.5.6: + resolution: {integrity: sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==} dependencies: tslib: 2.4.0 dev: true @@ -1592,12 +1592,12 @@ packages: jest-worker: 27.5.1 schema-utils: 3.1.1 serialize-javascript: 6.0.0 - terser: 5.14.1 + terser: 5.14.2 webpack: 5.73.0 dev: true - /terser/5.14.1: - resolution: {integrity: sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==} + /terser/5.14.2: + resolution: {integrity: sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==} engines: {node: '>=10'} hasBin: true dependencies: @@ -1644,13 +1644,13 @@ packages: hasBin: true dev: true - /update-browserslist-db/1.0.4_browserslist@4.21.1: + /update-browserslist-db/1.0.4_browserslist@4.21.2: resolution: {integrity: sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.21.1 + browserslist: 4.21.2 escalade: 3.1.1 picocolors: 1.0.0 dev: true @@ -1701,7 +1701,7 @@ packages: '@webassemblyjs/wasm-parser': 1.11.1 acorn: 8.7.1 acorn-import-assertions: 1.8.0_acorn@8.7.1 - browserslist: 4.21.1 + browserslist: 4.21.2 chrome-trace-event: 1.0.3 enhanced-resolve: 5.10.0 es-module-lexer: 0.9.3 diff --git a/frontend/src/components/settings/index.tsx b/frontend/src/components/settings/index.tsx index f9c84c7b..eb3a8bbd 100644 --- a/frontend/src/components/settings/index.tsx +++ b/frontend/src/components/settings/index.tsx @@ -1,7 +1,7 @@ import { SidebarNavigation } from 'decky-frontend-lib'; -import GeneralSettings from './pages/GeneralSettings'; -import PluginList from './pages/PluginList'; +import GeneralSettings from './pages/general'; +import PluginList from './pages/plugin_list'; export default function SettingsPage() { return ( diff --git a/frontend/src/components/settings/pages/general/Updater.tsx b/frontend/src/components/settings/pages/general/Updater.tsx new file mode 100644 index 00000000..264acca2 --- /dev/null +++ b/frontend/src/components/settings/pages/general/Updater.tsx @@ -0,0 +1,86 @@ +import { DialogButton, Field, ProgressBarWithInfo, Spinner, sleep } from 'decky-frontend-lib'; +import { useEffect, useState } from 'react'; +import { FaArrowDown } from 'react-icons/fa'; + +import { callUpdaterMethod, finishUpdate } from '../../../../updater'; + +interface VerInfo { + current: string; + remote: { + assets: { + browser_download_url: string; + created_at: string; + }[]; + name: string; + body: string; + prerelease: boolean; + published_at: string; + tag_name: string; + } | null; + updatable: boolean; +} + +export default function UpdaterSettings() { + const [versionInfo, setVersionInfo] = useState<VerInfo | null>(null); + const [updateProgress, setUpdateProgress] = useState<number>(-1); + const [reloading, setReloading] = useState<boolean>(false); + useEffect(() => { + (async () => { + const res = (await callUpdaterMethod('get_version')) as { result: VerInfo }; + setVersionInfo(res.result); + })(); + }, []); + + return ( + <Field + label="Updates" + description={ + versionInfo && ( + <span style={{ whiteSpace: 'pre-line' }}>{`Current version: ${versionInfo.current}\n${ + versionInfo.updatable ? `Latest version: ${versionInfo.remote?.tag_name}` : '' + }`}</span> + ) + } + icon={ + !versionInfo ? ( + <Spinner style={{ width: '1em', height: 20, display: 'block' }} /> + ) : ( + <FaArrowDown style={{ display: 'block' }} /> + ) + } + > + {updateProgress == -1 ? ( + <DialogButton + disabled={ + !versionInfo?.updatable || !versionInfo?.remote || versionInfo.remote.tag_name == versionInfo.current + } + onClick={async () => { + window.DeckyUpdater = { + updateProgress: (i) => { + setUpdateProgress(i); + }, + finish: async () => { + setUpdateProgress(0); + setReloading(true); + await finishUpdate(); + }, + }; + setUpdateProgress(0); + callUpdaterMethod('do_update'); + }} + > + Update + </DialogButton> + ) : ( + <ProgressBarWithInfo + layout="inline" + bottomSeparator={false} + nProgress={updateProgress} + nTransitionSec={0.01} + indeterminate={reloading} + sOperationText={reloading ? 'Reloading' : 'Updating'} + /> + )} + </Field> + ); +} diff --git a/frontend/src/components/settings/pages/GeneralSettings.tsx b/frontend/src/components/settings/pages/general/index.tsx index 1cc8076d..7dc5cfa4 100644 --- a/frontend/src/components/settings/pages/GeneralSettings.tsx +++ b/frontend/src/components/settings/pages/general/index.tsx @@ -2,7 +2,8 @@ import { DialogButton, Field, TextField } from 'decky-frontend-lib'; import { useState } from 'react'; import { FaShapes } from 'react-icons/fa'; -import { installFromURL } from '../../store/Store'; +import { installFromURL } from '../../../store/Store'; +import UpdaterSettings from './Updater'; export default function GeneralSettings() { const [pluginURL, setPluginURL] = useState(''); @@ -18,12 +19,15 @@ export default function GeneralSettings() { onChange={(e) => setChecked(e)} /> </Field> */} + <UpdaterSettings /> <Field label="Manual plugin install" description={<TextField label={'URL'} value={pluginURL} onChange={(e) => setPluginURL(e?.target.value)} />} icon={<FaShapes style={{ display: 'block' }} />} > - <DialogButton onClick={() => installFromURL(pluginURL)}>Install</DialogButton> + <DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}> + Install + </DialogButton> </Field> </div> ); diff --git a/frontend/src/components/settings/pages/PluginList.tsx b/frontend/src/components/settings/pages/plugin_list/index.tsx index bf01f85a..a554236a 100644 --- a/frontend/src/components/settings/pages/PluginList.tsx +++ b/frontend/src/components/settings/pages/plugin_list/index.tsx @@ -1,7 +1,7 @@ import { DialogButton, staticClasses } from 'decky-frontend-lib'; import { FaTrash } from 'react-icons/fa'; -import { useDeckyState } from '../../DeckyState'; +import { useDeckyState } from '../../../DeckyState'; export default function PluginList() { const { plugins } = useDeckyState(); diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 5cf2ed14..364ccb1b 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,8 +1,10 @@ import PluginLoader from './plugin-loader'; +import { DeckyUpdater } from './updater'; declare global { interface Window { DeckyPluginLoader: PluginLoader; + DeckyUpdater?: DeckyUpdater; importDeckyPlugin: Function; syncDeckyPlugins: Function; } diff --git a/frontend/src/updater.ts b/frontend/src/updater.ts new file mode 100644 index 00000000..692a7a70 --- /dev/null +++ b/frontend/src/updater.ts @@ -0,0 +1,30 @@ +import { sleep } from 'decky-frontend-lib'; + +export enum Branches { + Release, + Prerelease, + Nightly, +} + +export interface DeckyUpdater { + updateProgress: (val: number) => void; + finish: () => void; +} + +export async function callUpdaterMethod(methodName: string, args = {}) { + const response = await fetch(`http://127.0.0.1:1337/updater/${methodName}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(args), + }); + + return response.json(); +} + +export async function finishUpdate() { + callUpdaterMethod('do_restart'); + await sleep(3000); + location.reload(); +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 13b0c350..7e88185b 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -5,6 +5,7 @@ "target": "ES2020", "jsx": "react", "jsxFactory": "window.SP_REACT.createElement", + "jsxFragmentFactory": "window.SP_REACT.Fragment", "declaration": false, "moduleResolution": "node", "noUnusedLocals": true, |
