From ba147ac56c258ebcb7b9cf471105952fcad45ef4 Mon Sep 17 00:00:00 2001 From: Mukhtar Akere Date: Fri, 20 Sep 2024 21:09:26 +0100 Subject: [PATCH] Adds Support for Downloader --- Dockerfile | 1 + common/regex.go | 3 +- go.mod | 23 +++++ go.sum | 55 +++++++++++ pkg/debrid/debrid.go | 8 +- pkg/debrid/realdebrid.go | 49 ++++++++-- pkg/debrid/structs/realdebrid.go | 13 ++- pkg/debrid/torrent.go | 36 ++++--- pkg/qbit/downloader.go | 162 +++++++++++++++++++++++++++++++ pkg/qbit/downloaders/fasthttp.go | 53 ++++++++++ pkg/qbit/downloaders/http.go | 44 +++++++++ pkg/qbit/handlers_torrent.go | 5 + pkg/qbit/main.go | 5 +- pkg/qbit/middleware.go | 2 +- pkg/qbit/qbit.go | 101 ++++--------------- pkg/qbit/worker.go | 11 ++- 16 files changed, 457 insertions(+), 114 deletions(-) create mode 100644 pkg/qbit/downloader.go create mode 100644 pkg/qbit/downloaders/fasthttp.go create mode 100644 pkg/qbit/downloaders/http.go diff --git a/Dockerfile b/Dockerfile index 8923540..a50aea2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ ADD . . RUN CGO_ENABLED=0 GOOS=$(echo $TARGETPLATFORM | cut -d '/' -f1) GOARCH=$(echo $TARGETPLATFORM | cut -d '/' -f2) go build -o /blackhole FROM scratch +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=builder /blackhole /blackhole EXPOSE 8181 diff --git a/common/regex.go b/common/regex.go index 242a21c..0749ea3 100644 --- a/common/regex.go +++ b/common/regex.go @@ -8,6 +8,7 @@ import ( var ( VIDEOMATCH = "(?i)(\\.)(YUV|WMV|WEBM|VOB|VIV|SVI|ROQ|RMVB|RM|OGV|OGG|NSV|MXF|MPG|MPEG|M2V|MP2|MPE|MPV|MP4|M4P|M4V|MOV|QT|MNG|MKV|FLV|DRC|AVI|ASF|AMV|MKA|F4V|3GP|3G2|DIVX|X264|X265)$" + MUSICMATCH = "(?i)(\\.)(?:MP3|WAV|FLAC|AAC|OGG|WMA|AIFF|ALAC|M4A|APE|AC3|DTS|M4P|MID|MIDI|MKA|MP2|MPA|RA|VOC|WV|AMR)$" SUBMATCH = "(?i)(\\.)(SRT|SUB|SBV|ASS|VTT|TTML|DFXP|STL|SCC|CAP|SMI|TTXT|TDS|USF|JSS|SSA|PSB|RT|LRC|SSB)$" SAMPLEMATCH = `(?i)(^|[\\/]|[._-])(sample|trailer|thumb)s?([._-]|$)` ) @@ -36,7 +37,7 @@ func RemoveInvalidChars(value string) string { } func RemoveExtension(value string) string { - re := regexp.MustCompile(VIDEOMATCH + "|" + SUBMATCH + "|" + SAMPLEMATCH) + re := regexp.MustCompile(VIDEOMATCH + "|" + SUBMATCH + "|" + SAMPLEMATCH + "|" + MUSICMATCH) // Find the last index of the matched extension loc := re.FindStringIndex(value) diff --git a/go.mod b/go.mod index 334a897..1457939 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22 require ( github.com/anacrolix/torrent v1.55.0 + github.com/cavaliergopher/grab/v3 v3.0.1 github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 github.com/go-chi/chi/v5 v5.1.0 @@ -13,11 +14,33 @@ require ( ) require ( + github.com/Code-Hex/pget v0.2.1 // indirect + github.com/Code-Hex/updater v0.0.0-20160712085121-c3f278672520 // indirect + github.com/Songmu/prompter v0.5.0 // indirect + github.com/VividCortex/ewma v1.1.1 // indirect github.com/anacrolix/missinggo v1.3.0 // indirect github.com/anacrolix/missinggo/v2 v2.7.3 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/antonholmquist/jason v1.0.1-0.20160829104012-962e09b85496 // indirect + github.com/asaskevich/govalidator v0.0.0-20161001163130-7b3beb6df3c4 // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect + github.com/cheggaaa/pb/v3 v3.0.8 // indirect + github.com/fatih/color v1.10.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect + github.com/jessevdk/go-flags v1.5.0 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.12 // indirect + github.com/mcuadros/go-version v0.0.0-20141206211339-d52711f8d6be // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.55.0 // indirect golang.org/x/net v0.27.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect ) diff --git a/go.sum b/go.sum index 7363131..ec6c510 100644 --- a/go.sum +++ b/go.sum @@ -3,11 +3,19 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Code-Hex/pget v0.2.1 h1:1NLINikZSxqwRJN1ovZuyD1UvYvkqb4wOeNwbRibXXo= +github.com/Code-Hex/pget v0.2.1/go.mod h1:jcJWKwjmg022+6AxA8pJDX+7Jd0j3EUTP73fU3fW/aA= +github.com/Code-Hex/updater v0.0.0-20160712085121-c3f278672520 h1:AhI5ytq4dAam2scBpgeQY/9kz/covK9/NMyzO3e8350= +github.com/Code-Hex/updater v0.0.0-20160712085121-c3f278672520/go.mod h1:RZRMRhdqo/22EdcyGiDJdIdCrptsRDEbqQ8/bswHV1E= github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= 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/Songmu/prompter v0.5.0 h1:uf60xlFItY5nW+rlLJ2XIUfaUReo4gUEeftuUeHpio8= +github.com/Songmu/prompter v0.5.0/go.mod h1:S4Eg25l60kPlnfB2ttFVpvBKYw7RKJexzB3gzpAansY= +github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 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= @@ -35,7 +43,13 @@ github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pm github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= github.com/anacrolix/torrent v1.55.0 h1:s9yh/YGdPmbN9dTa+0Inh2dLdrLQRvEAj1jdFW/Hdd8= github.com/anacrolix/torrent v1.55.0/go.mod h1:sBdZHBSZNj4de0m+EbYg7vvs/G/STubxu/GzzNbojsE= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/antonholmquist/jason v1.0.1-0.20160829104012-962e09b85496 h1:dESITdufxuiwgQh1YPiPupEXORHTYvY8tr40nvrWelo= +github.com/antonholmquist/jason v1.0.1-0.20160829104012-962e09b85496/go.mod h1:+GxMEKI0Va2U8h3os6oiUAetHAlGMvxjdpAH/9uvUMA= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/asaskevich/govalidator v0.0.0-20161001163130-7b3beb6df3c4 h1:roUAANycAr9TS5tnrZboqlI+bGfcY8n9nDyD1WDgn74= +github.com/asaskevich/govalidator v0.0.0-20161001163130-7b3beb6df3c4/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -44,7 +58,11 @@ github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2w github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og= +github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= +github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA= +github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -59,6 +77,8 @@ github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 h1:1NyRx2f4W4WBRyg github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380/go.mod h1:thX175TtLTzLj3p7N/Q9IiKZ7NF+p72cvL91emV0hzo= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -115,12 +135,16 @@ github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63 github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -130,7 +154,16 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mcuadros/go-version v0.0.0-20141206211339-d52711f8d6be h1:tj81VrKAa9Vv71Ugze2mtnOiU2ozpJau8itbf3XP3fo= +github.com/mcuadros/go-version v0.0.0-20141206211339-d52711f8d6be/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -146,6 +179,7 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -166,6 +200,9 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -187,6 +224,10 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o 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= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= +github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= @@ -220,6 +261,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -229,8 +272,20 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= diff --git a/pkg/debrid/debrid.go b/pkg/debrid/debrid.go index d77d5c2..9993c4e 100644 --- a/pkg/debrid/debrid.go +++ b/pkg/debrid/debrid.go @@ -10,8 +10,8 @@ import ( type Service interface { SubmitMagnet(torrent *Torrent) (*Torrent, error) - CheckStatus(torrent *Torrent) (*Torrent, error) - DownloadLink(torrent *Torrent) error + CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error) + GetDownloadLinks(torrent *Torrent) error DeleteTorrent(torrent *Torrent) IsAvailable(infohashes []string) map[string]bool GetMountPath() string @@ -120,7 +120,7 @@ func GetLocalCache(infohashes []string, cache *common.Cache) ([]string, map[stri return hashes, result } -func ProcessQBitTorrent(d Service, magnet *common.Magnet, arr *Arr) (*Torrent, error) { +func ProcessQBitTorrent(d Service, magnet *common.Magnet, arr *Arr, isSymlink bool) (*Torrent, error) { debridTorrent := &Torrent{ InfoHash: magnet.InfoHash, Magnet: magnet, @@ -144,5 +144,5 @@ func ProcessQBitTorrent(d Service, magnet *common.Magnet, arr *Arr) (*Torrent, e logger.Printf("Error submitting magnet: %s", err) return nil, err } - return d.CheckStatus(debridTorrent) + return d.CheckStatus(debridTorrent, isSymlink) } diff --git a/pkg/debrid/realdebrid.go b/pkg/debrid/realdebrid.go index 38f361b..cb8b03b 100644 --- a/pkg/debrid/realdebrid.go +++ b/pkg/debrid/realdebrid.go @@ -40,7 +40,9 @@ func GetTorrentFiles(data structs.RealDebridTorrentInfo) []TorrentFile { files := make([]TorrentFile, 0) for _, f := range data.Files { name := filepath.Base(f.Path) - if (!common.RegexMatch(common.VIDEOMATCH, name) && !common.RegexMatch(common.SUBMATCH, name)) || common.RegexMatch(common.SAMPLEMATCH, name) { + if (!common.RegexMatch(common.VIDEOMATCH, name) && + !common.RegexMatch(common.SUBMATCH, name) && + !common.RegexMatch(common.MUSICMATCH, name)) || common.RegexMatch(common.SAMPLEMATCH, name) { continue } fileId := f.ID @@ -149,12 +151,13 @@ func (r *RealDebrid) GetTorrent(id string) (*Torrent, error) { torrent.Seeders = data.Seeders torrent.Filename = data.Filename torrent.OriginalFilename = data.OriginalFilename + torrent.Links = data.Links files := GetTorrentFiles(data) torrent.Files = files return torrent, nil } -func (r *RealDebrid) CheckStatus(torrent *Torrent) (*Torrent, error) { +func (r *RealDebrid) CheckStatus(torrent *Torrent, isSymlink bool) (*Torrent, error) { url := fmt.Sprintf("%s/torrents/info/%s", r.Host, torrent.Id) for { resp, err := r.client.MakeRequest(http.MethodGet, url, nil) @@ -174,6 +177,8 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent) (*Torrent, error) { torrent.Progress = data.Progress torrent.Speed = data.Speed torrent.Seeders = data.Seeders + torrent.Links = data.Links + torrent.Status = status if status == "error" || status == "dead" || status == "magnet_error" { return torrent, fmt.Errorf("torrent: %s has error", torrent.Name) } else if status == "waiting_files_selection" { @@ -195,11 +200,16 @@ func (r *RealDebrid) CheckStatus(torrent *Torrent) (*Torrent, error) { return torrent, err } } else if status == "downloaded" { - log.Printf("Torrent: %s downloaded\n", torrent.Name) - err = r.DownloadLink(torrent) - if err != nil { - return torrent, err + files := GetTorrentFiles(data) + torrent.Files = files + log.Printf("Torrent: %s downloaded to RD\n", torrent.Name) + if !isSymlink { + err = r.GetDownloadLinks(torrent) + if err != nil { + return torrent, err + } } + break } else if status == "downloading" { if !r.DownloadUncached { @@ -225,7 +235,32 @@ func (r *RealDebrid) DeleteTorrent(torrent *Torrent) { } } -func (r *RealDebrid) DownloadLink(torrent *Torrent) error { +func (r *RealDebrid) GetDownloadLinks(torrent *Torrent) error { + url := fmt.Sprintf("%s/unrestrict/link/", r.Host) + downloadLinks := make([]TorrentDownloadLinks, 0) + for _, link := range torrent.Links { + if link == "" { + continue + } + payload := gourl.Values{ + "link": {link}, + } + resp, err := r.client.MakeRequest(http.MethodPost, url, strings.NewReader(payload.Encode())) + if err != nil { + return err + } + var data structs.RealDebridUnrestrictResponse + if err = json.Unmarshal(resp, &data); err != nil { + return err + } + download := TorrentDownloadLinks{ + Link: data.Link, + Filename: data.Filename, + DownloadLink: data.Download, + } + downloadLinks = append(downloadLinks, download) + } + torrent.DownloadLinks = downloadLinks return nil } diff --git a/pkg/debrid/structs/realdebrid.go b/pkg/debrid/structs/realdebrid.go index 0fd1ed2..0ccc128 100644 --- a/pkg/debrid/structs/realdebrid.go +++ b/pkg/debrid/structs/realdebrid.go @@ -93,4 +93,15 @@ type RealDebridTorrentInfo struct { Seeders int `json:"seeders,omitempty"` } -// 5e6e2e77fd3921a7903a41336c844cc409bf8788/14527C07BDFDDFC642963238BB6E7507B9742947/66A1CD1A5C7F4014877A51AC2620E857E3BB4D16 +type RealDebridUnrestrictResponse struct { + Id string `json:"id"` + Filename string `json:"filename"` + MimeType string `json:"mimeType"` + Filesize int64 `json:"filesize"` + Link string `json:"link"` + Host string `json:"host"` + Chunks int64 `json:"chunks"` + Crc int64 `json:"crc"` + Download string `json:"download"` + Streamable int `json:"streamable"` +} diff --git a/pkg/debrid/torrent.go b/pkg/debrid/torrent.go index f32459c..46a66e9 100644 --- a/pkg/debrid/torrent.go +++ b/pkg/debrid/torrent.go @@ -25,25 +25,33 @@ type ArrHistorySchema struct { } type Torrent struct { - Id string `json:"id"` - InfoHash string `json:"info_hash"` - Name string `json:"name"` - Folder string `json:"folder"` - Filename string `json:"filename"` - OriginalFilename string `json:"original_filename"` - Size int64 `json:"size"` - Bytes int64 `json:"bytes"` // Size of only the files that are downloaded - Magnet *common.Magnet `json:"magnet"` - Files []TorrentFile `json:"files"` - Status string `json:"status"` - Progress float64 `json:"progress"` - Speed int64 `json:"speed"` - Seeders int `json:"seeders"` + Id string `json:"id"` + InfoHash string `json:"info_hash"` + Name string `json:"name"` + Folder string `json:"folder"` + Filename string `json:"filename"` + OriginalFilename string `json:"original_filename"` + Size int64 `json:"size"` + Bytes int64 `json:"bytes"` // Size of only the files that are downloaded + Magnet *common.Magnet `json:"magnet"` + Files []TorrentFile `json:"files"` + Status string `json:"status"` + Progress float64 `json:"progress"` + Speed int64 `json:"speed"` + Seeders int `json:"seeders"` + Links []string `json:"links"` + DownloadLinks []TorrentDownloadLinks `json:"download_links"` Debrid *Debrid Arr *Arr } +type TorrentDownloadLinks struct { + Filename string `json:"filename"` + Link string `json:"link"` + DownloadLink string `json:"download_link"` +} + func (t *Torrent) GetSymlinkFolder(parent string) string { return filepath.Join(parent, t.Arr.Name, t.Folder) } diff --git a/pkg/qbit/downloader.go b/pkg/qbit/downloader.go new file mode 100644 index 0000000..096774c --- /dev/null +++ b/pkg/qbit/downloader.go @@ -0,0 +1,162 @@ +package qbit + +import ( + "github.com/cavaliergopher/grab/v3" + "goBlack/common" + "goBlack/pkg/debrid" + "goBlack/pkg/qbit/downloaders" + "os" + "path/filepath" + "sync" + "time" +) + +func (q *QBit) processManualFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr) { + q.logger.Printf("Downloading %d files...", len(debridTorrent.DownloadLinks)) + parent := common.RemoveInvalidChars(filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrent.Name)) + err := os.MkdirAll(parent, os.ModePerm) + if err != nil { + q.logger.Printf("Failed to create directory: %s\n", parent) + q.MarkAsFailed(torrent) + return + } + q.downloadFiles(debridTorrent, parent) + q.UpdateTorrent(torrent, debridTorrent) + q.RefreshArr(arr) +} + +func (q *QBit) downloadFile(client *grab.Client, link debrid.TorrentDownloadLinks, parent string, wg *sync.WaitGroup, semaphore chan struct{}) { + url := link.DownloadLink + defer wg.Done() + defer func() { <-semaphore }() + req, _ := grab.NewRequest(parent, url) + resp := client.Do(req) + //t := time.NewTicker(5 * time.Second) + //defer t.Stop() + //Loop: + // for { + // select { + // case <-t.C: + // fmt.Printf(" %s: transferred %d / %d bytes (%.2f%%)\n", + // resp.Filename, + // resp.BytesComplete(), + // resp.Size, + // 100*resp.Progress()) + // + // case <-resp.Done: + // // download is complete + // break Loop + // } + // } + + // Check for errors + if err := resp.Err(); err != nil { + q.logger.Printf("Error downloading %v: %v\n", url, err) + return + } + q.logger.Printf("Downloaded %s successfully\n", link.DownloadLink) +} + +func (q *QBit) downloadFiles(debridTorrent *debrid.Torrent, parent string) { + var wg sync.WaitGroup + client := downloaders.GetFastHTTPClient() + for _, link := range debridTorrent.DownloadLinks { + if link.DownloadLink == "" { + q.logger.Printf("No download link found for %s\n", link.Filename) + continue + } + wg.Add(1) + go func(link debrid.TorrentDownloadLinks) { + defer wg.Done() + err := downloaders.NormalFastHTTP(client, link.DownloadLink, filepath.Join(parent, link.Filename)) + if err != nil { + q.logger.Printf("Error downloading %s: %v\n", link.DownloadLink, err) + } else { + q.logger.Printf("Downloaded %s successfully\n", link.DownloadLink) + } + }(link) + } + wg.Wait() + q.logger.Printf("Downloaded all files for %s\n", debridTorrent.Name) +} + +func (q *QBit) processSymlink(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr) { + var wg sync.WaitGroup + files := debridTorrent.Files + ready := make(chan debrid.TorrentFile, len(files)) + + q.logger.Printf("Checking %d files...", len(files)) + rCloneBase := q.debrid.GetMountPath() + torrentPath, err := q.getTorrentPath(rCloneBase, debridTorrent) // /MyTVShow/ + if err != nil { + q.MarkAsFailed(torrent) + q.logger.Printf("Error: %v", err) + return + } + + torrentSymlinkPath := filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentPath) // /mnt/symlinks/{category}/MyTVShow/ + err = os.MkdirAll(torrentSymlinkPath, os.ModePerm) + if err != nil { + q.logger.Printf("Failed to create directory: %s\n", torrentSymlinkPath) + q.MarkAsFailed(torrent) + return + } + torrentRclonePath := filepath.Join(rCloneBase, torrentPath) + for _, file := range files { + wg.Add(1) + go checkFileLoop(&wg, torrentRclonePath, file, ready) + } + + go func() { + wg.Wait() + close(ready) + }() + + for f := range ready { + q.logger.Println("File is ready:", f.Path) + q.createSymLink(torrentSymlinkPath, torrentRclonePath, f) + } + // Update the torrent when all files are ready + torrent.TorrentPath = filepath.Base(torrentPath) // Quite important + q.UpdateTorrent(torrent, debridTorrent) + q.RefreshArr(arr) +} + +func (q *QBit) getTorrentPath(rclonePath string, debridTorrent *debrid.Torrent) (string, error) { + pathChan := make(chan string) + errChan := make(chan error) + + go func() { + for { + torrentPath := debridTorrent.GetMountFolder(rclonePath) + if torrentPath != "" { + pathChan <- torrentPath + return + } + time.Sleep(time.Second) + } + }() + + select { + case path := <-pathChan: + return path, nil + case err := <-errChan: + return "", err + } +} + +func (q *QBit) createSymLink(path string, torrentMountPath string, file debrid.TorrentFile) { + + // Combine the directory and filename to form a full path + fullPath := filepath.Join(path, file.Name) // /mnt/symlinks/{category}/MyTVShow/MyTVShow.S01E01.720p.mkv + // Create a symbolic link if file doesn't exist + torrentFilePath := filepath.Join(torrentMountPath, file.Name) // debridFolder/MyTVShow/MyTVShow.S01E01.720p.mkv + err := os.Symlink(torrentFilePath, fullPath) + if err != nil { + q.logger.Printf("Failed to create symlink: %s\n", fullPath) + } + // Check if the file exists + if !common.FileReady(fullPath) { + q.logger.Printf("Symlink not ready: %s\n", fullPath) + } +} diff --git a/pkg/qbit/downloaders/fasthttp.go b/pkg/qbit/downloaders/fasthttp.go new file mode 100644 index 0000000..9311825 --- /dev/null +++ b/pkg/qbit/downloaders/fasthttp.go @@ -0,0 +1,53 @@ +package downloaders + +import ( + "crypto/tls" + "fmt" + "github.com/valyala/fasthttp" + "io" + "os" +) + +func GetFastHTTPClient() *fasthttp.Client { + return &fasthttp.Client{ + TLSConfig: &tls.Config{InsecureSkipVerify: true}, + } +} + +func NormalFastHTTP(client *fasthttp.Client, url, filename string) error { + req := fasthttp.AcquireRequest() + resp := fasthttp.AcquireResponse() + defer fasthttp.ReleaseRequest(req) + defer fasthttp.ReleaseResponse(resp) + + req.SetRequestURI(url) + req.Header.SetMethod(fasthttp.MethodGet) + + if err := client.Do(req, resp); err != nil { + return err + } + + // Check the response status code + if resp.StatusCode() != fasthttp.StatusOK { + return fmt.Errorf("unexpected status code: %d", resp.StatusCode()) + } + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + bodyStream := resp.BodyStream() + if bodyStream == nil { + return fmt.Errorf("bodyStream is nil") + } + defer func() { + if rc, ok := bodyStream.(io.Closer); ok { + rc.Close() + } + }() + + if _, err := io.Copy(file, bodyStream); err != nil { + return err + } + return nil +} diff --git a/pkg/qbit/downloaders/http.go b/pkg/qbit/downloaders/http.go new file mode 100644 index 0000000..3358518 --- /dev/null +++ b/pkg/qbit/downloaders/http.go @@ -0,0 +1,44 @@ +package downloaders + +import ( + "crypto/tls" + "fmt" + "io" + "net/http" + "os" +) + +func GetHTTPClient() *http.Client { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + return &http.Client{Transport: tr} +} + +func NormalHTTP(client *http.Client, url, filename string) error { + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + // Send the HTTP GET request + resp, err := client.Get(url) + if err != nil { + fmt.Println("Error downloading file:", err) + return err + } + defer resp.Body.Close() + + // Check server response + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("server returned non-200 status: %d %s", resp.StatusCode, resp.Status) + } + + // Write the response body to file + _, err = io.Copy(file, resp.Body) + if err != nil { + return err + } + return nil +} diff --git a/pkg/qbit/handlers_torrent.go b/pkg/qbit/handlers_torrent.go index 62c4801..891115d 100644 --- a/pkg/qbit/handlers_torrent.go +++ b/pkg/qbit/handlers_torrent.go @@ -1,6 +1,7 @@ package qbit import ( + "context" "net/http" "path/filepath" "strings" @@ -36,6 +37,8 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) { } } + isSymlink := strings.ToLower(r.FormValue("sequentialDownload")) != "true" + q.logger.Printf("isSymlink: %v\n", isSymlink) urls := r.FormValue("urls") category := r.FormValue("category") @@ -44,6 +47,8 @@ func (q *QBit) handleTorrentsAdd(w http.ResponseWriter, r *http.Request) { urlList = strings.Split(urls, "\n") } + ctx = context.WithValue(ctx, "isSymlink", isSymlink) + for _, url := range urlList { if err := q.AddMagnet(ctx, url, category); err != nil { q.logger.Printf("Error adding magnet: %v\n", err) diff --git a/pkg/qbit/main.go b/pkg/qbit/main.go index aaec3fd..37dcf1f 100644 --- a/pkg/qbit/main.go +++ b/pkg/qbit/main.go @@ -11,6 +11,7 @@ import ( "log" "net/http" "os" + "sync" "time" ) @@ -34,7 +35,7 @@ type QBit struct { storage *TorrentStorage debug bool logger *log.Logger - arrs map[string]string // host:token (Used for refreshing in worker) + arrs sync.Map // host:token (Used for refreshing in worker) RefreshInterval int } @@ -54,7 +55,7 @@ func NewQBit(config *common.Config, deb debrid.Service, cache *common.Cache) *QB debug: cfg.Debug, storage: storage, logger: common.NewLogger("QBit", os.Stdout), - arrs: make(map[string]string), + arrs: sync.Map{}, RefreshInterval: refreshInterval, } } diff --git a/pkg/qbit/middleware.go b/pkg/qbit/middleware.go index 5a206b6..76b06fd 100644 --- a/pkg/qbit/middleware.go +++ b/pkg/qbit/middleware.go @@ -52,7 +52,7 @@ func (q *QBit) authContext(next http.Handler) http.Handler { if err == nil { ctx = context.WithValue(r.Context(), "host", host) ctx = context.WithValue(ctx, "token", token) - q.arrs[host] = token + q.arrs.Store(host, token) next.ServeHTTP(w, r.WithContext(ctx)) return } diff --git a/pkg/qbit/qbit.go b/pkg/qbit/qbit.go index aa3db57..cb980eb 100644 --- a/pkg/qbit/qbit.go +++ b/pkg/qbit/qbit.go @@ -8,10 +8,7 @@ import ( "goBlack/pkg/debrid" "io" "mime/multipart" - "os" - "path/filepath" "strings" - "sync" "time" ) @@ -53,7 +50,8 @@ func (q *QBit) Process(ctx context.Context, magnet *common.Magnet, category stri Token: ctx.Value("token").(string), Host: ctx.Value("host").(string), } - debridTorrent, err := debrid.ProcessQBitTorrent(q.debrid, magnet, arr) + isSymlink := ctx.Value("isSymlink").(bool) + debridTorrent, err := debrid.ProcessQBitTorrent(q.debrid, magnet, arr, isSymlink) if err != nil || debridTorrent == nil { if err == nil { err = fmt.Errorf("failed to process torrent") @@ -64,7 +62,7 @@ func (q *QBit) Process(ctx context.Context, magnet *common.Magnet, category stri torrent.DebridTorrent = debridTorrent torrent.Name = debridTorrent.Name q.storage.AddOrUpdate(torrent) - go q.processFiles(torrent, debridTorrent, arr) // We can send async for file processing not to delay the response + go q.processFiles(torrent, debridTorrent, arr, isSymlink) // We can send async for file processing not to delay the response return nil } @@ -97,83 +95,22 @@ func (q *QBit) CreateTorrentFromMagnet(magnet *common.Magnet, category string) * return torrent } -func (q *QBit) processFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr) { - var wg sync.WaitGroup - files := debridTorrent.Files - ready := make(chan debrid.TorrentFile, len(files)) - - q.logger.Printf("Checking %d files...", len(files)) - rCloneBase := q.debrid.GetMountPath() - torrentPath, err := q.getTorrentPath(rCloneBase, debridTorrent) // /MyTVShow/ - if err != nil { - q.MarkAsFailed(torrent) - q.logger.Printf("Error: %v", err) - return - } - - torrentSymlinkPath := filepath.Join(q.DownloadFolder, debridTorrent.Arr.Name, torrentPath) // /mnt/symlinks/{category}/MyTVShow/ - err = os.MkdirAll(torrentSymlinkPath, os.ModePerm) - if err != nil { - q.logger.Printf("Failed to create directory: %s\n", torrentSymlinkPath) - q.MarkAsFailed(torrent) - return - } - torrentRclonePath := filepath.Join(rCloneBase, torrentPath) - for _, file := range files { - wg.Add(1) - go checkFileLoop(&wg, torrentRclonePath, file, ready) - } - - go func() { - wg.Wait() - close(ready) - }() - - for f := range ready { - q.logger.Println("File is ready:", f.Path) - q.createSymLink(torrentSymlinkPath, torrentRclonePath, f) - } - // Update the torrent when all files are ready - torrent.TorrentPath = filepath.Base(torrentPath) // Quite important - q.UpdateTorrent(torrent, debridTorrent) - q.RefreshArr(arr) -} - -func (q *QBit) getTorrentPath(rclonePath string, debridTorrent *debrid.Torrent) (string, error) { - pathChan := make(chan string) - errChan := make(chan error) - - go func() { - for { - torrentPath := debridTorrent.GetMountFolder(rclonePath) - if torrentPath != "" { - pathChan <- torrentPath - return - } - time.Sleep(time.Second) +func (q *QBit) processFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr, isSymlink bool) { + for debridTorrent.Status != "downloaded" { + progress := debridTorrent.Progress + q.logger.Printf("Progress: %.2f%%", progress) + time.Sleep(5 * time.Second) + dbT, err := q.debrid.CheckStatus(debridTorrent, isSymlink) + if err != nil { + q.logger.Printf("Error checking status: %v", err) + q.MarkAsFailed(torrent) + return } - }() - - select { - case path := <-pathChan: - return path, nil - case err := <-errChan: - return "", err - } -} - -func (q *QBit) createSymLink(path string, torrentMountPath string, file debrid.TorrentFile) { - - // Combine the directory and filename to form a full path - fullPath := filepath.Join(path, file.Name) // /mnt/symlinks/{category}/MyTVShow/MyTVShow.S01E01.720p.mkv - // Create a symbolic link if file doesn't exist - torrentFilePath := filepath.Join(torrentMountPath, file.Name) // debridFolder/MyTVShow/MyTVShow.S01E01.720p.mkv - err := os.Symlink(torrentFilePath, fullPath) - if err != nil { - q.logger.Printf("Failed to create symlink: %s\n", fullPath) - } - // Check if the file exists - if !common.FileReady(fullPath) { - q.logger.Printf("Symlink not ready: %s\n", fullPath) + debridTorrent = dbT + } + if isSymlink { + q.processSymlink(torrent, debridTorrent, arr) + } else { + q.processManualFiles(torrent, debridTorrent, arr) } } diff --git a/pkg/qbit/worker.go b/pkg/qbit/worker.go index 082de68..299691d 100644 --- a/pkg/qbit/worker.go +++ b/pkg/qbit/worker.go @@ -30,12 +30,19 @@ func (q *QBit) RefreshArrs() { if len(torrents) == 0 { return } - for host, token := range q.arrs { + + q.arrs.Range(func(key, value interface{}) bool { + host, ok := key.(string) + token, ok2 := value.(string) + if !ok || !ok2 { + return true + } arr := &debrid.Arr{ Name: "", Token: token, Host: host, } q.RefreshArr(arr) - } + return true + }) }