refactor: align CI and native build with python torrent backend

This commit is contained in:
Chubby Granny Chaser
2026-03-23 23:38:56 +00:00
parent 49e46bcd3d
commit 4c522c9b81
12 changed files with 5 additions and 1609 deletions
-44
View File
@@ -19,10 +19,6 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
VCPKG_COMMIT: b472291f295551b7127359ea38fdd2ca092f6f1b
VCPKG_BINARY_CACHE: 'C:\Users\runneradmin\AppData\Local\vcpkg\archives'
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -36,46 +32,6 @@ jobs:
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Cache vcpkg binary archives (Windows)
id: cache-vcpkg
if: matrix.os == 'windows-2022'
uses: actions/cache/restore@v4
with:
path: ${{ env.VCPKG_BINARY_CACHE }}
key: ${{ runner.os }}-vcpkg-${{ env.VCPKG_COMMIT }}-x64-windows-static-md
restore-keys: |
${{ runner.os }}-vcpkg-${{ env.VCPKG_COMMIT }}-
- name: Install libtorrent dependencies (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y pkg-config
sudo apt-get install -y libtorrent-rasterbar2.0-dev || sudo apt-get install -y libtorrent-rasterbar-dev
- name: Install libtorrent dependencies (Windows)
if: matrix.os == 'windows-2022'
shell: pwsh
run: |
$vcpkgRoot = Join-Path $env:RUNNER_TEMP "vcpkg"
git clone --depth 1 https://github.com/microsoft/vcpkg $vcpkgRoot
git -C $vcpkgRoot fetch --depth 1 origin $env:VCPKG_COMMIT
git -C $vcpkgRoot checkout $env:VCPKG_COMMIT
& "$vcpkgRoot/bootstrap-vcpkg.bat" -disableMetrics
New-Item -ItemType Directory -Path $env:VCPKG_BINARY_CACHE -Force | Out-Null
Add-Content -Path $env:GITHUB_ENV -Value "VCPKG_DEFAULT_BINARY_CACHE=$env:VCPKG_BINARY_CACHE"
Add-Content -Path $env:GITHUB_ENV -Value "VCPKG_BINARY_SOURCES=clear;files,$env:VCPKG_BINARY_CACHE,readwrite"
& "$vcpkgRoot/vcpkg.exe" install --clean-after-build "libtorrent:x64-windows-static-md" "boost-throw-exception:x64-windows-static-md"
Add-Content -Path $env:GITHUB_ENV -Value "VCPKG_ROOT=$vcpkgRoot"
Add-Content -Path $env:GITHUB_ENV -Value "VCPKGRS_TRIPLET=x64-windows-static-md"
- name: Save vcpkg binary archives (Windows)
if: matrix.os == 'windows-2022' && steps.cache-vcpkg.outputs.cache-hit != 'true' && always()
uses: actions/cache/save@v4
with:
path: ${{ env.VCPKG_BINARY_CACHE }}
key: ${{ steps.cache-vcpkg.outputs.cache-primary-key }}
- name: Install dependencies - name: Install dependencies
run: yarn --frozen-lockfile run: yarn --frozen-lockfile
-44
View File
@@ -17,10 +17,6 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
VCPKG_COMMIT: b472291f295551b7127359ea38fdd2ca092f6f1b
VCPKG_BINARY_CACHE: 'C:\Users\runneradmin\AppData\Local\vcpkg\archives'
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -34,46 +30,6 @@ jobs:
- name: Install Rust - name: Install Rust
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Cache vcpkg binary archives (Windows)
id: cache-vcpkg
if: matrix.os == 'windows-2022'
uses: actions/cache/restore@v4
with:
path: ${{ env.VCPKG_BINARY_CACHE }}
key: ${{ runner.os }}-vcpkg-${{ env.VCPKG_COMMIT }}-x64-windows-static-md
restore-keys: |
${{ runner.os }}-vcpkg-${{ env.VCPKG_COMMIT }}-
- name: Install libtorrent dependencies (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y pkg-config
sudo apt-get install -y libtorrent-rasterbar2.0-dev || sudo apt-get install -y libtorrent-rasterbar-dev
- name: Install libtorrent dependencies (Windows)
if: matrix.os == 'windows-2022'
shell: pwsh
run: |
$vcpkgRoot = Join-Path $env:RUNNER_TEMP "vcpkg"
git clone --depth 1 https://github.com/microsoft/vcpkg $vcpkgRoot
git -C $vcpkgRoot fetch --depth 1 origin $env:VCPKG_COMMIT
git -C $vcpkgRoot checkout $env:VCPKG_COMMIT
& "$vcpkgRoot/bootstrap-vcpkg.bat" -disableMetrics
New-Item -ItemType Directory -Path $env:VCPKG_BINARY_CACHE -Force | Out-Null
Add-Content -Path $env:GITHUB_ENV -Value "VCPKG_DEFAULT_BINARY_CACHE=$env:VCPKG_BINARY_CACHE"
Add-Content -Path $env:GITHUB_ENV -Value "VCPKG_BINARY_SOURCES=clear;files,$env:VCPKG_BINARY_CACHE,readwrite"
& "$vcpkgRoot/vcpkg.exe" install --clean-after-build "libtorrent:x64-windows-static-md" "boost-throw-exception:x64-windows-static-md"
Add-Content -Path $env:GITHUB_ENV -Value "VCPKG_ROOT=$vcpkgRoot"
Add-Content -Path $env:GITHUB_ENV -Value "VCPKGRS_TRIPLET=x64-windows-static-md"
- name: Save vcpkg binary archives (Windows)
if: matrix.os == 'windows-2022' && steps.cache-vcpkg.outputs.cache-hit != 'true' && always()
uses: actions/cache/save@v4
with:
path: ${{ env.VCPKG_BINARY_CACHE }}
key: ${{ steps.cache-vcpkg.outputs.cache-primary-key }}
- name: Install dependencies - name: Install dependencies
run: yarn --frozen-lockfile run: yarn --frozen-lockfile
-1
View File
@@ -34,7 +34,6 @@ Please, refer to our Documentation pages: [docs.hydralauncher.gg](https://docs.h
- Node.js + Yarn - Node.js + Yarn
- Python 3.9+ with `pip install -r requirements.txt` - Python 3.9+ with `pip install -r requirements.txt`
- Rust toolchain (for `hydra-native`) - Rust toolchain (for `hydra-native`)
- `libtorrent-rasterbar` development package (Linux/macOS, usually `libtorrent-rasterbar2.0-dev` or `libtorrent-rasterbar-dev`) or vcpkg `libtorrent` (Windows)
After installing dependencies, `postinstall` now builds the Rust native addon automatically (`hydra-native/hydra-native.node`). After installing dependencies, `postinstall` now builds the Rust native addon automatically (`hydra-native/hydra-native.node`).
+1 -531
View File
@@ -8,21 +8,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.102" version = "1.0.102"
@@ -59,59 +44,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "cc"
version = "1.2.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_lex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "codespan-reporting"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
dependencies = [
"serde",
"termcolor",
"unicode-width",
]
[[package]] [[package]]
name = "color_quant" name = "color_quant"
version = "1.1.0" version = "1.1.0"
@@ -152,79 +90,6 @@ version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1"
[[package]]
name = "cxx"
version = "1.0.194"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747d8437319e3a2f43d93b341c137927ca70c0f5dabeea7a005a73665e247c7e"
dependencies = [
"cc",
"cxx-build",
"cxxbridge-cmd",
"cxxbridge-flags",
"cxxbridge-macro",
"foldhash 0.2.0",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.194"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0f4697d190a142477b16aef7da8a99bfdc41e7e8b1687583c0d23a79c7afc1e"
dependencies = [
"cc",
"codespan-reporting",
"indexmap",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-cmd"
version = "1.0.194"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0956799fa8678d4c50eed028f2de1c0552ae183c76e976cf7ca8c4e36a7c328"
dependencies = [
"clap",
"codespan-reporting",
"indexmap",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.194"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23384a836ab4f0ad98ace7e3955ad2de39de42378ab487dc28d3990392cb283a"
[[package]]
name = "cxxbridge-macro"
version = "1.0.194"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6acc6b5822b9526adfb4fc377b67128fdd60aac757cc4a741a6278603f763cf"
dependencies = [
"indexmap",
"proc-macro2",
"quote",
"syn",
]
[[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]] [[package]]
name = "dtor" name = "dtor"
version = "0.1.1" version = "0.1.1"
@@ -255,12 +120,6 @@ dependencies = [
"simd-adler32", "simd-adler32",
] ]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.9" version = "1.1.9"
@@ -277,21 +136,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.32" version = "0.3.32"
@@ -409,7 +253,7 @@ version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [ dependencies = [
"foldhash 0.1.5", "foldhash",
] ]
[[package]] [[package]]
@@ -428,103 +272,13 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
name = "hydra-native" name = "hydra-native"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"cxx",
"cxx-build",
"image", "image",
"mime_guess", "mime_guess",
"napi", "napi",
"napi-build", "napi-build",
"napi-derive", "napi-derive",
"once_cell",
"pkg-config",
"regex",
"sysinfo", "sysinfo",
"url",
"urlencoding",
"uuid", "uuid",
"vcpkg",
]
[[package]]
name = "icu_collections"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
dependencies = [
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
[[package]]
name = "icu_provider"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
] ]
[[package]] [[package]]
@@ -533,27 +287,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]] [[package]]
name = "image" name = "image"
version = "0.25.10" version = "0.25.10"
@@ -632,21 +365,6 @@ dependencies = [
"windows-link 0.2.1", "windows-link 0.2.1",
] ]
[[package]]
name = "link-cplusplus"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82"
dependencies = [
"cc",
]
[[package]]
name = "litemap"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.29" version = "0.4.29"
@@ -802,24 +520,12 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.17" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "png" name = "png"
version = "0.18.1" version = "0.18.1"
@@ -833,15 +539,6 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "potential_utf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
[[package]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.2.37" version = "0.2.37"
@@ -888,35 +585,6 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "regex"
version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "2.1.1" version = "2.1.1"
@@ -929,12 +597,6 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "scratch"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2"
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.27" version = "1.0.27"
@@ -948,7 +610,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [ dependencies = [
"serde_core", "serde_core",
"serde_derive",
] ]
[[package]] [[package]]
@@ -984,12 +645,6 @@ dependencies = [
"zmij", "zmij",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "simd-adler32" name = "simd-adler32"
version = "0.3.8" version = "0.3.8"
@@ -1002,24 +657,6 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.117" version = "2.0.117"
@@ -1031,17 +668,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "sysinfo" name = "sysinfo"
version = "0.37.2" version = "0.37.2"
@@ -1056,25 +682,6 @@ dependencies = [
"windows", "windows",
] ]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "tinystr"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.50.0" version = "1.50.0"
@@ -1102,42 +709,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.6" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "url"
version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.22.0" version = "1.22.0"
@@ -1149,12 +726,6 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "wasip2" name = "wasip2"
version = "1.0.2+wasi-0.2.9" version = "1.0.2+wasi-0.2.9"
@@ -1274,15 +845,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
[[package]] [[package]]
name = "winapi-x86_64-pc-windows-gnu" name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0" version = "0.4.0"
@@ -1397,15 +959,6 @@ dependencies = [
"windows-link 0.1.3", "windows-link 0.1.3",
] ]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link 0.2.1",
]
[[package]] [[package]]
name = "windows-threading" name = "windows-threading"
version = "0.1.0" version = "0.1.0"
@@ -1503,89 +1056,6 @@ dependencies = [
"wasmparser", "wasmparser",
] ]
[[package]]
name = "writeable"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yoke"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
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 = "zerotrie"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.21" version = "1.0.21"
-9
View File
@@ -7,21 +7,12 @@ edition = "2021"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
anyhow = "1.0.100"
image = { version = "0.25.8", default-features = false, features = ["gif", "jpeg", "png", "webp"] } image = { version = "0.25.8", default-features = false, features = ["gif", "jpeg", "png", "webp"] }
cxx = "1.0.189"
mime_guess = "2.0.5" mime_guess = "2.0.5"
napi = { version = "3.5.2", default-features = false, features = ["napi8", "tokio_rt"] } napi = { version = "3.5.2", default-features = false, features = ["napi8", "tokio_rt"] }
napi-derive = "3.3.2" napi-derive = "3.3.2"
once_cell = "1.21.3"
regex = "1.12.2"
sysinfo = "0.37.2" sysinfo = "0.37.2"
url = "2.5.7"
urlencoding = "2.1.3"
uuid = { version = "1.11.0", features = ["v4"] } uuid = { version = "1.11.0", features = ["v4"] }
[build-dependencies] [build-dependencies]
cxx-build = "1.0.189"
napi-build = "2.3.1" napi-build = "2.3.1"
pkg-config = "0.3.32"
vcpkg = "0.2.15"
+2 -96
View File
@@ -1,100 +1,6 @@
fn main() { fn main() {
napi_build::setup(); napi_build::setup();
let mut build = cxx_build::bridge("src/libtorrent_bridge.rs"); println!("cargo:rerun-if-changed=build.rs");
build println!("cargo:rerun-if-changed=src/lib.rs");
.file("cpp/libtorrent_bridge.cc")
.file("cpp/bridge_state.cc")
.file("cpp/bridge_utils.cc")
.file("cpp/torrent_helpers.cc")
.include(".")
.include("cpp")
.std("c++17");
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os == "linux" || target_os == "macos" {
let library = pkg_config::Config::new()
.probe("libtorrent-rasterbar")
.unwrap_or_else(|error| {
panic!(
"libtorrent-rasterbar development package is required for hydra-native: {error}"
)
});
for include_path in library.include_paths {
build.include(include_path);
}
} else if target_os == "windows" {
build.flag_if_supported("/EHsc");
build.define("_WIN32_WINNT", Some("0x0A00"));
build.define("TORRENT_ABI_VERSION", Some("3"));
let library = vcpkg::Config::new()
.emit_includes(true)
.find_package("libtorrent")
.unwrap_or_else(|error| {
panic!(
"vcpkg libtorrent package is required for hydra-native (set VCPKG_ROOT and install port libtorrent): {error}"
)
});
for include_path in library.include_paths {
build.include(include_path);
}
for link_path in &library.link_paths {
emit_matching_link_lib(link_path, "boost_throw_exception");
emit_matching_link_lib(link_path, "boost_exception");
let manual_link_path = link_path.join("manual-link");
emit_matching_link_lib(&manual_link_path, "boost_throw_exception");
emit_matching_link_lib(&manual_link_path, "boost_exception");
}
println!("cargo:rustc-link-lib=bcrypt");
println!("cargo:rustc-link-lib=mswsock");
println!("cargo:rustc-link-lib=ws2_32");
println!("cargo:rustc-link-lib=iphlpapi");
println!("cargo:rustc-link-lib=dbghelp");
println!("cargo:rustc-link-lib=crypt32");
println!("cargo:rustc-link-lib=user32");
}
build.compile("hydra_libtorrent_bridge");
println!("cargo:rerun-if-changed=src/libtorrent_bridge.rs");
println!("cargo:rerun-if-changed=cpp/bridge_state.h");
println!("cargo:rerun-if-changed=cpp/bridge_state.cc");
println!("cargo:rerun-if-changed=cpp/bridge_utils.h");
println!("cargo:rerun-if-changed=cpp/bridge_utils.cc");
println!("cargo:rerun-if-changed=cpp/torrent_helpers.h");
println!("cargo:rerun-if-changed=cpp/torrent_helpers.cc");
println!("cargo:rerun-if-changed=cpp/libtorrent_bridge.h");
println!("cargo:rerun-if-changed=cpp/libtorrent_bridge.cc");
}
fn emit_matching_link_lib(link_path: &std::path::Path, prefix: &str) {
let entries = match std::fs::read_dir(link_path) {
Ok(entries) => entries,
Err(_) => return,
};
for entry in entries.flatten() {
let path = entry.path();
let extension = path.extension().and_then(|value| value.to_str());
if extension != Some("lib") {
continue;
}
let stem = match path.file_stem().and_then(|value| value.to_str()) {
Some(stem) => stem,
None => continue,
};
if stem.starts_with(prefix) {
println!("cargo:rustc-link-lib={stem}");
return;
}
}
} }
-5
View File
@@ -3,11 +3,6 @@ use std::io::BufReader;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{cmp::Ordering, collections::HashMap}; use std::{cmp::Ordering, collections::HashMap};
mod libtorrent_bridge;
#[path = "torrent_libtorrent.rs"]
mod torrent;
use image::codecs::gif::GifDecoder; use image::codecs::gif::GifDecoder;
use image::codecs::png::PngDecoder; use image::codecs::png::PngDecoder;
use image::codecs::webp::WebPDecoder; use image::codecs::webp::WebPDecoder;
@@ -1,62 +0,0 @@
#[cxx::bridge(namespace = "hydra::libtorrent_bridge")]
pub mod ffi {
pub struct BridgeTorrentFileEntry {
pub index: u32,
pub path: String,
pub length: i64,
}
pub struct BridgeTorrentFilesResult {
pub ok: bool,
pub error: String,
pub name: String,
pub total_size: i64,
pub files: Vec<BridgeTorrentFileEntry>,
}
pub struct BridgeTorrentStatusResult {
pub present: bool,
pub progress: f64,
pub num_peers: u32,
pub num_seeds: u32,
pub download_speed: i64,
pub upload_speed: i64,
pub bytes_downloaded: i64,
pub file_size: i64,
pub folder_name: String,
pub status: u32,
}
unsafe extern "C++" {
include!("cpp/libtorrent_bridge.h");
pub fn init_session(listen_port_start: u16, listen_port_end: u16) -> String;
pub fn set_download_limit(max_download_speed_bytes_per_second: i64) -> String;
pub fn start_torrent(
game_id: &str,
magnet: &str,
save_path: &str,
trackers: &Vec<String>,
file_indices: &Vec<u32>,
selective: bool,
upload_mode: bool,
timeout_ms: u32,
) -> String;
pub fn pause_torrent(game_id: &str) -> String;
pub fn cancel_torrent(game_id: &str) -> String;
pub fn get_torrent_status(game_id: &str) -> BridgeTorrentStatusResult;
pub fn get_torrent_files(
magnet: &str,
save_path: &str,
trackers: &Vec<String>,
timeout_ms: u32,
max_files: u32,
) -> BridgeTorrentFilesResult;
}
}
@@ -1,766 +0,0 @@
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::time::{Duration, Instant};
use napi::bindgen_prelude::Error;
use napi_derive::napi;
use once_cell::sync::Lazy;
use regex::Regex;
use url::Url;
use crate::libtorrent_bridge::ffi as bridge;
const TORRENT_FILES_CACHE_TTL_SECONDS: u64 = 300;
const TORRENT_FILES_CACHE_MAX_ITEMS: usize = 128;
const TORRENT_MAX_FILES: usize = 100_000;
const HYDRA_TORRENT_STATUS_SEEDING: u32 = 5;
static MAGNET_HASH_HEX_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[a-fA-F0-9]{40}$").expect("valid regex"));
static MAGNET_HASH_BASE32_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[a-zA-Z2-7]{32}$").expect("valid regex"));
const DEFAULT_TRACKERS: &[&str] = &[
"udp://tracker.opentrackr.org:1337/announce",
"http://tracker.opentrackr.org:1337/announce",
"udp://open.tracker.cl:1337/announce",
"udp://open.demonii.com:1337/announce",
"udp://open.stealth.si:80/announce",
"udp://tracker.torrent.eu.org:451/announce",
"udp://exodus.desync.com:6969/announce",
"udp://tracker.theoks.net:6969/announce",
"udp://tracker-udp.gbitt.info:80/announce",
"udp://explodie.org:6969/announce",
"https://tracker.tamersunion.org:443/announce",
"udp://tracker2.dler.org:80/announce",
"udp://tracker1.myporn.club:9337/announce",
"udp://tracker.tiny-vps.com:6969/announce",
"udp://tracker.dler.org:6969/announce",
"udp://tracker.bittor.pw:1337/announce",
"udp://tracker.0x7c0.com:6969/announce",
"udp://retracker01-msk-virt.corbina.net:80/announce",
"udp://opentracker.io:6969/announce",
"udp://open.free-tracker.ga:6969/announce",
"udp://new-line.net:6969/announce",
"udp://moonburrow.club:6969/announce",
"udp://leet-tracker.moe:1337/announce",
"udp://bt2.archive.org:6969/announce",
"udp://bt1.archive.org:6969/announce",
"http://tracker2.dler.org:80/announce",
"http://tracker1.bt.moack.co.kr:80/announce",
"http://tracker.dler.org:6969/announce",
"http://tr.kxmp.cf:80/announce",
"udp://u.peer-exchange.download:6969/announce",
"udp://ttk2.nbaonlineservice.com:6969/announce",
"udp://tracker.tryhackx.org:6969/announce",
"udp://tracker.srv00.com:6969/announce",
"udp://tracker.skynetcloud.site:6969/announce",
"udp://tracker.jamesthebard.net:6969/announce",
"udp://tracker.fnix.net:6969/announce",
"udp://tracker.filemail.com:6969/announce",
"udp://tracker.farted.net:6969/announce",
"udp://tracker.edkj.club:6969/announce",
"udp://tracker.dump.cl:6969/announce",
"udp://tracker.deadorbit.nl:6969/announce",
"udp://tracker.darkness.services:6969/announce",
"udp://tracker.ccp.ovh:6969/announce",
"udp://tamas3.ynh.fr:6969/announce",
"udp://ryjer.com:6969/announce",
"udp://run.publictracker.xyz:6969/announce",
"udp://public.tracker.vraphim.com:6969/announce",
"udp://p4p.arenabg.com:1337/announce",
"udp://p2p.publictracker.xyz:6969/announce",
"udp://open.u-p.pw:6969/announce",
"udp://open.publictracker.xyz:6969/announce",
"udp://open.dstud.io:6969/announce",
"udp://open.demonoid.ch:6969/announce",
"udp://odd-hd.fr:6969/announce",
"udp://martin-gebhardt.eu:25/announce",
"udp://jutone.com:6969/announce",
"udp://isk.richardsw.club:6969/announce",
"udp://evan.im:6969/announce",
"udp://epider.me:6969/announce",
"udp://d40969.acod.regrucolo.ru:6969/announce",
"udp://bt.rer.lol:6969/announce",
"udp://amigacity.xyz:6969/announce",
"udp://1c.premierzal.ru:6969/announce",
"https://trackers.run:443/announce",
"https://tracker.yemekyedim.com:443/announce",
"https://tracker.renfei.net:443/announce",
"https://tracker.pmman.tech:443/announce",
"https://tracker.lilithraws.org:443/announce",
"https://tracker.imgoingto.icu:443/announce",
"https://tracker.cloudit.top:443/announce",
"https://tracker-zhuqiy.dgj055.icu:443/announce",
"http://tracker.renfei.net:8080/announce",
"http://tracker.mywaifu.best:6969/announce",
"http://tracker.ipv6tracker.org:80/announce",
"http://tracker.files.fm:6969/announce",
"http://tracker.edkj.club:6969/announce",
"http://tracker.bt4g.com:2095/announce",
"http://tracker-zhuqiy.dgj055.icu:80/announce",
"http://t1.aag.moe:17715/announce",
"http://t.overflow.biz:6969/announce",
"http://bittorrent-tracker.e-n-c-r-y-p-t.net:1337/announce",
"udp://torrents.artixlinux.org:6969/announce",
"udp://mail.artixlinux.org:6969/announce",
"udp://ipv4.rer.lol:2710/announce",
"udp://concen.org:6969/announce",
"udp://bt.rer.lol:2710/announce",
"udp://aegir.sexy:6969/announce",
"https://www.peckservers.com:9443/announce",
"https://tracker.ipfsscan.io:443/announce",
"https://tracker.gcrenwp.top:443/announce",
"http://www.peckservers.com:9000/announce",
"http://tracker1.itzmx.com:8080/announce",
"http://ch3oh.ru:6969/announce",
"http://bvarf.tracker.sh:2086/announce",
];
#[napi(object)]
#[derive(Clone)]
pub struct TorrentFileEntry {
pub index: u32,
pub path: String,
pub length: i64,
}
#[napi(object)]
#[derive(Clone)]
pub struct TorrentFilesPayload {
pub info_hash: String,
pub name: String,
pub total_size: i64,
pub files: Vec<TorrentFileEntry>,
}
#[napi(object)]
#[derive(Clone)]
pub struct TorrentStatusPayload {
pub progress: f64,
pub num_peers: u32,
pub num_seeds: u32,
pub estimated_seeds: u32,
pub download_speed: i64,
pub upload_speed: i64,
pub bytes_downloaded: i64,
pub file_size: i64,
pub folder_name: String,
pub status: u32,
}
#[napi(object)]
#[derive(Clone)]
pub struct TorrentSeedStatusPayload {
pub game_id: String,
pub progress: f64,
pub num_peers: u32,
pub num_seeds: u32,
pub estimated_seeds: u32,
pub download_speed: i64,
pub upload_speed: i64,
pub bytes_downloaded: i64,
pub file_size: i64,
pub folder_name: String,
pub status: u32,
}
#[napi(object)]
pub struct StartTorrentPayload {
pub game_id: String,
pub url: String,
pub save_path: String,
pub folder_name: Option<String>,
pub file_indices: Option<Vec<u32>>,
pub timeout_ms: Option<u32>,
}
#[napi(object)]
pub struct ResumeSeedingPayload {
pub game_id: String,
pub url: String,
pub save_path: String,
pub folder_name: Option<String>,
}
struct CachedTorrentFiles {
cached_at: Instant,
payload: TorrentFilesPayload,
}
fn sanitize_folder_component(value: &str) -> Option<String> {
let trimmed = value.trim();
if trimmed.is_empty() {
return None;
}
let mut sanitized = String::with_capacity(trimmed.len());
for ch in trimmed.chars() {
let mapped = match ch {
'<' | '>' | ':' | '"' | '/' | '\\' | '|' | '?' | '*' => '_',
_ if ch.is_control() => '_',
_ => ch,
};
sanitized.push(mapped);
}
let compact = sanitized.trim_matches(['.', ' ']);
if compact.is_empty() {
return None;
}
Some(compact.to_string())
}
fn build_reserved_output_folder(
root_save_path: &str,
torrent_name: Option<&str>,
file_count: usize,
info_hash: &str,
) -> String {
if file_count <= 1 {
return root_save_path.to_string();
}
let fallback_hash_len = info_hash.len().min(12);
let fallback_name = format!("torrent-{}", &info_hash[..fallback_hash_len]);
let folder_name = torrent_name
.and_then(sanitize_folder_component)
.unwrap_or(fallback_name);
PathBuf::from(root_save_path)
.join(folder_name)
.to_string_lossy()
.to_string()
}
fn has_single_top_level_folder(files_payload: &TorrentFilesPayload) -> bool {
if files_payload.files.is_empty() {
return false;
}
let mut root_segment: Option<String> = None;
for file in &files_payload.files {
let normalized_path = file.path.replace('\\', "/");
let parts = normalized_path
.split('/')
.filter(|segment| !segment.trim().is_empty())
.collect::<Vec<_>>();
if parts.len() < 2 {
return false;
}
let first_segment = parts[0].to_ascii_lowercase();
if let Some(existing_segment) = &root_segment {
if existing_segment != &first_segment {
return false;
}
} else {
root_segment = Some(first_segment);
}
}
root_segment.is_some()
}
fn resolve_existing_folder_candidate(
root_save_path: &str,
folder_name: Option<&str>,
) -> Option<String> {
let sanitized_folder_name = folder_name
.and_then(sanitize_folder_component)
.filter(|value| !value.is_empty())?;
let existing_path = PathBuf::from(root_save_path).join(sanitized_folder_name);
if existing_path.is_dir() {
return Some(existing_path.to_string_lossy().to_string());
}
None
}
fn resolve_torrent_output_folder(
root_save_path: &str,
magnet: &str,
info_hash: &str,
timeout_ms: u32,
folder_name: Option<&str>,
) -> String {
if let Some(existing_path) = resolve_existing_folder_candidate(root_save_path, folder_name) {
return existing_path;
}
match fetch_torrent_files_internal(magnet.to_string(), info_hash.to_string(), timeout_ms) {
Ok(files_payload) => {
if has_single_top_level_folder(&files_payload) {
root_save_path.to_string()
} else {
build_reserved_output_folder(
root_save_path,
Some(&files_payload.name),
files_payload.files.len(),
info_hash,
)
}
}
Err(_) => root_save_path.to_string(),
}
}
fn ensure_reserved_output_folder_exists(
root_save_path: &str,
resolved_save_path: &str,
) -> napi::Result<()> {
if Path::new(resolved_save_path) == Path::new(root_save_path) {
return Ok(());
}
std::fs::create_dir_all(resolved_save_path).map_err(|err| Error::from_reason(err.to_string()))
}
struct TorrentManager {
downloading_game_id: Option<String>,
seeding_game_ids: HashSet<String>,
current_download_limit: Option<u32>,
torrent_files_cache: HashMap<String, CachedTorrentFiles>,
}
impl TorrentManager {
fn new() -> Self {
Self {
downloading_game_id: None,
seeding_game_ids: HashSet::new(),
current_download_limit: None,
torrent_files_cache: HashMap::new(),
}
}
fn normalize_timeout_ms(timeout_ms: Option<u32>, default_ms: u32) -> u32 {
let value = timeout_ms.unwrap_or(default_ms);
value.clamp(5_000, 120_000)
}
fn cache_get(&mut self, info_hash: &str) -> Option<TorrentFilesPayload> {
self.prune_expired_cache();
self.torrent_files_cache
.get(info_hash)
.map(|item| item.payload.clone())
}
fn cache_set(&mut self, info_hash: String, payload: TorrentFilesPayload) {
self.prune_expired_cache();
if self.torrent_files_cache.len() >= TORRENT_FILES_CACHE_MAX_ITEMS {
let oldest_key = self
.torrent_files_cache
.iter()
.min_by_key(|(_, value)| value.cached_at)
.map(|(key, _)| key.clone());
if let Some(key) = oldest_key {
self.torrent_files_cache.remove(&key);
}
}
self.torrent_files_cache.insert(
info_hash,
CachedTorrentFiles {
cached_at: Instant::now(),
payload,
},
);
}
fn prune_expired_cache(&mut self) {
let ttl = Duration::from_secs(TORRENT_FILES_CACHE_TTL_SECONDS);
self.torrent_files_cache
.retain(|_, value| value.cached_at.elapsed() <= ttl);
}
}
static TORRENT_MANAGER: Lazy<Mutex<TorrentManager>> =
Lazy::new(|| Mutex::new(TorrentManager::new()));
fn map_bridge_error(error: String) -> napi::Result<()> {
if error.is_empty() {
Ok(())
} else {
Err(Error::from_reason(error))
}
}
fn ensure_session() -> napi::Result<()> {
let current_limit = {
TORRENT_MANAGER
.lock()
.map_err(|_| Error::from_reason("internal_error"))?
.current_download_limit
};
map_bridge_error(bridge::init_session(5881, 5892))?;
if let Some(limit) = current_limit {
map_bridge_error(bridge::set_download_limit(limit as i64))?;
}
Ok(())
}
fn validate_magnet_uri(raw_magnet: &str) -> napi::Result<(String, String)> {
let magnet = raw_magnet.trim();
if !magnet.starts_with("magnet:") || magnet.len() > 8192 {
return Err(Error::from_reason("invalid_magnet"));
}
let parsed = Url::parse(magnet).map_err(|_| Error::from_reason("invalid_magnet"))?;
if parsed.scheme() != "magnet" {
return Err(Error::from_reason("invalid_magnet"));
}
for (key, value) in parsed.query_pairs() {
if key != "xt" {
continue;
}
if !value.starts_with("urn:btih:") {
continue;
}
let hash = value[9..].trim().to_lowercase();
if MAGNET_HASH_HEX_RE.is_match(&hash) || MAGNET_HASH_BASE32_RE.is_match(&hash) {
return Ok((magnet.to_string(), hash));
}
}
Err(Error::from_reason("invalid_magnet"))
}
fn parse_magnet_trackers(magnet: &str) -> Vec<String> {
let mut trackers = Vec::new();
if let Ok(parsed) = Url::parse(magnet) {
for (key, value) in parsed.query_pairs() {
if key == "tr" {
let tracker = value.to_string();
if !trackers.contains(&tracker) {
trackers.push(tracker);
}
}
}
}
for value in DEFAULT_TRACKERS {
let tracker = value.to_string();
if !trackers.contains(&tracker) {
trackers.push(tracker);
}
}
trackers
}
fn fetch_torrent_files_internal(
magnet: String,
info_hash: String,
timeout_ms: u32,
) -> napi::Result<TorrentFilesPayload> {
{
let mut manager = TORRENT_MANAGER
.lock()
.map_err(|_| Error::from_reason("internal_error"))?;
if let Some(cached) = manager.cache_get(&info_hash) {
return Ok(cached);
}
}
ensure_session()?;
let trackers = parse_magnet_trackers(&magnet);
let temp_save_path = std::env::temp_dir().to_string_lossy().to_string();
let bridge_result = bridge::get_torrent_files(
&magnet,
&temp_save_path,
&trackers,
timeout_ms,
TORRENT_MAX_FILES as u32,
);
if !bridge_result.ok {
return Err(Error::from_reason(bridge_result.error));
}
let payload = TorrentFilesPayload {
info_hash: info_hash.clone(),
name: bridge_result.name,
total_size: bridge_result.total_size,
files: bridge_result
.files
.into_iter()
.map(|file| TorrentFileEntry {
index: file.index,
path: file.path,
length: file.length,
})
.collect(),
};
let mut manager = TORRENT_MANAGER
.lock()
.map_err(|_| Error::from_reason("internal_error"))?;
manager.cache_set(info_hash, payload.clone());
Ok(payload)
}
#[napi]
pub fn torrent_get_status(game_id: String) -> napi::Result<Option<TorrentStatusPayload>> {
if game_id.trim().is_empty() {
return Ok(None);
}
let status = bridge::get_torrent_status(&game_id);
if !status.present {
return Ok(None);
}
Ok(Some(TorrentStatusPayload {
progress: status.progress,
num_peers: status.num_peers,
num_seeds: status.num_seeds,
estimated_seeds: 0,
download_speed: status.download_speed,
upload_speed: status.upload_speed,
bytes_downloaded: status.bytes_downloaded,
file_size: status.file_size,
folder_name: status.folder_name,
status: status.status,
}))
}
#[napi]
pub fn torrent_get_seed_status() -> napi::Result<Vec<TorrentSeedStatusPayload>> {
let game_ids = {
let manager = TORRENT_MANAGER
.lock()
.map_err(|_| Error::from_reason("internal_error"))?;
manager
.seeding_game_ids
.iter()
.cloned()
.collect::<Vec<String>>()
};
let mut payload = Vec::new();
for game_id in game_ids {
let status = bridge::get_torrent_status(&game_id);
if !status.present {
continue;
}
if status.status != HYDRA_TORRENT_STATUS_SEEDING {
continue;
}
payload.push(TorrentSeedStatusPayload {
game_id,
progress: status.progress,
num_peers: status.num_peers,
num_seeds: status.num_seeds,
estimated_seeds: 0,
download_speed: status.download_speed,
upload_speed: status.upload_speed,
bytes_downloaded: status.bytes_downloaded,
file_size: status.file_size,
folder_name: status.folder_name,
status: status.status,
});
}
Ok(payload)
}
#[napi]
pub async fn torrent_get_files(
magnet: String,
timeout_ms: Option<u32>,
) -> napi::Result<TorrentFilesPayload> {
let (magnet, info_hash) = validate_magnet_uri(&magnet)?;
let timeout_ms = TorrentManager::normalize_timeout_ms(timeout_ms, 30_000);
fetch_torrent_files_internal(magnet, info_hash, timeout_ms)
}
#[napi]
pub async fn torrent_start(payload: StartTorrentPayload) -> napi::Result<()> {
if payload.save_path.trim().is_empty() {
return Err(Error::from_reason("invalid_save_path"));
}
let (magnet, info_hash) = validate_magnet_uri(&payload.url)?;
let selective = payload.file_indices.is_some();
let output_resolution_timeout_ms =
TorrentManager::normalize_timeout_ms(payload.timeout_ms, 10_000);
let timeout_ms = TorrentManager::normalize_timeout_ms(
payload.timeout_ms,
if selective { 60_000 } else { 30_000 },
);
let resolved_save_path = resolve_torrent_output_folder(
&payload.save_path,
&magnet,
&info_hash,
output_resolution_timeout_ms,
payload.folder_name.as_deref(),
);
eprintln!(
"[hydra-native][torrent_start] game_id={} save_path={} resolved_save_path={} folder_name_hint={}",
payload.game_id,
payload.save_path,
resolved_save_path,
payload.folder_name.as_deref().unwrap_or("")
);
ensure_reserved_output_folder_exists(&payload.save_path, &resolved_save_path)?;
ensure_session()?;
let trackers = parse_magnet_trackers(&magnet);
let file_indices = payload.file_indices.unwrap_or_default();
map_bridge_error(bridge::start_torrent(
&payload.game_id,
&magnet,
&resolved_save_path,
&trackers,
&file_indices,
selective,
false,
timeout_ms,
))?;
let mut manager = TORRENT_MANAGER
.lock()
.map_err(|_| Error::from_reason("internal_error"))?;
manager.downloading_game_id = Some(payload.game_id.clone());
manager.seeding_game_ids.remove(&payload.game_id);
Ok(())
}
#[napi]
pub fn torrent_pause(game_id: String) -> napi::Result<()> {
map_bridge_error(bridge::pause_torrent(&game_id))?;
let mut manager = TORRENT_MANAGER
.lock()
.map_err(|_| Error::from_reason("internal_error"))?;
if manager.downloading_game_id.as_deref() == Some(game_id.as_str()) {
manager.downloading_game_id = None;
}
Ok(())
}
#[napi]
pub fn torrent_cancel(game_id: String) -> napi::Result<()> {
map_bridge_error(bridge::cancel_torrent(&game_id))?;
let mut manager = TORRENT_MANAGER
.lock()
.map_err(|_| Error::from_reason("internal_error"))?;
manager.seeding_game_ids.remove(&game_id);
if manager.downloading_game_id.as_deref() == Some(game_id.as_str()) {
manager.downloading_game_id = None;
}
Ok(())
}
#[napi]
pub fn torrent_resume_seeding(payload: ResumeSeedingPayload) -> napi::Result<()> {
if payload.save_path.trim().is_empty() {
return Err(Error::from_reason("invalid_save_path"));
}
let (magnet, info_hash) = validate_magnet_uri(&payload.url)?;
ensure_session()?;
let resolved_save_path = resolve_torrent_output_folder(
&payload.save_path,
&magnet,
&info_hash,
15_000,
payload.folder_name.as_deref(),
);
eprintln!(
"[hydra-native][torrent_resume_seeding] game_id={} save_path={} resolved_save_path={} folder_name_hint={}",
payload.game_id,
payload.save_path,
resolved_save_path,
payload.folder_name.as_deref().unwrap_or("")
);
ensure_reserved_output_folder_exists(&payload.save_path, &resolved_save_path)?;
let trackers = parse_magnet_trackers(&magnet);
let no_file_indices = Vec::<u32>::new();
map_bridge_error(bridge::start_torrent(
&payload.game_id,
&magnet,
&resolved_save_path,
&trackers,
&no_file_indices,
false,
true,
30_000,
))?;
let mut manager = TORRENT_MANAGER
.lock()
.map_err(|_| Error::from_reason("internal_error"))?;
manager.seeding_game_ids.insert(payload.game_id);
Ok(())
}
#[napi]
pub fn torrent_pause_seeding(game_id: String) -> napi::Result<()> {
torrent_cancel(game_id)
}
#[napi]
pub fn torrent_set_download_limit(
max_download_speed_bytes_per_second: Option<u32>,
) -> napi::Result<()> {
let limit = max_download_speed_bytes_per_second.filter(|value| *value > 0);
{
let mut manager = TORRENT_MANAGER
.lock()
.map_err(|_| Error::from_reason("internal_error"))?;
manager.current_download_limit = limit;
}
ensure_session()?;
map_bridge_error(bridge::set_download_limit(
limit.map(|value| value as i64).unwrap_or(0),
))
}
#[napi]
pub fn torrent_backend() -> String {
"libtorrent".to_string()
}
-2
View File
@@ -1,4 +1,2 @@
libtorrent libtorrent
cx_Freeze == 7.2.3 cx_Freeze == 7.2.3
cx_Logging; sys_platform == 'win32'
pywin32; sys_platform == 'win32'
+1 -48
View File
@@ -72,52 +72,6 @@ const copySidecarLibrariesOnWindows = async (sourceDirectory) => {
} }
}; };
const listUnixDependencies = async (binaryPath) => {
if (process.platform === "linux") {
const { stdout } = await execFile("ldd", [binaryPath], {
cwd: projectRoot,
maxBuffer: 1024 * 1024 * 10,
});
return stdout
.split("\n")
.map((line) => line.trim())
.map((line) => {
const match = line.match(/=>\s+(\/[^\s]+)\s+\(/);
return match ? match[1] : null;
})
.filter(Boolean);
}
return [];
};
const shouldBundleUnixDependency = (dependencyPath) => {
const basename = path.basename(dependencyPath);
return (
basename.startsWith("libtorrent-rasterbar") ||
basename.startsWith("libboost_")
);
};
const copySidecarLibrariesOnUnix = async () => {
if (process.platform !== "linux") return;
const dependencies = await listUnixDependencies(outputNodePath);
for (const dependencyPath of dependencies) {
if (!shouldBundleUnixDependency(dependencyPath)) continue;
if (!fs.existsSync(dependencyPath)) continue;
const targetPath = path.join(outputDir, path.basename(dependencyPath));
if (!fs.existsSync(targetPath)) {
fs.copyFileSync(dependencyPath, targetPath);
}
}
};
const build = async () => { const build = async () => {
const sourceLibraryName = sourceLibraryNameByPlatform[process.platform]; const sourceLibraryName = sourceLibraryNameByPlatform[process.platform];
@@ -127,7 +81,7 @@ const build = async () => {
); );
} }
console.log("Building hydra-native Rust addon (libtorrent)..."); console.log("Building hydra-native Rust addon...");
const cargoArgs = [ const cargoArgs = [
"build", "build",
@@ -154,7 +108,6 @@ const build = async () => {
fs.copyFileSync(sourceLibraryPath, outputNodePath); fs.copyFileSync(sourceLibraryPath, outputNodePath);
await copySidecarLibrariesOnWindows(path.dirname(sourceLibraryPath)); await copySidecarLibrariesOnWindows(path.dirname(sourceLibraryPath));
await copySidecarLibrariesOnUnix();
await ensureDepsResolvableOnLinux(); await ensureDepsResolvableOnLinux();
console.log(`Hydra native addon ready at ${outputNodePath}`); console.log(`Hydra native addon ready at ${outputNodePath}`);
+1 -1
View File
@@ -280,7 +280,7 @@ app.on("before-quit", async (e) => {
if (!canAppBeClosed) { if (!canAppBeClosed) {
e.preventDefault(); e.preventDefault();
PowerSaveBlockerManager.reset(); PowerSaveBlockerManager.reset();
/* Disconnects libtorrent */ /* Disconnects Python RPC */
PythonRPC.kill(); PythonRPC.kill();
await clearGamesPlaytime(); await clearGamesPlaytime();
canAppBeClosed = true; canAppBeClosed = true;