diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..11f839f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,36 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. Linux Ubuntu 22.04] + - TorrServer Version [e.g. MatriX.129] + +**Smartphone or tvbox on Android (please complete the following information):** + - Device: [e.g. Ugoos am6] + - OS: [e.g. Android 9] + - TorrServer Version [e.g. MatriX.129] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..6269982 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[Feature]" +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/README.md b/README.md index f6d3d8e..cb9f942 100644 --- a/README.md +++ b/README.md @@ -62,10 +62,10 @@ curl -s https://raw.githubusercontent.com/YouROK/TorrServer/master/installTorrSe #### macOS -Run in console +Run in Terminal.app ```bash -curl -s https://raw.githubusercontent.com/YouROK/TorrServer/master/installTorrServerMac.sh -o installTorrserverMac.sh && chmod 755 installTorrServerMac.sh && sudo ./installTorrServerMac.sh +curl -s https://raw.githubusercontent.com/YouROK/TorrServer/master/installTorrServerMac.sh -o installTorrserverMac.sh && chmod 755 installTorrServerMac.sh && bash ./installTorrServerMac.sh ``` Alternative install script for Intel Macs: @@ -81,12 +81,12 @@ On FreeBSD (TrueNAS/FreeNAS) you can use this plugin: /dev/null 2>&1 - sudo rm -f /Library/LaunchDaemons/*torrserver* 1>/dev/null 2>&1 - sudo rm -f $HOME/Library/LaunchAgents/*torrserver* 1>/dev/null 2>&1 - sudo rm -f $HOME/Library/LaunchDaemons/*torrserver* 1>/dev/null 2>&1 + sudo rm -f /Library/LaunchAgents/*torrserver* + sudo rm -f /Library/LaunchDaemons/*torrserver* + sudo rm -f $HOME/Library/LaunchAgents/*torrserver* + sudo rm -f $HOME/Library/LaunchDaemons/*torrserver* killRunning } @@ -54,7 +54,6 @@ function uninstall() { echo "" } [[ $lang == "en" ]] && read -p ' Are you shure you want to delete TorrServer? (Yes/No) ' answer_del /dev/null 2>&1 else @@ -211,7 +212,7 @@ EOF [[ $lang == "en" ]] && echo " Use user \"$isAuthUser\" with password \"$isAuthPass\" for web auth" || echo " Для авторизации введите пользователя $isAuthUser с паролем $isAuthPass" echo "" fi - sleep 60 + sleep 30 } while true; do diff --git a/server/cmd/main.go b/server/cmd/main.go index 7e7a845..22b9525 100644 --- a/server/cmd/main.go +++ b/server/cmd/main.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "runtime" + "strconv" "strings" "time" @@ -25,12 +26,12 @@ import ( ) type args struct { - Port string `arg:"-p" help:"web server port, default 8090"` + Port string `arg:"-p" help:"web server port (default 8090)"` Ssl bool `help:"enables https"` SslPort string `help:"web server ssl port, If not set, will be set to default 8091 or taken from db(if stored previously). Accepted if --ssl enabled."` SslCert string `help:"path to ssl cert file. If not set, will be taken from db(if stored previously) or default self-signed certificate/key will be generated. Accepted if --ssl enabled."` SslKey string `help:"path to ssl key file. If not set, will be taken from db(if stored previously) or default self-signed certificate/key will be generated. Accepted if --ssl enabled."` - Path string `arg:"-d" help:"database dir path"` + Path string `arg:"-d" help:"database and config dir path"` LogPath string `arg:"-l" help:"server log file path"` WebLogPath string `arg:"-w" help:"web access log file path"` RDB bool `arg:"-r" help:"start in read-only DB mode"` @@ -38,10 +39,11 @@ type args struct { DontKill bool `arg:"-k" help:"don't kill server on signal"` UI bool `arg:"-u" help:"open torrserver page in browser"` TorrentsDir string `arg:"-t" help:"autoload torrents from dir"` - TorrentAddr string `help:"Torrent client address, default :32000"` + TorrentAddr string `help:"Torrent client address, like 127.0.0.1:1337 (default :PeersListenPort)"` PubIPv4 string `arg:"-4" help:"set public IPv4 addr"` PubIPv6 string `arg:"-6" help:"set public IPv6 addr"` SearchWA bool `arg:"-s" help:"search without auth"` + MaxSize string `arg:"-m" help:"max allowed stream size (in Bytes)"` } func (args) Version() string { @@ -104,6 +106,13 @@ func main() { go watchTDir(params.TorrentsDir) } + if params.MaxSize != "" { + maxSize, err := strconv.ParseInt(params.MaxSize, 10, 64) + if err == nil { + settings.MaxSize = maxSize + } + } + server.Start(params.Port, params.SslPort, params.SslCert, params.SslKey, params.Ssl, params.RDB, params.SearchWA) log.TLogln(server.WaitServer()) log.Close() diff --git a/server/dlna/dlna.go b/server/dlna/dlna.go index e27377a..9915b21 100644 --- a/server/dlna/dlna.go +++ b/server/dlna/dlna.go @@ -65,7 +65,7 @@ func Start() { FriendlyName: getDefaultFriendlyName(), NoTranscode: true, NoProbe: true, - StallEventSubscribe: true, + StallEventSubscribe: false, Icons: []dms.Icon{ { Width: 48, diff --git a/server/docs/docs.go b/server/docs/docs.go index b898681..ad7ff29 100644 --- a/server/docs/docs.go +++ b/server/docs/docs.go @@ -62,7 +62,7 @@ const docTemplate = `{ "parameters": [ { "type": "string", - "description": "Test file size", + "description": "Test file size (in MB)", "name": "size", "in": "path", "required": true @@ -98,7 +98,7 @@ const docTemplate = `{ } } }, - "/ffp": { + "/ffp/{hash}/{id}": { "get": { "description": "Gather informations using ffprobe.", "produces": [ @@ -113,14 +113,14 @@ const docTemplate = `{ "type": "string", "description": "Torrent hash", "name": "hash", - "in": "query", + "in": "path", "required": true }, { "type": "string", "description": "File index in torrent", "name": "id", - "in": "query", + "in": "path", "required": true } ], @@ -148,32 +148,6 @@ const docTemplate = `{ } } }, - "/msx": { - "get": { - "description": "Multi usage endpoint.", - "produces": [ - "application/json" - ], - "tags": [ - "MSX" - ], - "summary": "Multi usage endpoint", - "parameters": [ - { - "type": "string", - "description": "Magnet/hash/link to torrent", - "name": "link", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "Data returned according to query" - } - } - } - }, "/msx/imdb": { "get": { "description": "Get MSX IMDB informations.", @@ -191,7 +165,7 @@ const docTemplate = `{ } } }, - "/msx/imdb/:id": { + "/msx/imdb/{id}": { "get": { "description": "Get MSX IMDB informations.", "produces": [ @@ -217,7 +191,33 @@ const docTemplate = `{ } } }, - "/play": { + "/msx/{pth}": { + "get": { + "description": "Multi usage endpoint.", + "produces": [ + "application/json" + ], + "tags": [ + "MSX" + ], + "summary": "Multi usage endpoint", + "parameters": [ + { + "type": "string", + "description": "Route MSX pages", + "name": "link", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Data returned according to path" + } + } + } + }, + "/play/{hash}/{id}": { "get": { "description": "Play given torrent referenced by hash.", "produces": [ @@ -232,21 +232,15 @@ const docTemplate = `{ "type": "string", "description": "Torrent hash", "name": "hash", - "in": "query", + "in": "path", "required": true }, { "type": "string", "description": "File index in torrent", "name": "id", - "in": "query", + "in": "path", "required": true - }, - { - "type": "boolean", - "description": "Not authenticated", - "name": "not_auth", - "in": "query" } ], "responses": { @@ -458,7 +452,7 @@ const docTemplate = `{ }, { "type": "string", - "description": "Get m3u from last play", + "description": "Get M3U from last played file", "name": "fromlast", "in": "query" }, @@ -475,17 +469,10 @@ const docTemplate = `{ "in": "query", "required": true }, - { - "type": "string", - "description": "File index in torrent", - "name": "poster", - "in": "query", - "required": true - }, { "type": "string", "description": "Set poster link of torrent", - "name": "not_auth", + "name": "poster", "in": "query", "required": true } diff --git a/server/docs/swagger.json b/server/docs/swagger.json index c5645a4..454c1e7 100644 --- a/server/docs/swagger.json +++ b/server/docs/swagger.json @@ -55,7 +55,7 @@ "parameters": [ { "type": "string", - "description": "Test file size", + "description": "Test file size (in MB)", "name": "size", "in": "path", "required": true @@ -91,7 +91,7 @@ } } }, - "/ffp": { + "/ffp/{hash}/{id}": { "get": { "description": "Gather informations using ffprobe.", "produces": [ @@ -106,14 +106,14 @@ "type": "string", "description": "Torrent hash", "name": "hash", - "in": "query", + "in": "path", "required": true }, { "type": "string", "description": "File index in torrent", "name": "id", - "in": "query", + "in": "path", "required": true } ], @@ -141,32 +141,6 @@ } } }, - "/msx": { - "get": { - "description": "Multi usage endpoint.", - "produces": [ - "application/json" - ], - "tags": [ - "MSX" - ], - "summary": "Multi usage endpoint", - "parameters": [ - { - "type": "string", - "description": "Magnet/hash/link to torrent", - "name": "link", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "Data returned according to query" - } - } - } - }, "/msx/imdb": { "get": { "description": "Get MSX IMDB informations.", @@ -184,7 +158,7 @@ } } }, - "/msx/imdb/:id": { + "/msx/imdb/{id}": { "get": { "description": "Get MSX IMDB informations.", "produces": [ @@ -210,7 +184,33 @@ } } }, - "/play": { + "/msx/{pth}": { + "get": { + "description": "Multi usage endpoint.", + "produces": [ + "application/json" + ], + "tags": [ + "MSX" + ], + "summary": "Multi usage endpoint", + "parameters": [ + { + "type": "string", + "description": "Route MSX pages", + "name": "link", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Data returned according to path" + } + } + } + }, + "/play/{hash}/{id}": { "get": { "description": "Play given torrent referenced by hash.", "produces": [ @@ -225,21 +225,15 @@ "type": "string", "description": "Torrent hash", "name": "hash", - "in": "query", + "in": "path", "required": true }, { "type": "string", "description": "File index in torrent", "name": "id", - "in": "query", + "in": "path", "required": true - }, - { - "type": "boolean", - "description": "Not authenticated", - "name": "not_auth", - "in": "query" } ], "responses": { @@ -451,7 +445,7 @@ }, { "type": "string", - "description": "Get m3u from last play", + "description": "Get M3U from last played file", "name": "fromlast", "in": "query" }, @@ -468,17 +462,10 @@ "in": "query", "required": true }, - { - "type": "string", - "description": "File index in torrent", - "name": "poster", - "in": "query", - "required": true - }, { "type": "string", "description": "Set poster link of torrent", - "name": "not_auth", + "name": "poster", "in": "query", "required": true } diff --git a/server/docs/swagger.yaml b/server/docs/swagger.yaml index cd69f0f..79174d1 100644 --- a/server/docs/swagger.yaml +++ b/server/docs/swagger.yaml @@ -332,7 +332,7 @@ paths: get: description: Download the test file of given size (for speed testing purpose). parameters: - - description: Test file size + - description: Test file size (in MB) in: path name: size required: true @@ -360,17 +360,17 @@ paths: summary: Tests server status tags: - API - /ffp: + /ffp/{hash}/{id}: get: description: Gather informations using ffprobe. parameters: - description: Torrent hash - in: query + in: path name: hash required: true type: string - description: File index in torrent - in: query + in: path name: id required: true type: string @@ -393,12 +393,12 @@ paths: summary: Get HTML of magnet links tags: - Pages - /msx: + /msx/{pth}: get: description: Multi usage endpoint. parameters: - - description: Magnet/hash/link to torrent - in: query + - description: Route MSX pages + in: path name: link required: true type: string @@ -406,7 +406,7 @@ paths: - application/json responses: "200": - description: Data returned according to query + description: Data returned according to path summary: Multi usage endpoint tags: - MSX @@ -421,7 +421,7 @@ paths: summary: Get MSX IMDB informations tags: - MSX - /msx/imdb/:id: + /msx/imdb/{id}: get: description: Get MSX IMDB informations. parameters: @@ -438,24 +438,20 @@ paths: summary: Get MSX IMDB informations tags: - MSX - /play: + /play/{hash}/{id}: get: description: Play given torrent referenced by hash. parameters: - description: Torrent hash - in: query + in: path name: hash required: true type: string - description: File index in torrent - in: query + in: path name: id required: true type: string - - description: Not authenticated - in: query - name: not_auth - type: boolean produces: - application/octet-stream responses: @@ -592,7 +588,7 @@ paths: in: query name: m3u type: string - - description: Get m3u from last play + - description: Get M3U from last played file in: query name: fromlast type: string @@ -605,14 +601,9 @@ paths: name: title required: true type: string - - description: File index in torrent - in: query - name: poster - required: true - type: string - description: Set poster link of torrent in: query - name: not_auth + name: poster required: true type: string produces: diff --git a/server/go.mod b/server/go.mod index 5f40be3..de08781 100644 --- a/server/go.mod +++ b/server/go.mod @@ -2,51 +2,52 @@ module server go 1.20 -replace github.com/anacrolix/torrent v1.53.1 => github.com/tsynik/torrent v1.2.11 +replace github.com/anacrolix/torrent v1.54.1 => github.com/tsynik/torrent v1.2.16 require ( github.com/agnivade/levenshtein v1.1.1 github.com/alexflint/go-arg v1.4.3 github.com/anacrolix/dms v1.6.0 - github.com/anacrolix/log v0.14.5 - github.com/anacrolix/missinggo v1.3.0 + github.com/anacrolix/log v0.15.0 + github.com/anacrolix/missinggo/v2 v2.7.3 github.com/anacrolix/publicip v0.3.0 - github.com/anacrolix/torrent v1.53.1 - github.com/gin-contrib/cors v1.4.0 + github.com/anacrolix/torrent v1.54.1 + github.com/gin-contrib/cors v1.5.0 github.com/gin-contrib/location v0.0.2 github.com/gin-gonic/gin v1.9.1 - github.com/kljensen/snowball v0.8.0 - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 + github.com/kljensen/snowball v0.9.0 + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/pkg/errors v0.9.1 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 - github.com/swaggo/swag v1.16.2 - go.etcd.io/bbolt v1.3.8 - golang.org/x/image v0.14.0 - golang.org/x/time v0.4.0 + github.com/swaggo/swag v1.16.3 + go.etcd.io/bbolt v1.3.9 + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 + golang.org/x/image v0.15.0 + golang.org/x/time v0.5.0 gopkg.in/vansante/go-ffprobe.v2 v2.1.1 ) require ( github.com/KyleBanks/depth v1.2.1 // indirect - github.com/RoaringBitmap/roaring v1.6.0 // indirect + github.com/RoaringBitmap/roaring v1.9.0 // indirect github.com/alecthomas/atomic v0.1.0-alpha2 // indirect github.com/alexflint/go-scalar v1.2.0 // indirect - github.com/anacrolix/chansync v0.3.0 // indirect - github.com/anacrolix/dht/v2 v2.21.0 // indirect + github.com/anacrolix/chansync v0.4.0 // indirect + github.com/anacrolix/dht/v2 v2.21.1 // indirect github.com/anacrolix/ffprobe v1.1.0 // indirect - github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13 // indirect + github.com/anacrolix/generics v0.0.1 // indirect + github.com/anacrolix/missinggo v1.3.0 // indirect github.com/anacrolix/missinggo/perf v1.0.0 // indirect - github.com/anacrolix/missinggo/v2 v2.7.3 // indirect github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7 // indirect github.com/anacrolix/stm v0.5.0 // indirect github.com/anacrolix/sync v0.5.1 // indirect - github.com/anacrolix/upnp v0.1.3 // indirect + github.com/anacrolix/upnp v0.1.4 // indirect github.com/anacrolix/utp v0.2.0 // indirect github.com/benbjohnson/immutable v0.4.3 // indirect - github.com/bits-and-blooms/bitset v1.11.0 // indirect + github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect - github.com/bytedance/sonic v1.10.2 // indirect + github.com/bytedance/sonic v1.11.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -55,38 +56,38 @@ require ( github.com/frankban/quicktest v1.14.6 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-openapi/jsonpointer v0.20.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/spec v0.20.9 // indirect - github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-openapi/jsonpointer v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/spec v0.20.14 // indirect + github.com/go-openapi/swag v0.22.9 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.16.0 // indirect + github.com/go-playground/validator/v10 v10.18.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/google/btree v1.1.2 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.6 // indirect - github.com/leodido/go-urn v1.2.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mschoch/smat v0.2.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect - golang.org/x/arch v0.6.0 // indirect - golang.org/x/crypto v0.15.0 // indirect - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect - golang.org/x/net v0.18.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.14.0 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.7.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.15.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + golang.org/x/tools v0.18.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/server/go.sum b/server/go.sum index ec3752e..e39e922 100644 --- a/server/go.sum +++ b/server/go.sum @@ -8,8 +8,8 @@ github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6Xge 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/RoaringBitmap/roaring v1.6.0 h1:dc7kRiroETgJcHhWX6BerXkZz2b3JgLGg9nTURJL/og= -github.com/RoaringBitmap/roaring v1.6.0/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= +github.com/RoaringBitmap/roaring v1.9.0 h1:lwKhr90/j0jVXJyh5X+vQN1VVn77rQFfYnh6RDRGCcE= +github.com/RoaringBitmap/roaring v1.9.0/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/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= @@ -25,10 +25,10 @@ github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258m github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= -github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U= -github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= -github.com/anacrolix/dht/v2 v2.21.0 h1:8nzI+faaynY9jOKmVgdmBZVrTo8B7ZE/LKEgN3Vl/Bs= -github.com/anacrolix/dht/v2 v2.21.0/go.mod h1:SDGC+sEs1pnO2sJGYuhvIis7T8749dDHNfcjtdH4e3g= +github.com/anacrolix/chansync v0.4.0 h1:Md0HM7zYCAO4KwNwgcIRgxNsMxiRuk7D1Ha0Uo+2y60= +github.com/anacrolix/chansync v0.4.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= +github.com/anacrolix/dht/v2 v2.21.1 h1:s1rKkfLLcmBHKv4v/mtMkIeHIEptzEFiB6xVu54+5/o= +github.com/anacrolix/dht/v2 v2.21.1/go.mod h1:SDGC+sEs1pnO2sJGYuhvIis7T8749dDHNfcjtdH4e3g= github.com/anacrolix/dms v1.6.0 h1:v2g1Y+Fc/ICSEc+7M6B92oFcfcqa5LXYPhE4Hcm5tVA= github.com/anacrolix/dms v1.6.0/go.mod h1:5fAMpBcPFG4WQFh91zhf2E7/KYZ3/WmmRAf/WMoL0Q0= github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= @@ -38,14 +38,14 @@ github.com/anacrolix/ffprobe v1.0.0/go.mod h1:BIw+Bjol6CWjm/CRWrVLk2Vy+UYlkgmBZ0 github.com/anacrolix/ffprobe v1.1.0 h1:eKBudnERW9zRJ0+ge6FzkQ0pWLyq142+FJrwRwSRMT4= github.com/anacrolix/ffprobe v1.1.0/go.mod h1:MXe+zG/RRa5OdIf5+VYYfS/CfsSqOH7RrvGIqJBzqhI= github.com/anacrolix/generics v0.0.0-20230113004304-d6428d516633/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8= -github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13 h1:qwOprPTDMM3BASJRf84mmZnTXRsPGGJ8xoHKQS7m3so= -github.com/anacrolix/generics v0.0.0-20230911070922-5dd7545c6b13/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8= +github.com/anacrolix/generics v0.0.1 h1:4WVhK6iLb3UAAAQP6I3uYlMOHcp9FqJC9j4n81Wv9Ks= +github.com/anacrolix/generics v0.0.1/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8= 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.14.5 h1:OkMjBquVSRb742LkecSGDGaGpNoSrw4syRIm0eRdmrg= -github.com/anacrolix/log v0.14.5/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY= +github.com/anacrolix/log v0.15.0 h1:QIhbW5NDUL6P1Aml+ZfdaXJ+QFAnrO0K1EpFYs/nh9M= +github.com/anacrolix/log v0.15.0/go.mod h1:m0poRtlr41mriZlXBQ9SOVZ8yZBkLjOkDhd5Li5pITA= 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= github.com/anacrolix/missinggo v1.2.1/go.mod h1:J5cMhif8jPmFoC3+Uvob3OXXNIhOUikzMt+uUjeM21Y= @@ -70,8 +70,8 @@ github.com/anacrolix/sync v0.5.1/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DC 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/upnp v0.1.3 h1:NlYEhE75adz2npEJKjbqyqnyW9qU4STookvSNXBJ5ao= -github.com/anacrolix/upnp v0.1.3/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic= +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= github.com/anacrolix/utp v0.2.0/go.mod h1:HGk4GYQw1O/3T1+yhqT/F6EcBd+AAwlo9dYErNy7mj8= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -82,17 +82,17 @@ github.com/benbjohnson/immutable v0.4.3/go.mod h1:qJIKKSmdqz1tVzNtst1DZzvaqOU1on 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= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bits-and-blooms/bitset v1.11.0 h1:RMyy2mBBShArUAhfVRZJ2xyBO58KCBCtZFShw3umo6k= -github.com/bits-and-blooms/bitset v1.11.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= +github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= 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/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= -github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.11.1 h1:JC0+6c9FoWYYxakaoa+c5QTtJeiSZNeByOBhXtAFSn4= +github.com/bytedance/sonic v1.11.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= @@ -122,14 +122,14 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= -github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= +github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= +github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/location v0.0.2 h1:QZKh1+K/LLR4KG/61eIO3b7MLuKi8tytQhV6texLgP4= github.com/gin-contrib/location v0.0.2/go.mod h1:NGoidiRlf0BlA/VKSVp+g3cuSMeTmip/63PhEjRhUAc= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= @@ -142,36 +142,25 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ= -github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= -github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q= +github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs= +github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= +github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do= +github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= +github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= +github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -189,7 +178,6 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -200,8 +188,9 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -227,31 +216,25 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV 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/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= -github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/kljensen/snowball v0.8.0 h1:WU4cExxK6sNW33AiGdbn4e8RvloHrhkAssu2mVJ11kg= -github.com/kljensen/snowball v0.8.0/go.mod h1:OGo5gFWjaeXqCu4iIrMl5OYip9XUJHGOU5eSkPjVg2A= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/kljensen/snowball v0.9.0 h1:OpXkQBcic6vcPG+dChOGLIA/GNuVg47tbbIJ2s7Keas= +github.com/kljensen/snowball v0.9.0/go.mod h1:OGo5gFWjaeXqCu4iIrMl5OYip9XUJHGOU5eSkPjVg2A= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 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= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -267,18 +250,16 @@ github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -305,6 +286,7 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn 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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8= github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= @@ -323,57 +305,53 @@ github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= -github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04= -github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= 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/tsynik/torrent v1.2.11 h1:wPJfxhO/ri10T1Xfi4X8DftwOF3A8geZ4cRQWKxnC2s= -github.com/tsynik/torrent v1.2.11/go.mod h1:AjuETm1Xae+Vk31UrvrSrb29bBunwMKGZLXK6T+AgPo= +github.com/tsynik/torrent v1.2.16 h1:jEXIkdPKIOerO+8b/12wTvEoLnjGFcHX7dMitTmrWH0= +github.com/tsynik/torrent v1.2.16/go.mod h1:c/K0Twt8E0qXJj02tl5FmjhGXSl0XzbFYrzkXTGqU+w= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= -go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= 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= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= -golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 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-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= -golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 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-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= -golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= -golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= +golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= 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= @@ -381,6 +359,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl 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.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= 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= @@ -397,8 +376,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220524220425-1d687d428aca/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -409,8 +388,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.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= @@ -427,17 +406,15 @@ golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -449,8 +426,8 @@ 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY= -golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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= @@ -460,8 +437,8 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= -golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= 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= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -480,16 +457,13 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -500,10 +474,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/server/server.go b/server/server.go index 4fbaf26..1aece68 100644 --- a/server/server.go +++ b/server/server.go @@ -45,7 +45,7 @@ func Start(port, sslport, sslCert, sslKey string, sslEnabled, roSets, searchWA b l.Close() } if err != nil { - log.TLogln("Port", sslport, "already in use! Please set different port for HTTPS. Abort") + log.TLogln("Port", sslport, "already in use! Please set different ssl port for HTTPS. Abort") os.Exit(1) } } @@ -59,7 +59,7 @@ func Start(port, sslport, sslCert, sslKey string, sslEnabled, roSets, searchWA b l.Close() } if err != nil { - log.TLogln("Port", port, "already in use! Please set different sslport for HTTP. Abort") + log.TLogln("Port", port, "already in use! Please set different port for HTTP. Abort") os.Exit(1) } // remove old disk caches diff --git a/server/settings/db.go b/server/settings/db.go index 03ec985..b2447b6 100644 --- a/server/settings/db.go +++ b/server/settings/db.go @@ -15,7 +15,7 @@ type TDB struct { db *bolt.DB } -func NewTDB() *TDB { +func NewTDB() TorrServerDB { db, err := bolt.Open(filepath.Join(Path, "config.db"), 0o666, &bolt.Options{Timeout: 5 * time.Second}) if err != nil { log.TLogln(err) @@ -68,10 +68,6 @@ func (v *TDB) Get(xpath, name string) []byte { } func (v *TDB) Set(xpath, name string, value []byte) { - if ReadOnly { - return - } - spath := strings.Split(xpath, "/") if len(spath) == 0 { return @@ -139,10 +135,6 @@ func (v *TDB) List(xpath string) []string { } func (v *TDB) Rem(xpath, name string) { - if ReadOnly { - return - } - spath := strings.Split(xpath, "/") if len(spath) == 0 { return diff --git a/server/settings/dbreadcache.go b/server/settings/dbreadcache.go new file mode 100644 index 0000000..77b700f --- /dev/null +++ b/server/settings/dbreadcache.go @@ -0,0 +1,70 @@ +package settings + +import "server/log" + +type DBReadCache struct { + db TorrServerDB + listCache map[string][]string + dataCache map[[2]string][]byte +} + +func NewDBReadCache(db TorrServerDB) TorrServerDB { + cdb := &DBReadCache{ + db: db, + listCache: map[string][]string{}, + dataCache: map[[2]string][]byte{}, + } + return cdb +} + +func (v *DBReadCache) CloseDB() { + v.db.CloseDB() + v.db = nil + v.listCache = nil + v.dataCache = nil +} + +func (v *DBReadCache) Get(xPath, name string) []byte { + cacheKey := v.makeDataCacheKey(xPath, name) + if data, ok := v.dataCache[cacheKey]; ok { + return data + } + data := v.db.Get(xPath, name) + v.dataCache[cacheKey] = data + return data +} + +func (v *DBReadCache) Set(xPath, name string, value []byte) { + if ReadOnly { + log.TLogln("DB.Set: Read-only DB mode!", name) + return + } + cacheKey := v.makeDataCacheKey(xPath, name) + v.dataCache[cacheKey] = value + delete(v.listCache, xPath) + v.db.Set(xPath, name, value) +} + +func (v *DBReadCache) List(xPath string) []string { + if names, ok := v.listCache[xPath]; ok { + return names + } + names := v.db.List(xPath) + v.listCache[xPath] = names + return names +} + +func (v *DBReadCache) Rem(xPath, name string) { + if ReadOnly { + log.TLogln("DB.Rem: Read-only DB mode!", name) + return + } + cacheKey := v.makeDataCacheKey(xPath, name) + delete(v.dataCache, cacheKey) + delete(v.listCache, xPath) + v.db.Rem(xPath, name) +} + +func (v *DBReadCache) makeDataCacheKey(xPath, name string) [2]string { + return [2]string{xPath, name} +} diff --git a/server/settings/jsondb.go b/server/settings/jsondb.go new file mode 100644 index 0000000..6185edb --- /dev/null +++ b/server/settings/jsondb.go @@ -0,0 +1,159 @@ +package settings + +import ( + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + "sync" + + "server/log" +) + +type JsonDB struct { + Path string + filenameDelimiter string + filenameExtension string + fileMode fs.FileMode + xPathDelimeter string +} + +var jsonDbLocks = make(map[string]*sync.Mutex) + +func NewJsonDB() TorrServerDB { + settingsDB := &JsonDB{ + Path: Path, + filenameDelimiter: ".", + filenameExtension: ".json", + fileMode: fs.FileMode(0o666), + xPathDelimeter: "/", + } + return settingsDB +} + +func (v *JsonDB) CloseDB() { + // Not necessary +} + +func (v *JsonDB) Set(xPath, name string, value []byte) { + var err error = nil + jsonObj := map[string]interface{}{} + + if err := json.Unmarshal(value, &jsonObj); err == nil { + if filename, err := v.xPathToFilename(xPath); err == nil { + v.lock(filename) + defer v.unlock(filename) + if root, err := v.readJsonFileAsMap(filename); err == nil { + root[name] = jsonObj + if err = v.writeMapAsJsonFile(filename, root); err == nil { + return + } + } + } + } + v.log(fmt.Sprintf("Set: error writing entry %s->%s", xPath, name), err) +} + +func (v *JsonDB) Get(xPath, name string) []byte { + var err error = nil + if filename, err := v.xPathToFilename(xPath); err == nil { + v.lock(filename) + defer v.unlock(filename) + if root, err := v.readJsonFileAsMap(filename); err == nil { + if jsonData, ok := root[name]; ok { + if byteData, err := json.Marshal(jsonData); err == nil { + return byteData + } + } else { + // We assume this is not 'error' but 'no entry' which is normal + return nil + } + } + } + v.log(fmt.Sprintf("Get: error reading entry %s->%s", xPath, name), err) + return nil +} + +func (v *JsonDB) List(xPath string) []string { + var err error = nil + if filename, err := v.xPathToFilename(xPath); err == nil { + v.lock(filename) + defer v.unlock(filename) + if root, err := v.readJsonFileAsMap(filename); err == nil { + nameList := make([]string, 0, len(root)) + for k := range root { + nameList = append(nameList, k) + } + return nameList + } + } + v.log(fmt.Sprintf("List: error reading entries in xPath %s", xPath), err) + return nil +} + +func (v *JsonDB) Rem(xPath, name string) { + var err error = nil + if filename, err := v.xPathToFilename(xPath); err == nil { + v.lock(filename) + defer v.unlock(filename) + if root, err := v.readJsonFileAsMap(filename); err == nil { + delete(root, name) + v.writeMapAsJsonFile(filename, root) + return + } + } + v.log(fmt.Sprintf("Rem: error removing entry %s->%s", xPath, name), err) +} + +func (v *JsonDB) lock(filename string) { + var mtx sync.Mutex + if mtx, ok := jsonDbLocks[filename]; !ok { + mtx = new(sync.Mutex) + jsonDbLocks[v.Path] = mtx + } + mtx.Lock() +} + +func (v *JsonDB) unlock(filename string) { + if mtx, ok := jsonDbLocks[filename]; ok { + mtx.Unlock() + } +} + +func (v *JsonDB) xPathToFilename(xPath string) (string, error) { + if pathComponents := strings.Split(xPath, v.xPathDelimeter); len(pathComponents) > 0 { + return strings.ToLower(strings.Join(pathComponents, v.filenameDelimiter) + v.filenameExtension), nil + } + return "", errors.New("xPath has no components") +} + +func (v *JsonDB) readJsonFileAsMap(filename string) (map[string]interface{}, error) { + var err error = nil + jsonData := map[string]interface{}{} + path := filepath.Join(v.Path, filename) + if fileData, err := os.ReadFile(path); err == nil { + err = json.Unmarshal(fileData, &jsonData) + } + return jsonData, err +} + +func (v *JsonDB) writeMapAsJsonFile(filename string, o map[string]interface{}) error { + var err error = nil + path := filepath.Join(v.Path, filename) + + if fileData, err := json.MarshalIndent(o, "", " "); err == nil { + err = os.WriteFile(path, fileData, v.fileMode) + } + return err +} + +func (v *JsonDB) log(s string, params ...interface{}) { + if len(params) > 0 { + log.TLogln(fmt.Sprintf("JsonDB: %s: %s", s, fmt.Sprint(params...))) + } else { + log.TLogln(fmt.Sprintf("JsonDB: %s", s)) + } +} diff --git a/server/settings/migrate.go b/server/settings/migrate.go index e0c6ce1..314778a 100644 --- a/server/settings/migrate.go +++ b/server/settings/migrate.go @@ -2,13 +2,17 @@ package settings import ( "encoding/binary" + "encoding/json" + "errors" "fmt" "os" "path/filepath" + "reflect" - bolt "go.etcd.io/bbolt" "server/log" "server/web/api/utils" + + bolt "go.etcd.io/bbolt" ) var dbTorrentsName = []byte("Torrents") @@ -22,7 +26,8 @@ type torrentOldDB struct { Timestamp int64 } -func Migrate() { +// Migrate from torrserver.db to config.db +func Migrate1() { if _, err := os.Lstat(filepath.Join(Path, "torrserver.db")); os.IsNotExist(err) { return } @@ -100,3 +105,87 @@ func Migrate() { func b2i(v []byte) int64 { return int64(binary.BigEndian.Uint64(v)) } + +/* + === Migrate 2 === + +Migrate 'Settings' and 'Viewed' buckets from BBolt ('config.db') +to separate JSON files ('settings.json' and 'viewed.json') + +'Torrents' data continues to remain in the BBolt database ('config.db') +due to the fact that BLOBs are stored there + +To make user be able to roll settings back, no data is deleted from 'config.db' file. +*/ +func Migrate2(bboltDB, jsonDB TorrServerDB) error { + var err error = nil + + const XPATH_SETTINGS = "Settings" + const NAME_BITTORR = "BitTorr" + const XPATH_VIEWED = "Viewed" + + if BTsets != nil { + msg := "Migrate0002 MUST be called before initializing BTSets" + log.TLogln(msg) + os.Exit(1) + } + + isByteArraysEqualJson := func(a, b []byte) (bool, error) { + var objectA interface{} + var objectB interface{} + var err error = nil + if err = json.Unmarshal(a, &objectA); err == nil { + if err = json.Unmarshal(b, &objectB); err == nil { + return reflect.DeepEqual(objectA, objectB), nil + } else { + err = fmt.Errorf("Error unmashalling B: %s", err.Error()) + } + } else { + err = fmt.Errorf("Error unmashalling A: %s", err.Error()) + } + return false, err + } + + migrateXPath := func(xPath, name string) error { + if jsonDB.Get(xPath, name) == nil { + bboltDBBlob := bboltDB.Get(xPath, name) + if bboltDBBlob != nil { + log.TLogln(fmt.Sprintf("Attempting to migrate %s->%s from TDB to JsonDB", xPath, name)) + jsonDB.Set(xPath, name, bboltDBBlob) + jsonDBBlob := jsonDB.Get(xPath, name) + if isEqual, err := isByteArraysEqualJson(bboltDBBlob, jsonDBBlob); err == nil { + if isEqual { + log.TLogln(fmt.Sprintf("Migrated %s->%s successful", xPath, name)) + } else { + msg := fmt.Sprintf("Failed to migrate %s->%s TDB to JsonDB: equality check failed", xPath, name) + log.TLogln(msg) + return errors.New(msg) + } + } else { + msg := fmt.Sprintf("Failed to migrate %s->%s TDB to JsonDB: %s", xPath, name, err) + log.TLogln(msg) + return errors.New(msg) + } + } + } + return nil + } + + if err = migrateXPath(XPATH_SETTINGS, NAME_BITTORR); err != nil { + return err + } + + jsonDBViewedNames := jsonDB.List(XPATH_VIEWED) + if len(jsonDBViewedNames) <= 0 { + bboltDBViewedNames := bboltDB.List(XPATH_VIEWED) + if len(bboltDBViewedNames) > 0 { + for _, name := range bboltDBViewedNames { + err = migrateXPath(XPATH_VIEWED, name) + if err != nil { + break + } + } + } + } + return err +} diff --git a/server/settings/settings.go b/server/settings/settings.go index 8c5906b..7e44949 100644 --- a/server/settings/settings.go +++ b/server/settings/settings.go @@ -1,6 +1,7 @@ package settings import ( + "fmt" "os" "path/filepath" @@ -8,7 +9,7 @@ import ( ) var ( - tdb *TDB + tdb TorrServerDB Path string Port string Ssl bool @@ -19,18 +20,40 @@ var ( PubIPv4 string PubIPv6 string TorAddr string + MaxSize int64 ) func InitSets(readOnly, searchWA bool) { ReadOnly = readOnly SearchWA = searchWA - tdb = NewTDB() - if tdb == nil { - log.TLogln("Error open db:", filepath.Join(Path, "config.db")) + + bboltDB := NewTDB() + if bboltDB == nil { + log.TLogln("Error open bboltDB:", filepath.Join(Path, "config.db")) + os.Exit(1) + } + + jsonDB := NewJsonDB() + if jsonDB == nil { + log.TLogln("Error open jsonDB") + os.Exit(1) + } + + dbRouter := NewXPathDBRouter() + // First registered DB becomes default route + dbRouter.RegisterRoute(jsonDB, "Settings") + dbRouter.RegisterRoute(jsonDB, "Viewed") + dbRouter.RegisterRoute(bboltDB, "Torrents") + + tdb = NewDBReadCache(dbRouter) + + // We migrate settings here, it must be done before loadBTSets() + if err := Migrate2(bboltDB, jsonDB); err != nil { + log.TLogln(fmt.Sprintf("Migrate2 failed")) os.Exit(1) } loadBTSets() - Migrate() + Migrate1() } func CloseDB() { diff --git a/server/settings/torrserverdb.go b/server/settings/torrserverdb.go new file mode 100644 index 0000000..52edc12 --- /dev/null +++ b/server/settings/torrserverdb.go @@ -0,0 +1,9 @@ +package settings + +type TorrServerDB interface { + CloseDB() + Get(xPath, name string) []byte + Set(xPath, name string, value []byte) + List(xPath string) []string + Rem(xPath, name string) +} diff --git a/server/settings/xpathdbrouter.go b/server/settings/xpathdbrouter.go new file mode 100644 index 0000000..fa9db82 --- /dev/null +++ b/server/settings/xpathdbrouter.go @@ -0,0 +1,119 @@ +package settings + +import ( + "errors" + "fmt" + "reflect" + "sort" + "strings" + + "server/log" + + "golang.org/x/exp/slices" +) + +type XPathDBRouter struct { + dbs []TorrServerDB + routes []string + route2db map[string]TorrServerDB + dbNames map[TorrServerDB]string +} + +func NewXPathDBRouter() *XPathDBRouter { + router := &XPathDBRouter{ + dbs: []TorrServerDB{}, + dbNames: map[TorrServerDB]string{}, + routes: []string{}, + route2db: map[string]TorrServerDB{}, + } + return router +} + +func (v *XPathDBRouter) RegisterRoute(db TorrServerDB, xPath string) error { + newRoute := v.xPathToRoute(xPath) + + if slices.Contains(v.routes, newRoute) { + return errors.New(fmt.Sprintf("route \"%s\" already in routing table", newRoute)) + } + + // First DB becomes Default DB with default route + if len(v.dbs) == 0 && len(newRoute) != 0 { + v.RegisterRoute(db, "") + } + + if !slices.Contains(v.dbs, db) { + v.dbs = append(v.dbs, db) + v.dbNames[db] = reflect.TypeOf(db).Elem().Name() + v.log(fmt.Sprintf("Registered new DB \"%s\", total %d DBs registered", v.getDBName(db), len(v.dbs))) + } + + v.route2db[newRoute] = db + v.routes = append(v.routes, newRoute) + + // Sort routes by length descending. + // It is important later to help selecting + // most suitable route in getDBForXPath(xPath) + sort.Slice(v.routes, func(iLeft, iRight int) bool { + return len(v.routes[iLeft]) > len(v.routes[iRight]) + }) + v.log(fmt.Sprintf("Registered new route \"%s\" for DB \"%s\", total %d routes", newRoute, v.getDBName(db), len(v.routes))) + return nil +} + +func (v *XPathDBRouter) xPathToRoute(xPath string) string { + return strings.ToLower(strings.TrimSpace(xPath)) +} + +func (v *XPathDBRouter) getDBForXPath(xPath string) TorrServerDB { + if len(v.dbs) == 0 { + return nil + } + lookup_route := v.xPathToRoute(xPath) + var db TorrServerDB = nil + // Expected v.routes sorted by length descending + for _, route_prefix := range v.routes { + if strings.HasPrefix(lookup_route, route_prefix) { + db = v.route2db[route_prefix] + break + } + } + return db +} + +func (v *XPathDBRouter) Get(xPath, name string) []byte { + return v.getDBForXPath(xPath).Get(xPath, name) +} + +func (v *XPathDBRouter) Set(xPath, name string, value []byte) { + v.getDBForXPath(xPath).Set(xPath, name, value) +} + +func (v *XPathDBRouter) List(xPath string) []string { + return v.getDBForXPath(xPath).List(xPath) +} + +func (v *XPathDBRouter) Rem(xPath, name string) { + v.getDBForXPath(xPath).Rem(xPath, name) +} + +func (v *XPathDBRouter) CloseDB() { + for _, db := range v.dbs { + db.CloseDB() + } + v.dbs = nil + v.routes = nil + v.route2db = nil + v.dbNames = nil +} + +func (v *XPathDBRouter) getDBName(db TorrServerDB) string { + return v.dbNames[db] +} + +func (v *XPathDBRouter) log(s string, params ...interface{}) { + if len(params) > 0 { + log.TLogln(fmt.Sprintf("XPathDBRouter: %s: %s", s, fmt.Sprint(params...))) + } else { + log.TLogln(fmt.Sprintf("XPathDBRouter: %s", s)) + } +} diff --git a/server/torr/apihelper.go b/server/torr/apihelper.go index 3a005d7..60a9e33 100644 --- a/server/torr/apihelper.go +++ b/server/torr/apihelper.go @@ -150,19 +150,24 @@ func SetTorrent(hashHex, title, poster, data string) *Torrent { } func RemTorrent(hashHex string) { + if sets.ReadOnly { + log.TLogln("API RemTorrent: Read-only DB mode!", hashHex) + return + } hash := metainfo.NewHashFromHex(hashHex) - if sets.BTsets.UseDisk && hashHex != "" && hashHex != "/" { - name := filepath.Join(sets.BTsets.TorrentsSavePath, hashHex) - ff, _ := os.ReadDir(name) - for _, f := range ff { - os.Remove(filepath.Join(name, f.Name())) - } - err := os.Remove(name) - if err != nil { - log.TLogln("Error remove cache:", err) + if bts.RemoveTorrent(hash) { + if sets.BTsets.UseDisk && hashHex != "" && hashHex != "/" { + name := filepath.Join(sets.BTsets.TorrentsSavePath, hashHex) + ff, _ := os.ReadDir(name) + for _, f := range ff { + os.Remove(filepath.Join(name, f.Name())) + } + err := os.Remove(name) + if err != nil { + log.TLogln("Error remove cache:", err) + } } } - bts.RemoveTorrent(hash) RemTorrentDB(hash) } @@ -199,6 +204,7 @@ func DropTorrent(hashHex string) { func SetSettings(set *sets.BTSets) { if sets.ReadOnly { + log.TLogln("API SetSettings: Read-only DB mode!") return } sets.SetBTSets(set) @@ -215,6 +221,7 @@ func SetSettings(set *sets.BTSets) { func SetDefSettings() { if sets.ReadOnly { + log.TLogln("API SetDefSettings: Read-only DB mode!") return } sets.SetDefaultConfig() diff --git a/server/torr/btserver.go b/server/torr/btserver.go index 0878695..30e0a64 100644 --- a/server/torr/btserver.go +++ b/server/torr/btserver.go @@ -88,14 +88,13 @@ func (bt *BTServer) configure(ctx context.Context) { upnpID := "TorrServer/" + version.Version cliVers := userAgent - // bt.config.AlwaysWantConns = true bt.config.Debug = settings.BTsets.EnableDebug bt.config.DisableIPv6 = !settings.BTsets.EnableIPv6 bt.config.DisableTCP = settings.BTsets.DisableTCP bt.config.DisableUTP = settings.BTsets.DisableUTP // https://github.com/anacrolix/torrent/issues/703 - // bt.config.DisableWebtorrent = true // TODO: check memory usage - // bt.config.DisableWebseeds = false + // bt.config.DisableWebtorrent = true // NE + // bt.config.DisableWebseeds = false // NE bt.config.NoDefaultPortForwarding = settings.BTsets.DisableUPNP bt.config.NoDHT = settings.BTsets.DisableDHT bt.config.DisablePEX = settings.BTsets.DisablePEX @@ -109,13 +108,13 @@ func (bt *BTServer) configure(ctx context.Context) { bt.config.EstablishedConnsPerTorrent = settings.BTsets.ConnectionsLimit bt.config.TotalHalfOpenConns = 500 // Encryption/Obfuscation - bt.config.EncryptionPolicy = torrent.EncryptionPolicy{ - ForceEncryption: settings.BTsets.ForceEncrypt, - } - // bt.config.HeaderObfuscationPolicy = torrent.HeaderObfuscationPolicy{ - // RequirePreferred: settings.BTsets.ForceEncrypt, - // Preferred: true, - // } + bt.config.EncryptionPolicy = torrent.EncryptionPolicy{ // OE + ForceEncryption: settings.BTsets.ForceEncrypt, // OE + } // OE + // bt.config.HeaderObfuscationPolicy = torrent.HeaderObfuscationPolicy{ // NE + // RequirePreferred: settings.BTsets.ForceEncrypt, // NE + // Preferred: true, // NE + // } // NE if settings.BTsets.DownloadRateLimit > 0 { bt.config.DownloadRateLimiter = utils.Limit(settings.BTsets.DownloadRateLimit * 1024) } @@ -130,21 +129,8 @@ func (bt *BTServer) configure(ctx context.Context) { log.Println("Set listen port", settings.BTsets.PeersListenPort) bt.config.ListenPort = settings.BTsets.PeersListenPort } else { - // lport := 32000 - // for { - // log.Println("Check listen port", lport) - // l, err := net.Listen("tcp", ":"+strconv.Itoa(lport)) - // if l != nil { - // l.Close() - // } - // if err == nil { - // break - // } - // lport++ - // } - // log.Println("Set listen port", lport) log.Println("Set listen port to random autoselect (0)") - bt.config.ListenPort = 0 // lport + bt.config.ListenPort = 0 } } @@ -200,10 +186,11 @@ func (bt *BTServer) ListTorrents() map[metainfo.Hash]*Torrent { return list } -func (bt *BTServer) RemoveTorrent(hash torrent.InfoHash) { +func (bt *BTServer) RemoveTorrent(hash torrent.InfoHash) bool { if torr, ok := bt.torrents[hash]; ok { - torr.Close() + return torr.Close() } + return false } func isPrivateIP(ip net.IP) bool { diff --git a/server/torr/preload.go b/server/torr/preload.go index ac18d2b..1b83309 100644 --- a/server/torr/preload.go +++ b/server/torr/preload.go @@ -62,7 +62,7 @@ func (t *Torrent) Preload(index int, size int64) { // Запуск лога в отдельном потоке go func() { for t.Stat == state.TorrentPreload { - stat := fmt.Sprint(file.Torrent().InfoHash().HexString(), " ", utils2.Format(float64(t.PreloadedBytes)), "/", utils2.Format(float64(t.PreloadSize)), " Speed:", utils2.Format(t.DownloadSpeed), " Peers:[", t.Torrent.Stats().ConnectedSeeders, "]", t.Torrent.Stats().ActivePeers, "/", t.Torrent.Stats().TotalPeers) + stat := fmt.Sprint(file.Torrent().InfoHash().HexString(), " ", utils2.Format(float64(t.PreloadedBytes)), "/", utils2.Format(float64(t.PreloadSize)), " Speed:", utils2.Format(t.DownloadSpeed), " Peers:", t.Torrent.Stats().ActivePeers, "/", t.Torrent.Stats().TotalPeers, " [Seeds:", t.Torrent.Stats().ConnectedSeeders, "]") log.TLogln("Preload:", stat) t.AddExpiredTime(time.Second * time.Duration(settings.BTsets.TorrentDisconnectTimeout)) time.Sleep(time.Second) @@ -158,7 +158,7 @@ func (t *Torrent) Preload(index int, size int64) { wg.Wait() } - log.TLogln("End preload:", file.Torrent().InfoHash().HexString(), "Peers:[", t.Torrent.Stats().ConnectedSeeders, "]", t.Torrent.Stats().ActivePeers, "/", t.Torrent.Stats().TotalPeers) + log.TLogln("End preload:", file.Torrent().InfoHash().HexString(), "Peers:", t.Torrent.Stats().ActivePeers, "/", t.Torrent.Stats().TotalPeers, "[ Seeds:", t.Torrent.Stats().ConnectedSeeders, "]") } func (t *Torrent) findFileIndex(index int) *torrent.File { diff --git a/server/torr/storage/torrstor/cache.go b/server/torr/storage/torrstor/cache.go index 5a16480..3019570 100644 --- a/server/torr/storage/torrstor/cache.go +++ b/server/torr/storage/torrstor/cache.go @@ -314,6 +314,21 @@ func (c *Cache) NewReader(file *torrent.File) *Reader { return newReader(file, c) } +func (c *Cache) GetUseReaders() int { + if c == nil { + return 0 + } + c.muReaders.Lock() + defer c.muReaders.Unlock() + readers := 0 + for reader := range c.readers { + if reader.isUse { + readers++ + } + } + return readers +} + func (c *Cache) Readers() int { if c == nil { return 0 diff --git a/server/torr/storage/torrstor/storage.go b/server/torr/storage/torrstor/storage.go index 31b23bb..195fbb7 100644 --- a/server/torr/storage/torrstor/storage.go +++ b/server/torr/storage/torrstor/storage.go @@ -25,20 +25,20 @@ func NewStorage(capacity int64) *Storage { } func (s *Storage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (ts.TorrentImpl, error) { - // capFunc := func() (int64, bool) { - // return s.capacity, true - // } + // capFunc := func() (int64, bool) { // NE + // return s.capacity, true // NE + // } // NE s.mu.Lock() defer s.mu.Unlock() ch := NewCache(s.capacity, s) ch.Init(info, infoHash) s.caches[infoHash] = ch - return ch, nil - // return ts.TorrentImpl{ - // Piece: ch.Piece, - // Close: ch.Close, - // Capacity: &capFunc, - // }, nil + return ch, nil // OE + // return ts.TorrentImpl{ // NE + // Piece: ch.Piece, // NE + // Close: ch.Close, // NE + // Capacity: &capFunc, // NE + // }, nil // NE } func (s *Storage) CloseHash(hash metainfo.Hash) { diff --git a/server/torr/stream.go b/server/torr/stream.go index 665b98f..240f771 100644 --- a/server/torr/stream.go +++ b/server/torr/stream.go @@ -10,7 +10,7 @@ import ( "time" "github.com/anacrolix/dms/dlna" - "github.com/anacrolix/missinggo/httptoo" + "github.com/anacrolix/missinggo/v2/httptoo" "github.com/anacrolix/torrent" mt "server/mimetype" @@ -47,6 +47,10 @@ func (t *Torrent) Stream(fileID int, req *http.Request, resp http.ResponseWriter if file == nil { return fmt.Errorf("file with id %v not found", fileID) } + if int64(sets.MaxSize) > 0 && file.Length() > int64(sets.MaxSize) { + log.Println("file", file.DisplayPath(), "size exceeded max allowed", sets.MaxSize, "bytes") + return fmt.Errorf("file size exceeded max allowed %d bytes", sets.MaxSize) + } reader := t.NewReader(file) diff --git a/server/torr/torrent.go b/server/torr/torrent.go index d28fa76..278db2f 100644 --- a/server/torr/torrent.go +++ b/server/torr/torrent.go @@ -6,6 +6,8 @@ import ( "sync" "time" + utils2 "server/utils" + "github.com/anacrolix/torrent" "github.com/anacrolix/torrent/metainfo" @@ -266,7 +268,10 @@ func (t *Torrent) drop() { } } -func (t *Torrent) Close() { +func (t *Torrent) Close() bool { + if t.cache != nil && t.cache.GetUseReaders() > 0 { + return false + } t.Stat = state.TorrentClosed t.bt.mu.Lock() @@ -274,6 +279,7 @@ func (t *Torrent) Close() { t.bt.mu.Unlock() t.drop() + return true } func (t *Torrent) Status() *state.TorrentStatus { @@ -329,7 +335,7 @@ func (t *Torrent) Status() *state.TorrentStatus { files := t.Files() sort.Slice(files, func(i, j int) bool { - return files[i].Path() < files[j].Path() + return utils2.CompareStrings(files[i].Path(), files[j].Path()) }) for i, f := range files { st.FileStats = append(st.FileStats, &state.TorrentFileStat{ diff --git a/server/utils/strings.go b/server/utils/strings.go index 0660520..5767f54 100644 --- a/server/utils/strings.go +++ b/server/utils/strings.go @@ -3,6 +3,8 @@ package utils import ( "fmt" "strconv" + "strings" + "unicode" ) const ( @@ -46,3 +48,52 @@ func Format(b float64) string { return fmt.Sprintf("%.2f%s", value, multiple) } + +func CommonPrefix(first, second string) string { + var result strings.Builder + + minLength := len(first) + if len(second) < minLength { + minLength = len(second) + } + + for i := 0; i < minLength; i++ { + if first[i] != second[i] { + break + } + result.WriteByte(first[i]) + } + + return result.String() +} + +func NumberPrefix(str string) (int, error) { + var result strings.Builder + + for i := 0; i < len(str); i++ { + if !unicode.IsDigit(rune(str[i])) { + break + } + result.WriteByte(str[i]) + } + + return strconv.Atoi(result.String()) +} + +func CompareStrings(first, second string) bool { + commonPrefix := CommonPrefix(first, second) + resultStr1 := strings.TrimPrefix(first, commonPrefix) + resultStr2 := strings.TrimPrefix(second, commonPrefix) + num1, err1 := NumberPrefix(resultStr1) + num2, err2 := NumberPrefix(resultStr2) + + if err1 == nil && err2 == nil { + return num1 < num2 + } + if err1 == nil { + return true + } else if err2 == nil { + return false + } + return resultStr1 < resultStr2 +} diff --git a/server/version/version.go b/server/version/version.go index dc7537a..1ae6008 100644 --- a/server/version/version.go +++ b/server/version/version.go @@ -6,7 +6,7 @@ import ( // "github.com/anacrolix/torrent" ) -const Version = "MatriX.127.2" +const Version = "MatriX.130" func GetTorrentVersion() string { bi, ok := debug.ReadBuildInfo() diff --git a/server/web/api/download.go b/server/web/api/download.go index 9610643..06893d4 100644 --- a/server/web/api/download.go +++ b/server/web/api/download.go @@ -47,7 +47,7 @@ func (f *fileReader) Seek(offset int64, whence int) (int64, error) { // // @Tags API // -// @Param size path string true "Test file size" +// @Param size path string true "Test file size (in MB)" // // @Produce application/octet-stream // @Success 200 {file} file diff --git a/server/web/api/ffprobe.go b/server/web/api/ffprobe.go index 423a619..5a7f2ce 100644 --- a/server/web/api/ffprobe.go +++ b/server/web/api/ffprobe.go @@ -18,12 +18,12 @@ import ( // // @Tags API // -// @Param hash query string true "Torrent hash" -// @Param id query string true "File index in torrent" +// @Param hash path string true "Torrent hash" +// @Param id path string true "File index in torrent" // // @Produce json // @Success 200 "Data returned from ffprobe" -// @Router /ffp [get] +// @Router /ffp/{hash}/{id} [get] func ffp(c *gin.Context) { hash := c.Param("hash") indexStr := c.Param("id") diff --git a/server/web/api/m3u.go b/server/web/api/m3u.go index b01ad27..726ad83 100644 --- a/server/web/api/m3u.go +++ b/server/web/api/m3u.go @@ -11,7 +11,7 @@ import ( "strings" "time" - "github.com/anacrolix/missinggo/httptoo" + "github.com/anacrolix/missinggo/v2/httptoo" sets "server/settings" "server/torr" diff --git a/server/web/api/play.go b/server/web/api/play.go index dae7298..a17dbc7 100644 --- a/server/web/api/play.go +++ b/server/web/api/play.go @@ -19,17 +19,16 @@ import ( // // @Tags API // -// @Param hash query string true "Torrent hash" -// @Param id query string true "File index in torrent" -// @Param not_auth query bool false "Not authenticated" +// @Param hash path string true "Torrent hash" +// @Param id path string true "File index in torrent" // // @Produce application/octet-stream // @Success 200 "Torrent data" -// @Router /play [get] +// @Router /play/{hash}/{id} [get] func play(c *gin.Context) { hash := c.Param("hash") indexStr := c.Param("id") - notAuth := c.GetBool("not_auth") + notAuth := c.GetBool("auth_required") && c.GetString(gin.AuthUserKey) == "" if hash == "" || indexStr == "" { c.AbortWithError(http.StatusNotFound, errors.New("link should not be empty")) diff --git a/server/web/api/route.go b/server/web/api/route.go index 53c8ebe..731f874 100644 --- a/server/web/api/route.go +++ b/server/web/api/route.go @@ -1,6 +1,9 @@ package api import ( + config "server/settings" + "server/web/auth" + "github.com/gin-gonic/gin" ) @@ -8,15 +11,17 @@ type requestI struct { Action string `json:"action,omitempty"` } -func SetupRoute(route *gin.RouterGroup) { - route.GET("/shutdown", shutdown) +func SetupRoute(route gin.IRouter) { + authorized := route.Group("/", auth.CheckAuth()) - route.POST("/settings", settings) + authorized.GET("/shutdown", shutdown) - route.POST("/torrents", torrents) - route.POST("/torrent/upload", torrentUpload) + authorized.POST("/settings", settings) - route.POST("/cache", cache) + authorized.POST("/torrents", torrents) + authorized.POST("/torrent/upload", torrentUpload) + + authorized.POST("/cache", cache) route.HEAD("/stream", stream) route.HEAD("/stream/*fname", stream) @@ -27,15 +32,19 @@ func SetupRoute(route *gin.RouterGroup) { route.HEAD("/play/:hash/:id", play) route.GET("/play/:hash/:id", play) - route.POST("/viewed", viewed) + authorized.POST("/viewed", viewed) - route.GET("/playlistall/all.m3u", allPlayList) + authorized.GET("/playlistall/all.m3u", allPlayList) route.GET("/playlist", playList) route.GET("/playlist/*fname", playList) // Is this endpoint still needed ? `fname` is never used in handler - route.GET("/download/:size", download) + authorized.GET("/download/:size", download) - route.GET("/search/*query", rutorSearch) + if config.SearchWA { + route.GET("/search/*query", rutorSearch) + } else { + authorized.GET("/search/*query", rutorSearch) + } - route.GET("/ffp/:hash/:id", ffp) + authorized.GET("/ffp/:hash/:id", ffp) } diff --git a/server/web/api/stream.go b/server/web/api/stream.go index ca5aed6..7c9cc9c 100644 --- a/server/web/api/stream.go +++ b/server/web/api/stream.go @@ -41,11 +41,10 @@ import ( // @Param stat query string false "Get statistics from torrent" // @Param save query string false "Should save torrent" // @Param m3u query string false "Get torrent as M3U playlist" -// @Param fromlast query string false "Get m3u from last play" +// @Param fromlast query string false "Get M3U from last played file" // @Param play query string false "Start stream torrent" // @Param title query string true "Set title of torrent" -// @Param poster query string true "File index in torrent" -// @Param not_auth query string true "Set poster link of torrent" +// @Param poster query string true "Set poster link of torrent" // // @Produce application/octet-stream // @Success 200 "Data returned according to query" @@ -62,7 +61,7 @@ func stream(c *gin.Context) { title := c.Query("title") poster := c.Query("poster") data := "" - notAuth := c.GetBool("not_auth") + notAuth := c.GetBool("auth_required") && c.GetString(gin.AuthUserKey) == "" if notAuth && (play || m3u) { streamNoAuth(c) diff --git a/server/web/api/torrents.go b/server/web/api/torrents.go index 1559329..40811e7 100644 --- a/server/web/api/torrents.go +++ b/server/web/api/torrents.go @@ -73,6 +73,10 @@ func torrents(c *gin.Context) { { dropTorrent(req, c) } + case "wipe": + { + wipeTorrents(req, c) + } } } @@ -188,3 +192,16 @@ func dropTorrent(req torrReqJS, c *gin.Context) { torr.DropTorrent(req.Hash) c.Status(200) } + +func wipeTorrents(req torrReqJS, c *gin.Context) { + torrents := torr.ListTorrent() + for _, t := range torrents { + torr.RemTorrent(t.TorrentSpec.InfoHash.HexString()) + } + // TODO: remove (copied todo from remTorrent()) + if set.BTsets.EnableDLNA { + dlna.Stop() + dlna.Start() + } + c.Status(200) +} diff --git a/server/web/auth/auth.go b/server/web/auth/auth.go index cb5eb08..d810240 100644 --- a/server/web/auth/auth.go +++ b/server/web/auth/auth.go @@ -6,8 +6,6 @@ import ( "net/http" "os" "path/filepath" - "reflect" - "strings" "unsafe" "github.com/gin-gonic/gin" @@ -16,15 +14,15 @@ import ( "server/settings" ) -func SetupAuth(engine *gin.Engine) *gin.RouterGroup { +func SetupAuth(engine *gin.Engine) { if !settings.HttpAuth { - return nil + return } accs := getAccounts() if accs == nil { - return nil + return } - return engine.Group("/", BasicAuth(accs)) + engine.Use(BasicAuth(accs)) } func getAccounts() gin.Accounts { @@ -61,21 +59,27 @@ func (a authPairs) searchCredential(authValue string) (string, bool) { func BasicAuth(accounts gin.Accounts) gin.HandlerFunc { pairs := processAccounts(accounts) return func(c *gin.Context) { + c.Set("auth_required", true) + user, found := pairs.searchCredential(c.Request.Header.Get("Authorization")) - if !found { // always accessible - if strings.HasPrefix(c.FullPath(), "/stream") || - c.FullPath() == "/site.webmanifest" || - // https://github.com/YouROK/TorrServer/issues/172 - (strings.HasPrefix(c.FullPath(), "/play") && c.FullPath() != "/playlistall/all.m3u") || - (settings.SearchWA && strings.HasPrefix(c.FullPath(), "/search")) { - c.Set("not_auth", true) - return - } - c.Header("WWW-Authenticate", "Basic realm=Authorization Required") - c.AbortWithStatus(http.StatusUnauthorized) + if found { + c.Set(gin.AuthUserKey, user) + } + } +} + +func CheckAuth() gin.HandlerFunc { + return func(c *gin.Context) { + if !settings.HttpAuth { return } - c.Set(gin.AuthUserKey, user) + + if _, ok := c.Get(gin.AuthUserKey); ok { + return + } + + c.Header("WWW-Authenticate", "Basic realm=Authorization Required") + c.AbortWithStatus(http.StatusUnauthorized) } } @@ -97,8 +101,5 @@ func authorizationHeader(user, password string) string { } func StringToBytes(s string) (b []byte) { - sh := *(*reflect.StringHeader)(unsafe.Pointer(&s)) - bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) - bh.Data, bh.Len, bh.Cap = sh.Data, sh.Len, sh.Len - return b + return unsafe.Slice(unsafe.StringData(s), len(s)) } diff --git a/server/web/msx/msx.go b/server/web/msx/msx.go index 6e0ff56..9b99511 100644 --- a/server/web/msx/msx.go +++ b/server/web/msx/msx.go @@ -7,6 +7,7 @@ import ( "sync" "server/version" + "server/web/auth" "github.com/gin-gonic/gin" ) @@ -30,10 +31,12 @@ func asset(c *gin.Context, t string, d []byte) { c.Data(200, t+"; charset=UTF-8", d) } -func SetupRoute(r *gin.RouterGroup) { - r.GET("/msx/:pth", msxPTH) - r.GET("/msx/imdb", msxIMDB) - r.GET("/msx/imdb/:id", msxIMDBID) +func SetupRoute(r gin.IRouter) { + authorized := r.Group("/", auth.CheckAuth()) + + authorized.GET("/msx/:pth", msxPTH) + authorized.GET("/msx/imdb", msxIMDB) + authorized.GET("/msx/imdb/:id", msxIMDBID) } // msxPTH godoc @@ -43,11 +46,11 @@ func SetupRoute(r *gin.RouterGroup) { // // @Tags MSX // -// @Param link query string true "Magnet/hash/link to torrent" +// @Param link path string true "Route MSX pages" // // @Produce json -// @Success 200 "Data returned according to query" -// @Router /msx [get] +// @Success 200 "Data returned according to path" +// @Router /msx/{pth} [get] func msxPTH(c *gin.Context) { js := []string{"http://msx.benzac.de/js/tvx-plugin.min.js"} switch p := c.Param("pth"); p { @@ -114,7 +117,7 @@ func msxIMDB(c *gin.Context) { // // @Produce json // @Success 200 "JSON MSX IMDB informations" -// @Router /msx/imdb/:id [get] +// @Router /msx/imdb/{id} [get] func msxIMDBID(c *gin.Context) { idb.Lock() defer idb.Unlock() diff --git a/server/web/pages/route.go b/server/web/pages/route.go index 90a5090..3c2ad8f 100644 --- a/server/web/pages/route.go +++ b/server/web/pages/route.go @@ -6,13 +6,27 @@ import ( "server/settings" "server/torr" + "server/web/auth" "server/web/pages/template" + + "golang.org/x/exp/slices" ) -func SetupRoute(route *gin.RouterGroup) { - template.RouteWebPages(route) - route.GET("/stat", statPage) - route.GET("/magnets", getTorrents) +func SetupRoute(route gin.IRouter) { + authorized := route.Group("/", auth.CheckAuth()) + + webPagesAuth := route.Group("/", func() gin.HandlerFunc { + return func(c *gin.Context) { + if slices.Contains([]string{"/site.webmanifest"}, c.FullPath()) { + return + } + auth.CheckAuth()(c) + } + }()) + + template.RouteWebPages(webPagesAuth) + authorized.GET("/stat", statPage) + authorized.GET("/magnets", getTorrents) } // stat godoc diff --git a/server/web/pages/template/html.go b/server/web/pages/template/html.go index a4b615b..13672a6 100644 --- a/server/web/pages/template/html.go +++ b/server/web/pages/template/html.go @@ -118,20 +118,20 @@ var Mstile150x150png []byte //go:embed pages/site.webmanifest var Sitewebmanifest []byte -//go:embed pages/static/js/2.41a752aa.chunk.js -var Staticjs241a752aachunkjs []byte +//go:embed pages/static/js/2.916c2545.chunk.js +var Staticjs2916c2545chunkjs []byte -//go:embed pages/static/js/2.41a752aa.chunk.js.LICENSE.txt -var Staticjs241a752aachunkjsLICENSEtxt []byte +//go:embed pages/static/js/2.916c2545.chunk.js.LICENSE.txt +var Staticjs2916c2545chunkjsLICENSEtxt []byte -//go:embed pages/static/js/2.41a752aa.chunk.js.map -var Staticjs241a752aachunkjsmap []byte +//go:embed pages/static/js/2.916c2545.chunk.js.map +var Staticjs2916c2545chunkjsmap []byte -//go:embed pages/static/js/main.b1d76117.chunk.js -var Staticjsmainb1d76117chunkjs []byte +//go:embed pages/static/js/main.55f380e1.chunk.js +var Staticjsmain55f380e1chunkjs []byte -//go:embed pages/static/js/main.b1d76117.chunk.js.map -var Staticjsmainb1d76117chunkjsmap []byte +//go:embed pages/static/js/main.55f380e1.chunk.js.map +var Staticjsmain55f380e1chunkjsmap []byte //go:embed pages/static/js/runtime-main.f542387e.js var Staticjsruntimemainf542387ejs []byte diff --git a/server/web/pages/template/pages/asset-manifest.json b/server/web/pages/template/pages/asset-manifest.json index dae95cc..c6b5e05 100644 --- a/server/web/pages/template/pages/asset-manifest.json +++ b/server/web/pages/template/pages/asset-manifest.json @@ -1,17 +1,17 @@ { "files": { - "main.js": "/static/js/main.b1d76117.chunk.js", - "main.js.map": "/static/js/main.b1d76117.chunk.js.map", + "main.js": "/static/js/main.55f380e1.chunk.js", + "main.js.map": "/static/js/main.55f380e1.chunk.js.map", "runtime-main.js": "/static/js/runtime-main.f542387e.js", "runtime-main.js.map": "/static/js/runtime-main.f542387e.js.map", - "static/js/2.41a752aa.chunk.js": "/static/js/2.41a752aa.chunk.js", - "static/js/2.41a752aa.chunk.js.map": "/static/js/2.41a752aa.chunk.js.map", + "static/js/2.916c2545.chunk.js": "/static/js/2.916c2545.chunk.js", + "static/js/2.916c2545.chunk.js.map": "/static/js/2.916c2545.chunk.js.map", "index.html": "/index.html", - "static/js/2.41a752aa.chunk.js.LICENSE.txt": "/static/js/2.41a752aa.chunk.js.LICENSE.txt" + "static/js/2.916c2545.chunk.js.LICENSE.txt": "/static/js/2.916c2545.chunk.js.LICENSE.txt" }, "entrypoints": [ "static/js/runtime-main.f542387e.js", - "static/js/2.41a752aa.chunk.js", - "static/js/main.b1d76117.chunk.js" + "static/js/2.916c2545.chunk.js", + "static/js/main.55f380e1.chunk.js" ] } \ No newline at end of file diff --git a/server/web/pages/template/pages/index.html b/server/web/pages/template/pages/index.html index 77289f0..b489fb1 100644 --- a/server/web/pages/template/pages/index.html +++ b/server/web/pages/template/pages/index.html @@ -1 +1 @@ -TorrServer MatriX
\ No newline at end of file +TorrServer MatriX
\ No newline at end of file diff --git a/server/web/pages/template/route.go b/server/web/pages/template/route.go index ac40164..3800b6f 100644 --- a/server/web/pages/template/route.go +++ b/server/web/pages/template/route.go @@ -1,191 +1,331 @@ package template import ( + "crypto/md5" + "fmt" "github.com/gin-gonic/gin" ) -func RouteWebPages(route *gin.RouterGroup) { +func RouteWebPages(route gin.IRouter) { route.GET("/", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Indexhtml)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "text/html; charset=utf-8", Indexhtml) }) route.GET("/apple-splash-1125-2436.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash11252436jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash11252436jpg) }) route.GET("/apple-splash-1136-640.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash1136640jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash1136640jpg) }) route.GET("/apple-splash-1170-2532.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash11702532jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash11702532jpg) }) route.GET("/apple-splash-1242-2208.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash12422208jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash12422208jpg) }) route.GET("/apple-splash-1242-2688.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash12422688jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash12422688jpg) }) route.GET("/apple-splash-1284-2778.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash12842778jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash12842778jpg) }) route.GET("/apple-splash-1334-750.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash1334750jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash1334750jpg) }) route.GET("/apple-splash-1536-2048.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash15362048jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash15362048jpg) }) route.GET("/apple-splash-1620-2160.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash16202160jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash16202160jpg) }) route.GET("/apple-splash-1668-2224.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash16682224jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash16682224jpg) }) route.GET("/apple-splash-1668-2388.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash16682388jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash16682388jpg) }) route.GET("/apple-splash-1792-828.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash1792828jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash1792828jpg) }) route.GET("/apple-splash-2048-1536.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash20481536jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash20481536jpg) }) route.GET("/apple-splash-2048-2732.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash20482732jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash20482732jpg) }) route.GET("/apple-splash-2160-1620.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash21601620jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash21601620jpg) }) route.GET("/apple-splash-2208-1242.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash22081242jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash22081242jpg) }) route.GET("/apple-splash-2224-1668.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash22241668jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash22241668jpg) }) route.GET("/apple-splash-2388-1668.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash23881668jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash23881668jpg) }) route.GET("/apple-splash-2436-1125.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash24361125jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash24361125jpg) }) route.GET("/apple-splash-2532-1170.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash25321170jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash25321170jpg) }) route.GET("/apple-splash-2688-1242.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash26881242jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash26881242jpg) }) route.GET("/apple-splash-2732-2048.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash27322048jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash27322048jpg) }) route.GET("/apple-splash-2778-1284.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash27781284jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash27781284jpg) }) route.GET("/apple-splash-640-1136.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash6401136jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash6401136jpg) }) route.GET("/apple-splash-750-1334.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash7501334jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash7501334jpg) }) route.GET("/apple-splash-828-1792.jpg", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Applesplash8281792jpg)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/jpeg", Applesplash8281792jpg) }) route.GET("/asset-manifest.json", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Assetmanifestjson)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "application/json", Assetmanifestjson) }) route.GET("/browserconfig.xml", func(c *gin.Context) { - c.Data(200, "text/xml; charset=utf-8", Browserconfigxml) + etag := fmt.Sprintf("%x", md5.Sum(Browserconfigxml)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) + c.Data(200, "application/xml; charset=utf-8", Browserconfigxml) }) route.GET("/dlnaicon-120.png", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Dlnaicon120png)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/png", Dlnaicon120png) }) route.GET("/dlnaicon-48.png", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Dlnaicon48png)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/png", Dlnaicon48png) }) route.GET("/favicon-16x16.png", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Favicon16x16png)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/png", Favicon16x16png) }) route.GET("/favicon-32x32.png", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Favicon32x32png)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/png", Favicon32x32png) }) route.GET("/favicon.ico", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Faviconico)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/vnd.microsoft.icon", Faviconico) }) route.GET("/icon.png", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Iconpng)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/png", Iconpng) }) route.GET("/index.html", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Indexhtml)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "text/html; charset=utf-8", Indexhtml) }) route.GET("/logo.png", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Logopng)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/png", Logopng) }) route.GET("/mstile-150x150.png", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Mstile150x150png)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "image/png", Mstile150x150png) }) route.GET("/site.webmanifest", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Sitewebmanifest)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "application/manifest+json", Sitewebmanifest) }) - route.GET("/static/js/2.41a752aa.chunk.js", func(c *gin.Context) { - c.Data(200, "text/javascript; charset=utf-8", Staticjs241a752aachunkjs) + route.GET("/static/js/2.916c2545.chunk.js", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Staticjs2916c2545chunkjs)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) + c.Data(200, "application/javascript; charset=utf-8", Staticjs2916c2545chunkjs) }) - route.GET("/static/js/2.41a752aa.chunk.js.LICENSE.txt", func(c *gin.Context) { - c.Data(200, "text/plain; charset=utf-8", Staticjs241a752aachunkjsLICENSEtxt) + route.GET("/static/js/2.916c2545.chunk.js.LICENSE.txt", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Staticjs2916c2545chunkjsLICENSEtxt)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) + c.Data(200, "text/plain; charset=utf-8", Staticjs2916c2545chunkjsLICENSEtxt) }) - route.GET("/static/js/2.41a752aa.chunk.js.map", func(c *gin.Context) { - c.Data(200, "application/json", Staticjs241a752aachunkjsmap) + route.GET("/static/js/2.916c2545.chunk.js.map", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Staticjs2916c2545chunkjsmap)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) + c.Data(200, "application/json", Staticjs2916c2545chunkjsmap) }) - route.GET("/static/js/main.b1d76117.chunk.js", func(c *gin.Context) { - c.Data(200, "text/javascript; charset=utf-8", Staticjsmainb1d76117chunkjs) + route.GET("/static/js/main.55f380e1.chunk.js", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Staticjsmain55f380e1chunkjs)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) + c.Data(200, "application/javascript; charset=utf-8", Staticjsmain55f380e1chunkjs) }) - route.GET("/static/js/main.b1d76117.chunk.js.map", func(c *gin.Context) { - c.Data(200, "application/json", Staticjsmainb1d76117chunkjsmap) + route.GET("/static/js/main.55f380e1.chunk.js.map", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Staticjsmain55f380e1chunkjsmap)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) + c.Data(200, "application/json", Staticjsmain55f380e1chunkjsmap) }) route.GET("/static/js/runtime-main.f542387e.js", func(c *gin.Context) { - c.Data(200, "text/javascript; charset=utf-8", Staticjsruntimemainf542387ejs) + etag := fmt.Sprintf("%x", md5.Sum(Staticjsruntimemainf542387ejs)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) + c.Data(200, "application/javascript; charset=utf-8", Staticjsruntimemainf542387ejs) }) route.GET("/static/js/runtime-main.f542387e.js.map", func(c *gin.Context) { + etag := fmt.Sprintf("%x", md5.Sum(Staticjsruntimemainf542387ejsmap)) + c.Header("Cache-Control", "public, max-age=31536000") + c.Header("ETag", etag) c.Data(200, "application/json", Staticjsruntimemainf542387ejsmap) }) } diff --git a/server/web/server.go b/server/web/server.go index 69aa3f9..873e5b6 100644 --- a/server/web/server.go +++ b/server/web/server.go @@ -71,19 +71,14 @@ func Start() { route := gin.New() route.Use(log.WebLogger(), blocker.Blocker(), gin.Recovery(), cors.New(corsCfg), location.Default()) + auth.SetupAuth(route) route.GET("/echo", echo) - routeAuth := auth.SetupAuth(route) - if routeAuth != nil { - api.SetupRoute(routeAuth) - msx.SetupRoute(routeAuth) - pages.SetupRoute(routeAuth) - } else { - api.SetupRoute(&route.RouterGroup) - msx.SetupRoute(&route.RouterGroup) - pages.SetupRoute(&route.RouterGroup) - } + api.SetupRoute(route) + msx.SetupRoute(route) + pages.SetupRoute(route) + if settings.BTsets.EnableDLNA { dlna.Start() } diff --git a/web/src/components/About/index.jsx b/web/src/components/About/index.jsx index 20fc7cf..70cd2b6 100644 --- a/web/src/components/About/index.jsx +++ b/web/src/components/About/index.jsx @@ -85,13 +85,16 @@ export default function AboutDialog() { + - - + + + + diff --git a/web/src/components/Add/LeftSideComponent.jsx b/web/src/components/Add/LeftSideComponent.jsx index 7bb785a..8f4289c 100644 --- a/web/src/components/Add/LeftSideComponent.jsx +++ b/web/src/components/Add/LeftSideComponent.jsx @@ -39,7 +39,11 @@ export default function LeftSideComponent({ } const [isTorrentSourceActive, setIsTorrentSourceActive] = useState(false) - const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop: handleCapture, accept: '.torrent' }) + const { getRootProps, getInputProps, isDragActive } = useDropzone({ + onDrop: handleCapture, + accept: '.torrent', + multiple: false, + }) const handleTorrentSourceChange = ({ target: { value } }) => setTorrentSource(value) diff --git a/web/src/components/CloseServer.jsx b/web/src/components/CloseServer.jsx index 160c1d9..d444ed5 100644 --- a/web/src/components/CloseServer.jsx +++ b/web/src/components/CloseServer.jsx @@ -1,12 +1,14 @@ import { useState } from 'react' import { Button, DialogActions, DialogTitle, ListItemIcon, ListItemText } from '@material-ui/core' import { StyledDialog, StyledMenuButtonWrapper } from 'style/CustomMaterialUiStyles' -import { PowerSettingsNew as PowerSettingsNewIcon } from '@material-ui/icons' +import { PowerSettingsNew as PowerSettingsNewIcon, PowerOff as PowerOffIcon } from '@material-ui/icons' import { shutdownHost } from 'utils/Hosts' import { useTranslation } from 'react-i18next' import { isStandaloneApp } from 'utils/Utils' import useOnStandaloneAppOutsideClick from 'utils/useOnStandaloneAppOutsideClick' +import UnsafeButton from './UnsafeButton' + export default function CloseServer({ isOffline, isLoading }) { const { t } = useTranslation() const [open, setOpen] = useState(false) @@ -41,7 +43,9 @@ export default function CloseServer({ isOffline, isLoading }) { {t('Cancel')} - + diff --git a/web/src/components/DialogTorrentDetailsContent/helpers.js b/web/src/components/DialogTorrentDetailsContent/helpers.js index cb164e5..450703e 100644 --- a/web/src/components/DialogTorrentDetailsContent/helpers.js +++ b/web/src/components/DialogTorrentDetailsContent/helpers.js @@ -12,6 +12,7 @@ const playableExtList = [ 'avchd', 'avi', 'drc', + 'dv', 'flv', 'iso', 'm2v', @@ -27,9 +28,9 @@ const playableExtList = [ 'mpeg', 'mpg', 'mpv', + 'mts', 'mxf', 'nsv', - 'ogg', 'ogv', 'ts', 'qt', @@ -46,6 +47,9 @@ const playableExtList = [ 'aiff', 'ape', 'au', + 'dsd', + 'dff', + 'dsf', 'flac', 'gsm', 'it', @@ -55,11 +59,15 @@ const playableExtList = [ 'mod', 'mp3', 'mpa', + 'oga', + 'ogg', + 'opus', 'pls', 'ra', 's3m', 'sid', 'wav', + 'weba', 'wma', 'xm', ] diff --git a/web/src/components/DialogTorrentDetailsContent/widgets/index.jsx b/web/src/components/DialogTorrentDetailsContent/widgets/index.jsx index 14151dc..e50629a 100644 --- a/web/src/components/DialogTorrentDetailsContent/widgets/index.jsx +++ b/web/src/components/DialogTorrentDetailsContent/widgets/index.jsx @@ -52,7 +52,7 @@ export const PeersWidget = ({ data }) => { return ( { fetch(torrentsHost(), { method: 'post', - body: JSON.stringify({ action: 'list' }), + body: JSON.stringify({ action: 'wipe' }), headers: { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json', }, }) - .then(res => res.json()) - .then(json => { - json.forEach(torr => { - fetch(torrentsHost(), { - method: 'post', - body: JSON.stringify({ action: 'rem', hash: torr.hash }), - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json', - }, - }) - }) - }) } export default function RemoveAll({ isOffline, isLoading }) { @@ -54,7 +43,9 @@ export default function RemoveAll({ isOffline, isLoading }) { {t('Cancel')} - + diff --git a/web/src/components/TorrentList/AddFirstTorrent.jsx b/web/src/components/TorrentList/AddFirstTorrent.jsx index 68f509c..4e8dad8 100644 --- a/web/src/components/TorrentList/AddFirstTorrent.jsx +++ b/web/src/components/TorrentList/AddFirstTorrent.jsx @@ -16,10 +16,11 @@ export default function AddFirstTorrent() { <> handleClickOpen(true)} isButton>
{t('NoTorrentsAdded')}
diff --git a/web/src/components/UnsafeButton.jsx b/web/src/components/UnsafeButton.jsx new file mode 100644 index 0000000..a4ae7de --- /dev/null +++ b/web/src/components/UnsafeButton.jsx @@ -0,0 +1,29 @@ +import { Button } from '@material-ui/core' +import { useEffect, useState } from 'react' + +export default function UnsafeButton({ timeout, children, disabled, ...props }) { + const [timeLeft, setTimeLeft] = useState(timeout || 7) + const [buttonDisabled, setButtonDisabled] = useState(disabled || timeLeft > 0) + const handleTimerTick = () => { + const newTimeLeft = timeLeft - 1 + setTimeLeft(newTimeLeft) + if (newTimeLeft <= 0) { + setButtonDisabled(disabled) + } + } + const getTimerText = () => (!disabled && timeLeft > 0 ? ` (${timeLeft})` : '') + useEffect(() => { + if (disabled || !timeLeft) { + return + } + const intervalId = setInterval(handleTimerTick, 1000) + return () => clearInterval(intervalId) + // eslint-disable-next-line + }, [timeLeft]) + + return ( + + ) +} diff --git a/web/src/locales/bg/translation.json b/web/src/locales/bg/translation.json index 177d460..ad276c8 100644 --- a/web/src/locales/bg/translation.json +++ b/web/src/locales/bg/translation.json @@ -72,7 +72,7 @@ "Offline": "Извън линия", "OK": "OK", "OpenLink": "Отвори линк", - "Peers": "Пиъри", + "Peers": "Пиъри·Сийдъри", "PiecesCount": "Брой парчета", "PiecesLength": "Дължина на парчетата", "Playlist": "Плейлист", diff --git a/web/src/locales/en/translation.json b/web/src/locales/en/translation.json index 9b53921..f2826e5 100644 --- a/web/src/locales/en/translation.json +++ b/web/src/locales/en/translation.json @@ -72,7 +72,7 @@ "Offline": "Offline", "OK": "OK", "OpenLink": "Open link", - "Peers": "Peers", + "Peers": "Peers·Seeds", "PiecesCount": "Pieces count", "PiecesLength": "Pieces length", "Playlist": "Playlist", diff --git a/web/src/locales/ru/translation.json b/web/src/locales/ru/translation.json index db4b2ef..456cd65 100644 --- a/web/src/locales/ru/translation.json +++ b/web/src/locales/ru/translation.json @@ -72,7 +72,7 @@ "Offline": "Сервер недоступен", "OK": "OK", "OpenLink": "Открыть", - "Peers": "Пиры", + "Peers": "Пиры·Сиды", "PiecesCount": "Кол-во блоков", "PiecesLength": "Размер блока", "Playlist": "Плейлист", diff --git a/web/src/locales/ua/translation.json b/web/src/locales/ua/translation.json index b137ac4..85d63b6 100644 --- a/web/src/locales/ua/translation.json +++ b/web/src/locales/ua/translation.json @@ -72,7 +72,7 @@ "Offline": "Сервер не доступний", "OK": "OK", "OpenLink": "Відкрити", - "Peers": "Піри", + "Peers": "Піри·Сіди", "PiecesCount": "К-сть блоків", "PiecesLength": "Розмір блоку", "Playlist": "Плейлист", diff --git a/web/src/locales/zh/translation.json b/web/src/locales/zh/translation.json index 1e2aeca..5d86d04 100644 --- a/web/src/locales/zh/translation.json +++ b/web/src/locales/zh/translation.json @@ -72,7 +72,7 @@ "Offline": "离线", "OK": "确定", "OpenLink": "打开链接", - "Peers": "Peers", + "Peers": "Peers·Seeds", "PiecesCount": "块数量", "PiecesLength": "块长度", "Playlist": "播放列表", diff --git a/web/src/utils/Utils.js b/web/src/utils/Utils.js index 782a097..a54bfc6 100644 --- a/web/src/utils/Utils.js +++ b/web/src/utils/Utils.js @@ -20,8 +20,9 @@ export function humanizeSpeed(speed) { } export function getPeerString(torrent) { - if (!torrent || !torrent.connected_seeders) return null - return `${torrent.connected_seeders} · ${torrent.active_peers} / ${torrent.total_peers}` + if (!torrent || !torrent.active_peers) return null + const seeders = typeof torrent.connected_seeders !== 'undefined' ? torrent.connected_seeders : 0 + return `${torrent.active_peers} / ${torrent.total_peers} · ${seeders}` } export const shortenText = (text, sympolAmount) => diff --git a/web/yarn.lock b/web/yarn.lock index d35d7f3..936a1ea 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -5230,13 +5230,14 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50: - version "0.10.62" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" - integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.62, es5-ext@~0.10.14: + version "0.10.63" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.63.tgz#9c222a63b6a332ac80b1e373b426af723b895bd6" + integrity sha512-hUCZd2Byj/mNKjfP9jXrdVZ62B8KuA/VoK7X8nUh5qT+AxDmcbvZz041oDVZdbIN1qW6XY9VDNwzkvKnZvK2TQ== dependencies: es6-iterator "^2.0.3" es6-symbol "^3.1.3" + esniff "^2.0.1" next-tick "^1.1.0" es6-iterator@2.0.3, es6-iterator@^2.0.1, es6-iterator@^2.0.3: @@ -5559,6 +5560,16 @@ eslint@^7.11.0, eslint@^7.27.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +esniff@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" + integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== + dependencies: + d "^1.0.1" + es5-ext "^0.10.62" + event-emitter "^0.3.5" + type "^2.7.2" + espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" @@ -5622,6 +5633,14 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + dependencies: + d "1" + es5-ext "~0.10.14" + eventemitter3@^4.0.0: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -6037,9 +6056,9 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2: readable-stream "^2.3.6" follow-redirects@^1.0.0, follow-redirects@^1.15.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== for-each@^0.3.3: version "0.3.3" @@ -7063,9 +7082,9 @@ ip-regex@^2.1.0: integrity sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw== ip@^1.1.0, ip@^1.1.5: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" - integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== + version "1.1.9" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396" + integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ== ipaddr.js@1.9.1, ipaddr.js@^1.9.0: version "1.9.1"