diff --git a/android/app/libs/libmtorrentserver.aar b/android/app/libs/libmtorrentserver.aar
index 18ec1766..8c9eeadd 100644
Binary files a/android/app/libs/libmtorrentserver.aar and b/android/app/libs/libmtorrentserver.aar differ
diff --git a/go/go.mod b/go/go.mod
index e24894fb..3863aa06 100644
--- a/go/go.mod
+++ b/go/go.mod
@@ -3,27 +3,27 @@ module server
go 1.25.0
require (
- github.com/anacrolix/torrent v1.58.1
+ github.com/anacrolix/torrent v1.61.0
github.com/rs/cors v1.11.1
)
require (
github.com/RoaringBitmap/roaring v1.9.4 // indirect
- github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
- github.com/anacrolix/chansync v0.6.0 // indirect
- github.com/anacrolix/dht/v2 v2.22.1 // indirect
+ github.com/anacrolix/btree v0.0.0-20251201064447-d86c3fa41bd8 // indirect
+ github.com/anacrolix/chansync v0.7.0 // indirect
+ github.com/anacrolix/dht/v2 v2.23.0 // indirect
github.com/anacrolix/envpprof v1.4.0 // indirect
- github.com/anacrolix/generics v0.0.3 // indirect
+ github.com/anacrolix/generics v0.1.1-0.20251125230353-15d98d46693b // indirect
github.com/anacrolix/go-libutp v1.3.2 // indirect
- github.com/anacrolix/log v0.16.0 // indirect
+ github.com/anacrolix/log v0.17.1-0.20251118025802-918f1157b7bb // indirect
github.com/anacrolix/missinggo v1.3.0 // indirect
github.com/anacrolix/missinggo/perf v1.0.0 // indirect
- github.com/anacrolix/missinggo/v2 v2.8.0 // indirect
+ github.com/anacrolix/missinggo/v2 v2.10.0 // indirect
github.com/anacrolix/mmsg v1.1.1 // indirect
github.com/anacrolix/multiless v0.4.0 // indirect
github.com/anacrolix/stm v0.5.0 // indirect
- github.com/anacrolix/sync v0.5.4 // indirect
+ github.com/anacrolix/sync v0.5.5-0.20251119100342-d78dd1f686f1 // indirect
github.com/anacrolix/upnp v0.1.4 // indirect
github.com/anacrolix/utp v0.2.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
@@ -39,6 +39,7 @@ require (
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/btree v1.1.3 // indirect
+ github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
@@ -71,20 +72,21 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
- github.com/tidwall/btree v1.8.0 // indirect
+ github.com/tidwall/btree v1.8.1 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
go.etcd.io/bbolt v1.4.2 // indirect
- go.opentelemetry.io/auto/sdk v1.1.0 // indirect
- go.opentelemetry.io/otel v1.37.0 // indirect
- go.opentelemetry.io/otel/metric v1.37.0 // indirect
- go.opentelemetry.io/otel/trace v1.37.0 // indirect
+ go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/otel v1.38.0 // indirect
+ go.opentelemetry.io/otel/metric v1.38.0 // indirect
+ go.opentelemetry.io/otel/trace v1.38.0 // indirect
golang.org/x/crypto v0.50.0 // indirect
- golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
+ golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
golang.org/x/mobile v0.0.0-20260410095206-2cfb76559b7b // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
- golang.org/x/time v0.12.0 // indirect
+ golang.org/x/text v0.36.0 // indirect
+ golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.44.0 // indirect
lukechampine.com/blake3 v1.4.1 // indirect
modernc.org/libc v1.66.3 // indirect
diff --git a/go/go.sum b/go/go.sum
index b75cbc95..4794c4f1 100644
--- a/go/go.sum
+++ b/go/go.sum
@@ -14,8 +14,6 @@ github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv
github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
-github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0=
-github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k=
github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk=
github.com/alecthomas/assert/v2 v2.0.0-alpha3/go.mod h1:+zD0lmDXTeQj7TgDgCt0ePWxb0hMC1G+PGTsTCv1B9o=
github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8=
@@ -26,28 +24,31 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/anacrolix/chansync v0.6.0 h1:/aQVvZ1yLRhmqEYrr9dC92JwzNBQ/SNnFi4uk+fTkQY=
-github.com/anacrolix/chansync v0.6.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
-github.com/anacrolix/dht/v2 v2.22.1 h1:mgsljPXyA/EWA7uUDSNjx7wf6gsfhppjVIp9auVeR6w=
-github.com/anacrolix/dht/v2 v2.22.1/go.mod h1:seXRz6HLw8zEnxlysf9ye2eQbrKUmch6PyOHpe/Nb/U=
+github.com/anacrolix/btree v0.0.0-20251201064447-d86c3fa41bd8 h1:c02PsmoaChabVqAFm7pqPI1UIkDdDAjUaWa6ZmfxybQ=
+github.com/anacrolix/btree v0.0.0-20251201064447-d86c3fa41bd8/go.mod h1:7stWJ39LeusmMI8mjJuhFNRqep//vx0AsaySRoK9or0=
+github.com/anacrolix/chansync v0.7.0 h1:wgwxbsJRmOqNjil4INpxHrDp4rlqQhECxR8/WBP4Et0=
+github.com/anacrolix/chansync v0.7.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
+github.com/anacrolix/dht/v2 v2.23.0 h1:EuD17ykTTEkAMPLjBsS5QjGOwuBgLTdQhds6zPAjeVY=
+github.com/anacrolix/dht/v2 v2.23.0/go.mod h1:seXRz6HLw8zEnxlysf9ye2eQbrKUmch6PyOHpe/Nb/U=
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4=
github.com/anacrolix/envpprof v1.4.0 h1:QHeIcrgHcRChhnxR8l6rlaLlRQx9zd7Q2NII6Zbt83w=
github.com/anacrolix/envpprof v1.4.0/go.mod h1:7QIG4CaX1uexQ3tqd5+BRa/9e2D02Wcertl6Yh0jCB0=
github.com/anacrolix/generics v0.0.0-20230113004304-d6428d516633/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8=
-github.com/anacrolix/generics v0.0.3 h1:wMkQgQzq0obSy1tMkxDu7Ife7PsegOBWHDRaSW31EnM=
-github.com/anacrolix/generics v0.0.3/go.mod h1:MN3ve08Z3zSV/rTuX/ouI4lNdlfTxgdafQJiLzyNRB8=
+github.com/anacrolix/generics v0.1.1-0.20251125230353-15d98d46693b h1:Kuvx/A/TTJuT9x8mn7DeGx2KW9tWn1LI8bira67xdT0=
+github.com/anacrolix/generics v0.1.1-0.20251125230353-15d98d46693b/go.mod h1:NGehhfeXJPBujPx0s6cstSj8B+TERsTY32Xckfx5ftc=
github.com/anacrolix/go-libutp v1.3.2 h1:WswiaxTIogchbkzNgGHuHRfbrYLpv4o290mlvcx+++M=
github.com/anacrolix/go-libutp v1.3.2/go.mod h1:fCUiEnXJSe3jsPG554A200Qv+45ZzIIyGEvE56SHmyA=
github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
github.com/anacrolix/log v0.14.2/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
-github.com/anacrolix/log v0.16.0 h1:DSuyb5kAJwl3Y0X1TRcStVrTS9ST9b0BHW+7neE4Xho=
-github.com/anacrolix/log v0.16.0/go.mod h1:m0poRtlr41mriZlXBQ9SOVZ8yZBkLjOkDhd5Li5pITA=
-github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
+github.com/anacrolix/log v0.17.1-0.20251118025802-918f1157b7bb h1:nGNLCQbxFQZz7/9PXLGQ9GmavI/W+eX66pSwVeUwugU=
+github.com/anacrolix/log v0.17.1-0.20251118025802-918f1157b7bb/go.mod h1:YjBZbwe2v3RsU7WdoBlVSPVpfKuOAno9SRQ/8tIl+hk=
github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM=
+github.com/anacrolix/lsan v0.1.0 h1:TbgB8fdVXgBwrNsJGHtht9+9FepNFu5H7dU8ek6XYAY=
+github.com/anacrolix/lsan v0.1.0/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM=
github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
github.com/anacrolix/missinggo v1.1.2-0.20190815015349-b888af804467/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
@@ -58,8 +59,8 @@ github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy
github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ=
github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY=
github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA=
-github.com/anacrolix/missinggo/v2 v2.8.0 h1:6pGnVOlR6TWL9JM5Msyezij8YHU3+oHO7r82Eql/kpA=
-github.com/anacrolix/missinggo/v2 v2.8.0/go.mod h1:vVO5FEziQm+NFmJesc7StpkquZk+WJFCaL0Wp//2sa0=
+github.com/anacrolix/missinggo/v2 v2.10.0 h1:pg0iO4Z/UhP2MAnmGcaMtp5ZP9kyWsusENWN9aolrkY=
+github.com/anacrolix/missinggo/v2 v2.10.0/go.mod h1:nCRMW6bRCMOVcw5z9BnSYKF+kDbtenx+hQuphf4bK8Y=
github.com/anacrolix/mmsg v1.0.1/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc=
github.com/anacrolix/mmsg v1.1.1 h1:4ce/3I5kM7qSF6T5A8MOmDCfac3UqYlk5Bzh5XsWebM=
github.com/anacrolix/mmsg v1.1.1/go.mod h1:lPCXEN1eDDQtKktdKEzdw+roswx6wWPpeXAl/WpWVDU=
@@ -70,13 +71,13 @@ github.com/anacrolix/stm v0.5.0 h1:9df1KBpttF0TzLgDq51Z+TEabZKMythqgx89f1FQJt8=
github.com/anacrolix/stm v0.5.0/go.mod h1:MOwrSy+jCm8Y7HYfMAwPj7qWVu7XoVvjOiYwJmpeB/M=
github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk=
github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g=
-github.com/anacrolix/sync v0.5.4 h1:yXZLIjXh/G+Rh2mYGCAPmszmF/fvEPadDy7/pPChpKM=
-github.com/anacrolix/sync v0.5.4/go.mod h1:21cUWerw9eiu/3T3kyoChu37AVO+YFue1/H15qqubS0=
+github.com/anacrolix/sync v0.5.5-0.20251119100342-d78dd1f686f1 h1:oLCfNgEOR3/Z98mSwmwTM1pcqCDb/1zIjxCNn7dzVaE=
+github.com/anacrolix/sync v0.5.5-0.20251119100342-d78dd1f686f1/go.mod h1:21cUWerw9eiu/3T3kyoChu37AVO+YFue1/H15qqubS0=
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
-github.com/anacrolix/torrent v1.58.1 h1:6FP+KH57b1gyT2CpVL9fEqf9MGJEgh3xw1VA8rI0pW8=
-github.com/anacrolix/torrent v1.58.1/go.mod h1:/7ZdLuHNKgtCE1gjYJCfbtG9JodBcDaF5ip5EUWRtk8=
+github.com/anacrolix/torrent v1.61.0 h1:vxo+B4SwnoP5AQWbhvnTYIaTgPSX+llYUVuQVsN4Jg8=
+github.com/anacrolix/torrent v1.61.0/go.mod h1:yKUKuZSSDdyOsCbuH+rDOpswl/g546gICapdrU7aUmQ=
github.com/anacrolix/upnp v0.1.4 h1:+2t2KA6QOhm/49zeNyeVwDu1ZYS9dB9wfxyVvh/wk7U=
github.com/anacrolix/upnp v0.1.4/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
@@ -299,8 +300,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
-github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8=
@@ -328,10 +329,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/tidwall/btree v1.8.0 h1:kHHy8hSBauQUe0KPHMFLOt0olAj1nDnkHPJhr8+HFkM=
-github.com/tidwall/btree v1.8.0/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/tidwall/btree v1.8.1 h1:27ehoXvm5AG/g+1VxLS1SD3vRhp/H7LuEfwNvddEdmA=
+github.com/tidwall/btree v1.8.1/go.mod h1:jBbTdUWhSZClZWoDg54VnvV7/54modSOzDN7VXftj1A=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
@@ -346,27 +347,25 @@ go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
-go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
-go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
-go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
-go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
-go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
-go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
-go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
-golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
-golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
-golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
-golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
+golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
+golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -377,8 +376,8 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
-golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
+golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
+golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -396,8 +395,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
-golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
-golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -411,8 +408,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
-golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -436,8 +432,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
-golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -453,11 +447,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
-golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
-golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
+golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
+golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
+golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -468,8 +461,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
-golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
+golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/go/server.go b/go/server.go
index f5322bc5..32454476 100644
--- a/go/server.go
+++ b/go/server.go
@@ -33,14 +33,20 @@ var (
)
const (
- maxJSONBodyBytes int64 = 1 << 20
- maxTorrentUploadSize int64 = 100 << 20
- minStreamReadahead = 1 << 20
- baseStreamReadahead = 4 << 20
- maxStreamReadahead = 32 << 20
- seekBoostThreshold = 4 << 20
- seekBoostWindow = 32 << 20
- seekBoostReadahead = 16 << 20
+ maxJSONBodyBytes int64 = 1 << 20
+ maxTorrentUploadSize int64 = 100 << 20
+ minStreamReadahead = 1 << 20
+ baseStreamReadahead = 4 << 20
+ maxStreamReadahead = 32 << 20
+ seekBoostThreshold = 4 << 20
+ seekBoostWindow = 32 << 20
+ seekBoostReadahead = 16 << 20
+ httpConnectTimeout = 5 * time.Second
+ httpKeepAlive = 30 * time.Second
+ httpIdleConnTimeout = 90 * time.Second
+ httpTLSHandshakeTimeout = 5 * time.Second
+ httpResponseHeaderTimeout = 15 * time.Second
+ httpClientTimeout = 45 * time.Second
)
// WorkerPool manages a pool of goroutines for concurrent tasks
@@ -140,6 +146,12 @@ func Start(config *Config) (int, error) {
torrentcliCfg = torrent.NewDefaultClientConfig()
torrentcliCfg.DataDir = sanitizedConfig.Path
+ torrentHTTPTransport := newTorrentHTTPTransport()
+ torrentcliCfg.WebTransport = torrentHTTPTransport
+ torrentcliCfg.MetainfoSourcesClient = &http.Client{
+ Transport: torrentHTTPTransport,
+ Timeout: httpClientTimeout,
+ }
// Performance optimizations
torrentcliCfg.DisableUTP = false
@@ -147,10 +159,13 @@ func Start(config *Config) (int, error) {
torrentcliCfg.NoDefaultPortForwarding = false
torrentcliCfg.DisablePEX = false
torrentcliCfg.AcceptPeerConnections = true
+ torrentcliCfg.DropDuplicatePeerIds = true
torrentcliCfg.EstablishedConnsPerTorrent = 80
torrentcliCfg.HalfOpenConnsPerTorrent = 25
torrentcliCfg.TorrentPeersHighWater = 200
torrentcliCfg.TorrentPeersLowWater = 50
+ torrentcliCfg.HandshakesTimeout = 8 * time.Second
+ torrentcliCfg.PieceHashersPerTorrent = pieceHashersPerTorrent()
torrentcliCfg.Seed = false
log.Printf("[INFO] Download directory: %s", torrentcliCfg.DataDir)
@@ -230,6 +245,35 @@ func configureCORS() *cors.Cors {
})
}
+func pieceHashersPerTorrent() int {
+ workers := runtime.NumCPU() / 2
+ if workers < 2 {
+ workers = 2
+ }
+ if workers > 8 {
+ workers = 8
+ }
+ return workers
+}
+
+func newTorrentHTTPTransport() *http.Transport {
+ return &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: (&net.Dialer{
+ Timeout: httpConnectTimeout,
+ KeepAlive: httpKeepAlive,
+ }).DialContext,
+ ForceAttemptHTTP2: true,
+ MaxIdleConns: 128,
+ MaxIdleConnsPerHost: 32,
+ MaxConnsPerHost: 64,
+ IdleConnTimeout: httpIdleConnTimeout,
+ TLSHandshakeTimeout: httpTLSHandshakeTimeout,
+ ResponseHeaderTimeout: httpResponseHeaderTimeout,
+ ExpectContinueTimeout: time.Second,
+ }
+}
+
func setupGracefulShutdown(server *http.Server) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
@@ -573,7 +617,7 @@ func getInfoWithTimeout(t *torrent.Torrent, timeout time.Duration) error {
}
}
-func initMagnet(w http.ResponseWriter, magnet string, alldn, alltr []string) *torrent.Torrent {
+func initMagnet(w http.ResponseWriter, magnet string, alldn, alltr, sources, webseeds []string) *torrent.Torrent {
var sb strings.Builder
sb.WriteString(magnet)
@@ -599,10 +643,70 @@ func initMagnet(w http.ResponseWriter, magnet string, alldn, alltr []string) *to
// Cache metadata
cacheTorrentMetadata(t)
+ applyTorrentFallbacks(t, sources, webseeds)
return t
}
+func normalizeHTTPURLs(values []string) []string {
+ if len(values) == 0 {
+ return nil
+ }
+
+ urls := make([]string, 0, len(values))
+ seen := make(map[string]struct{}, len(values))
+ for _, value := range values {
+ value = strings.TrimSpace(value)
+ if value == "" {
+ continue
+ }
+
+ parsedURL, err := url.Parse(value)
+ if err != nil {
+ continue
+ }
+ if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
+ continue
+ }
+
+ normalizedURL := parsedURL.String()
+ if _, exists := seen[normalizedURL]; exists {
+ continue
+ }
+ seen[normalizedURL] = struct{}{}
+ urls = append(urls, normalizedURL)
+ }
+
+ return urls
+}
+
+func applyTorrentFallbacks(t *torrent.Torrent, sources, webseeds []string) {
+ if t == nil {
+ return
+ }
+
+ normalizedSources := normalizeHTTPURLs(sources)
+ if len(normalizedSources) > 0 {
+ t.AddSources(normalizedSources)
+ }
+
+ normalizedWebseeds := normalizeHTTPURLs(webseeds)
+ if len(normalizedWebseeds) > 0 {
+ t.AddWebSeeds(normalizedWebseeds)
+ }
+}
+
+func collectTorrentFallbacks(values url.Values) ([]string, []string) {
+ sources := append([]string{}, values["source"]...)
+ sources = append(sources, values["xs"]...)
+ sources = append(sources, values["as"]...)
+
+ webseeds := append([]string{}, values["webseed"]...)
+ webseeds = append(webseeds, values["ws"]...)
+
+ return sources, webseeds
+}
+
func getTorrent(w http.ResponseWriter, infoHash string) *torrent.Torrent {
if len(infoHash) != 40 {
httpJSONError(w, "InfoHash not valid", http.StatusBadRequest)
@@ -750,7 +854,7 @@ func addMagnet(w http.ResponseWriter, r *http.Request) {
return
}
- t := initMagnet(w, amBody.Magnet, []string{}, []string{})
+ t := initMagnet(w, amBody.Magnet, []string{}, []string{}, amBody.Sources, amBody.Webseeds)
if t == nil {
return
}
@@ -824,6 +928,7 @@ func streamTorrent(w http.ResponseWriter, r *http.Request) {
tFile.SetPriority(torrent.PiecePriorityHigh)
fileReader := tFile.NewReader()
+ fileReader.SetContext(r.Context())
streamReader := configureStreamReader(fileReader, tFile.Length(), r.Header.Get("Range") != "")
defer streamReader.Close()
@@ -1176,6 +1281,7 @@ func AddTorrent(w http.ResponseWriter, r *http.Request) {
// Cache metadata
cacheTorrentMetadata(torrent)
+ applyTorrentFallbacks(torrent, r.MultipartForm.Value["source"], r.MultipartForm.Value["webseed"])
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(torrent.InfoHash().HexString()))
@@ -1191,6 +1297,7 @@ func playTorrent(w http.ResponseWriter, r *http.Request) {
magnet, magOk := r.URL.Query()["magnet"]
displayName := r.URL.Query()["dn"]
trackers := r.URL.Query()["tr"]
+ sources, webseeds := collectTorrentFallbacks(r.URL.Query())
files, fOk := r.URL.Query()["file"]
if !magOk && !ihOk {
@@ -1200,11 +1307,12 @@ func playTorrent(w http.ResponseWriter, r *http.Request) {
var t *torrent.Torrent
if magOk && !ihOk {
- t = initMagnet(w, magnet[0], displayName, trackers)
+ t = initMagnet(w, magnet[0], displayName, trackers, sources, webseeds)
}
if ihOk && !magOk {
t = getTorrent(w, infoHash[0])
+ applyTorrentFallbacks(t, sources, webseeds)
}
if t == nil {
@@ -1244,6 +1352,8 @@ type addMagnetBody struct {
Magnet string
AllFiles bool
Files []string
+ Sources []string
+ Webseeds []string
}
type addMagnetRes struct {
diff --git a/go/server_test.go b/go/server_test.go
index 742eb74a..1270efea 100644
--- a/go/server_test.go
+++ b/go/server_test.go
@@ -5,6 +5,7 @@ import (
"context"
"io"
"net/http/httptest"
+ "net/url"
"strings"
"testing"
@@ -15,6 +16,7 @@ type fakeTorrentReader struct {
pos int64
responsive bool
readaheadFunc torrent.ReadaheadFunc
+ ctx context.Context
}
func (f *fakeTorrentReader) Read(p []byte) (int, error) {
@@ -38,6 +40,10 @@ func (f *fakeTorrentReader) ReadContext(_ context.Context, p []byte) (int, error
return f.Read(p)
}
+func (f *fakeTorrentReader) SetContext(ctx context.Context) {
+ f.ctx = ctx
+}
+
func (f *fakeTorrentReader) SetReadahead(_ int64) {}
func (f *fakeTorrentReader) SetReadaheadFunc(fn torrent.ReadaheadFunc) {
@@ -117,6 +123,43 @@ func TestIsAllowedOriginOnlyAcceptsLocalOrigins(t *testing.T) {
}
}
+func TestNewTorrentHTTPTransportIsTunedForFallbacks(t *testing.T) {
+ transport := newTorrentHTTPTransport()
+ if transport == nil {
+ t.Fatal("expected a transport instance")
+ }
+ if !transport.ForceAttemptHTTP2 {
+ t.Fatal("expected HTTP/2 to be enabled")
+ }
+ if transport.MaxIdleConnsPerHost < 16 {
+ t.Fatalf("expected connection pooling per host, got %d", transport.MaxIdleConnsPerHost)
+ }
+ if transport.ResponseHeaderTimeout != httpResponseHeaderTimeout {
+ t.Fatalf("expected response header timeout %s, got %s", httpResponseHeaderTimeout, transport.ResponseHeaderTimeout)
+ }
+}
+
+func TestCollectTorrentFallbacksIncludesLegacyMagnetParams(t *testing.T) {
+ values, err := url.ParseQuery(
+ "magnet=magnet%3A%3Fxt%3Durn%3Abtih%3Aabc123%26dn%3DExample&xs=https%3A%2F%2Fmeta.example%2Fmovie.torrent&as=https%3A%2F%2Fbackup.example%2Fmovie.torrent&ws=https%3A%2F%2Fcdn.example%2Fmovie.mkv&source=https%3A%2F%2Fexplicit.example%2Fmovie.torrent&webseed=https%3A%2F%2Fexplicit-cdn.example%2Fmovie.mkv",
+ )
+ if err != nil {
+ t.Fatalf("ParseQuery failed: %v", err)
+ }
+
+ sources, webseeds := collectTorrentFallbacks(values)
+
+ if len(sources) != 3 {
+ t.Fatalf("expected 3 sources, got %d: %#v", len(sources), sources)
+ }
+ if len(webseeds) != 2 {
+ t.Fatalf("expected 2 webseeds, got %d: %#v", len(webseeds), webseeds)
+ }
+ if values.Get("magnet") != "magnet:?xt=urn:btih:abc123&dn=Example" {
+ t.Fatalf("expected magnet payload to stay intact, got %q", values.Get("magnet"))
+ }
+}
+
func TestConfigureStreamReaderBoostsReadaheadAfterLargeSeek(t *testing.T) {
fakeReader := &fakeTorrentReader{}
streamReader := configureStreamReader(fakeReader, 1<<30, true)
@@ -128,6 +171,12 @@ func TestConfigureStreamReaderBoostsReadaheadAfterLargeSeek(t *testing.T) {
t.Fatal("expected stream reader to install a readahead function")
}
+ ctx := context.Background()
+ fakeReader.SetContext(ctx)
+ if fakeReader.ctx != ctx {
+ t.Fatal("expected reader context to be stored")
+ }
+
baseReadahead := fakeReader.readaheadFunc(torrent.ReadaheadContext{})
if _, err := streamReader.Seek(32<<20, io.SeekStart); err != nil {
t.Fatalf("seek failed: %v", err)
diff --git a/ios/Frameworks/Libmtorrentserver.xcframework/Info.plist b/ios/Frameworks/Libmtorrentserver.xcframework/Info.plist
index 37272fd9..24afd6a3 100644
--- a/ios/Frameworks/Libmtorrentserver.xcframework/Info.plist
+++ b/ios/Frameworks/Libmtorrentserver.xcframework/Info.plist
@@ -4,6 +4,20 @@
AvailableLibraries
+
+ BinaryPath
+ Libmtorrentserver.framework/Libmtorrentserver
+ LibraryIdentifier
+ ios-arm64
+ LibraryPath
+ Libmtorrentserver.framework
+ SupportedArchitectures
+
+ arm64
+
+ SupportedPlatform
+ ios
+
BinaryPath
Libmtorrentserver.framework/Libmtorrentserver
@@ -21,20 +35,6 @@
SupportedPlatformVariant
simulator
-
- BinaryPath
- Libmtorrentserver.framework/Libmtorrentserver
- LibraryIdentifier
- ios-arm64
- LibraryPath
- Libmtorrentserver.framework
- SupportedArchitectures
-
- arm64
-
- SupportedPlatform
- ios
-
CFBundlePackageType
XFWK
diff --git a/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64/Libmtorrentserver.framework/Info.plist b/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64/Libmtorrentserver.framework/Info.plist
index 94f6cf96..55869d95 100644
--- a/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64/Libmtorrentserver.framework/Info.plist
+++ b/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64/Libmtorrentserver.framework/Info.plist
@@ -9,9 +9,9 @@
MinimumOSVersion
100.0
CFBundleShortVersionString
- 0.0.1776077599
+ 0.0.1776080429
CFBundleVersion
- 0.0.1776077599
+ 0.0.1776080429
CFBundlePackageType
FMWK
diff --git a/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64/Libmtorrentserver.framework/Libmtorrentserver b/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64/Libmtorrentserver.framework/Libmtorrentserver
index e6bfadad..960acacf 100644
Binary files a/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64/Libmtorrentserver.framework/Libmtorrentserver and b/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64/Libmtorrentserver.framework/Libmtorrentserver differ
diff --git a/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64_x86_64-simulator/Libmtorrentserver.framework/Info.plist b/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64_x86_64-simulator/Libmtorrentserver.framework/Info.plist
index 94f6cf96..55869d95 100644
--- a/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64_x86_64-simulator/Libmtorrentserver.framework/Info.plist
+++ b/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64_x86_64-simulator/Libmtorrentserver.framework/Info.plist
@@ -9,9 +9,9 @@
MinimumOSVersion
100.0
CFBundleShortVersionString
- 0.0.1776077599
+ 0.0.1776080429
CFBundleVersion
- 0.0.1776077599
+ 0.0.1776080429
CFBundlePackageType
FMWK
diff --git a/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64_x86_64-simulator/Libmtorrentserver.framework/Libmtorrentserver b/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64_x86_64-simulator/Libmtorrentserver.framework/Libmtorrentserver
index 944d7ae5..ed3fe899 100644
Binary files a/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64_x86_64-simulator/Libmtorrentserver.framework/Libmtorrentserver and b/ios/Frameworks/Libmtorrentserver.xcframework/ios-arm64_x86_64-simulator/Libmtorrentserver.framework/Libmtorrentserver differ
diff --git a/lib/services/torrent_server.dart b/lib/services/torrent_server.dart
index 065e2372..ffacd92c 100644
--- a/lib/services/torrent_server.dart
+++ b/lib/services/torrent_server.dart
@@ -11,6 +11,56 @@ import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/utils/extensions/string_extensions.dart';
import 'package:mangayomi/ffi/torrent_server_ffi.dart' as libmtorrentserver_ffi;
+String _buildQueryString(Map> parameters) {
+ final segments = [];
+ parameters.forEach((key, values) {
+ for (final value in values) {
+ segments.add(
+ '${Uri.encodeQueryComponent(key)}=${Uri.encodeQueryComponent(value)}',
+ );
+ }
+ });
+ return segments.join('&');
+}
+
+List _normalizeHttpUrls(Iterable values) {
+ final normalized = [];
+ final seen = {};
+ for (final value in values) {
+ final trimmed = value.trim();
+ if (trimmed.isEmpty) continue;
+ final uri = Uri.tryParse(trimmed);
+ if (uri == null) continue;
+ if (uri.scheme != 'http' && uri.scheme != 'https') continue;
+ final normalizedValue = uri.toString();
+ if (seen.add(normalizedValue)) {
+ normalized.add(normalizedValue);
+ }
+ }
+ return normalized;
+}
+
+(List, List) _extractMagnetFallbacks(String magnetUrl) {
+ final uri = Uri.tryParse(magnetUrl);
+ if (uri == null || uri.scheme != 'magnet') {
+ return (const [], const []);
+ }
+
+ final sources = _normalizeHttpUrls([
+ ...uri.queryParametersAll['xs'] ?? const [],
+ ...uri.queryParametersAll['as'] ?? const [],
+ ]);
+ final webseeds = _normalizeHttpUrls(uri.queryParametersAll['ws'] ?? const []);
+ return (sources, webseeds);
+}
+
+List _mergeTorrentFallbacks(
+ Iterable base,
+ Iterable discovered,
+) {
+ return _normalizeHttpUrls([...base, ...discovered]);
+}
+
class MTorrentServer {
final http = MClient.init();
Future removeTorrent(String? inforHash) async {
@@ -41,7 +91,12 @@ class MTorrentServer {
}
}
- Future getInfohash(String url, bool isFilePath) async {
+ Future getInfohash(
+ String url,
+ bool isFilePath, {
+ List sources = const [],
+ List webseeds = const [],
+ }) async {
try {
final torrentByte = isFilePath
? File(url).readAsBytesSync()
@@ -54,6 +109,10 @@ class MTorrentServer {
request.files.add(
MultipartFile.fromBytes('file', torrentByte, filename: 'file.torrent'),
);
+ request.fields.addAll({
+ for (final source in sources) 'source': source,
+ for (final webseed in webseeds) 'webseed': webseed,
+ });
final response = await http.send(request);
return await response.stream.bytesToString();
} catch (e) {
@@ -63,8 +122,10 @@ class MTorrentServer {
Future<(List