diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 204ef846..e9a91e0c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -33,9 +33,9 @@ body: attributes: label: Additional information and data description: | - Add screenshots and upload your logs file here. - Logs location on Windows: "%appdata%/hydra" - Logs location on Linux: "~/.config/hydra/" + Add screenshots and upload your all logs file here. + Logs location on Windows: "%appdata%/hydralauncher/logs" + Logs location on Linux: "~/.config/hydralauncher/logs" validations: required: true - type: input diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1bb4ce07..45ed8aaf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,23 +25,13 @@ jobs: node-version: 20.18.0 - name: Install dependencies - run: yarn + run: yarn --frozen-lockfile - name: Install Python uses: actions/setup-python@v5 with: python-version: 3.9 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: rustfmt - - - name: Build Rust - run: cargo build --release - working-directory: ./rust_rpc - - name: Install dependencies run: pip install -r requirements.txt diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0a17b329..0a20e469 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: node-version: 20.18.0 - name: Install dependencies - run: yarn + run: yarn --frozen-lockfile - name: Validate current commit (last commit) with commitlint run: npx commitlint --last --verbose diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 818d2d9c..d7098e44 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,23 +26,13 @@ jobs: node-version: 20.18.0 - name: Install dependencies - run: yarn + run: yarn --frozen-lockfile - name: Install Python uses: actions/setup-python@v5 with: python-version: 3.9 - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - components: rustfmt - - - name: Build Rust - run: cargo build --release - working-directory: ./rust_rpc - - name: Install dependencies run: pip install -r requirements.txt @@ -98,6 +88,18 @@ jobs: dist/*.blockmap dist/*.pacman + - name: Upload build + env: + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + S3_ENDPOINT: ${{ secrets.S3_ENDPOINT }} + S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }} + S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }} + S3_BUILDS_BUCKET_NAME: ${{ secrets.S3_BUILDS_BUCKET_NAME }} + BUILDS_URL: ${{ secrets.BUILDS_URL }} + BUILD_WEBHOOK_URL: ${{ secrets.BUILD_WEBHOOK_URL }} + GITHUB_ACTOR: ${{ github.actor }} + run: node scripts/upload-build.cjs + - name: Release uses: softprops/action-gh-release@v2 with: diff --git a/.gitignore b/.gitignore index 0df3955a..eb0592e9 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,4 @@ hydra-python-rpc/ *storybook.log - -target/ +aria2/ diff --git a/binaries/7zz b/binaries/7zz old mode 100644 new mode 100755 diff --git a/binaries/7zzs b/binaries/7zzs old mode 100644 new mode 100755 diff --git a/binaries/hydra-httpdl.exe b/binaries/hydra-httpdl.exe deleted file mode 100644 index 7a686d9e..00000000 Binary files a/binaries/hydra-httpdl.exe and /dev/null differ diff --git a/electron-builder.yml b/electron-builder.yml index 74f5b6fa..dd10e81a 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -3,6 +3,7 @@ productName: Hydra directories: buildResources: build extraResources: + - aria2 - ludusavi - hydra-python-rpc - seeds @@ -22,7 +23,6 @@ win: extraResources: - from: binaries/7z.exe - from: binaries/7z.dll - - from: rust_rpc/target/release/hydra-httpdl.exe target: - nsis - portable @@ -40,7 +40,6 @@ mac: entitlementsInherit: build/entitlements.mac.plist extraResources: - from: binaries/7zz - - from: rust_rpc/target/release/hydra-httpdl extendInfo: - NSCameraUsageDescription: Application requests access to the device's camera. - NSMicrophoneUsageDescription: Application requests access to the device's microphone. @@ -52,7 +51,6 @@ dmg: linux: extraResources: - from: binaries/7zzs - - from: rust_rpc/target/release/hydra-httpdl target: - AppImage - snap diff --git a/package.json b/package.json index 1c860062..d74f5a92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "3.4.2", + "version": "3.4.4", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", @@ -21,7 +21,7 @@ "typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false", "typecheck": "npm run typecheck:node && npm run typecheck:web", "start": "electron-vite preview", - "dev": "cargo build --manifest-path=rust_rpc/Cargo.toml && electron-vite dev", + "dev": "electron-vite dev", "build": "npm run typecheck && electron-vite build", "postinstall": "electron-builder install-app-deps && node ./scripts/postinstall.cjs", "build:unpack": "npm run build && electron-builder --dir", @@ -48,7 +48,7 @@ "classnames": "^2.5.1", "color": "^4.2.3", "color.js": "^1.2.0", - "create-desktop-shortcuts": "^1.11.0", + "create-desktop-shortcuts": "^1.11.1", "date-fns": "^3.6.0", "dexie": "^4.0.10", "diskusage": "^1.2.0", @@ -59,7 +59,6 @@ "i18next-browser-languagedetector": "^7.2.1", "jsdom": "^24.0.0", "jsonwebtoken": "^9.0.2", - "kill-port": "^2.0.1", "lodash-es": "^4.17.21", "parse-torrent": "^11.0.17", "piscina": "^4.7.0", diff --git a/python_rpc/http_downloader.py b/python_rpc/http_downloader.py index b61688fc..71e4b57e 100644 --- a/python_rpc/http_downloader.py +++ b/python_rpc/http_downloader.py @@ -1,94 +1,48 @@ -import os -import subprocess -import json +import aria2p class HttpDownloader: - def __init__(self, hydra_httpdl_bin: str): - self.hydra_exe = hydra_httpdl_bin - self.process = None - self.last_status = None - - def start_download(self, url: str, save_path: str, header: str = None, allow_multiple_connections: bool = False, connections_limit: int = 1): - cmd = [self.hydra_exe] - - cmd.append(url) - - cmd.extend([ - "--chunk-size", "10", - "--buffer-size", "16", - "--force-download", - "--log", - "--silent" - ]) - - if header: - cmd.extend(["--header", header]) - - if allow_multiple_connections: - cmd.extend(["--connections", str(connections_limit)]) - else: - cmd.extend(["--connections", "1"]) - - print(f"running hydra-httpdl: {' '.join(cmd)}") - - try: - self.process = subprocess.Popen( - cmd, - cwd=save_path, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True + def __init__(self): + self.download = None + self.aria2 = aria2p.API( + aria2p.Client( + host="http://localhost", + port=6800, + secret="" ) - except Exception as e: - print(f"error running hydra-httpdl: {e}") - return None + ) + def start_download(self, url: str, save_path: str, header: str, out: str = None): + if self.download: + self.aria2.resume([self.download]) + else: + downloads = self.aria2.add(url, options={"header": header, "dir": save_path, "out": out}) + + self.download = downloads[0] + + def pause_download(self): + if self.download: + self.aria2.pause([self.download]) + + def cancel_download(self): + if self.download: + self.aria2.remove([self.download]) + self.download = None def get_download_status(self): - - if not self.process: + if self.download == None: return None - - try: - line = self.process.stdout.readline() - if line: - status = json.loads(line.strip()) - self.last_status = status - elif self.last_status: - status = self.last_status - else: - return None - - response = { - "status": "active", - "progress": status["progress"], - "downloadSpeed": status["speed_bps"], - "numPeers": 0, - "numSeeds": 0, - "bytesDownloaded": status["downloaded_bytes"], - "fileSize": status["total_bytes"], - "folderName": status["filename"] - } - - if status["progress"] == 1: - response["status"] = "complete" - - return response - - except Exception as e: - print(f"error getting download status: {e}") - return None - - - - def stop_download(self): - if self.process: - self.process.terminate() - self.process = None - self.last_status = None - - def pause_download(self): - self.stop_download() - - def cancel_download(self): - self.stop_download() + + download = self.aria2.get_download(self.download.gid) + + response = { + 'folderName': download.name, + 'fileSize': download.total_length, + 'progress': download.completed_length / download.total_length if download.total_length else 0, + 'downloadSpeed': download.download_speed, + 'numPeers': 0, + 'numSeeds': 0, + 'status': download.status, + 'bytesDownloaded': download.completed_length, + } + + return response diff --git a/python_rpc/main.py b/python_rpc/main.py index 864b4e50..8a64e7c3 100644 --- a/python_rpc/main.py +++ b/python_rpc/main.py @@ -13,7 +13,6 @@ http_port = sys.argv[2] rpc_password = sys.argv[3] start_download_payload = sys.argv[4] start_seeding_payload = sys.argv[5] -hydra_httpdl_bin = sys.argv[6] downloads = {} # This can be streamed down from Node @@ -33,10 +32,10 @@ if start_download_payload: except Exception as e: print("Error starting torrent download", e) else: - http_downloader = HttpDownloader(hydra_httpdl_bin) + http_downloader = HttpDownloader() downloads[initial_download['game_id']] = http_downloader try: - http_downloader.start_download(initial_download['url'], initial_download['save_path'], initial_download.get('header'), initial_download.get('allow_multiple_connections', False), initial_download.get('connections_limit', 24)) + http_downloader.start_download(initial_download['url'], initial_download['save_path'], initial_download.get('header'), initial_download.get('out')) except Exception as e: print("Error starting http download", e) @@ -148,11 +147,11 @@ def action(): torrent_downloader.start_download(url, data['save_path']) else: if existing_downloader and isinstance(existing_downloader, HttpDownloader): - existing_downloader.start_download(url, data['save_path'], data.get('header'), data.get('allow_multiple_connections', False), data.get('connections_limit', 24)) + existing_downloader.start_download(url, data['save_path'], data.get('header'), data.get('out')) else: - http_downloader = HttpDownloader(hydra_httpdl_bin) + http_downloader = HttpDownloader() downloads[game_id] = http_downloader - http_downloader.start_download(url, data['save_path'], data.get('header'), data.get('allow_multiple_connections', False), data.get('connections_limit', 24)) + http_downloader.start_download(url, data['save_path'], data.get('header'), data.get('out')) downloading_game_id = game_id @@ -183,4 +182,3 @@ def action(): if __name__ == "__main__": app.run(host="0.0.0.0", port=int(http_port)) - \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index bad56c4b..ffdfb59b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ pywin32; sys_platform == 'win32' psutil Pillow flask +aria2p diff --git a/rust_rpc/Cargo.lock b/rust_rpc/Cargo.lock deleted file mode 100644 index 2e14f7d9..00000000 --- a/rust_rpc/Cargo.lock +++ /dev/null @@ -1,2040 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "anstream" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" - -[[package]] -name = "anstyle-parse" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" -dependencies = [ - "anstyle", - "once_cell", - "windows-sys 0.59.0", -] - -[[package]] -name = "anyhow" -version = "1.0.97" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" - -[[package]] -name = "async-trait" -version = "0.1.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" - -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "cc" -version = "1.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" -dependencies = [ - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "4.5.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" - -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "console" -version = "0.15.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" -dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "unicode-width", - "windows-sys 0.59.0", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", -] - -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - -[[package]] -name = "h2" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "http" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hydra-httpdl" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "bitvec", - "bytes", - "clap", - "futures", - "indicatif", - "reqwest", - "serde_json", - "sha2", - "tokio", - "tokio-util", - "urlencoding", -] - -[[package]] -name = "hyper" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" -dependencies = [ - "futures-util", - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "libc", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "indicatif" -version = "0.17.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" -dependencies = [ - "console", - "number_prefix", - "portable-atomic", - "unicode-width", - "web-time", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - -[[package]] -name = "itoa" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" - -[[package]] -name = "js-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "libc" -version = "0.2.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - -[[package]] -name = "litemap" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" -dependencies = [ - "adler2", -] - -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", -] - -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "number_prefix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" - -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "openssl" -version = "0.10.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "portable-atomic" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" - -[[package]] -name = "proc-macro2" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "redox_syscall" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" -dependencies = [ - "bitflags", -] - -[[package]] -name = "reqwest" -version = "0.12.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tokio-util", - "tower", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "windows-registry", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.15", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustix" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustls" -version = "0.23.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" - -[[package]] -name = "rustls-webpki" -version = "0.103.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" - -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "schannel" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.219" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "socket2" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" -dependencies = [ - "fastrand", - "getrandom 0.3.2", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.44.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" - -[[package]] -name = "unicode-ident" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" - -[[package]] -name = "unicode-width" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "windows-link" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" - -[[package]] -name = "windows-registry" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" -dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.53.0", -] - -[[package]] -name = "windows-result" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" -dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/rust_rpc/Cargo.toml b/rust_rpc/Cargo.toml deleted file mode 100644 index fb8d4296..00000000 --- a/rust_rpc/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "hydra-httpdl" -version = "0.1.0" -edition = "2021" - -[dependencies] -tokio = { version = "1", features = ["full", "macros", "rt-multi-thread"] } -reqwest = { version = "0.12.5", features = ["stream"] } -futures = "0.3" -bytes = "1.4" -indicatif = "0.17" -anyhow = "1.0" -async-trait = "0.1" -tokio-util = { version = "0.7", features = ["io"] } -clap = { version = "4.4", features = ["derive"] } -urlencoding = "2.1" -serde_json = "1.0" -bitvec = "1.0" -sha2 = "0.10" -[profile.release] -opt-level = 3 -lto = "fat" -codegen-units = 1 -panic = "abort" -strip = true \ No newline at end of file diff --git a/rust_rpc/src/main.rs b/rust_rpc/src/main.rs deleted file mode 100644 index 884366a8..00000000 --- a/rust_rpc/src/main.rs +++ /dev/null @@ -1,966 +0,0 @@ -use anyhow::Result; -use bitvec::prelude::*; -use clap::Parser; -use futures::stream::{FuturesUnordered, StreamExt}; -use indicatif::{ProgressBar, ProgressStyle}; -use reqwest::{Client, StatusCode, Url}; -use serde_json::json; -use sha2::{Digest, Sha256}; -use std::fs::{File, OpenOptions}; -use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write}; -use std::path::Path; -use std::sync::Arc; -use tokio::sync::Mutex; - -const DEFAULT_MAX_RETRIES: usize = 3; -const RETRY_BACKOFF_MS: u64 = 500; -const DEFAULT_OUTPUT_FILENAME: &str = "output.bin"; -const DEFAULT_CONNECTIONS: usize = 16; -const DEFAULT_CHUNK_SIZE_MB: usize = 5; -const DEFAULT_BUFFER_SIZE_MB: usize = 8; -const DEFAULT_VERBOSE: bool = false; -const DEFAULT_SILENT: bool = false; -const DEFAULT_LOG: bool = false; -const DEFAULT_FORCE_NEW: bool = false; -const DEFAULT_RESUME_ONLY: bool = false; -const DEFAULT_FORCE_DOWNLOAD: bool = false; -const HEADER_SIZE: usize = 4096; -const MAGIC_NUMBER: &[u8; 5] = b"HYDRA"; -const FORMAT_VERSION: u8 = 1; -const FINALIZE_BUFFER_SIZE: usize = 1024 * 1024; - -#[derive(Parser)] -#[command(name = "hydra-httpdl")] -#[command(author = "los-broxas")] -#[command(version = "0.2.0")] -#[command(about = "high speed and low resource usage http downloader with resume capability", long_about = None)] -struct CliArgs { - /// file url to download - #[arg(required = true)] - url: String, - - /// output file path (or directory to save with original filename) - #[arg(default_value = DEFAULT_OUTPUT_FILENAME)] - output: String, - - /// number of concurrent connections for parallel download - #[arg(short = 'c', long, default_value_t = DEFAULT_CONNECTIONS)] - connections: usize, - - /// chunk size in MB for each connection - #[arg(short = 'k', long, default_value_t = DEFAULT_CHUNK_SIZE_MB)] - chunk_size: usize, - - /// buffer size in MB for file writing - #[arg(short, long, default_value_t = DEFAULT_BUFFER_SIZE_MB)] - buffer_size: usize, - - /// show detailed progress information - #[arg(short = 'v', long, default_value_t = DEFAULT_VERBOSE)] - verbose: bool, - - /// suppress progress bar - #[arg(short = 's', long, default_value_t = DEFAULT_SILENT)] - silent: bool, - - /// log download statistics in JSON format every second - #[arg(short = 'l', long, default_value_t = DEFAULT_LOG)] - log: bool, - - /// force new download, ignore existing partial files - #[arg(short = 'f', long, default_value_t = DEFAULT_FORCE_NEW)] - force_new: bool, - - /// only resume existing download, exit if no partial file exists - #[arg(short = 'r', long, default_value_t = DEFAULT_RESUME_ONLY)] - resume_only: bool, - - /// force download, ignore some verification checks - #[arg(short = 'F', long, default_value_t = DEFAULT_FORCE_DOWNLOAD)] - force_download: bool, - - /// HTTP headers to send with request (format: "Key: Value") - #[arg(short = 'H', long)] - header: Vec, -} - -struct DownloadConfig { - url: String, - output_path: String, - num_connections: usize, - chunk_size: usize, - buffer_size: usize, - verbose: bool, - silent: bool, - log: bool, - force_new: bool, - resume_only: bool, - headers: Vec, - force_download: bool, -} - -impl DownloadConfig { - fn should_log(&self) -> bool { - self.verbose && !self.silent - } - - fn should_log_stats(&self) -> bool { - self.log - } -} - -struct DownloadStats { - progress_percent: f64, - bytes_downloaded: u64, - total_size: u64, - speed_bytes_per_sec: f64, - eta_seconds: u64, - elapsed_seconds: u64, -} - -struct HydraHeader { - magic: [u8; 5], // "HYDRA" identifier - version: u8, // header version - file_size: u64, // file size - etag: [u8; 32], // etag hash - url_hash: [u8; 32], // url hash - chunk_size: u32, // chunk size - chunk_count: u32, // chunk count - chunks_bitmap: BitVec, // chunks bitmap -} - -impl HydraHeader { - fn new(file_size: u64, etag: &str, url: &str, chunk_size: u32) -> Self { - let chunk_count = ((file_size as f64) / (chunk_size as f64)).ceil() as u32; - let chunks_bitmap = bitvec![u8, Lsb0; 0; chunk_count as usize]; - - let mut etag_hash = [0u8; 32]; - let etag_digest = Sha256::digest(etag.as_bytes()); - etag_hash.copy_from_slice(&etag_digest[..]); - - let mut url_hash = [0u8; 32]; - let url_digest = Sha256::digest(url.as_bytes()); - url_hash.copy_from_slice(&url_digest[..]); - - Self { - magic: *MAGIC_NUMBER, - version: FORMAT_VERSION, - file_size, - etag: etag_hash, - url_hash, - chunk_size, - chunk_count, - chunks_bitmap, - } - } - - fn write_to_file(&self, writer: &mut W) -> Result<()> { - writer.write_all(&self.magic)?; - writer.write_all(&[self.version])?; - writer.write_all(&self.file_size.to_le_bytes())?; - writer.write_all(&self.etag)?; - writer.write_all(&self.url_hash)?; - writer.write_all(&self.chunk_size.to_le_bytes())?; - writer.write_all(&self.chunk_count.to_le_bytes())?; - - let bitmap_bytes = self.chunks_bitmap.as_raw_slice(); - writer.write_all(bitmap_bytes)?; - - let header_size = 5 + 1 + 8 + 32 + 32 + 4 + 4 + bitmap_bytes.len(); - let padding_size = HEADER_SIZE - header_size; - let padding = vec![0u8; padding_size]; - writer.write_all(&padding)?; - - Ok(()) - } - - fn read_from_file(reader: &mut R) -> Result { - let mut magic = [0u8; 5]; - reader.read_exact(&mut magic)?; - - if magic != *MAGIC_NUMBER { - anyhow::bail!("Not a valid Hydra download file"); - } - - let mut version = [0u8; 1]; - reader.read_exact(&mut version)?; - - if version[0] != FORMAT_VERSION { - anyhow::bail!("Incompatible format version"); - } - - let mut file_size_bytes = [0u8; 8]; - reader.read_exact(&mut file_size_bytes)?; - let file_size = u64::from_le_bytes(file_size_bytes); - - let mut etag = [0u8; 32]; - reader.read_exact(&mut etag)?; - - let mut url_hash = [0u8; 32]; - reader.read_exact(&mut url_hash)?; - - let mut chunk_size_bytes = [0u8; 4]; - reader.read_exact(&mut chunk_size_bytes)?; - let chunk_size = u32::from_le_bytes(chunk_size_bytes); - - let mut chunk_count_bytes = [0u8; 4]; - reader.read_exact(&mut chunk_count_bytes)?; - let chunk_count = u32::from_le_bytes(chunk_count_bytes); - - let bitmap_bytes_len = (chunk_count as usize + 7) / 8; - let mut bitmap_bytes = vec![0u8; bitmap_bytes_len]; - reader.read_exact(&mut bitmap_bytes)?; - - let chunks_bitmap = BitVec::::from_vec(bitmap_bytes); - - reader.seek(SeekFrom::Start(HEADER_SIZE as u64))?; - - Ok(Self { - magic, - version: version[0], - file_size, - etag, - url_hash, - chunk_size, - chunk_count, - chunks_bitmap, - }) - } - - fn set_chunk_complete(&mut self, chunk_index: usize) -> Result<()> { - if chunk_index >= self.chunk_count as usize { - anyhow::bail!("Chunk index out of bounds"); - } - - self.chunks_bitmap.set(chunk_index, true); - Ok(()) - } - - fn is_chunk_complete(&self, chunk_index: usize) -> bool { - if chunk_index >= self.chunk_count as usize { - return false; - } - - self.chunks_bitmap[chunk_index] - } - - fn get_incomplete_chunks(&self) -> Vec<(u64, u64)> { - let incomplete_count = self.chunk_count as usize - self.chunks_bitmap.count_ones(); - let mut chunks = Vec::with_capacity(incomplete_count); - let chunk_size = self.chunk_size as u64; - - for i in 0..self.chunk_count as usize { - if !self.is_chunk_complete(i) { - let start = i as u64 * chunk_size; - let end = std::cmp::min((i as u64 + 1) * chunk_size - 1, self.file_size - 1); - chunks.push((start, end)); - } - } - - chunks - } - - fn is_download_complete(&self) -> bool { - self.chunks_bitmap.count_ones() == self.chunk_count as usize - } -} - -struct ProgressTracker { - bar: Option, -} - -impl ProgressTracker { - fn new(file_size: u64, silent: bool, enable_stats: bool) -> Result { - let bar = if !silent || enable_stats { - let pb = ProgressBar::new(file_size); - pb.set_style( - ProgressStyle::default_bar() - .template("[{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta})")? - ); - if silent { - pb.set_draw_target(indicatif::ProgressDrawTarget::hidden()); - } - Some(pb) - } else { - None - }; - - Ok(Self { bar }) - } - - fn increment(&self, amount: u64) { - if let Some(pb) = &self.bar { - pb.inc(amount); - } - } - - fn finish(&self) { - if let Some(pb) = &self.bar { - pb.finish_with_message("Download complete"); - } - } - - fn get_stats(&self) -> Option { - if let Some(pb) = &self.bar { - let position = pb.position(); - let total = pb.length().unwrap_or(1); - - Some(DownloadStats { - progress_percent: position as f64 / total as f64, - bytes_downloaded: position, - total_size: total, - speed_bytes_per_sec: pb.per_sec(), - eta_seconds: pb.eta().as_secs(), - elapsed_seconds: pb.elapsed().as_secs(), - }) - } else { - None - } - } -} - -struct Downloader { - client: Client, - config: DownloadConfig, -} - -impl Downloader { - async fn download(&self) -> Result<()> { - let (file_size, filename, etag) = self.get_file_info().await?; - let output_path = self.determine_output_path(filename); - - if self.config.should_log() { - println!("Detected filename: {}", output_path); - } - - let resume_manager = ResumeManager::try_from_file( - &output_path, - file_size, - &etag, - &self.config.url, - self.config.chunk_size as u32, - self.config.force_new, - self.config.resume_only, - )?; - - let file = self.prepare_output_file(&output_path, file_size)?; - let progress = ProgressTracker::new(file_size, self.config.silent, self.config.log)?; - - let chunks = if resume_manager.is_download_complete() { - if self.config.should_log() { - println!("File is already fully downloaded, finalizing..."); - } - resume_manager.finalize_download()?; - return Ok(()); - } else { - let completed_chunks = resume_manager.header.chunks_bitmap.count_ones() as u32; - let total_chunks = resume_manager.header.chunk_count; - - if completed_chunks > 0 { - if self.config.should_log() { - let percent_done = (completed_chunks as f64 / total_chunks as f64) * 100.0; - println!("Resuming download: {:.1}% already downloaded", percent_done); - } - - if let Some(pb) = &progress.bar { - let downloaded = file_size * completed_chunks as u64 / total_chunks as u64; - pb.inc(downloaded); - } - } - - resume_manager.get_incomplete_chunks() - }; - - if self.config.should_log() { - println!( - "Downloading {} chunks of total {}", - chunks.len(), - resume_manager.header.chunk_count - ); - } - - self.process_chunks_with_resume( - chunks, - file, - file_size, - progress, - output_path.clone(), - resume_manager, - ) - .await?; - - Ok(()) - } - - fn determine_output_path(&self, filename: Option) -> String { - if Path::new(&self.config.output_path) - .file_name() - .unwrap_or_default() - == DEFAULT_OUTPUT_FILENAME - && filename.is_some() - { - filename.unwrap() - } else { - self.config.output_path.clone() - } - } - - fn prepare_output_file(&self, path: &str, size: u64) -> Result>>> { - let file = if Path::new(path).exists() { - OpenOptions::new().read(true).write(true).open(path)? - } else { - let file = File::create(path)?; - file.set_len(HEADER_SIZE as u64 + size)?; - file - }; - - Ok(Arc::new(Mutex::new(BufWriter::with_capacity( - self.config.buffer_size, - file, - )))) - } - - async fn process_chunks_with_resume( - &self, - chunks: Vec<(u64, u64)>, - file: Arc>>, - _file_size: u64, - progress: ProgressTracker, - real_filename: String, - resume_manager: ResumeManager, - ) -> Result<()> { - let mut tasks = FuturesUnordered::new(); - - let log_progress = if self.config.should_log_stats() { - let progress_clone = progress.bar.clone(); - let filename = real_filename.clone(); - - let (log_cancel_tx, mut log_cancel_rx) = tokio::sync::oneshot::channel(); - - let log_task = tokio::spawn(async move { - let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(1)); - let tracker = ProgressTracker { - bar: progress_clone, - }; - - loop { - tokio::select! { - _ = interval.tick() => { - if let Some(stats) = tracker.get_stats() { - let json_output = json!({ - "progress": stats.progress_percent, - "speed_bps": stats.speed_bytes_per_sec, - "downloaded_bytes": stats.bytes_downloaded, - "total_bytes": stats.total_size, - "eta_seconds": stats.eta_seconds, - "elapsed_seconds": stats.elapsed_seconds, - "filename": filename - }); - println!("{}", json_output); - } - } - _ = &mut log_cancel_rx => { - break; - } - } - } - }); - Some((log_task, log_cancel_tx)) - } else { - None - }; - - let resume_manager = Arc::new(Mutex::new(resume_manager)); - - for (start, end) in chunks { - let client = self.client.clone(); - let url = self.config.url.clone(); - let file_clone = Arc::clone(&file); - let pb_clone = progress.bar.clone(); - let manager_clone = Arc::clone(&resume_manager); - let headers = self.config.headers.clone(); - let force_download = self.config.force_download; - let should_log = self.config.should_log(); - - let chunk_size = self.config.chunk_size as u64; - let chunk_index = (start / chunk_size) as usize; - - tasks.push(tokio::spawn(async move { - let result = Self::download_chunk_with_retry( - client, - url, - start, - end, - file_clone, - pb_clone, - DEFAULT_MAX_RETRIES, - &headers, - force_download, - should_log, - ) - .await; - - if result.is_ok() { - let mut manager = manager_clone.lock().await; - manager.set_chunk_complete(chunk_index)?; - } - - result - })); - - if tasks.len() >= self.config.num_connections { - if let Some(result) = tasks.next().await { - result??; - } - } - } - - while let Some(result) = tasks.next().await { - result??; - } - - { - let mut writer = file.lock().await; - writer.flush()?; - } - - progress.finish(); - - if let Some((log_handle, log_cancel_tx)) = log_progress { - if self.config.should_log_stats() { - let json_output = json!({ - "progress": 1.0, - "speed_bps": 0.0, - "downloaded_bytes": _file_size, - "total_bytes": _file_size, - "eta_seconds": 0, - "elapsed_seconds": if let Some(pb) = &progress.bar { pb.elapsed().as_secs() } else { 0 }, - "filename": real_filename - }); - println!("{}", json_output); - } - - let _ = log_cancel_tx.send(()); - let _ = log_handle.await; - } - - let manager = resume_manager.lock().await; - if manager.is_download_complete() { - if self.config.should_log() { - println!("Download complete, finalizing file..."); - } - manager.finalize_download()?; - } - - Ok(()) - } - - async fn download_chunk_with_retry( - client: Client, - url: String, - start: u64, - end: u64, - file: Arc>>, - progress_bar: Option, - max_retries: usize, - headers: &[String], - force_download: bool, - should_log: bool, - ) -> Result<()> { - let mut retries = 0; - loop { - match Self::download_chunk( - client.clone(), - url.clone(), - start, - end, - file.clone(), - progress_bar.clone(), - headers, - force_download, - should_log, - ) - .await - { - Ok(_) => return Ok(()), - Err(e) => { - retries += 1; - if retries >= max_retries { - return Err(e); - } - tokio::time::sleep(tokio::time::Duration::from_millis( - RETRY_BACKOFF_MS * (2_u64.pow(retries as u32 - 1)), - )) - .await; - } - } - } - } - - async fn download_chunk( - client: Client, - url: String, - start: u64, - end: u64, - file: Arc>>, - progress_bar: Option, - headers: &[String], - force_download: bool, - should_log: bool, - ) -> Result<()> { - let mut req = client - .get(&url) - .header("Range", format!("bytes={}-{}", start, end)); - - for header in headers { - if let Some(idx) = header.find(':') { - let (name, value) = header.split_at(idx); - let value = value[1..].trim(); - req = req.header(name.trim(), value); - } - } - - let resp = req.send().await?; - - if resp.status() != StatusCode::PARTIAL_CONTENT && resp.status() != StatusCode::OK { - if !force_download { - anyhow::bail!("Server does not support Range requests"); - } else if should_log { - println!("Server does not support Range requests, ignoring..."); - } - } - - let mut stream = resp.bytes_stream(); - let mut position = start; - let mut total_bytes = 0; - let expected_bytes = end - start + 1; - - while let Some(chunk_result) = stream.next().await { - let chunk = chunk_result?; - let chunk_size = chunk.len() as u64; - - total_bytes += chunk_size; - if total_bytes > expected_bytes { - let remaining = expected_bytes - (total_bytes - chunk_size); - let mut writer = file.lock().await; - writer.seek(SeekFrom::Start(HEADER_SIZE as u64 + position))?; - writer.write_all(&chunk[..remaining as usize])?; - - let tracker = ProgressTracker { - bar: progress_bar.clone(), - }; - tracker.increment(remaining); - break; - } - - let mut writer = file.lock().await; - writer.seek(SeekFrom::Start(HEADER_SIZE as u64 + position))?; - writer.write_all(&chunk)?; - drop(writer); - - position += chunk_size; - let tracker = ProgressTracker { - bar: progress_bar.clone(), - }; - tracker.increment(chunk_size); - } - - Ok(()) - } - - async fn get_file_info(&self) -> Result<(u64, Option, String)> { - let mut req = self.client.head(&self.config.url); - - for header in &self.config.headers { - if let Some(idx) = header.find(':') { - let (name, value) = header.split_at(idx); - let value = value[1..].trim(); - req = req.header(name.trim(), value); - } - } - - let resp = req.send().await?; - - let accepts_ranges = resp - .headers() - .get("accept-ranges") - .and_then(|v| v.to_str().ok()) - .map(|v| v.contains("bytes")) - .unwrap_or(false); - - if !accepts_ranges { - let range_check = self - .client - .get(&self.config.url) - .header("Range", "bytes=0-0") - .send() - .await?; - - if range_check.status() != StatusCode::PARTIAL_CONTENT { - if !self.config.force_download { - anyhow::bail!( - "Server does not support Range requests, cannot continue with parallel download" - ); - } else if self.config.should_log() { - println!("Server does not support Range requests, ignoring..."); - } - } - } - - let file_size = if let Some(content_length) = resp.headers().get("content-length") { - content_length.to_str()?.parse()? - } else { - anyhow::bail!("Could not determine file size") - }; - - let etag = if let Some(etag_header) = resp.headers().get("etag") { - etag_header.to_str()?.to_string() - } else { - format!( - "no-etag-{}", - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs() - ) - }; - - let filename = self.extract_filename_from_response(&resp); - - Ok((file_size, filename, etag)) - } - - fn extract_filename_from_response(&self, resp: &reqwest::Response) -> Option { - if let Some(disposition) = resp.headers().get("content-disposition") { - if let Ok(disposition_str) = disposition.to_str() { - if let Some(filename) = Self::parse_content_disposition(disposition_str) { - return Some(filename); - } - } - } - - Self::extract_filename_from_url(&self.config.url) - } - - fn parse_content_disposition(disposition: &str) -> Option { - if let Some(idx) = disposition.find("filename=") { - let start = idx + 9; - let mut end = disposition.len(); - - if disposition.as_bytes().get(start) == Some(&b'"') { - let quoted_name = &disposition[start + 1..]; - if let Some(quote_end) = quoted_name.find('"') { - return Some(quoted_name[..quote_end].to_string()); - } - } else { - if let Some(semicolon) = disposition[start..].find(';') { - end = start + semicolon; - } - return Some(disposition[start..end].to_string()); - } - } - None - } - - fn extract_filename_from_url(url: &str) -> Option { - if let Ok(parsed_url) = Url::parse(url) { - let path = parsed_url.path(); - if let Some(path_filename) = Path::new(path).file_name() { - if let Some(filename_str) = path_filename.to_str() { - if !filename_str.is_empty() { - if let Ok(decoded) = urlencoding::decode(filename_str) { - return Some(decoded.to_string()); - } - } - } - } - } - None - } -} - -struct ResumeManager { - header: HydraHeader, - file_path: String, -} - -impl ResumeManager { - fn try_from_file( - path: &str, - file_size: u64, - etag: &str, - url: &str, - chunk_size: u32, - force_new: bool, - resume_only: bool, - ) -> Result { - if force_new { - if Path::new(path).exists() { - std::fs::remove_file(path)?; - } - - return Self::create_new_file(path, file_size, etag, url, chunk_size); - } - - if let Ok(file) = File::open(path) { - let mut reader = BufReader::new(file); - match HydraHeader::read_from_file(&mut reader) { - Ok(header) => { - let current_url_hash = Sha256::digest(url.as_bytes()); - - let url_matches = header.url_hash == current_url_hash.as_slice(); - let size_matches = header.file_size == file_size; - - if url_matches && size_matches { - return Ok(Self { - header, - file_path: path.to_string(), - }); - } - - if resume_only { - anyhow::bail!( - "Existing file is not compatible and resume_only option is active" - ); - } - - std::fs::remove_file(path)?; - } - Err(e) => { - if resume_only { - return Err(anyhow::anyhow!("Could not read file to resume: {}", e)); - } - - std::fs::remove_file(path)?; - } - } - } else if resume_only { - anyhow::bail!("File not found and resume_only option is active"); - } - - Self::create_new_file(path, file_size, etag, url, chunk_size) - } - - fn create_new_file( - path: &str, - file_size: u64, - etag: &str, - url: &str, - chunk_size: u32, - ) -> Result { - let header = HydraHeader::new(file_size, etag, url, chunk_size); - let file = File::create(path)?; - file.set_len(HEADER_SIZE as u64 + file_size)?; - - let mut writer = BufWriter::new(file); - header.write_to_file(&mut writer)?; - writer.flush()?; - - Ok(Self { - header, - file_path: path.to_string(), - }) - } - - fn get_incomplete_chunks(&self) -> Vec<(u64, u64)> { - self.header.get_incomplete_chunks() - } - - fn set_chunk_complete(&mut self, chunk_index: usize) -> Result<()> { - self.header.set_chunk_complete(chunk_index)?; - - let file = OpenOptions::new().write(true).open(&self.file_path)?; - let mut writer = BufWriter::new(file); - - let bitmap_offset = 5 + 1 + 8 + 32 + 32 + 4 + 4; - writer.seek(SeekFrom::Start(bitmap_offset as u64))?; - - let bitmap_bytes = self.header.chunks_bitmap.as_raw_slice(); - writer.write_all(bitmap_bytes)?; - writer.flush()?; - - Ok(()) - } - - fn is_download_complete(&self) -> bool { - self.header.is_download_complete() - } - - fn finalize_download(&self) -> Result<()> { - if !self.is_download_complete() { - anyhow::bail!("Download is not complete"); - } - - let temp_path = format!("{}.tmp", self.file_path); - let source = File::open(&self.file_path)?; - let dest = File::create(&temp_path)?; - - let mut reader = BufReader::with_capacity(FINALIZE_BUFFER_SIZE, source); - let mut writer = BufWriter::with_capacity(FINALIZE_BUFFER_SIZE, dest); - - reader.seek(SeekFrom::Start(HEADER_SIZE as u64))?; - - std::io::copy(&mut reader, &mut writer)?; - writer.flush()?; - drop(writer); - - match std::fs::rename(&temp_path, &self.file_path) { - Ok(_) => Ok(()), - Err(_) => { - let _ = std::fs::remove_file(&self.file_path); - std::fs::rename(&temp_path, &self.file_path)?; - Ok(()) - } - } - } -} - -#[tokio::main] -async fn main() -> Result<()> { - let args = CliArgs::parse(); - - let config = DownloadConfig { - url: args.url.clone(), - output_path: args.output, - num_connections: args.connections, - chunk_size: args.chunk_size * 1024 * 1024, - buffer_size: args.buffer_size * 1024 * 1024, - verbose: args.verbose, - silent: args.silent, - log: args.log, - force_new: args.force_new, - resume_only: args.resume_only, - headers: args.header, - force_download: args.force_download, - }; - - if config.force_new && config.resume_only { - eprintln!("Error: --force-new and --resume-only options cannot be used together"); - std::process::exit(1); - } - - let downloader = Downloader { - client: Client::new(), - config, - }; - - if downloader.config.should_log() { - println!( - "Starting download with {} connections, chunk size: {}MB, buffer: {}MB", - downloader.config.num_connections, args.chunk_size, args.buffer_size - ); - println!("URL: {}", args.url); - - if downloader.config.force_new { - println!("Forcing new download, ignoring existing files"); - } else if downloader.config.resume_only { - println!("Only resuming existing download"); - } else { - println!("Resuming download if possible"); - } - } - - downloader.download().await?; - - Ok(()) -} diff --git a/scripts/postinstall.cjs b/scripts/postinstall.cjs index b7099027..8deddeaa 100644 --- a/scripts/postinstall.cjs +++ b/scripts/postinstall.cjs @@ -1,14 +1,18 @@ const { default: axios } = require("axios"); +const tar = require("tar"); const util = require("node:util"); const fs = require("node:fs"); const path = require("node:path"); +const { spawnSync } = require("node:child_process"); const exec = util.promisify(require("node:child_process").exec); +const ludusaviVersion = "0.29.0"; + const fileName = { - win32: "ludusavi-v0.25.0-win64.zip", - linux: "ludusavi-v0.25.0-linux.zip", - darwin: "ludusavi-v0.25.0-mac.zip", + win32: `ludusavi-v${ludusaviVersion}-win64.zip`, + linux: `ludusavi-v${ludusaviVersion}-linux.tar.gz`, + darwin: `ludusavi-v${ludusaviVersion}-mac.tar.gz`, }; const downloadLudusavi = async () => { @@ -18,7 +22,7 @@ const downloadLudusavi = async () => { } const file = fileName[process.platform]; - const downloadUrl = `https://github.com/mtkennerly/ludusavi/releases/download/v0.25.0/${file}`; + const downloadUrl = `https://github.com/mtkennerly/ludusavi/releases/download/v${ludusaviVersion}/${file}`; console.log(`Downloading ${file}...`); @@ -30,10 +34,18 @@ const downloadLudusavi = async () => { console.log(`Downloaded ${file}, extracting...`); const pwd = process.cwd(); - const targetPath = path.join(pwd, "ludusavi"); - await exec(`npx extract-zip ${file} ${targetPath}`); + await fs.promises.mkdir(targetPath, { recursive: true }); + + if (process.platform === "win32") { + await exec(`npx extract-zip ${file} ${targetPath}`); + } else { + await tar.x({ + file: file, + cwd: targetPath, + }); + } if (process.platform !== "win32") { fs.chmodSync(path.join(targetPath, "ludusavi"), 0o755); @@ -46,11 +58,79 @@ const downloadLudusavi = async () => { }); }; +const downloadAria2WindowsAndLinux = async () => { + const file = + process.platform === "win32" + ? "aria2-1.37.0-win-64bit-build1.zip" + : "aria2-1.37.0-1-x86_64.pkg.tar.zst"; + + const downloadUrl = + process.platform === "win32" + ? `https://github.com/aria2/aria2/releases/download/release-1.37.0/${file}` + : "https://archlinux.org/packages/extra/x86_64/aria2/download/"; + + console.log(`Downloading ${file}...`); + + const response = await axios.get(downloadUrl, { responseType: "stream" }); + + const stream = response.data.pipe(fs.createWriteStream(file)); + + stream.on("finish", async () => { + console.log(`Downloaded ${file}, extracting...`); + + if (process.platform === "win32") { + await exec(`npx extract-zip ${file}`); + console.log("Extracted. Renaming folder..."); + + fs.mkdirSync("aria2"); + fs.copyFileSync( + path.join(file.replace(".zip", ""), "aria2c.exe"), + "aria2/aria2c.exe" + ); + fs.rmSync(file.replace(".zip", ""), { recursive: true }); + } else { + await exec(`tar --zstd -xvf ${file} usr/bin/aria2c`); + console.log("Extracted. Copying binary file..."); + fs.mkdirSync("aria2"); + fs.copyFileSync("usr/bin/aria2c", "aria2/aria2c"); + fs.rmSync("usr", { recursive: true }); + } + + console.log(`Extracted ${file}, removing compressed downloaded file...`); + fs.rmSync(file); + }); +}; + +const copyAria2Macos = async () => { + console.log("Checking if aria2 is installed..."); + + const isAria2Installed = spawnSync("which", ["aria2c"]).status; + + if (isAria2Installed != 0) { + console.log("Please install aria2"); + console.log("brew install aria2"); + return; + } + + console.log("Copying aria2 binary..."); + fs.mkdirSync("aria2"); + await exec(`cp $(which aria2c) aria2/aria2c`); +}; + +const copyAria2 = () => { + const aria2Path = + process.platform === "win32" ? "aria2/aria2c.exe" : "aria2/aria2c"; + + if (fs.existsSync(aria2Path)) { + console.log("Aria2 already exists, skipping download..."); + return; + } + if (process.platform == "darwin") { + copyAria2Macos(); + } else { + downloadAria2WindowsAndLinux(); + } +}; + +copyAria2(); downloadLudusavi(); - -if (process.platform !== "win32") { - const binariesPath = path.join(__dirname, "..", "binaries"); - - fs.chmodSync(path.join(binariesPath, "7zz"), 0o755); - fs.chmodSync(path.join(binariesPath, "7zzs"), 0o755); -} diff --git a/src/locales/ar/translation.json b/src/locales/ar/translation.json index e1511d57..782d6b51 100644 --- a/src/locales/ar/translation.json +++ b/src/locales/ar/translation.json @@ -120,7 +120,7 @@ "options": "خيارات", "executable_section_title": "ملف التشغيل", "executable_section_description": "مسار الملف الذي سيتم تشغيله عند النقر على \"تشغيل\"", - "downloads_secion_title": "التنزيلات", + "downloads_section_title": "التنزيلات", "downloads_section_description": "تحقق من التحديثات أو الإصدارات الأخرى لهذه اللعبة", "danger_zone_section_title": "منطقة الخطر", "danger_zone_section_description": "إزالة هذه اللعبة من مكتبتك أو الملفات التي تم تنزيلها بواسطة Hydra", @@ -188,8 +188,8 @@ "reset_achievements_error": "فشل في إعادة تعيين الإنجازات", "download_error_gofile_quota_exceeded": "لقد تجاوزت الحصة الشهرية لـ Gofile. يرجى الانتظار حتى إعادة تعيين الحصة.", "download_error_real_debrid_account_not_authorized": "حساب Real-Debrid الخاص بك غير مصرح له بإجراء تنزيلات جديدة. يرجى مراجعة إعدادات الحساب والمحاولة مرة أخرى.", - "download_error_not_cached_in_real_debrid": "هذا التنزيل غير متوفر على Real-Debrid وجلب حالة التنزيل من Real-Debrid غير متاح حاليًا.", - "download_error_not_cached_in_torbox": "هذا التنزيل غير متوفر على Torbox وجلب حالة التنزيل من Torbox غير متاح حاليًا.", + "download_error_not_cached_on_real_debrid": "هذا التنزيل غير متوفر على Real-Debrid وجلب حالة التنزيل من Real-Debrid غير متاح حاليًا.", + "download_error_not_cached_on_torbox": "هذا التنزيل غير متوفر على TorBox وجلب حالة التنزيل من TorBox غير متاح حاليًا.", "game_removed_from_favorites": "تمت إزالة اللعبة من المفضلة", "game_added_to_favorites": "تمت إضافة اللعبة إلى المفضلة" }, @@ -330,7 +330,7 @@ "delete_theme_description": "سيؤدي هذا إلى حذف السمة {{theme}}", "cancel": "إلغاء", "appearance": "المظهر", - "enable_torbox": "تفعيل Torbox", + "enable_torbox": "تفعيل TorBox", "torbox_description": "TorBox هي خدمة seedbox متميزة تنافس أفضل الخوادم في السوق.", "torbox_account_linked": "تم ربط حساب TorBox", "real_debrid_account_linked": "تم ربط حساب Real-Debrid", diff --git a/src/locales/bg/translation.json b/src/locales/bg/translation.json index 8d5952e2..4d91aa92 100644 --- a/src/locales/bg/translation.json +++ b/src/locales/bg/translation.json @@ -122,7 +122,7 @@ "options": "Опции", "executable_section_title": "Стартиращ файл", "executable_section_description": "Пътят на файла, който ще се изпълни, когато се щракне върху \"Пускане\"", - "downloads_secion_title": "Свалени", + "downloads_section_title": "Свалени", "downloads_section_description": "Вижте актуализации или други версии на тази игра", "danger_zone_section_title": "Опасна зона", "danger_zone_section_description": "Премахнете тази игра от библиотеката си или от файловете, изтеглени от Hydra", diff --git a/src/locales/ca/translation.json b/src/locales/ca/translation.json index ff7b3e03..aa69001f 100644 --- a/src/locales/ca/translation.json +++ b/src/locales/ca/translation.json @@ -107,7 +107,7 @@ "options": "Opcions", "executable_section_title": "Executable", "executable_section_description": "Directori del fitxer des d'on s'executarà quan es cliqui a \"Executar\"", - "downloads_secion_title": "Descàrregues", + "downloads_section_title": "Descàrregues", "downloads_section_description": "Comprova actualitzacions o altres versions del videojoc", "danger_zone_section_title": "Zona de perill", "danger_zone_section_description": "Elimina aquest videojoc del teu catàleg o els fitxers descarregats per Hydra", diff --git a/src/locales/cs/translation.json b/src/locales/cs/translation.json index 3f478f95..9b501b54 100644 --- a/src/locales/cs/translation.json +++ b/src/locales/cs/translation.json @@ -123,7 +123,7 @@ "options": "Možnosti", "executable_section_title": "Spustitelné", "executable_section_description": "Umístění souboru který bude spuštěn při kliknutí na \"Hrát\"", - "downloads_secion_title": "Stažené soubory", + "downloads_section_title": "Stažené soubory", "downloads_section_description": "Zkontrolovat jestli není nová / odlišná verze hry", "danger_zone_section_title": "Nebezpečná zóna", "danger_zone_section_description": "Odebrat hru z knihovny / soubory stažené Hydrou", @@ -193,8 +193,8 @@ "reset_achievements_error": "Nepodařilo se resetovat achievementy", "download_error_gofile_quota_exceeded": "Překročili jste vaši měsíční GoFile kvótu. Prosím vyčkejte na resetování kvóty.", "download_error_real_debrid_account_not_authorized": "Váš Real-Debrid účet není autorizován pro vytváření nových stahování. Prosím zkontrolujte nastavení vašeho účtu a zkuste to znovu.", - "download_error_not_cached_in_real_debrid": "Toto stahování není dostupné na Real-Debrid a získávání informací o stahování z Real-Debrid není zatím dostupné.", - "download_error_not_cached_in_torbox": "Toto stahování není dostupné na Torbox a získávání informací o stahování z Torbox není zatím dostupné.", + "download_error_not_cached_on_real_debrid": "Toto stahování není dostupné na Real-Debrid a získávání informací o stahování z Real-Debrid není zatím dostupné.", + "download_error_not_cached_on_torbox": "Toto stahování není dostupné na TorBox a získávání informací o stahování z TorBox není zatím dostupné.", "game_removed_from_favorites": "Hra odebrána z oblíbených", "game_added_to_favorites": "Hra přidána do oblíbených", "automatically_extract_downloaded_files": "Automaticky rozbalit stažené soubory" diff --git a/src/locales/da/translation.json b/src/locales/da/translation.json index 151940fc..618f085c 100644 --- a/src/locales/da/translation.json +++ b/src/locales/da/translation.json @@ -111,7 +111,7 @@ "options": "Valgmuligheder", "executable_section_title": "Eksekverbar fil", "executable_section_description": "Sti til filen som skal bruges når \"Spil\" bliver klikket", - "downloads_secion_title": "Downloads", + "downloads_section_title": "Downloads", "downloads_section_description": "Undersøg opdateringer eller andre versioner af dette spil", "danger_zone_section_title": "Farezonen", "danger_zone_section_description": "Fjern dette spil fra dit bibliotek eller filerne der er blevet downloadet af Hydra", diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index d992dfac..dad29fa8 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -107,7 +107,7 @@ "options": "Optionen", "executable_section_title": "Ausführbare Datei", "executable_section_description": "Pfad der Datei, die bei Klick auf \"Play\" ausgeführt wird", - "downloads_secion_title": "Downloads", + "downloads_section_title": "Downloads", "downloads_section_description": "Sieh dir Updates oder andere Versionen dieses Spiels an", "danger_zone_section_title": "Gefahrenzone", "danger_zone_section_description": "Entferne dieses Spiel aus deiner Bibliothek oder die von Hydra heruntergeladenen Dateien", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 25c660db..a76745b1 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -123,7 +123,7 @@ "options": "Options", "executable_section_title": "Executable", "executable_section_description": "Path of the file that will be executed when \"Play\" is clicked", - "downloads_secion_title": "Downloads", + "downloads_section_title": "Downloads", "downloads_section_description": "Check out updates or other versions of this game", "danger_zone_section_title": "Danger zone", "danger_zone_section_description": "Remove this game from your library or the files downloaded by Hydra", @@ -193,8 +193,9 @@ "reset_achievements_error": "Failed to reset achievements", "download_error_gofile_quota_exceeded": "You have exceeded your Gofile monthly quota. Please await the quota to reset.", "download_error_real_debrid_account_not_authorized": "Your Real-Debrid account is not authorized to make new downloads. Please check your account settings and try again.", - "download_error_not_cached_in_real_debrid": "This download is not available on Real-Debrid and polling download status from Real-Debrid is not yet available.", - "download_error_not_cached_in_torbox": "This download is not available on Torbox and polling download status from Torbox is not yet available.", + "download_error_not_cached_on_real_debrid": "This download is not available on Real-Debrid and polling download status from Real-Debrid is not yet available.", + "download_error_not_cached_on_torbox": "This download is not available on TorBox and polling download status from TorBox is not yet available.", + "download_error_not_cached_on_hydra": "This download is not available on Nimbus.", "game_removed_from_favorites": "Game removed from favorites", "game_added_to_favorites": "Game added to favorites", "automatically_extract_downloaded_files": "Automatically extract downloaded files" @@ -338,7 +339,7 @@ "delete_theme_description": "This will delete the theme {{theme}}", "cancel": "Cancel", "appearance": "Appearance", - "enable_torbox": "Enable Torbox", + "enable_torbox": "Enable TorBox", "torbox_description": "TorBox is your premium seedbox service rivaling even the best servers on the market.", "torbox_account_linked": "TorBox account linked", "create_real_debrid_account": "Click here if you don't have a Real-Debrid account yet", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 469ae45f..ef816d9f 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -44,6 +44,9 @@ "downloading_metadata": "Descargando metadatos de {{title}}…", "downloading": "Descargando {{title}}… ({{percentage}} completado) - Finalizando {{eta}} - {{speed}}", "calculating_eta": "Descargando {{title}}… ({{percentage}} completado) - Calculando tiempo restante…", + "installation_complete": "Instalación completada", + "installation_complete_message": "Common redistributables instalados exitosamente", + "installing_common_redist": "{{log}}…", "checking_files": "Verificando archivos de {{title}}… ({{percentage}} completado)" }, "catalogue": { @@ -59,6 +62,8 @@ }, "game_details": { "open_download_options": "Ver opciones de descargas", + "automatically_extract_downloaded_files": "Extraer automáticamente archivos descargados", + "download_error_not_cached_on_hydra": "Esta descarga no está disponible en Nimbus.", "download_options_zero": "No hay opciones de descargas disponibles", "download_options_one": "{{count}} opción de descarga", "download_options_other": "{{count}} opciones de descargas", @@ -119,7 +124,7 @@ "options": "Opciones", "executable_section_title": "Ejecutable", "executable_section_description": "Ruta del archivo que se ejecutará cuando se presione \"Jugar\"", - "downloads_secion_title": "Descargas", + "downloads_section_title": "Descargas", "downloads_section_description": "Buscar actualizaciones u otras versiones de este juego", "danger_zone_section_title": "Opciones Avanzadas", "danger_zone_section_description": "Eliminar este juego de tu librería o los archivos descargados por Hydra (Esto solo eliminará los archivos de instalación y no el juego instalado)", @@ -190,8 +195,8 @@ "reset_achievements_error": "Se produjo un error al reiniciar los logros", "download_error_gofile_quota_exceeded": "Has excedido la cuota mensual de Gofile. Por favor espera a que se reinicie la cuota.", "download_error_real_debrid_account_not_authorized": "Tu cuenta de Real-Debrid no está autorizada para nueva descargas. Por favor, revisa los ajustes de tu cuenta e intenta de nuevo.", - "download_error_not_cached_in_real_debrid": "Esta descarga no está disponible en Real-Debrid y el estado de descarga del sondeo de Real-Debrid aún no está disponible.", - "download_error_not_cached_in_torbox": "Esta descarga no está disponible en Torbox y el estado de descarga del sondeo aún no está disponible.", + "download_error_not_cached_on_real_debrid": "Esta descarga no está disponible en Real-Debrid y el estado de descarga del sondeo de Real-Debrid aún no está disponible.", + "download_error_not_cached_on_torbox": "Esta descarga no está disponible en TorBox y el estado de descarga del sondeo aún no está disponible.", "game_added_to_favorites": "Juego añadido a favoritos", "game_removed_from_favorites": "Juego removido de favoritos" }, @@ -230,10 +235,19 @@ "seeding": "Seeding", "stop_seeding": "Detener seeding", "resume_seeding": "Continuar seeding", + "extract": "Extraer archivos", + "extracting": "Extrayendo archivos…", "options": "Gestionar" }, "settings": { "downloads_path": "Ruta de descarga", + "common_redist": "Common redistributables", + "common_redist_description": "Las Common redistributables son requeridos para ejecutar algunos juegos. Es recomendado instalarlos para evitar problemas.", + "create_real_debrid_account": "Presiona acá si no tienes una cuenta de Real-Debrid aún", + "create_torbox_account": "Presiona acá si no tienes una cuenta de TorBox aún", + "install_common_redist": "Instalar", + "installing_common_redist": "Instalando…", + "show_download_speed_in_megabytes": "Mostrar velocidad de descargar en megabytes por segundo", "change": "Cambiar", "notifications": "Notificaciones", "enable_download_notifications": "Cuando se completa una descarga", @@ -326,7 +340,7 @@ "editor_tab_code": "Código", "editor_tab_info": "Info", "editor_tab_save": "Guardar", - "enable_torbox": "Habilitar Torbox", + "enable_torbox": "Habilitar TorBox", "error_importing_theme": "Error al importar el tema", "import_theme": "Importar tema", "import_theme_description": "Vas a importar el tema {{theme}} desde la tienda de temas", @@ -346,6 +360,8 @@ }, "notifications": { "download_complete": "Descarga completada", + "extraction_complete": "Extracción completada", + "game_extracted": "{{title}} extraído exitosamente", "game_ready_to_install": "{{title}} está listo para instalarse", "repack_list_updated": "Lista de repacks actualizadas", "repack_count_one": "{{count}} repack ha sido añadido", @@ -475,6 +491,7 @@ }, "hydra_cloud": { "subscription_tour_title": "Suscripción Hydra Cloud", + "debrid_description": "Descargas hasta x4 más rápidas con Nimbus", "subscribe_now": "Suscribirse ahora", "cloud_saving": "Guardado en la nube", "cloud_achievements": "Guarda tus logros en la nube", diff --git a/src/locales/et/translation.json b/src/locales/et/translation.json index 1c097dd0..119e1aab 100644 --- a/src/locales/et/translation.json +++ b/src/locales/et/translation.json @@ -111,7 +111,7 @@ "options": "Valikud", "executable_section_title": "Käivitusfail", "executable_section_description": "Faili tee, mida käivitatakse \"Mängi\" nupule vajutades", - "downloads_secion_title": "Allalaadimised", + "downloads_section_title": "Allalaadimised", "downloads_section_description": "Vaata uuendusi või selle mängu teisi versioone", "danger_zone_section_title": "Ohutsoon", "danger_zone_section_description": "Eemalda see mäng oma kogust või Hydra poolt allalaaditud failid", diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index 3a852409..aab6e019 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -1,9 +1,16 @@ { "language_name": "Français", + "app": { + "successfully_signed_in": "Connecté avec succès" + }, "home": { "featured": "En vedette", "surprise_me": "Surprenez-moi", - "no_results": "Aucun résultat trouvé" + "no_results": "Aucun résultat trouvé", + "start_typing": "Commencez à taper pour rechercher...", + "hot": "Tendance", + "weekly": "📅 Meilleurs jeux de la semaine", + "achievements": "🏆 Jeux à terminer" }, "sidebar": { "catalogue": "Catalogue", @@ -12,24 +19,46 @@ "my_library": "Ma bibliothèque", "downloading_metadata": "{{title}} (Téléchargement des métadonnées…)", "paused": "{{title}} (En pause)", - "downloading": "{{title}} ({{percentage}} - Téléchargement en cours…)", + "downloading": "{{title}} ({{percentage}} - Téléchargement…)", "filter": "Filtrer la bibliothèque", - "home": "Page d’accueil", + "home": "Page d'accueil", + "queued": "{{title}} (En file d'attente)", + "game_has_no_executable": "Aucun exécutable sélectionné pour ce jeu", + "sign_in": "Se connecter", + "friends": "Amis", + "need_help": "Besoin d'aide ?", "favorites": "Favoris" }, "header": { - "search": "Recherche", - + "search": "Rechercher", + "home": "Accueil", "catalogue": "Catalogue", "downloads": "Téléchargements", "search_results": "Résultats de la recherche", "settings": "Paramètres", - "home": "Accueil" + "version_available_install": "Version {{version}} disponible. Cliquez ici pour redémarrer et installer.", + "version_available_download": "Version {{version}} disponible. Cliquez ici pour télécharger." }, "bottom_panel": { "no_downloads_in_progress": "Aucun téléchargement en cours", "downloading_metadata": "Téléchargement des métadonnées de {{title}}…", - "downloading": "Téléchargement de {{title}}… ({{percentage}} terminé) - Fin dans {{eta}} - {{speed}}" + "downloading": "Téléchargement de {{title}}… ({{percentage}} terminé) - Fin dans {{eta}} - {{speed}}", + "calculating_eta": "Téléchargement de {{title}}… ({{percentage}} terminé) - Calcul du temps restant…", + "checking_files": "Vérification des fichiers de {{title}}… ({{percentage}} terminé)", + "installing_common_redist": "{{log}}…", + "installation_complete": "Installation terminée", + "installation_complete_message": "Redistribuables communs installés avec succès" + }, + "catalogue": { + "search": "Filtrer…", + "developers": "Développeurs", + "genres": "Genres", + "tags": "Tags", + "publishers": "Éditeurs", + "download_sources": "Sources de téléchargement", + "result_count": "{{resultCount}} résultats", + "filter_count": "{{filterCount}} disponibles", + "clear_filters": "Effacer {{filterCount}} sélectionnés" }, "game_details": { "open_download_options": "Ouvrir les options de téléchargement", @@ -37,36 +66,139 @@ "download_options_one": "{{count}} option de téléchargement", "download_options_other": "{{count}} options de téléchargement", "updated_at": "Mis à jour le {{updated_at}}", + "install": "Installer", "resume": "Reprendre", "pause": "Pause", "cancel": "Annuler", "remove": "Supprimer", - "space_left_on_disk": "{{space}} restant sur le disque", + "space_left_on_disk": "{{space}} restants sur le disque", "eta": "Fin dans {{eta}}", - "downloading_metadata": "Téléchargement des métadonnées en cours…", + "calculating_eta": "Calcul du temps restant…", + "downloading_metadata": "Téléchargement des métadonnées…", "filter": "Filtrer les repacks", "requirements": "Configuration requise", "minimum": "Minimum", "recommended": "Recommandée", + "paused": "En pause", "release_date": "Sorti le {{date}}", - "publisher": "Édité par {{publisher}}", + "publisher": "Publié par {{publisher}}", "hours": "heures", "minutes": "minutes", "amount_hours": "{{amount}} heures", "amount_minutes": "{{amount}} minutes", "accuracy": "{{accuracy}}% précision", "add_to_library": "Ajouter à la bibliothèque", - "remove_from_library": "Supprimer de la bibliothèque", + "remove_from_library": "Retirer de la bibliothèque", "no_downloads": "Aucun téléchargement disponible", - "next_suggestion": "Suggestion suivante", - "play_time": "Joué pour {{amount}}", - "install": "Installer", - "play": "Jouer", + "play_time": "{{amount}} de temps de jeu", + "last_time_played": "Dernière partie {{period}}", "not_played_yet": "Vous n'avez pas encore joué à {{title}}", + "next_suggestion": "Suggestion suivante", + "play": "Jouer", + "deleting": "Suppression de l'installateur…", "close": "Fermer", - "deleting": "Suppression du programme d'installation…", - "playing_now": "Jeu en cours", - "last_time_played": "Dernièrement joué {{period}}" + "playing_now": "En cours de jeu", + "change": "Changer", + "repacks_modal_description": "Choisissez le repack que vous souhaitez télécharger", + "select_folder_hint": "Pour changer le dossier par défaut, allez dans les <0>Paramètres", + "download_now": "Télécharger maintenant", + "no_shop_details": "Impossible d'obtenir les détails du magasin.", + "download_options": "Options de téléchargement", + "download_path": "Emplacement de téléchargement", + "previous_screenshot": "Capture précédente", + "next_screenshot": "Capture suivante", + "screenshot": "Capture d'écran {{number}}", + "open_screenshot": "Ouvrir la capture {{number}}", + "download_settings": "Paramètres de téléchargement", + "downloader": "Téléchargeur", + "select_executable": "Sélectionner", + "no_executable_selected": "Aucun exécutable sélectionné", + "open_folder": "Ouvrir le dossier", + "open_download_location": "Voir les fichiers téléchargés", + "create_shortcut": "Créer un raccourci sur le bureau", + "clear": "Effacer", + "remove_files": "Supprimer les fichiers", + "remove_from_library_title": "Êtes-vous sûr ?", + "remove_from_library_description": "Ceci supprimera {{game}} de votre bibliothèque", + "options": "Options", + "executable_section_title": "Exécutable", + "executable_section_description": "Chemin du fichier lancé quand \"Jouer\" est cliqué", + "downloads_section_title": "Téléchargements", + "downloads_section_description": "Découvrez les mises à jour ou autres versions de ce jeu", + "danger_zone_section_title": "Zone de danger", + "danger_zone_section_description": "Supprimez ce jeu de votre bibliothèque ou les fichiers téléchargés par Hydra", + "download_in_progress": "Téléchargement en cours", + "download_paused": "Téléchargement en pause", + "last_downloaded_option": "Dernière option téléchargée", + "create_shortcut_success": "Raccourci créé avec succès", + "create_shortcut_error": "Erreur lors de la création du raccourci", + "nsfw_content_title": "Ce jeu contient du contenu inapproprié", + "nsfw_content_description": "{{title}} contient du contenu pouvant ne pas convenir à tous les âges. Voulez-vous continuer ?", + "allow_nsfw_content": "Continuer", + "refuse_nsfw_content": "Retourner", + "stats": "Statistiques", + "download_count": "Téléchargements", + "player_count": "Joueurs actifs", + "download_error": "Cette option de téléchargement n'est pas disponible", + "download": "Télécharger", + "executable_path_in_use": "Exécutable déjà utilisé par \"{{game}}\"", + "warning": "Attention :", + "hydra_needs_to_remain_open": "Pour ce téléchargement, Hydra doit rester ouvert jusqu'à la fin. Si Hydra se ferme avant, la progression sera perdue.", + "achievements": "Succès", + "achievements_count": "Succès {{unlockedCount}}/{{achievementsCount}}", + "cloud_save": "Sauvegarde Cloud", + "cloud_save_description": "Sauvegardez vos progrès dans le cloud et continuez à jouer sur n'importe quel appareil", + "backups": "Sauvegardes", + "install_backup": "Restaurer", + "delete_backup": "Supprimer", + "create_backup": "Nouvelle sauvegarde", + "last_backup_date": "Dernière sauvegarde le {{date}}", + "no_backup_preview": "Aucune sauvegarde trouvée pour ce titre", + "restoring_backup": "Restauration de la sauvegarde ({{progress}} terminé)…", + "uploading_backup": "Envoi de la sauvegarde…", + "no_backups": "Vous n'avez pas encore créé de sauvegarde pour ce jeu", + "backup_uploaded": "Sauvegarde envoyée", + "backup_deleted": "Sauvegarde supprimée", + "backup_restored": "Sauvegarde restaurée", + "see_all_achievements": "Voir tous les succès", + "sign_in_to_see_achievements": "Connectez-vous pour voir les succès", + "mapping_method_automatic": "Automatique", + "mapping_method_manual": "Manuel", + "mapping_method_label": "Méthode de mappage", + "files_automatically_mapped": "Fichiers mappés automatiquement", + "no_backups_created": "Aucune sauvegarde créée pour ce jeu", + "manage_files": "Gérer les fichiers", + "loading_save_preview": "Recherche de jeux sauvegardés…", + "wine_prefix": "Wine Prefix", + "wine_prefix_description": "Le préfixe Wine utilisé pour lancer ce jeu", + "launch_options": "Options de lancement", + "launch_options_description": "Les utilisateurs avancés peuvent modifier les options de lancement (fonction expérimentale)", + "launch_options_placeholder": "Aucun paramètre spécifié", + "no_download_option_info": "Pas d'information disponible", + "backup_deletion_failed": "Échec de la suppression de la sauvegarde", + "max_number_of_artifacts_reached": "Nombre maximal de sauvegardes atteint pour ce jeu", + "achievements_not_sync": "Voir comment synchroniser vos succès", + "manage_files_description": "Gérer les fichiers qui seront sauvegardés et restaurés", + "select_folder": "Sélectionner un dossier", + "backup_from": "Sauvegarde du {{date}}", + "automatic_backup_from": "Sauvegarde automatique du {{date}}", + "enable_automatic_cloud_sync": "Activer la synchronisation cloud automatique", + "custom_backup_location_set": "Emplacement de sauvegarde personnalisé défini", + "no_directory_selected": "Aucun dossier sélectionné", + "no_write_permission": "Impossible de télécharger dans ce dossier. Cliquez ici pour en savoir plus.", + "reset_achievements": "Réinitialiser les succès", + "reset_achievements_description": "Ceci réinitialisera tous les succès pour {{game}}", + "reset_achievements_title": "Êtes-vous sûr ?", + "reset_achievements_success": "Succès réinitialisés avec succès", + "reset_achievements_error": "Échec de la réinitialisation des succès", + "download_error_gofile_quota_exceeded": "Vous avez dépassé votre quota mensuel Gofile. Attendez la remise à zéro du quota.", + "download_error_real_debrid_account_not_authorized": "Votre compte Real-Debrid n'est pas autorisé à effectuer de nouveaux téléchargements. Veuillez vérifier les paramètres de votre compte et réessayer.", + "download_error_not_cached_on_real_debrid": "Ce téléchargement n'est pas disponible sur Real-Debrid, et le suivi n'est pas encore disponible.", + "download_error_not_cached_on_torbox": "Ce téléchargement n'est pas disponible sur TorBox, et le suivi n'est pas encore disponible.", + "download_error_not_cached_on_hydra": "Ce téléchargement n'est pas disponible sur Nimbus.", + "game_removed_from_favorites": "Jeu retiré des favoris", + "game_added_to_favorites": "Jeu ajouté aux favoris", + "automatically_extract_downloaded_files": "Extraire automatiquement les fichiers téléchargés" }, "activation": { "title": "Activer Hydra", @@ -83,51 +215,292 @@ "paused": "En pause", "verifying": "Vérification en cours…", "completed": "Terminé", + "removed": "Non téléchargé", "cancel": "Annuler", "filter": "Filtrer les jeux téléchargés", "remove": "Supprimer", "downloading_metadata": "Téléchargement des métadonnées en cours…", - "delete": "Supprimer le programme d'installation", - "delete_modal_description": "Cela supprimera tous les fichiers d'installation de votre ordinateur", - "delete_modal_title": "Es-tu sûr?", "deleting": "Suppression du programme d'installation…", - "install": "Installer" + "delete": "Supprimer le programme d'installation", + "delete_modal_title": "Êtes-vous sûr ?", + "delete_modal_description": "Cela supprimera tous les fichiers d'installation de votre ordinateur", + "install": "Installer", + "download_in_progress": "En cours", + "queued_downloads": "Téléchargements en attente", + "downloads_completed": "Terminés", + "queued": "En attente", + "no_downloads_title": "Tellement vide", + "no_downloads_description": "Vous n'avez encore rien téléchargé avec Hydra, mais il n'est jamais trop tard pour commencer.", + "checking_files": "Vérification des fichiers…", + "seeding": "Partage", + "stop_seeding": "Arrêter le partage", + "resume_seeding": "Reprendre le partage", + "options": "Gérer", + "extract": "Extraire les fichiers", + "extracting": "Extraction des fichiers…" }, "settings": { "downloads_path": "Chemin des téléchargements", "change": "Mettre à jour", "notifications": "Notifications", - "enable_download_notifications": "Quand un téléchargement est terminé", - "enable_repack_list_notifications": "Quand un nouveau repack est ajouté", + "enable_download_notifications": "Lorsqu'un téléchargement est terminé", + "enable_repack_list_notifications": "Lorsqu'un nouveau repack est ajouté", + "real_debrid_api_token_label": "Jeton API Real-Debrid", + "quit_app_instead_hiding": "Ne pas masquer Hydra à la fermeture", + "launch_with_system": "Lancer Hydra au démarrage du système", + "general": "Général", + "behavior": "Comportement", + "download_sources": "Sources de téléchargement", "language": "Langue", + "api_token": "Jeton API", + "enable_real_debrid": "Activer Real-Debrid", + "real_debrid_description": "Real-Debrid est un téléchargeur sans restriction qui vous permet de télécharger rapidement des fichiers, uniquement limités par votre vitesse Internet.", + "debrid_invalid_token": "Jeton API invalide", + "debrid_api_token_hint": "Vous pouvez obtenir votre jeton API <0>ici", + "real_debrid_free_account_error": "Le compte \"{{username}}\" est un compte gratuit. Veuillez vous abonner à Real-Debrid", + "debrid_linked_message": "Compte \"{{username}}\" lié", + "save_changes": "Enregistrer les modifications", + "changes_saved": "Modifications enregistrées avec succès", + "download_sources_description": "Hydra récupère les liens de téléchargement à partir de ces sources. L'URL source doit être un lien direct vers un fichier .json contenant les liens de téléchargement.", + "validate_download_source": "Valider", + "remove_download_source": "Supprimer", + "add_download_source": "Ajouter une source", + "download_count_zero": "Aucune option de téléchargement", + "download_count_one": "{{countFormatted}} option de téléchargement", + "download_count_other": "{{countFormatted}} options de téléchargement", + "download_source_url": "URL de la source", + "add_download_source_description": "Insérez l'URL du fichier .json", + "download_source_up_to_date": "À jour", + "download_source_errored": "Erreur", + "sync_download_sources": "Synchroniser les sources", + "removed_download_source": "Source de téléchargement supprimée", + "removed_download_sources": "Sources de téléchargement supprimées", "cancel_button_confirmation_delete_all_sources": "Non", "confirm_button_confirmation_delete_all_sources": "Oui, tout supprimer", - "description_confirmation_delete_all_sources": "Vous supprimerez toutes les sources de téléchargement", "title_confirmation_delete_all_sources": "Supprimer toutes les sources de téléchargement", - "removed_download_sources": "Sources de téléchargement supprimées", - "button_delete_all_sources": "Supprimer toutes les sources de téléchargement" + "description_confirmation_delete_all_sources": "Vous supprimerez toutes les sources de téléchargement", + "button_delete_all_sources": "Tout supprimer", + "added_download_source": "Source de téléchargement ajoutée", + "download_sources_synced": "Toutes les sources de téléchargement sont synchronisées", + "insert_valid_json_url": "Insérez une URL JSON valide", + "found_download_option_zero": "Aucune option de téléchargement trouvée", + "found_download_option_one": "{{countFormatted}} option trouvée", + "found_download_option_other": "{{countFormatted}} options trouvées", + "import": "Importer", + "public": "Publique", + "private": "Privé", + "friends_only": "Amis uniquement", + "privacy": "Confidentialité", + "profile_visibility": "Visibilité du profil", + "profile_visibility_description": "Choisissez qui peut voir votre profil et bibliothèque", + "required_field": "Ce champ est requis", + "source_already_exists": "Cette source a déjà été ajoutée", + "must_be_valid_url": "La source doit être une URL valide", + "blocked_users": "Utilisateurs bloqués", + "user_unblocked": "Utilisateur débloqué", + "enable_achievement_notifications": "Quand un succès est débloqué", + "launch_minimized": "Lancer Hydra minimisé", + "disable_nsfw_alert": "Désactiver l'alerte NSFW", + "seed_after_download_complete": "Partager après téléchargement", + "show_hidden_achievement_description": "Afficher la description des succès cachés avant de les débloquer", + "account": "Compte", + "no_users_blocked": "Aucun utilisateur bloqué", + "subscription_active_until": "Votre Hydra Cloud est actif jusqu'au {{date}}", + "manage_subscription": "Gérer l'abonnement", + "update_email": "Modifier l'email", + "update_password": "Modifier le mot de passe", + "current_email": "Email actuel :", + "no_email_account": "Vous n'avez pas encore défini d'email", + "account_data_updated_successfully": "Informations du compte mises à jour", + "renew_subscription": "Renouveler Hydra Cloud", + "subscription_expired_at": "Votre abonnement a expiré le {{date}}", + "no_subscription": "Profitez d'Hydra de la meilleure façon possible", + "become_subscriber": "Devenir membre Hydra Cloud", + "subscription_renew_cancelled": "Le renouvellement automatique est désactivé", + "subscription_renews_on": "Votre abonnement sera renouvelé le {{date}}", + "bill_sent_until": "Votre prochaine facture sera envoyée à cette date", + "no_themes": "Vous n'avez pas encore de thèmes, cliquez ici pour créer votre premier chef-d'œuvre.", + "editor_tab_code": "Code", + "editor_tab_info": "Info", + "editor_tab_save": "Enregistrer", + "web_store": "Web store", + "clear_themes": "Effacer", + "create_theme": "Créer", + "create_theme_modal_title": "Créer un thème personnalisé", + "create_theme_modal_description": "Créer un nouveau thème pour personnaliser l'apparence d'Hydra", + "theme_name": "Nom du thème", + "insert_theme_name": "Entrez le nom du thème", + "set_theme": "Activer le thème", + "unset_theme": "Désactiver le thème", + "delete_theme": "Supprimer le thème", + "edit_theme": "Modifier le thème", + "delete_all_themes": "Supprimer tous les thèmes", + "delete_all_themes_description": "Cela supprimera tous vos thèmes personnalisés", + "delete_theme_description": "Cela supprimera le thème {{theme}}", + "cancel": "Annuler", + "appearance": "Apparence", + "enable_torbox": "Activer TorBox", + "torbox_description": "TorBox est votre service de seedbox premium qui rivalise avec les meilleurs serveurs du marché.", + "torbox_account_linked": "Compte TorBox lié", + "create_real_debrid_account": "Cliquez ici pour créer un compte Real-Debrid", + "create_torbox_account": "Cliquez ici pour créer un compte TorBox", + "real_debrid_account_linked": "Compte Real-Debrid lié", + "name_min_length": "Le nom du thème doit comporter au moins 3 caractères", + "import_theme": "Importer un thème", + "import_theme_description": "Vous allez importer {{theme}} du magasin de thèmes", + "error_importing_theme": "Erreur lors de l'importation du thème", + "theme_imported": "Thème importé avec succès", + "enable_friend_request_notifications": "Lors de réception de demande d'ami", + "enable_auto_install": "Télécharger les mises à jour automatiquement", + "common_redist": "Redistribuables communs", + "common_redist_description": "Certains jeux nécessitent les redistribuables communs. L'installation est recommandée.", + "install_common_redist": "Installer", + "installing_common_redist": "Installation…", + "show_download_speed_in_megabytes": "Afficher la vitesse de téléchargement en mégaoctets par seconde" }, "notifications": { "download_complete": "Téléchargement terminé", "game_ready_to_install": "{{title}} est prêt à être installé", "repack_list_updated": "Liste de repacks mise à jour", "repack_count_one": "{{count}} repack ajouté", - "repack_count_other": "{{count}} repacks ajoutés" + "repack_count_other": "{{count}} repacks ajoutés", + "new_update_available": "Version {{version}} disponible", + "restart_to_install_update": "Redémarrez Hydra pour installer la mise à jour", + "notification_achievement_unlocked_title": "Succès débloqué pour {{game}}", + "notification_achievement_unlocked_body": "{{achievement}} et {{count}} autre(s) débloqués", + "new_friend_request_description": "Vous avez reçu une nouvelle demande d'ami", + "new_friend_request_title": "Nouvelle demande d'ami", + "extraction_complete": "Extraction terminée", + "game_extracted": "{{title}} extrait avec succès" }, "system_tray": { "open": "Ouvrir Hydra", "quit": "Quitter" }, "game_card": { + "available_one": "Disponible", + "available_other": "Disponibles", "no_downloads": "Aucun téléchargement disponible" }, "binary_not_found_modal": { + "title": "Programmes non installés", "description": "Les exécutables Wine ou Lutris sont introuvables sur votre système", - "instructions": "Vérifiez la bonne façon d'installer l'un d'entre eux sur votre distribution Linux afin que le jeu puisse fonctionner normalement", - "title": "Programmes non installés" + "instructions": "Vérifiez la bonne façon d'installer l'un d'entre eux sur votre distribution Linux afin que le jeu puisse fonctionner normalement" }, - "catalogue": { - "next_page": "Page suivante", - "previous_page": "Page précédente" + "modal": { + "close": "Fermer la fenêtre" + }, + "forms": { + "toggle_password_visibility": "Afficher/Masquer le mot de passe" + }, + "user_profile": { + "amount_hours": "{{amount}} heures", + "amount_minutes": "{{amount}} minutes", + "last_time_played": "Dernière partie {{period}}", + "activity": "Activité récente", + "library": "Bibliothèque", + "total_play_time": "Temps de jeu total", + "no_recent_activity_title": "Hmm… rien ici", + "no_recent_activity_description": "Vous n'avez pas joué récemment. Il est temps d'y remédier !", + "display_name": "Nom d'affichage", + "saving": "Enregistrement", + "save": "Enregistrer", + "edit_profile": "Modifier le profil", + "saved_successfully": "Enregistré avec succès", + "try_again": "Veuillez réessayer", + "sign_out_modal_title": "Êtes-vous sûr ?", + "cancel": "Annuler", + "successfully_signed_out": "Déconnecté avec succès", + "sign_out": "Se déconnecter", + "playing_for": "En jeu depuis {{amount}}", + "sign_out_modal_text": "Votre bibliothèque est liée à ce compte. Si vous vous déconnectez, elle ne sera plus visible et la progression ne sera pas sauvegardée. Continuer ?", + "add_friends": "Ajouter des amis", + "add": "Ajouter", + "friend_code": "Code ami", + "see_profile": "Voir le profil", + "sending": "Envoi", + "friend_request_sent": "Demande d'ami envoyée", + "friends": "Amis", + "friends_list": "Liste d'amis", + "user_not_found": "Utilisateur introuvable", + "block_user": "Bloquer l'utilisateur", + "add_friend": "Ajouter un ami", + "request_sent": "Demande envoyée", + "request_received": "Demande reçue", + "accept_request": "Accepter la demande", + "ignore_request": "Ignorer la demande", + "cancel_request": "Annuler la demande", + "undo_friendship": "Retirer de la liste d'amis", + "request_accepted": "Demande acceptée", + "user_blocked_successfully": "Utilisateur bloqué avec succès", + "user_block_modal_text": "Vous allez bloquer {{displayName}}", + "blocked_users": "Utilisateurs bloqués", + "unblock": "Débloquer", + "no_friends_added": "Vous n'avez pas encore d'amis", + "pending": "En attente", + "no_pending_invites": "Aucune invitation en attente", + "no_blocked_users": "Aucun utilisateur bloqué", + "friend_code_copied": "Code ami copié", + "undo_friendship_modal_text": "Vous allez retirer {{displayName}} de vos amis", + "privacy_hint": "Pour changer qui voit ceci, allez dans les <0>Paramètres", + "locked_profile": "Ce profil est privé", + "image_process_failure": "Erreur lors du traitement de l'image", + "required_field": "Ce champ est requis", + "displayname_min_length": "Le nom doit contenir au moins 3 caractères", + "displayname_max_length": "Le nom doit contenir au maximum 50 caractères", + "report_profile": "Signaler ce profil", + "report_reason": "Pourquoi signaler ce profil ?", + "report_description": "Informations supplémentaires", + "report_description_placeholder": "Infos en plus", + "report": "Signaler", + "report_reason_hate": "Discours de haine", + "report_reason_sexual_content": "Contenu sexuel", + "report_reason_violence": "Violence", + "report_reason_spam": "Spam", + "report_reason_other": "Autre", + "profile_reported": "Profil signalé", + "your_friend_code": "Votre code ami :", + "upload_banner": "Télécharger une bannière", + "uploading_banner": "Téléversement de la bannière…", + "background_image_updated": "Image de fond mise à jour", + "stats": "Statistiques", + "achievements": "Succès", + "games": "Jeux", + "top_percentile": "Top {{percentile}}%", + "ranking_updated_weekly": "Classement mis à jour chaque semaine", + "playing": "En train de jouer à {{game}}", + "achievements_unlocked": "Succès débloqués", + "earned_points": "Points gagnés", + "show_achievements_on_profile": "Afficher vos succès sur votre profil", + "show_points_on_profile": "Afficher vos points sur votre profil" + }, + "achievement": { + "achievement_unlocked": "Succès débloqué", + "user_achievements": "Succès de {{displayName}}", + "your_achievements": "Vos succès", + "unlocked_at": "Débloqué le : {{date}}", + "subscription_needed": "Un abonnement Hydra Cloud est requis", + "new_achievements_unlocked": "{{achievementCount}} nouveaux succès débloqués sur {{gameCount}} jeux", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} succès", + "achievements_unlocked_for_game": "{{achievementCount}} nouveaux succès débloqués sur {{gameTitle}}", + "hidden_achievement_tooltip": "Ce succès est caché", + "achievement_earn_points": "Gagnez {{points}} points avec ce succès", + "earned_points": "Points gagnés :", + "available_points": "Points disponibles :", + "how_to_earn_achievements_points": "Comment gagner des points de succès ?" + }, + "hydra_cloud": { + "subscription_tour_title": "Abonnement Hydra Cloud", + "subscribe_now": "S'abonner", + "cloud_saving": "Sauvegarde Cloud", + "cloud_achievements": "Sauvegardez vos succès dans le cloud", + "animated_profile_picture": "Photo de profil animée", + "premium_support": "Support premium", + "show_and_compare_achievements": "Montrez et comparez vos succès avec d'autres utilisateurs", + "animated_profile_banner": "Bannière de profil animée", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Vous avez découvert une fonctionnalité Hydra Cloud !", + "learn_more": "En savoir plus", + "debrid_description": "Téléchargez jusqu'à 4x plus vite avec Nimbus" } } diff --git a/src/locales/id/translation.json b/src/locales/id/translation.json index 2fc29b8e..4fa347fc 100644 --- a/src/locales/id/translation.json +++ b/src/locales/id/translation.json @@ -107,7 +107,7 @@ "options": "Opsi", "executable_section_title": "Eksekusi", "executable_section_description": "Path file eksekusi saat \"Main\" diklik", - "downloads_secion_title": "Unduhan", + "downloads_section_title": "Unduhan", "downloads_section_description": "Cek update atau versi lain dari game ini", "danger_zone_section_title": "Zona Berbahaya", "danger_zone_section_description": "Hapus game ini dari perpustakaan kamu atau file yang diunduh oleh Hydra", diff --git a/src/locales/kk/translation.json b/src/locales/kk/translation.json index d3bda8fe..bfb009a7 100644 --- a/src/locales/kk/translation.json +++ b/src/locales/kk/translation.json @@ -106,7 +106,7 @@ "options": "Параметрлер", "executable_section_title": "Файл", "executable_section_description": "\"Ойнау\" батырмасын басқанда іске қосылатын файл жолы", - "downloads_secion_title": "Жүктеулер", + "downloads_section_title": "Жүктеулер", "downloads_section_description": "Ойынның жаңартулары немесе басқа нұсқалары бар-жоғын тексеру", "danger_zone_section_title": "Қауіпті аймақ", "danger_zone_section_description": "Осы ойынды кітапханаңыздан жою немесе Hydra жүктеген файлдарды жою", diff --git a/src/locales/nb/translation.json b/src/locales/nb/translation.json index 7171bb85..8898ec7b 100644 --- a/src/locales/nb/translation.json +++ b/src/locales/nb/translation.json @@ -111,7 +111,7 @@ "options": "Valgmuligheter", "executable_section_title": "Kjørbar fil", "executable_section_description": "Sti til filen som skal brukes når det trykkes på \"Spill\"", - "downloads_secion_title": "Nedlastinger", + "downloads_section_title": "Nedlastinger", "downloads_section_description": "Sjekk for oppdateringer eller andre versjoner af dette spillet", "danger_zone_section_title": "Faresonen", "danger_zone_section_description": "Fjern dette spillet fra biblioteket ditt eller filene som har blitt lastet ned av Hydra", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 4baff652..25602fd2 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -111,7 +111,7 @@ "remove_from_library_title": "Tem certeza?", "executable_section_title": "Executável", "executable_section_description": "O caminho do arquivo que será executado ao clicar em \"Jogar\"", - "downloads_secion_title": "Downloads", + "downloads_section_title": "Downloads", "downloads_section_description": "Confira atualizações ou versões diferentes para este mesmo título", "danger_zone_section_title": "Zona de perigo", "danger_zone_section_description": "Remova o jogo da sua biblioteca ou os arquivos que foram baixados pelo Hydra", @@ -182,8 +182,9 @@ "no_write_permission": "Não é possível baixar nesse diretório. Clique aqui para saber mais.", "download_error_gofile_quota_exceeded": "Você excedeu sua cota mensal do Gofile. Por favor, aguarde a cota resetar.", "download_error_real_debrid_account_not_authorized": "Sua conta do Real-Debrid não está autorizada a fazer novos downloads. Por favor, verifique sua assinatura e tente novamente.", - "download_error_not_cached_in_real_debrid": "Este download não está disponível no Real-Debrid e a verificação do status do download não está disponível.", - "download_error_not_cached_in_torbox": "Este download não está disponível no Torbox e a verificação do status do download não está disponível.", + "download_error_not_cached_on_real_debrid": "Este download não está disponível no Real-Debrid e a verificação do status do download não está disponível.", + "download_error_not_cached_on_torbox": "Este download não está disponível no TorBox e a verificação do status do download não está disponível.", + "download_error_not_cached_on_hydra": "Este download não está disponível no Nimbus.", "game_removed_from_favorites": "Jogo removido dos favoritos", "game_added_to_favorites": "Jogo adicionado aos favoritos", "automatically_extract_downloaded_files": "Extrair automaticamente os arquivos baixados" @@ -325,7 +326,7 @@ "delete_theme_description": "Isso irá deletar o tema {{theme}}", "cancel": "Cancelar", "appearance": "Aparência", - "enable_torbox": "Habilitar Torbox", + "enable_torbox": "Habilitar TorBox", "torbox_description": "TorBox é o seu serviço de seedbox premium que rivaliza até com os melhores servidores do mercado.", "torbox_account_linked": "Conta do TorBox vinculada", "create_real_debrid_account": "Clique aqui se você ainda não tem uma conta do Real-Debrid", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 3649afd7..35dde292 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -107,7 +107,7 @@ "remove_from_library_title": "Tens a certeza?", "executable_section_title": "Executável", "executable_section_description": "O caminho do ficheiro que vai ser executado ao clicar em \"Jogar\"", - "downloads_secion_title": "Transferências", + "downloads_section_title": "Transferências", "downloads_section_description": "Encontra atualizações ou versões diferentes para este mesmo título", "danger_zone_section_title": "Zona de perigo", "danger_zone_section_description": "Remove o jogo da tua biblioteca ou os ficheiros que foram transferidos pelo Hydra", @@ -175,8 +175,8 @@ "no_write_permission": "Não é possível descarregar neste diretório. Clique aqui para saber mais.", "download_error_gofile_quota_exceeded": "Você excedeu sua cota mensal do Gofile. Por favor, aguarde o reset da cota.", "download_error_real_debrid_account_not_authorized": "A sua conta do Real-Debrid não está autorizada a fazer novos downloads. Por favor, verifique a sua assinatura e tente novamente.", - "download_error_not_cached_in_real_debrid": "Este download não está disponível no Real-Debrid e a verificação do status do download não está disponível.", - "download_error_not_cached_in_torbox": "Este download não está disponível no Torbox e a verificação do status do download não está disponível.", + "download_error_not_cached_on_real_debrid": "Este download não está disponível no Real-Debrid e a verificação do status do download não está disponível.", + "download_error_not_cached_on_torbox": "Este download não está disponível no TorBox e a verificação do status do download não está disponível.", "game_removed_from_favorites": "Jogo removido dos favoritos", "game_added_to_favorites": "Jogo adicionado aos favoritos" }, @@ -321,7 +321,7 @@ "delete_theme_description": "Isto irá apagar o tema {{theme}}", "cancel": "Cancelar", "appearance": "Aparência", - "enable_torbox": "Ativar Torbox", + "enable_torbox": "Ativar TorBox", "torbox_description": "TorBox é um serviço de seedbox premium sendo um dos melhores servidores do mercado.", "torbox_account_linked": "Conta do TorBox associada", "real_debrid_account_linked": "Conta Real-Debrid associada", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index b4544367..00b7e5af 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -123,7 +123,7 @@ "options": "Настройки", "executable_section_title": "Файл", "executable_section_description": "Путь к файлу, который будет запущен при нажатии на \"Play\"", - "downloads_secion_title": "Загрузки", + "downloads_section_title": "Загрузки", "downloads_section_description": "Проверить наличие обновлений или других версий игры", "danger_zone_section_title": "Опасная зона", "danger_zone_section_description": "Вы можете удалить эту игру из вашей библиотеки или файлы скачанные из Hydra", @@ -193,8 +193,8 @@ "reset_achievements_error": "Не удалось сбросить достижения", "download_error_gofile_quota_exceeded": "Вы превысили месячную квоту Gofile. Пожалуйста, подождите, пока квота не будет восстановлена.", "download_error_real_debrid_account_not_authorized": "Ваш аккаунт Real-Debrid не авторизован для осуществления новых загрузок. Пожалуйста, проверьте настройки учетной записи и повторите попытку.", - "download_error_not_cached_in_real_debrid": "Эта загрузка недоступна на Real-Debrid, и получение статуса загрузки с Real-Debrid пока недоступно.", - "download_error_not_cached_in_torbox": "Эта загрузка недоступна на Torbox, и получить статус загрузки с Torbox пока невозможно.", + "download_error_not_cached_on_real_debrid": "Эта загрузка недоступна на Real-Debrid, и получение статуса загрузки с Real-Debrid пока недоступно.", + "download_error_not_cached_on_torbox": "Эта загрузка недоступна на TorBox, и получить статус загрузки с TorBox пока невозможно.", "game_added_to_favorites": "Игра добавлена в избранное", "game_removed_from_favorites": "Игра удалена из избранного", "automatically_extract_downloaded_files": "Автоматическая распаковка загруженных файлов" @@ -338,7 +338,7 @@ "delete_theme_description": "Это приведет к удалению темы {{theme}}", "cancel": "Отменить", "appearance": "Внешний вид", - "enable_torbox": "Включить Torbox", + "enable_torbox": "Включить TorBox", "torbox_description": "TorBox - это ваш премиум-сервис, конкурирующий даже с лучшими серверами на рынке.", "torbox_account_linked": "Аккаунт TorBox привязан", "real_debrid_account_linked": "Аккаунт Real-Debrid привязан", diff --git a/src/locales/tr/translation.json b/src/locales/tr/translation.json index f671ed06..76496c5d 100644 --- a/src/locales/tr/translation.json +++ b/src/locales/tr/translation.json @@ -123,7 +123,7 @@ "options": "Seçenekler", "executable_section_title": "Çalıştırılabilir dosya", "executable_section_description": "\"Oyna\" butonuna tıklandığında çalıştırılacak dosyanın yolu", - "downloads_secion_title": "İndirmeler", + "downloads_section_title": "İndirmeler", "downloads_section_description": "Bu oyun için güncellemeleri veya diğer sürümleri kontrol edin", "danger_zone_section_title": "Tehlike bölgesi", "danger_zone_section_description": "Bu oyunu kütüphanenizden kaldırın veya Hydra tarafından indirilen dosyaları silin.", @@ -193,8 +193,9 @@ "reset_achievements_error": "Başarımlar sıfırlanamadı", "download_error_gofile_quota_exceeded": "Gofile aylık kotanızı doldurdunuz. Kotanın yenilenmesini bekleyin.", "download_error_real_debrid_account_not_authorized": "Real-Debrid hesabınız yeni indirme işlemleri yapmak için yetkilendirilmemiş. Lütfen hesap ayarlarınızı kontrol edip tekrar deneyin.", - "download_error_not_cached_in_real_debrid": "Bu indirme Real-Debrid üzerinde mevcut değil ve Real-Debrid'den indirme durumu henüz sorgulanamıyor.", - "download_error_not_cached_in_torbox": "Bu indirme Torbox'ta mevcut değil ve Torbox'tan indirme durumu henüz sorgulanamıyor.", + "download_error_not_cached_on_real_debrid": "Bu indirme Real-Debrid üzerinde mevcut değil ve Real-Debrid'den indirme durumu henüz sorgulanamıyor.", + "download_error_not_cached_on_torbox": "Bu indirme TorBox'ta mevcut değil ve TorBox'tan indirme durumu henüz sorgulanamıyor.", + "download_error_not_cached_on_hydra": "Bu indirme Nimbus'ta mevcut değil.", "game_removed_from_favorites": "Oyun favorilerden silindi", "game_added_to_favorites": "Oyun favorilere eklendi", "automatically_extract_downloaded_files": "Yüklenmiş dosyaları otomatik olarak çıkart" @@ -338,7 +339,7 @@ "delete_theme_description": "Bu {{theme}} temasını silecektir", "cancel": "İptal", "appearance": "Görünüm", - "enable_torbox": "Torbox'u etkinleştir", + "enable_torbox": "TorBox'u etkinleştir", "torbox_description": "TorBox, piyasadaki en iyi sunucularla bile rekabet edebilen premium seedbox hizmetinizdir.", "torbox_account_linked": "TorBox hesabı bağlandı", "create_real_debrid_account": "Henüz bir Real-Debrid hesabınız yoksa buraya tıklayın", @@ -354,7 +355,8 @@ "common_redist": "Ortak bağımlılıklar", "common_redist_description": "Bazı oyunların çalışabilmesi için genel bağımlılıklar gereklidir. Sorun yaşamamak için bunların yüklenmesi önerilir.", "install_common_redist": "Yükle", - "installing_common_redist": "Yükleniyor…" + "installing_common_redist": "Yükleniyor…", + "show_download_speed_in_megabytes": "İndirme hızını megabayt/saniye (MB/s) cinsinden göster" }, "notifications": { "download_complete": "İndirme tamamlandı", @@ -498,6 +500,7 @@ "animated_profile_banner": "Animasyonlu profil afişi", "hydra_cloud": "Hydra Cloud", "hydra_cloud_feature_found": "Bir Hydra Cloud özelliği keşfettiniz!", - "learn_more": "Daha Fazla Bilgi Edinin" + "learn_more": "Daha Fazla Bilgi Edinin", + "debrid_description": "Nimbus ile 4 kata kadar daha hızlı indirin" } } diff --git a/src/locales/uk/translation.json b/src/locales/uk/translation.json index d5c5015e..2c650cc4 100644 --- a/src/locales/uk/translation.json +++ b/src/locales/uk/translation.json @@ -6,7 +6,11 @@ "home": { "featured": "Рекомендоване", "surprise_me": "Здивуй мене", - "no_results": "Результатів не знайдено" + "no_results": "Результатів не знайдено", + "start_typing": "Почніть набирати текст для пошуку...", + "hot": "Гарячі новинки", + "weekly": "📅 Найкращі ігри цього тижня", + "achievements": "🏆 Ігри на проходження" }, "sidebar": { "catalogue": "Каталог", @@ -21,11 +25,12 @@ "game_has_no_executable": "Не було вибрано файл для запуску гри", "queued": "{{title}} в черзі", "sign_in": "Увійти", - "favorites": "Улюблені" + "favorites": "Улюблені", + "friends": "Друзі", + "need_help": "Потрібна допомога?" }, "header": { "search": "Пошук", - "home": "Головна", "catalogue": "Каталог", "downloads": "Завантаження", @@ -38,11 +43,22 @@ "no_downloads_in_progress": "Немає активних завантажень", "downloading_metadata": "Завантаження метаданих {{title}}…", "downloading": "Завантаження {{title}}… ({{percentage}} завершено) - Закінчення {{eta}} - {{speed}}", - "calculating_eta": "Завантаження {{title}}… ({{percentage}} завершено) - Обчислення залишкового часу…" + "calculating_eta": "Завантаження {{title}}… ({{percentage}} завершено) - Обчислення залишкового часу…", + "checking_files": "Перевірка файлів {{title}} ({{percentage}} виповнено)", + "installing_common_redist": "{{log}}…", + "installation_complete": "Встановлення завершено", + "installation_complete_message": "Загальні розповсюджувані файли успішно встановлено" }, "catalogue": { - "next_page": "Наступна сторінка", - "previous_page": "Попередня сторінка" + "search": "Фільтрувати…", + "developers": "Розробники", + "genres": "Жанри", + "tags": "Теги", + "publishers": "Видавці", + "download_sources": "Джерела", + "result_count": "{{resultCount}} результатів", + "filter_count": "{{filterCount}} доступно", + "clear_filters": "Очистити {{filterCount}} вибрані" }, "game_details": { "open_download_options": "Відкрити варіанти завантаження", @@ -86,18 +102,31 @@ "download_now": "Завантажити зараз", "calculating_eta": "Обчислення залишкового часу…", "create_shortcut": "Створити ярлик на робочому столі", + "create_shortcut_success": "Ярлик успішно створено", + "create_shortcut_error": "Виникла помилка під час створення ярлику", + "nsfw_content_title": "Ця гра містить неприйнятний контент", + "nsfw_content_description": "{{title}} містить вміст, який може бути непридатним для всіх вікових груп. Ви впевнені, що хочете продовжити?", + "allow_nsfw_content": "Продовжити", + "refuse_nsfw_content": "Вернутись назад", + "stats": "Статистика", + "clear": "Очистити", "danger_zone_section_description": "Видалити цю гру з вашої бібліотеки або файли скачані Hydra", "danger_zone_section_title": "Небезпечна зона", + "player_count": "Грають зараз", + "download": "Завантажити", + "download_count": "Завантажень", "download_in_progress": "Триває завантаження.", "download_options": "Варіантів завантаження", "download_path": "Тека для завантажень", "download_paused": "Завантаження призупинено", "download_settings": "Налаштування завантаження", + "download_error": "Цей варіант завантаження не доступний", "downloader": "Завантажувач", - "downloads_secion_title": "Завантаження", + "downloads_section_title": "Завантаження", "downloads_section_description": "Перевірити наявність оновлень або інших версій гри", "executable_section_description": "Шлях до файлу, який буде запущений при натисканні на кнопку \"Play\"", "executable_section_title": "Файл", + "executable_path_in_use": "Виконуваний файл наразі використовується \"{{game}}\"", "last_downloaded_option": "Останній варіант завантаження", "next_screenshot": "Наступний скрішнот", "no_executable_selected": "Файл не вибрано", @@ -112,7 +141,64 @@ "remove_from_library_description": "{{game}} буде видалено з вашої бібліотеки", "remove_from_library_title": "Ви впевнені?", "screenshot": "Скріншот", - "select_executable": "Обрати" + "select_executable": "Обрати", + "warning": "Попередження:", + "hydra_needs_to_remain_open": "Hydra повинна залишатись відкритою до завершення завантаження. Якщо Hydra закриється до завершення, ви втратите прогрес.", + "achievements": "Досягнення", + "achievements_count": "Досягнень {{unlockedCount}}/{{achievementsCount}}", + "cloud_save": "Хмарне Збереження", + "cloud_save_description": "Збережіть свій ігровий процесс у хмарі та продовжіть грати з любого пристрою", + "backups": "Резервні копії", + "install_backup": "Встановити", + "delete_backup": "Видалити", + "create_backup": "Нова резервна копія", + "last_backup_date": "Остання резервна копія {{date}}", + "no_backup_preview": "Не було знайдено жодних збережень для цієї гри.", + "restoring_backup": "Відновлення резервної копії ({{progress}} виконано)…", + "uploading_backup": "Вивантаження резервної копії…", + "no_backups": "Ви ще не створили жодних резеврних копій для цієї гри", + "backup_uploaded": "Резервна копія вивантажена", + "backup_deleted": "Резервна копія видалена", + "backup_restored": "Резервна копія відновлена", + "see_all_achievements": "Переглянути всі досягнення", + "sign_in_to_see_achievements": "Увійдіть, що б переглянути усі досягнення", + "mapping_method_automatic": "Автоматичний", + "mapping_method_manual": "Ручний", + "mapping_method_label": "Спосіб визначення файлів для резервного копіювання", + "files_automatically_mapped": "Hydra автоматично вибере файли для резервного копіювання", + "no_backups_created": "Для цієї гри не було створено резервних копій", + "manage_files": "Керувати файлами", + "loading_save_preview": "Виконується пошук збережень гри...", + "wine_prefix": "Префікс Wine", + "wine_prefix_description": "Префікс Wine використовувався для запуску цієї гри", + "launch_options": "Параметри загрузки", + "launch_options_description": "Досвідчені користувачі можуть ввести власні модифікації до параметрів запуску (експериментальна функція).", + "launch_options_placeholder": "Параметри не вказано", + "no_download_option_info": "Немає інформації", + "backup_deletion_failed": "Виникла помилка при видалені резервної копії", + "max_number_of_artifacts_reached": "Максимальну кількість резервних копій для цієї гри досягнуто", + "achievements_not_sync": "Дізнайтеся, як синхронізувати свої досягнення", + "manage_files_description": "Керуйте файлами, які буде збережено та відновлено", + "select_folder": "Обрати папку", + "backup_from": "Резервна копія від {{date}}", + "automatic_backup_from": "Автоматична резервна копія від {{date}}", + "enable_automatic_cloud_sync": "Увімкнути автоматичну синхронізацію з хмарою", + "custom_backup_location_set": "Встановлено користувацьке місце збереження резервної копії", + "no_directory_selected": "Папку не було вибрано", + "no_write_permission": "Неможливо завантажити у дану папку. Натисніть сюда щоб дізнатись більше.", + "reset_achievements": "Скинути досягнення", + "reset_achievements_description": "Це скине всі досягнення для {{game}}", + "reset_achievements_title": "Ви впевнені?", + "reset_achievements_success": "Досягнення успішно скинуто", + "reset_achievements_error": "Не вдалося скинути досягнення", + "download_error_gofile_quota_exceeded": "Ви перевищили місячну квоту Gofile. Будь ласка, дочекайтесь, поки квота скинеться.", + "download_error_real_debrid_account_not_authorized": "Ваш обліковий запис Real-Debrid не авторизований для нових завантажень. Будь ласка, перевірте налаштування облікового запису та спробуйте ще раз.", + "download_error_not_cached_on_real_debrid": "Це завантаження недоступне на Real-Debrid, і перевірка статусу завантаження з Real-Debrid наразі недоступна.", + "download_error_not_cached_on_torbox": "Це завантаження недоступне на TorBox, і перевірка статусу завантаження з TorBox наразі недоступна.", + "download_error_not_cached_on_hydra": "Це завантаження недоступне через Nimbus.", + "game_removed_from_favorites": "Гра видалена з улюбленних", + "game_added_to_favorites": "Гра була добавлена у улюблені", + "automatically_extract_downloaded_files": "Автоматично розархівувати завантаженні файли" }, "activation": { "title": "Активувати Hydra", @@ -144,7 +230,14 @@ "no_downloads_title": "Тут так пусто...", "queued": "В черзі", "queued_downloads": "Завантаження в черзі", - "removed": "Не завантажено" + "removed": "Не завантажено", + "checking_files": "Перевірка файлів…", + "seeding": "Сідінг", + "stop_seeding": "Зупинити сідінг", + "resume_seeding": "Продовжити сідінг", + "options": "Налаштування", + "extract": "Розархівувати файли", + "extracting": "Розархівовування файлів…" }, "settings": { "downloads_path": "Тека завантажень", @@ -193,21 +286,101 @@ "removed_download_source": "Джерело завантажень було видалено", "save_changes": "Зберегти зміни", "sync_download_sources": "Синхронізувати джерела", - "validate_download_source": "Перевірити" + "validate_download_source": "Перевірити", + "public": "Публічний", + "private": "Приватний", + "friends_only": "Тільки для друзів", + "privacy": "Приватність", + "profile_visibility": "Видимість профілю", + "profile_visibility_description": "Оберіть хто може бачити ваш профіль та бібліотеку", + "required_field": "Це поле обов'язкове", + "source_already_exists": "Це джерело уже додано", + "must_be_valid_url": "Джерело має бути дійсною URL-адресою", + "blocked_users": "Заблоковані користувачі", + "user_unblocked": "Користувача було розблоковано", + "enable_achievement_notifications": "Коли отримано нове досягнення", + "launch_minimized": "Запустити Hydra у згорнутому вигляді", + "disable_nsfw_alert": "Вимкнути сповіщення про NSFW", + "seed_after_download_complete": "Cідувати по завершенню завантажень", + "show_hidden_achievement_description": "Показувати опис прихованих досягнень до їх розблокування", + "account": "Акаунт", + "no_users_blocked": "У вас немає заблокованих користувачів", + "subscription_active_until": "Ваша підписка на Hydra Cloud активна до {{date}}", + "manage_subscription": "Керування підпискою", + "update_email": "Змінити електронну пошту", + "update_password": "Зміна паролю", + "current_email": "Поточна електронна пошта", + "no_email_account": "Ви ще не вказали електронну пошту", + "account_data_updated_successfully": "Дані облікового запису успішно оновлено", + "renew_subscription": "Поновити Hydra Cloud", + "subscription_expired_at": "Ваша підписка закінчилася {{date}}", + "no_subscription": "Насолоджуйтесь Hydra найкращим можливим чином", + "become_subscriber": "Придбати підписку Hydra Cloud", + "subscription_renew_cancelled": "Автоматичне поновлення виключено", + "subscription_renews_on": "Ваша підписка поновиться {{date}}", + "bill_sent_until": "Ваш наступний рахунок буде надіслано до цього дня", + "no_themes": "Схоже, у вас ще немає тем, але не хвилюйтеся, натисніть тут, щоб створити свій перший шедевр.", + "editor_tab_code": "Код", + "editor_tab_info": "Інформація", + "editor_tab_save": "Зберегти", + "web_store": "Інтернет-магазин", + "clear_themes": "Очистити", + "create_theme": "Створити", + "create_theme_modal_title": "Створити користувацьку тему", + "create_theme_modal_description": "Створіть нову тему для налаштування зовнішнього вигляду Hydra", + "theme_name": "Назва", + "insert_theme_name": "Введіть назву теми", + "set_theme": "Включити тему", + "unset_theme": "Виключити тему", + "delete_theme": "Видалити тему", + "edit_theme": "Редагувати тему", + "delete_all_themes": "Видалити усі теми", + "delete_all_themes_description": "Це видалить усі ваші користувацькі теми", + "delete_theme_description": "Це видалить тему {{theme}}", + "cancel": "Відмінити", + "appearance": "Вигляд", + "enable_torbox": "Включити TorBox", + "torbox_description": "TorBox — це ваш преміум-сервіс для сідінгу, що конкурує навіть з найкращими серверами на ринку.", + "torbox_account_linked": "TorBox акаунт прив'язано", + "create_real_debrid_account": "Натисніть тут, якщо у вас ще немає облікового запису Real-Debrid", + "create_torbox_account": "Натисніть тут, якщо у вас ще немає облікового запису TorBox", + "real_debrid_account_linked": "Real-Debrid акаунт прив'язано", + "name_min_length": "Назва теми повинна містити не менше 3 символів", + "import_theme": "Імпортувати тему", + "import_theme_description": "Ви імпортуєте {{theme}} з магазину тем", + "error_importing_theme": "Помилка при імпорті теми", + "theme_imported": "Тема успішно імпортована", + "enable_friend_request_notifications": "При отриманні запиту на дружбу", + "enable_auto_install": "Автоматично завантажувати оновлення", + "common_redist": "Загальні розповсюджувані компоненти", + "common_redist_description": "Загальні розповсюджувані компоненти необхідні для запуску деяких ігор. Рекомендується їх встановити, щоб уникнути проблем.", + "install_common_redist": "Встановити", + "installing_common_redist": "Встановлюється…", + "show_download_speed_in_megabytes": "Показувати швидкість завантаження в мегабайтах на секунду" }, "notifications": { "download_complete": "Завантаження завершено", "game_ready_to_install": "{{title}} готова до встановлення", "repack_list_updated": "Список репаків оновлено", "repack_count_one": "{{count}} репак додано", - "repack_count_other": "{{count}} репаків додано" + "repack_count_other": "{{count}} репаків додано", + "new_update_available": "Версія {{version}} доступна", + "restart_to_install_update": "Перезавантажте Hydra, щоб встановити оновлення", + "notification_achievement_unlocked_title": "Розблоковано досягнення для {{game}}", + "notification_achievement_unlocked_body": "{{achievement}} та інші {{count}} було розблоковано", + "new_friend_request_description": "Ви отримали новий запит на дружбу", + "new_friend_request_title": "Новий запит на дружбу", + "extraction_complete": "Витягування завершено", + "game_extracted": "{{title}} успішно витягнуто" }, "system_tray": { "open": "Відкрити Hydra", "quit": "Вийти" }, "game_card": { - "no_downloads": "Немає доступних завантажень" + "no_downloads": "Немає доступних завантажень", + "available_one": "Доступний", + "available_other": "Доступні" }, "binary_not_found_modal": { "title": "Програми не встановлені", @@ -240,6 +413,94 @@ "sign_out_modal_title": "Ви впевнені?", "successfully_signed_out": "Успішний вихід з акаунту", "total_play_time": "Всього зіграно", - "try_again": "Будь ласка, попробуйте ще раз" + "try_again": "Будь ласка, попробуйте ще раз", + "add_friends": "Добавити друзів", + "add": "Добавити", + "friend_code": "Код друга", + "see_profile": "Переглянути профіль", + "sending": "Надсилання", + "friend_request_sent": "Запит на дружбу було надіслано", + "friends": "Друзі", + "friends_list": "Список друзів", + "user_not_found": "Користувача не найдено", + "block_user": "Заблокувати користувача", + "add_friend": "Добавити друга", + "request_sent": "Запит надіслано", + "request_received": "Запит отримано", + "accept_request": "Прийняти запит", + "ignore_request": "Ігнорувати запит", + "cancel_request": "Скасувати запит", + "undo_friendship": "Видалити з друзів", + "request_accepted": "Запит прийнято", + "user_blocked_successfully": "Користувач успішно заблокований", + "user_block_modal_text": "Це заблокує {{displayName}}", + "blocked_users": "Заблоковані користувачі", + "unblock": "Розблокувати", + "no_friends_added": "У вас немає доданих друзів", + "pending": "Очікування", + "no_pending_invites": "У вас немає запитів, що очікують на підтвердження", + "no_blocked_users": "У вас немає заблокованих користувачів", + "friend_code_copied": "Код друга скопійовано", + "undo_friendship_modal_text": "Це видалить з друзів {{displayName}}", + "privacy_hint": "Щоб налаштувати, хто може це бачити, перейдіть до <0>Settings", + "locked_profile": "Цей профіль приватний", + "image_process_failure": "Помилка при обробці зображення", + "required_field": "Це поле обов'язкове", + "displayname_min_length": "Ім'я користувача повинно містити не менше 3 символів", + "displayname_max_length": "Ім'я користувача повинно містити не більше 50 символів", + "report_profile": "Повідомити про цей профіль", + "report_reason": "Чому ви повідомляєте про цей профіль?", + "report_description": "Додаткова інформація", + "report_description_placeholder": "Додаткова інформація", + "report": "Повідомити", + "report_reason_hate": "Мова ненависті", + "report_reason_sexual_content": "Сексуальний контент", + "report_reason_violence": "Насильство", + "report_reason_spam": "Спам", + "report_reason_other": "Інше", + "profile_reported": "Профіль повідомлено", + "your_friend_code": "Ваш код друга:", + "upload_banner": "Upload banner", + "uploading_banner": "Завантажити банер...", + "background_image_updated": "Фонове зображення оновлено", + "stats": "Статистика", + "achievements": "Досягнення", + "games": "Ігри", + "top_percentile": "Топ {{percentile}}%", + "ranking_updated_weekly": "Рейтинг оновлюється щотижня", + "playing": "Грає у {{game}}", + "achievements_unlocked": "Досягнень розблоковано", + "earned_points": "Отримано балів", + "show_achievements_on_profile": "Покажіть свої досягнення у своєму профілі", + "show_points_on_profile": "Покажіть ваші отриманні бали у своєму профілі" + }, + "achievement": { + "achievement_unlocked": "Досягнення розблоковано", + "user_achievements": "Досягнення користувача {{displayName}}", + "your_achievements": "Ваші досягнення", + "unlocked_at": "Розблоковано: {{date}}", + "subscription_needed": "Для перегляду цього контенту потрібна підписка на Hydra Cloud", + "new_achievements_unlocked": "Розблоковано {{achievementCount}} нових досягнень з {{gameCount}} ігор", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} досягнень", + "achievements_unlocked_for_game": "Розблоковано {{achievementCount}} нових досягнень у грі {{gameTitle}}", + "hidden_achievement_tooltip": "Це приховане досягнення", + "achievement_earn_points": " Отримайте {{points}} балів за це досягнення", + "earned_points": "Отримано балів:", + "available_points": "Доступно балів:", + "how_to_earn_achievements_points": "Як заробляти бали за досягнення?" + }, + "hydra_cloud": { + "subscription_tour_title": "Підписка Hydra Cloud", + "subscribe_now": "Підписатися зараз", + "cloud_saving": "Збереження в хмарі", + "cloud_achievements": "Зберігайте свої досягнення в хмарі", + "animated_profile_picture": "Анімовані аватари", + "premium_support": "Преміум-підтримка", + "show_and_compare_achievements": "Показуйте та порівнюйте свої досягнення з іншими користувачами", + "animated_profile_banner": "Анімований банер профілю", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Ви щойно виявили функцію Hydra Cloud!", + "learn_more": "Дізнатися більше", + "debrid_description": "Завантажуйте до 4 разів швидше з Nimbus" } } diff --git a/src/locales/uz/translation.json b/src/locales/uz/translation.json new file mode 100644 index 00000000..d20a9677 --- /dev/null +++ b/src/locales/uz/translation.json @@ -0,0 +1,476 @@ +{ + "language_name": "Uzbek", + "app": { + "successfully_signed_in": "Kirish muvaffaqiyatli amalga oshirildi" + }, + "home": { + "featured": "Tavsiya etilgan", + "surprise_me": "Hayratda qoldir", + "no_results": "Natijalar topilmadi", + "hot": "Eng mashhur", + "start_typing": "Yozishni boshlayapman ...", + "weekly": "📅 Haftaning eng yaxshi o'yinlari", + "achievements": "🏆 Yutuqlar bilan o'yinlar" + }, + "sidebar": { + "catalogue": "Katalog", + "downloads": "Yuklab olishlar", + "settings": "Sozlamalar", + "my_library": "Mening kutubxonam", + "downloading_metadata": "{{title}} (Metamaʼlumotlar yuklanmoqda…)", + "paused": "{{title}} (To'xtatildi)", + "downloading": "{{title}} ({{percentage}} - Yuklanmoqda…)", + "filter": "Qidiruv", + "home": "Asosiy", + "queued": "{{title}} (Navbatda)", + "game_has_no_executable": "Oʻyinni ishga tushirish fayli tanlanmagan", + "sign_in": "Kirish", + "friends": "Do'stlar", + "need_help": "Yordam kerakmi?", + "favorites": "Sevimlilar" + }, + "header": { + "search": "Qidirish", + "home": "Asosiy", + "catalogue": "Katalog", + "downloads": "Yuklab olishlar", + "search_results": "Qidiruv natijalari", + "settings": "Sozlamalar", + "version_available_install": "{{version}} versiyasi mavjud. Oʻrnatish uchun shu yerni bosing.", + "version_available_download": "{{version}} versiyasi mavjud. Yuklab olish uchun shu yerni bosing." + }, + "bottom_panel": { + "no_downloads_in_progress": "Faol yuklab olishlar yo'q", + "downloading_metadata": "Metamaʼlumotlar yuklanmoqda {{title}}…", + "downloading": "Yuklanmoqda {{title}}… ({{percentage}} yakunlandi) - Tugash vaqti {{eta}} - {{speed}}", + "calculating_eta": "Yuklanmoqda {{title}}… ({{percentage}} yakunlandi) - Qolgan vaqt hisoblanmoqda…", + "checking_files": "Fayllar tekshirilmoqda {{title}}… ({{percentage}} yakunlandi)", + "installing_common_redist": "{{log}}…", + "installation_complete": "O'rnatish yakunlandi", + "installation_complete_message": "Kutubxonalar muvaffaqiyatli o'rnatildi" + }, + "catalogue": { + "search": "Filter…", + "developers": "Ishlab chiquvchilar", + "genres": "Janrlar", + "tags": "Teglar", + "publishers": "Nashriyotlar", + "download_sources": "Yuklab olish manbalari", + "result_count": "{{resultCount}} natija", + "filter_count": "{{filterCount}} mavjud", + "clear_filters": "{{filterCount}} tanlangan filtrni tozalash" + }, + "game_details": { + "play_time": "O'ynalgan vaqt {{amount}}", + "last_time_played": "Oxirgi ishga tushirilgan {{period}}", + "not_played_yet": "Siz hali {{title}}ni o'ynamagansiz", + "next_suggestion": "Keyingi taklif", + "play": "O'ynash", + "deleting": "O'rnatuvchi o'chirilmoqda…", + "close": "Yopish", + "playing_now": "Hozir o'ynalmoqda", + "change": "O'zgartirish", + "repacks_modal_description": "Yuklab olish uchun repakni tanlang", + "select_folder_hint": "Standart yuklab olish jildini o'zgartirish uchun <0>Sozlamalarni oching", + "download_now": "Hozir yuklab olish", + "no_shop_details": "Tavsif olib bo'lmadi", + "download_options": "Manbalar", + "download_path": "Yuklab olish yo'li", + "previous_screenshot": "Oldingi skrinshot", + "next_screenshot": "Keyingi skrinshot", + "screenshot": "Skrinshot {{number}}", + "open_screenshot": "{{number}}-skrinshotni ochish", + "download_settings": "Yuklab olish sozlamalari", + "downloader": "Yuklovchi", + "select_executable": "Tanlash", + "no_executable_selected": "Fayl tanlanmagan", + "open_folder": "Jildni ochish", + "open_download_location": "Yuklab olish jildini ko'rish", + "create_shortcut": "Ish stoliga yorliq yaratish", + "clear": "Tozalash", + "remove_files": "Fayllarni o'chirish", + "remove_from_library_title": "Ishonchingiz komilmi?", + "remove_from_library_description": "{{game}} kutubxonangizdan o'chiriladi.", + "options": "Sozlamalar", + "executable_section_title": "Fayl", + "executable_section_description": "\"O'ynash\" tugmasi bosilganda ishga tushiriladigan fayl yo'li", + "downloads_section_title": "Yuklab olishlar", + "downloads_section_description": "Yangilanishlar yoki o'yinning boshqa versiyalarini tekshirish", + "danger_zone_section_title": "Xavfli zona", + "danger_zone_section_description": "Bu o'yinni kutubxonangizdan yoki Hydradan yuklab olingan fayllarni o'chirishingiz mumkin", + "download_in_progress": "Yuklab olish davom etmoqda", + "download_paused": "Yuklab olish to'xtatilgan", + "last_downloaded_option": "Oxirgi yuklab olish varianti", + "create_shortcut_success": "Yorliq yaratildi", + "create_shortcut_error": "Yorliq yaratib bo'lmadi", + "allow_nsfw_content": "Davom etish", + "download": "Yuklab olish", + "download_count": "Yuklab olishlar", + "download_error": "Bu yuklab olish varianti mavjud emas", + "executable_path_in_use": "Bajarish fayli allaqachon \"{{game}}\" tomonidan ishlatilmoqda", + "nsfw_content_description": "{{title}} barcha yoshdagilar uchun mos bo'lmasligi mumkin bo'lgan tarkibni o'z ichiga oladi. \nDavom etishni xohlaysizmi?", + "nsfw_content_title": "Bu o'yin nomunosib tarkibga ega", + "refuse_nsfw_content": "Orqaga", + "stats": "Statistika", + "player_count": "Faol o'yinchilar", + "warning": "Ogohlantirish:", + "hydra_needs_to_remain_open": "Bu yuklab olish uchun Hydra tugaguncha ochiq qolishi kerak. Agar Hydra tugashidan oldin yopilsa, jarayonni yo'qotasiz.", + "achievements": "Yutuqlar", + "achievements_count": "Yutuqlar {{unlockedCount}}/{{achievementsCount}}", + "cloud_save": "Bulutli saqlash", + "cloud_save_description": "O'yin jarayoningizni bulutda saqlang va istalgan qurilmada o'ynashni davom ettiring", + "backups": "Zaxira nusxalar", + "install_backup": "O'rnatish", + "delete_backup": "O'chirish", + "create_backup": "Yangi zaxira nusxa yaratish", + "last_backup_date": "Oxirgi zaxira nusxa {{date}} dan", + "no_backup_preview": "Bu sarlavha uchun saqlashlar topilmadi", + "restoring_backup": "Zaxira nusxa tiklanmoqda ({{progress}} yakunlandi)…", + "uploading_backup": "Zaxira nusxa yuklanmoqda…", + "no_backups": "Siz hali bu o'yin uchun zaxira nusxa yaratmagansiz", + "backup_uploaded": "Zaxira nusxa yuklandi", + "backup_deleted": "Zaxira nusxa o'chirildi", + "backup_restored": "Zaxira nusxa tiklandi", + "see_all_achievements": "Barcha yutuqlarni ko'rish", + "sign_in_to_see_achievements": "Yutuqlarni ko'rish uchun tizimga kiring", + "mapping_method_automatic": "Avtomatik", + "mapping_method_manual": "Qo'lda", + "mapping_method_label": "Moslashtirish usuli", + "files_automatically_mapped": "Fayllar avtomatik moslashtirildi", + "no_backups_created": "Bu o'yin uchun zaxira nusxalar yaratilmagan", + "manage_files": "Fayllarni boshqarish", + "loading_save_preview": "Saqlashlar qidirilmoqda…", + "wine_prefix": "Wine prefiksi", + "wine_prefix_description": "Bu o'yinni ishga tushirish uchun ishlatiladigan Wine prefiksi", + "launch_options": "Ishga tushirish parametrlari", + "launch_options_description": "Tajribali foydalanuvchilar ishga tushirish parametrlarini o'zgartirishi mumkin", + "launch_options_placeholder": "Parametr ko'rsatilmagan", + "no_download_option_info": "Ma'lumot mavjud emas", + "backup_deletion_failed": "Zaxira nusxani o'chirib bo'lmadi", + "max_number_of_artifacts_reached": "Bu o'yin uchun maksimal zaxira nusxalar soniga yetildi", + "achievements_not_sync": "Yutuqlaringiz sinxronlanmagan", + "manage_files_description": "Saqlanishi va tiklanishi kerak bo'lgan fayllarni boshqaring", + "select_folder": "Jildni tanlash", + "backup_from": "{{date}} dan zaxira nusxa", + "automatic_backup_from": "{{date}} dan avtomatik zaxira nusxa", + "enable_automatic_cloud_sync": "Avtomatik bulutli sinxronlashni yoqish", + "custom_backup_location_set": "Maxsus zaxira nusxa joylashuvi o'rnatildi", + "no_directory_selected": "Katalog tanlanmagan", + "no_write_permission": "Bu katalogga yuklab bo'lmaydi. Ko'proq ma'lumot olish uchun bu yerga bosing.", + "reset_achievements": "Yutuqlarni tiklash", + "reset_achievements_description": "Bu {{game}} uchun barcha yutuqlarni tiklaydi", + "reset_achievements_title": "Ishonchingiz komilmi?", + "reset_achievements_success": "Yutuqlar muvaffaqiyatli tiklandi", + "reset_achievements_error": "Yutuqlarni tiklab bo'lmadi", + "download_error_gofile_quota_exceeded": "Siz Gofile oylik kvotangizni oshirib yubordingiz. Iltimos, kvota tiklanguncha kuting.", + "download_error_real_debrid_account_not_authorized": "Sizning Real-Debrid hisobingiz yangi yuklab olishlar uchun avtorizatsiya qilinmagan. Iltimos, hisob sozlamalarini tekshiring va qaytadan urinib ko'ring.", + "download_error_not_cached_on_real_debrid": "Bu yuklab olish Real-Debrid'da mavjud emas, va Real-Debrid'dan yuklab olish holatini olish hozircha mavjud emas.", + "download_error_not_cached_on_torbox": "Bu yuklab olish TorBox'da mavjud emas, va TorBox'dan yuklab olish holatini olish hozircha mumkin emas.", + "game_added_to_favorites": "O'yin sevimlilarga qo'shildi", + "game_removed_from_favorites": "O'yin sevimlilardan olib tashlandi", + "automatically_extract_downloaded_files": "Yuklab olingan fayllarni avtomatik chiqarish" + }, + "activation": { + "title": "Hydra'ni faollashtirish", + "installation_id": "O'rnatish ID'si:", + "enter_activation_code": "Faollashtirish kodini kiriting", + "message": "Agar qayerdan so'rash kerakligini bilmasangiz, u sizda bo'lmasligi kerak.", + "activate": "Faollashtirish", + "loading": "Yuklanmoqda…" + }, + "downloads": { + "resume": "Davom ettirish", + "pause": "To'xtatib turish", + "eta": "Tugash vaqti {{eta}}", + "paused": "To'xtatilgan", + "verifying": "Tekshirilmoqda…", + "completed": "Yakunlandi", + "removed": "Yuklab olinmagan", + "cancel": "Bekor qilish", + "filter": "Yuklab olingan o'yinlarni qidirish", + "remove": "O'chirish", + "downloading_metadata": "Metamaʼlumotlar yuklanmoqda…", + "deleting": "O'rnatuvchi o'chirilmoqda…", + "delete": "O'rnatuvchini o'chirish", + "delete_modal_title": "Ishonchingiz komilmi?", + "delete_modal_description": "Bu kompyuteringizdan barcha o'rnatuvchilarni o'chiradi", + "install": "O'rnatish", + "download_in_progress": "Jarayonda", + "queued_downloads": "Navbatdagi yuklab olishlar", + "downloads_completed": "Yakunlangan", + "queued": "Navbatda", + "no_downloads_title": "Bu yer juda bo'sh...", + "no_downloads_description": "Siz hali Hydra orqali hech narsa yuklab olmadingiz, lekin boshlash uchun hech qachon kech emas.", + "checking_files": "Fayllar tekshirilmoqda…", + "seeding": "Ulashish", + "stop_seeding": "Ulashishni to'xtatish", + "resume_seeding": "Ulashishni davom ettirish", + "options": "Boshqarish", + "extract": "Fayllarni chiqarish", + "extracting": "Fayllar chiqarilmoqda…" + }, + "settings": { + "downloads_path": "Yuklab olish yo'li", + "change": "O'zgartirish", + "notifications": "Bildirishnomalar", + "enable_download_notifications": "Yuklab olish tugaganda", + "enable_achievement_notifications": "Yutuq ochilganda", + "enable_repack_list_notifications": "Yangi repak qo'shilganda", + "real_debrid_api_token_label": "Real-Debrid API-tokeni", + "quit_app_instead_hiding": "Ilovani trayga yig'ish o'rniga yopish", + "launch_with_system": "Hydra'ni tizim bilan birga ishga tushirish", + "launch_minimized": "Hydra'ni yig'ilgan holda ishga tushirish", + "disable_nsfw_alert": "Noqulay kontent haqida ogohlantirishni o'chirish", + "general": "Asosiy", + "behavior": "Xatti-harakat", + "download_sources": "Yuklab olish manbalari", + "language": "Til", + "api_token": "API kalit", + "enable_real_debrid": "Real-Debrid'ni yoqish", + "real_debrid_description": "Real-Debrid - bu cheksiz yuklab oluvchi bo'lib, internetda joylashtirilgan fayllarni tezda yuklab olishga yoki ularni xususiy tarmoq orqali pleerga zudlik bilan o'tkazishga imkon beradi, bu esa har qanday blokirovkalarni chetlab o'tishga imkon beradi.", + "debrid_invalid_token": "Noto'g'ri API kalit", + "debrid_api_token_hint": "API kalitni <0>bu yerda olish mumkin", + "real_debrid_free_account_error": "\"{{username}}\" hisobi - obunaga ega emas. Iltimos, Real-Debrid obunasini rasmiylashtiring", + "debrid_linked_message": "\"{{username}}\" hisobi bog'langan", + "save_changes": "O'zgarishlarni saqlash", + "changes_saved": "O'zgarishlar muvaffaqiyatli saqlandi", + "download_sources_description": "Hydra yuklab olish havolalarini ushbu manbalardan oladi. URL yuklab olish uchun havolalar bilan .json-fayliga to'g'ridan-to'g'ri havolani o'z ichiga olishi kerak.", + "validate_download_source": "Tekshirish", + "remove_download_source": "O'chirish", + "add_download_source": "Manba qo'shish", + "download_count_zero": "Ro'yxatda yuklab olishlar yo'q", + "download_count_one": "Ro'yxatda {{countFormatted}} ta yuklab olish", + "download_count_other": "Ro'yxatda {{countFormatted}} ta yuklab olish", + "download_source_url": "Manba havolasi", + "add_download_source_description": ".json-fayliga havolani joylang", + "download_source_up_to_date": "Yangilangan", + "download_source_errored": "Xato", + "sync_download_sources": "Manbalarni yangilash", + "removed_download_source": "Manba o'chirildi", + "cancel_button_confirmation_delete_all_sources": "Yo'q", + "confirm_button_confirmation_delete_all_sources": "Ha, barchasini o'chirish", + "description_confirmation_delete_all_sources": "Siz barcha manbalarni o'chirasiz", + "title_confirmation_delete_all_sources": "Barcha manbalarni o'chirish", + "removed_download_sources": "Manbalar o'chirildi", + "button_delete_all_sources": "Barcha manbalarni o'chirish", + "added_download_source": "Manba qo'shildi", + "download_sources_synced": "Barcha manbalar yangilandi", + "insert_valid_json_url": "Amaldagi JSON-fayl URL'ini kiriting", + "found_download_option_zero": "Yuklab olish variantlari topilmadi", + "found_download_option_one": "{{countFormatted}} yuklab olish varianti topildi", + "found_download_option_other": "{{countFormatted}} yuklab olish varianti topildi", + "import": "Import qilish", + "blocked_users": "Bloklangan foydalanuvchilar", + "friends_only": "Faqat do'stlar uchun", + "must_be_valid_url": "Manbada to'g'ri URL bo'lishi kerak", + "privacy": "Maxfiylik", + "private": "Shaxsiy", + "profile_visibility": "Profil ko'rinuvchanligi", + "profile_visibility_description": "Kim sizning profilingiz va kutubxonangizni ko'ra olishini tanlang", + "public": "Ommaviy", + "required_field": "Bu maydon to'ldirilishi shart", + "source_already_exists": "Bu manba allaqachon qo'shilgan", + "user_unblocked": "Foydalanuvchi blokdan chiqarildi", + "seed_after_download_complete": "Yuklab olish tugagandan so'ng ulashish", + "show_hidden_achievement_description": "Yashirin yutuqlarning tavsifini ularni olishdan oldin ko'rsatish", + "account": "Hisob", + "no_users_blocked": "Sizda bloklangan foydalanuvchilar yo'q", + "subscription_active_until": "Hydra Cloud obunangiz {{date}} ga qadar faol", + "manage_subscription": "Obunani boshqarish", + "update_email": "Elektron pochtani yangilash", + "update_password": "Parolni yangilash", + "current_email": "Joriy elektron pochta:", + "no_email_account": "Siz hali elektron pochta o'rnatmagansiz", + "account_data_updated_successfully": "Hisob ma'lumotlari muvaffaqiyatli yangilandi", + "renew_subscription": "Hydra Cloud obunasini yangilash", + "subscription_expired_at": "Obunangiz muddati {{date}} da tugagan", + "no_subscription": "Hydra'dan maksimal darajada bahramand bo'ling", + "become_subscriber": "Hydra Cloud egasiga aylaning", + "subscription_renew_cancelled": "Avtomatik yangilash o'chirilgan", + "subscription_renews_on": "Obunangiz {{date}} da yangilanadi", + "bill_sent_until": "Keyingi hisobingiz shu kungacha yuboriladi", + "no_themes": "Sizda hali mavzular yo'qqa o'xshaydi, lekin tashvishlanmang, birinchi shoh asaringizni yaratish uchun shu yerni bosing", + "editor_tab_code": "Kod", + "editor_tab_info": "Ma'lumot", + "editor_tab_save": "Saqlash", + "web_store": "Veb-do'kon", + "clear_themes": "Tozalash", + "create_theme": "Yaratish", + "create_theme_modal_title": "Maxsus mavzu yaratish", + "create_theme_modal_description": "Hydra ko'rinishini sozlash uchun yangi mavzu yaratish", + "theme_name": "Nomi", + "insert_theme_name": "Mavzu nomini kiriting", + "set_theme": "Mavzuni o'rnatish", + "unset_theme": "Mavzuni olib tashlash", + "delete_theme": "Mavzuni o'chirish", + "edit_theme": "Mavzuni tahrirlash", + "delete_all_themes": "Barcha mavzularni o'chirish", + "delete_all_themes_description": "Bu barcha maxsus mavzularingizni o'chiradi", + "delete_theme_description": "Bu {{theme}} mavzusini o'chirishga olib keladi", + "cancel": "Bekor qilish", + "appearance": "Tashqi ko'rinish", + "enable_torbox": "TorBox'ni yoqish", + "torbox_description": "TorBox - bu bozordagi eng yaxshi serverlar bilan ham raqobatlashadigan premium xizmatingiz.", + "torbox_account_linked": "TorBox hisobi bog'langan", + "real_debrid_account_linked": "Real-Debrid hisobi bog'langan", + "create_real_debrid_account": "Agar sizda hali Real-Debrid hisobi bo'lmasa, shu yerni bosing", + "create_torbox_account": "Agar sizda hali TorBox hisobi bo'lmasa, shu yerni bosing", + "name_min_length": "Mavzu nomi kamida 3 ta belgi bo'lishi kerak", + "import_theme": "Mavzuni import qilish", + "import_theme_description": "Siz {{theme}} mavzusini mavzular do'konidan import qilmoqdasiz", + "error_importing_theme": "Mavzuni import qilishda xato", + "theme_imported": "Mavzu muvaffaqiyatli import qilindi", + "enable_friend_request_notifications": "Do'stlar so'rovi olinganda", + "enable_auto_install": "Yangilanishlarni avtomatik yuklab olish", + "common_redist": "Kutubxonalar", + "common_redist_description": "Ba'zi o'yinlarni ishga tushirish uchun kutubxonalar talab qilinadi. Muammolarning oldini olish uchun ularni o'rnatish tavsiya etiladi.", + "install_common_redist": "O'rnatish", + "installing_common_redist": "O'rnatilmoqda…", + "show_download_speed_in_megabytes": "Yuklab olish tezligini sekundiga megabaytlarda ko'rsatish" + }, + "notifications": { + "download_complete": "Yuklab olish yakunlandi", + "game_ready_to_install": "{{title}} o'rnatishga tayyor", + "repack_list_updated": "Repaklar ro'yxati yangilandi", + "repack_count_one": "{{count}} repak qo'shildi", + "repack_count_other": "{{count}} repak qo'shildi", + "new_update_available": "Yangi {{version}} versiyasi mavjud", + "restart_to_install_update": "Yangilanishni o'rnatish uchun Hydra'ni qayta ishga tushiring", + "notification_achievement_unlocked_title": "{{game}} uchun yutuq ochildi", + "notification_achievement_unlocked_body": "{{achievement}} va boshqa {{count}} ta yutuq ochildi", + "new_friend_request_title": "Yangi do'stlik so'rovi", + "new_friend_request_description": "Siz yangi do'stlik so'rovini oldingiz", + "extraction_complete": "Arxivdan chiqarish yakunlandi", + "game_extracted": "{{title}} muvaffaqiyatli arxivdan chiqarildi" + }, + "system_tray": { + "open": "Hydra'ni ochish", + "quit": "Chiqish" + }, + "game_card": { + "available_one": "Mavjud", + "available_other": "Mavjud", + "no_downloads": "Mavjud manbalar yo'q" + }, + "binary_not_found_modal": { + "title": "Dasturlar o'rnatilmagan", + "description": "Wine yoki Lutris topilmadi", + "instructions": "O'yin to'g'ri ishlashi uchun Linux distributivingizga ulardan birini o'rnatishning to'g'ri usulini bilib oling" + }, + "modal": { + "close": "Yopish" + }, + "forms": { + "toggle_password_visibility": "Parolni ko'rsatish" + }, + "user_profile": { + "amount_hours": "{{amount}} soat", + "amount_minutes": "{{amount}} daqiqa", + "last_time_played": "Oxirgi o'yin {{period}}", + "activity": "So'nggi faollik", + "library": "Kutubxona", + "total_play_time": "Jami o'ynalgan vaqt", + "no_recent_activity_title": "Hmm... Bu yerda hech narsa yo'q", + "no_recent_activity_description": "Siz uzoq vaqtdan beri o'ynamagansiz. Buni o'zgartirish vaqti keldi!", + "display_name": "Ko'rsatiladigan ism", + "saving": "Saqlanmoqda", + "save": "Saqlash", + "edit_profile": "Profilni tahrirlash", + "saved_successfully": "Muvaffaqiyatli saqlandi", + "try_again": "Iltimos, qayta urinib ko'ring", + "sign_out_modal_title": "Ishonchingiz komilmi?", + "cancel": "Bekor qilish", + "successfully_signed_out": "Tizimdan muvaffaqiyatli chiqdingiz", + "sign_out": "Chiqish", + "playing_for": "{{amount}} o'ynalgan", + "sign_out_modal_text": "Sizning kutubxonangiz joriy hisob qaydnomangizga bog'langan. Tizimdan chiqsangiz, kutubxonangiz mavjud bo'lmaydi va progress saqlanmaydi. Chiqasizmi?", + "add_friends": "Do'stlar qo'shish", + "add": "Qo'shish", + "friend_code": "Do'st kodi", + "see_profile": "Profilni ko'rish", + "sending": "Yuborilmoqda", + "friend_request_sent": "Do'stlik so'rovi yuborildi", + "friends": "Do'stlar", + "friends_list": "Do'stlar ro'yxati", + "user_not_found": "Foydalanuvchi topilmadi", + "block_user": "Foydalanuvchini bloklash", + "add_friend": "Do'st qo'shish", + "request_sent": "So'rov yuborildi", + "request_received": "So'rov qabul qilindi", + "accept_request": "So'rovni qabul qilish", + "ignore_request": "So'rovni e'tiborsiz qoldirish", + "cancel_request": "So'rovni bekor qilish", + "undo_friendship": "Do'stni o'chirish", + "request_accepted": "So'rov qabul qilindi", + "user_blocked_successfully": "Foydalanuvchi muvaffaqiyatli bloklandi", + "user_block_modal_text": "{{displayName}} bloklanadi", + "blocked_users": "Bloklangan foydalanuvchilar", + "unblock": "Blokdan chiqarish", + "no_friends_added": "Siz hali hech qanday do'st qo'shmagansiz", + "pending": "Kutilmoqda", + "no_pending_invites": "Sizda javob kutayotgan so'rovlar yo'q", + "no_blocked_users": "Siz hech kimni bloklamagansiz", + "friend_code_copied": "Do'st kodi nusxalandi", + "displayname_max_length": "Ko'rsatiladigan ism 50 ta belgidan oshmasligi kerak", + "displayname_min_length": "Ko'rsatiladigan ism kamida 3 ta belgidan iborat bo'lishi kerak", + "image_process_failure": "Rasmni qayta ishlashda xatolik yuz berdi", + "locked_profile": "Bu profil shaxsiy", + "privacy_hint": "Uni kimlar ko'rishi mumkinligini belgilash uchun <0>Sozlamalarga o'ting", + "profile_reported": "Profil haqida xabar berildi", + "report": "Xabar berish", + "report_description": "Qo'shimcha ma'lumot", + "report_description_placeholder": "Qo'shimcha ma'lumot", + "report_profile": "Bu profil haqida shikoyat qilish", + "report_reason": "Nega bu profil haqida shikoyat qilyapsiz?", + "report_reason_hate": "Nafrat qo'zg'atish", + "report_reason_other": "Boshqa", + "report_reason_sexual_content": "Jinsiy tarkib", + "report_reason_spam": "Spam", + "report_reason_violence": "Zo'ravonlik", + "required_field": "Bu maydon to'ldirilishi shart", + "undo_friendship_modal_text": "Bu {{displayName}} bilan do'stligingizni bekor qiladi", + "your_friend_code": "Sizning do'st kodingiz:", + "upload_banner": "Banner yuklash", + "uploading_banner": "Banner yuklanmoqda...", + "background_image_updated": "Fon rasmi yangilandi", + "stats": "Statistika", + "achievements": "Yutuqlar", + "games": "O'yinlar", + "top_percentile": "Yuqori {{percentile}}%", + "ranking_updated_weekly": "Reyting har hafta yangilanadi", + "playing": "{{game}}ni o'ynayapti", + "achievements_unlocked": "Yutuqlar ochildi", + "earned_points": "To'plangan ballar:", + "show_achievements_on_profile": "Yutuqlaringizni profilingizda ko'rsating", + "show_points_on_profile": "To'plangan ballarni profilingizda ko'rsating" + }, + "achievement": { + "achievement_unlocked": "Yutuq ochildi", + "user_achievements": "{{displayName}}ning yutuqlari", + "your_achievements": "Sizning yutuqlaringiz", + "unlocked_at": "Ochilgan sana: {{date}}", + "subscription_needed": "Bu kontentni ko'rish uchun Hydra Cloud obunasi kerak", + "new_achievements_unlocked": "{{gameCount}} o'yindan {{achievementCount}} ta yangi yutuq ochildi", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} yutuq", + "achievements_unlocked_for_game": "{{gameTitle}} uchun {{achievementCount}} ta yangi yutuq ochildi", + "hidden_achievement_tooltip": "Bu yashirin yutuq", + "achievement_earn_points": "Bu yutuq bilan {{points}} ball to'plang", + "earned_points": "To'plangan ballar:", + "available_points": "Mavjud ballar:", + "how_to_earn_achievements_points": "Yutuq ballarini qanday to'plash mumkin?" + }, + "hydra_cloud": { + "subscription_tour_title": "Hydra Cloud obunasi", + "subscribe_now": "Hoziroq obuna bo'ling", + "cloud_saving": "Bulutli saqlash", + "cloud_achievements": "Yutuqlaringizni bulutda saqlang", + "animated_profile_picture": "Animatsiyali profil rasmi", + "premium_support": "Premium qo'llab-quvvatlash", + "show_and_compare_achievements": "Yutuqlaringizni boshqa foydalanuvchilarning yutuqlari bilan solishtiring va ko'rsating", + "animated_profile_banner": "Animatsiyali profil banneri", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Siz hozirgina Hydra Cloud funksiyasini kashf etdingiz!", + "learn_more": "Batafsil ma'lumot", + "debrid_description": "Nimbus bilan 4 barobar tezroq yuklab oling" + } +} diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 83869c86..01605142 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -111,7 +111,7 @@ "options": "选项", "executable_section_title": "可执行文件", "executable_section_description": "点击 \"Play\" 时将执行的文件的路径", - "downloads_secion_title": "下载", + "downloads_section_title": "下载", "downloads_section_description": "查看此游戏的更新或其他版本", "danger_zone_section_title": "危险操作", "danger_zone_section_description": "从您的库或Hydra下载的文件中删除此游戏", diff --git a/src/main/events/cloud-save/upload-save-game.ts b/src/main/events/cloud-save/upload-save-game.ts index 891941a0..d2942cb4 100644 --- a/src/main/events/cloud-save/upload-save-game.ts +++ b/src/main/events/cloud-save/upload-save-game.ts @@ -1,8 +1,6 @@ import { CloudSync } from "@main/services"; import { registerEvent } from "../register-event"; import type { GameShop } from "@types"; -import i18next, { t } from "i18next"; -import { formatDate } from "date-fns"; const uploadSaveGame = async ( _event: Electron.IpcMainInvokeEvent, @@ -10,16 +8,11 @@ const uploadSaveGame = async ( shop: GameShop, downloadOptionTitle: string | null ) => { - const { language } = i18next; - return CloudSync.uploadSaveGame( objectId, shop, downloadOptionTitle, - t("backup_from", { - ns: "game_details", - date: formatDate(new Date(), language), - }) + CloudSync.getBackupLabel(false) ); }; diff --git a/src/main/index.ts b/src/main/index.ts index 7d9ad0a2..6999cf42 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,7 +3,6 @@ import updater from "electron-updater"; import i18n from "i18next"; import path from "node:path"; import url from "node:url"; -import kill from "kill-port"; import { electronApp, optimizer } from "@electron-toolkit/utils"; import { logger, WindowManager } from "@main/services"; import resources from "@locales"; @@ -58,7 +57,7 @@ app.whenReady().then(async () => { return net.fetch(url.pathToFileURL(decodeURI(filePath)).toString()); }); - await kill(PythonRPC.RPC_PORT).finally(() => loadState()); + await loadState(); const language = await db.get(levelKeys.language, { valueEncoding: "utf-8", diff --git a/src/main/main.ts b/src/main/main.ts index b9a37b00..5869ab31 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,4 +1,4 @@ -import { DownloadManager, Ludusavi, startMainLoop } from "./services"; +import { Aria2, DownloadManager, Ludusavi, startMainLoop } from "./services"; import { RealDebridClient } from "./services/download/real-debrid"; import { HydraApi } from "./services/hydra-api"; import { uploadGamesBatch } from "./services/library-sync"; @@ -20,6 +20,8 @@ export const loadState = async () => { await import("./events"); + Aria2.spawn(); + if (userPreferences?.realDebridApiToken) { RealDebridClient.authorize(userPreferences.realDebridApiToken); } @@ -30,7 +32,7 @@ export const loadState = async () => { Ludusavi.addManifestToLudusaviConfig(); - HydraApi.setupApi().then(() => { + await HydraApi.setupApi().then(() => { uploadGamesBatch(); }); diff --git a/src/main/services/aria2.ts b/src/main/services/aria2.ts new file mode 100644 index 00000000..e0001b82 --- /dev/null +++ b/src/main/services/aria2.ts @@ -0,0 +1,28 @@ +import path from "node:path"; +import cp from "node:child_process"; +import { app } from "electron"; + +export class Aria2 { + private static process: cp.ChildProcess | null = null; + + public static spawn() { + const binaryPath = app.isPackaged + ? path.join(process.resourcesPath, "aria2", "aria2c") + : path.join(__dirname, "..", "..", "aria2", "aria2c"); + + this.process = cp.spawn( + binaryPath, + [ + "--enable-rpc", + "--rpc-listen-all", + "--file-allocation=none", + "--allow-overwrite=true", + ], + { stdio: "inherit", windowsHide: true } + ); + } + + public static kill() { + this.process?.kill(); + } +} diff --git a/src/main/services/cloud-sync.ts b/src/main/services/cloud-sync.ts index aa0f5ff7..bc2ef0c1 100644 --- a/src/main/services/cloud-sync.ts +++ b/src/main/services/cloud-sync.ts @@ -13,9 +13,28 @@ import { logger } from "./logger"; import { WindowManager } from "./window-manager"; import axios from "axios"; import { Ludusavi } from "./ludusavi"; -import { SubscriptionRequiredError } from "@shared"; +import { formatDate, SubscriptionRequiredError } from "@shared"; +import i18next, { t } from "i18next"; export class CloudSync { + public static getBackupLabel(automatic: boolean) { + const language = i18next.language; + + const date = formatDate(new Date(), language); + + if (automatic) { + return t("automatic_backup_from", { + ns: "game_details", + date, + }); + } + + return t("backup_from", { + ns: "game_details", + date, + }); + } + private static async bundleBackup( shop: GameShop, objectId: string, @@ -25,7 +44,11 @@ export class CloudSync { // Remove existing backup if (fs.existsSync(backupPath)) { - fs.rmSync(backupPath, { recursive: true }); + try { + await fs.promises.rm(backupPath, { recursive: true }); + } catch (error) { + logger.error("Failed to remove backup path", error); + } } await Ludusavi.backupGame(shop, objectId, backupPath, winePrefix); @@ -101,11 +124,10 @@ export class CloudSync { true ); - fs.rm(bundleLocation, (err) => { - if (err) { - logger.error("Failed to remove tar file", err); - throw err; - } - }); + try { + await fs.promises.unlink(bundleLocation); + } catch (error) { + logger.error("Failed to remove tar file", error); + } } } diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index c3a104cb..57b3bac2 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -367,7 +367,7 @@ export class DownloadManager { case Downloader.RealDebrid: { const downloadUrl = await RealDebridClient.getDownloadUrl(download.uri); - if (!downloadUrl) throw new Error(DownloadError.NotCachedInRealDebrid); + if (!downloadUrl) throw new Error(DownloadError.NotCachedOnRealDebrid); return { action: "start", @@ -395,7 +395,7 @@ export class DownloadManager { download.uri ); - if (!downloadUrl) throw new Error(DownloadError.NotCachedInHydra); + if (!downloadUrl) throw new Error(DownloadError.NotCachedOnHydra); return { action: "start", diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 30b502f5..164b7b8d 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -11,3 +11,4 @@ export * from "./cloud-sync"; export * from "./7zip"; export * from "./game-files-manager"; export * from "./common-redist-manager"; +export * from "./aria2"; diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index de0e88da..fd3987df 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -6,9 +6,7 @@ import axios from "axios"; import { exec } from "child_process"; import { ProcessPayload } from "./download/types"; import { gamesSublevel, levelKeys } from "@main/level"; -import i18next, { t } from "i18next"; import { CloudSync } from "./cloud-sync"; -import { formatDate } from "date-fns"; const commands = { findWineDir: `lsof -c wine 2>/dev/null | grep '/drive_c/windows$' | head -n 1 | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`, @@ -229,17 +227,12 @@ function onOpenGame(game: Game) { if (game.remoteId) { updateGamePlaytime(game, 0, new Date()).catch(() => {}); - const { language } = i18next; - if (game.automaticCloudSync) { CloudSync.uploadSaveGame( game.objectId, game.shop, null, - t("automatic_backup_from", { - ns: "game_details", - date: formatDate(new Date(), language), - }) + CloudSync.getBackupLabel(true) ); } } else { @@ -298,8 +291,6 @@ const onCloseGame = (game: Game) => { )!; gamesPlaytime.delete(levelKeys.game(game.shop, game.objectId)); - const { language } = i18next; - if (game.remoteId) { updateGamePlaytime( game, @@ -312,10 +303,7 @@ const onCloseGame = (game: Game) => { game.objectId, game.shop, null, - t("automatic_backup_from", { - ns: "game_details", - date: formatDate(new Date(), language), - }) + CloudSync.getBackupLabel(true) ); } } else { diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 956dc86f..b3d3ff37 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -372,12 +372,12 @@ export class WindowManager { const sortedGames = sortBy(filteredGames, "lastTimePlayed", "DESC"); - return slice(sortedGames, 5); + return slice(sortedGames, 0, 5); }); const recentlyPlayedGames: Array = games.map(({ title, executablePath }) => ({ - label: title.length > 15 ? `${title.slice(0, 15)}…` : title, + label: title.length > 18 ? `${title.slice(0, 18)}…` : title, type: "normal", click: async () => { if (!executablePath) return; @@ -418,7 +418,10 @@ export class WindowManager { }, ]); - tray.setContextMenu(contextMenu); + if (process.platform === "linux") { + tray.setContextMenu(contextMenu); + } + return contextMenu; }; diff --git a/src/renderer/src/assets/icons/torbox.webp b/src/renderer/src/assets/icons/torbox.webp deleted file mode 100644 index 68d68531..00000000 Binary files a/src/renderer/src/assets/icons/torbox.webp and /dev/null differ diff --git a/src/renderer/src/components/sidebar/sidebar-profile.scss b/src/renderer/src/components/sidebar/sidebar-profile.scss index 4c93c050..7e634851 100644 --- a/src/renderer/src/components/sidebar/sidebar-profile.scss +++ b/src/renderer/src/components/sidebar/sidebar-profile.scss @@ -85,5 +85,6 @@ white-space: nowrap; width: 100%; text-align: left; + line-height: 1.15; } } diff --git a/src/renderer/src/hooks/use-date.ts b/src/renderer/src/hooks/use-date.ts index e0b3322a..d00d3ba3 100644 --- a/src/renderer/src/hooks/use-date.ts +++ b/src/renderer/src/hooks/use-date.ts @@ -1,7 +1,6 @@ import { formatDate, getDateLocale } from "@shared"; import { format, formatDistance, subMilliseconds } from "date-fns"; import type { FormatDistanceOptions } from "date-fns"; -import { enUS } from "date-fns/locale"; import { useTranslation } from "react-i18next"; export function useDate() { @@ -41,10 +40,10 @@ export function useDate() { }, formatDateTime: (date: number | Date | string): string => { - const locale = getDateLocale(language); return format( date, - locale == enUS ? "MM/dd/yyyy - HH:mm" : "dd/MM/yyyy HH:mm" + language == "en" ? "MM-dd-yyyy - hh:mm a" : "dd/MM/yyyy HH:mm", + { locale: getDateLocale(language) } ); }, diff --git a/src/renderer/src/hooks/use-feature.ts b/src/renderer/src/hooks/use-feature.ts index d601eae6..d4727105 100644 --- a/src/renderer/src/hooks/use-feature.ts +++ b/src/renderer/src/hooks/use-feature.ts @@ -2,7 +2,7 @@ import { useEffect, useState, useCallback } from "react"; enum Feature { CheckDownloadWritePermission = "CHECK_DOWNLOAD_WRITE_PERMISSION", - Torbox = "TORBOX", + TorBox = "TORBOX", Nimbus = "NIMBUS", NimbusPreview = "NIMBUS_PREVIEW", } diff --git a/src/renderer/src/pages/downloads/download-group.scss b/src/renderer/src/pages/downloads/download-group.scss index 4a67e857..7602307b 100644 --- a/src/renderer/src/pages/downloads/download-group.scss +++ b/src/renderer/src/pages/downloads/download-group.scss @@ -73,8 +73,11 @@ min-height: 140px; max-height: 140px; position: relative; - } + &--hydra { + box-shadow: 0px 0px 16px 0px rgba(12, 241, 202, 0.15); + } + } &__cover { width: 280px; min-width: 280px; @@ -145,4 +148,14 @@ padding: 8px; min-height: unset; } + + &__hydra-gradient { + background: linear-gradient(90deg, #01483c 0%, #0cf1ca 50%, #01483c 100%); + box-shadow: 0px 0px 8px 0px rgba(12, 241, 202, 0.15); + width: 100%; + position: absolute; + bottom: 0; + height: 2px; + z-index: 1; + } } diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index fa0be02a..baf2cfe0 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -1,4 +1,5 @@ import { useNavigate } from "react-router-dom"; +import cn from "classnames"; import type { GameShop, LibraryGame, SeedingStatus } from "@types"; @@ -32,8 +33,6 @@ import { XCircleIcon, } from "@primer/octicons-react"; -import torBoxLogo from "@renderer/assets/icons/torbox.webp"; - export interface DownloadGroupProps { library: LibraryGame[]; title: string; @@ -310,7 +309,13 @@ export function DownloadGroup({
    {library.map((game) => { return ( -
  • +
  • - {game.download?.downloader === Downloader.TorBox ? ( - - TorBox - TorBox - - ) : ( - - {DOWNLOADER_NAME[game.download!.downloader]} - - )} + {DOWNLOADER_NAME[game.download!.downloader]}
    @@ -376,18 +368,7 @@ export function DownloadGroup({ {game.download?.downloader === Downloader.Hydra && ( -
    +
    )}
  • ); diff --git a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx index b1dcc8c9..6a21b0be 100644 --- a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx @@ -96,14 +96,14 @@ export function DownloadSettingsModal({ return Downloader.Hydra; } - if (availableDownloaders.includes(Downloader.TorBox)) { - return Downloader.TorBox; - } - if (availableDownloaders.includes(Downloader.RealDebrid)) { return Downloader.RealDebrid; } + if (availableDownloaders.includes(Downloader.TorBox)) { + return Downloader.TorBox; + } + return availableDownloaders[0]; }, [] diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index ef6ea7da..71d61e66 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -362,7 +362,7 @@ export function GameOptionsModal({
    -

    {t("downloads_secion_title")}

    +

    {t("downloads_section_title")}

    {t("downloads_section_description")}

    diff --git a/src/renderer/src/pages/settings/settings-torbox.tsx b/src/renderer/src/pages/settings/settings-torbox.tsx index bbbb44ee..610dc942 100644 --- a/src/renderer/src/pages/settings/settings-torbox.tsx +++ b/src/renderer/src/pages/settings/settings-torbox.tsx @@ -16,7 +16,7 @@ const TORBOX_URL = torBoxReferralCode : "https://torbox.app"; const TORBOX_API_TOKEN_URL = "https://torbox.app/settings"; -export function SettingsTorbox() { +export function SettingsTorBox() { const userPreferences = useAppSelector( (state) => state.userPreferences.value ); diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index 391742b8..325c2e17 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -3,7 +3,6 @@ import { useTranslation } from "react-i18next"; import { SettingsRealDebrid } from "./settings-real-debrid"; import { SettingsGeneral } from "./settings-general"; import { SettingsBehavior } from "./settings-behavior"; -import torBoxLogo from "@renderer/assets/icons/torbox.webp"; import { SettingsDownloadSources } from "./settings-download-sources"; import { SettingsContextConsumer, @@ -14,7 +13,7 @@ import { useFeature, useUserDetails } from "@renderer/hooks"; import { useMemo } from "react"; import "./settings.scss"; import { SettingsAppearance } from "./aparence/settings-appearance"; -import { SettingsTorbox } from "./settings-torbox"; +import { SettingsTorBox } from "./settings-torbox"; export default function Settings() { const { t } = useTranslation("settings"); @@ -23,7 +22,7 @@ export default function Settings() { const { isFeatureEnabled, Feature } = useFeature(); - const isTorboxEnabled = isFeatureEnabled(Feature.Torbox); + const isTorBoxEnabled = isFeatureEnabled(Feature.TorBox); const categories = useMemo(() => { const categories = [ @@ -34,19 +33,10 @@ export default function Settings() { tabLabel: t("appearance"), contentTitle: t("appearance"), }, - ...(isTorboxEnabled + ...(isTorBoxEnabled ? [ { - tabLabel: ( - <> - TorBox{" "} - Torbox - - ), + tabLabel: "TorBox", contentTitle: "TorBox", }, ] @@ -60,7 +50,7 @@ export default function Settings() { { tabLabel: t("account"), contentTitle: t("account") }, ]; return categories; - }, [userDetails, t, isTorboxEnabled]); + }, [userDetails, t, isTorBoxEnabled]); return ( @@ -84,7 +74,7 @@ export default function Settings() { } if (currentCategoryIndex === 4) { - return ; + return ; } if (currentCategoryIndex === 5) { diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 54827ce3..448b7eec 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -53,11 +53,11 @@ export enum AuthPage { } export enum DownloadError { - NotCachedInRealDebrid = "download_error_not_cached_in_real_debrid", - NotCachedInTorbox = "download_error_not_cached_in_torbox", + NotCachedOnRealDebrid = "download_error_not_cached_on_real_debrid", + NotCachedOnTorBox = "download_error_not_cached_on_torbox", GofileQuotaExceeded = "download_error_gofile_quota_exceeded", RealDebridAccountNotAuthorized = "download_error_real_debrid_account_not_authorized", - NotCachedInHydra = "download_error_not_cached_in_hydra", + NotCachedOnHydra = "download_error_not_cached_on_hydra", } export const FILE_EXTENSIONS_TO_EXTRACT = [".rar", ".zip", ".7z"]; diff --git a/src/shared/index.ts b/src/shared/index.ts index 39b035ff..a7d0a8dc 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -173,7 +173,5 @@ export const formatDate = ( language: string ): string => { if (isNaN(new Date(date).getDate())) return "N/A"; - - const locale = getDateLocale(language); - return format(date, locale == enUS ? "MM/dd/yyyy" : "dd/MM/yyyy"); + return format(date, language == "en" ? "MM-dd-yyyy" : "dd/MM/yyyy"); }; diff --git a/src/types/download.types.ts b/src/types/download.types.ts index 6bc15fe0..004d8f27 100644 --- a/src/types/download.types.ts +++ b/src/types/download.types.ts @@ -22,7 +22,7 @@ export interface DownloadProgress { download: Download; } -/* Torbox */ +/* TorBox */ export interface TorBoxUser { id: number; email: string; diff --git a/yarn.lock b/yarn.lock index f6eeecae..85039dc0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1460,9 +1460,9 @@ optionalDependencies: global-agent "^3.0.0" -"@electron/node-gyp@git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2": +"@electron/node-gyp@https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2": version "10.2.0-electron.1" - resolved "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2" + resolved "https://github.com/electron/node-gyp#06b29aafb7708acef8b3669835c8a7857ebc92d2" dependencies: env-paths "^2.2.0" exponential-backoff "^3.1.1" @@ -4501,10 +4501,10 @@ crc@^3.8.0: dependencies: buffer "^5.1.0" -create-desktop-shortcuts@^1.11.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/create-desktop-shortcuts/-/create-desktop-shortcuts-1.11.0.tgz#8eed89329e9bce70dece46d02a80573fe1f2536d" - integrity sha512-nmVtPVqNyMuAyMpDnd7l++hb2laqCWZXnHQaFhqGT1YEi2Ve3unu6QyuyIpGxAwIscNHcG1Ehnl+lFw6ygB2nQ== +create-desktop-shortcuts@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/create-desktop-shortcuts/-/create-desktop-shortcuts-1.11.1.tgz#59f9dced7931bda551c0717791a909419472c809" + integrity sha512-EiHvxrMXXEp4xDD3Nvu1FKLueL9+aBWFYtuTlstYZLIg9H45SoYciizteNB+hvQAls3cRSpoXCM7c4q+lcJpyw== dependencies: which "2.0.2" @@ -5814,11 +5814,6 @@ get-symbol-description@^1.1.0: es-errors "^1.3.0" get-intrinsic "^1.2.6" -get-them-args@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/get-them-args/-/get-them-args-1.3.2.tgz#74a20ba8a4abece5ae199ad03f2bcc68fdfc9ba5" - integrity sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw== - git-raw-commits@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-4.0.0.tgz#b212fd2bff9726d27c1283a1157e829490593285" @@ -6826,14 +6821,6 @@ keyv@^4.0.0, keyv@^4.5.3: dependencies: json-buffer "3.0.1" -kill-port@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/kill-port/-/kill-port-2.0.1.tgz#e5e18e2706b13d54320938be42cb7d40609b15cf" - integrity sha512-e0SVOV5jFo0mx8r7bS29maVWp17qGqLBZ5ricNSajON6//kmb7qqqNnml4twNE8Dtj97UQD+gNFOaipS/q1zzQ== - dependencies: - get-them-args "1.3.2" - shell-exec "1.0.2" - language-subtag-registry@^0.3.20: version "0.3.23" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz#23529e04d9e3b74679d70142df3fd2eb6ec572e7" @@ -8459,11 +8446,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-exec@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/shell-exec/-/shell-exec-1.0.2.tgz#2e9361b0fde1d73f476c4b6671fa17785f696756" - integrity sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg== - side-channel-list@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad"