From 91b971255933b0c5def8b96c182d364447c8124c Mon Sep 17 00:00:00 2001 From: soup Date: Thu, 1 Jun 2023 21:50:10 +0200 Subject: [PATCH 01/21] feat(compare): allow multiple compare blocks (#62) * feat: enable setting 'compare' via config.toml * refactor to allow multiple compare blocks * fix multi-compare logic in RunCompare function * add option for custom flag * move tag select for dupe outside comparison loop * check err of qbCompare.SetTag * fix(compare): set tag and add dry run * chore: update deps * feat(compare): update flags and docs * feat(compare): update example config * feat(compare): update example config --------- Co-authored-by: Ludvig Lundgren --- .qbt.toml.example | 14 +++ README.md | 18 ++-- cmd/compare.go | 156 ++++++++++++++++++----------- go.sum | 206 ++++++++++++++++++++++++++++++++++++++ internal/config/config.go | 2 + internal/domain/config.go | 1 + 6 files changed, 335 insertions(+), 62 deletions(-) diff --git a/.qbt.toml.example b/.qbt.toml.example index 15b8024..788d7ab 100644 --- a/.qbt.toml.example +++ b/.qbt.toml.example @@ -13,3 +13,17 @@ interval = 7000 # interval between attempts in milliseconds [rules] enabled = true # enable or disable rules max_active_downloads = 2 # set max active downloads + +[[compare]] +addr = "http://100.100.100.100:6776" +login = "user" +password = "password" +#basicUser = "user" +#basicPass = "password" + +[[compare]] # you can specify multiple compare blocks +addr = "http://100.100.100.101:6776" +login = "user" +password = "password" +#basicUser = "user" +#basicPass = "password" diff --git a/README.md b/README.md index 52f8184..6154b51 100644 --- a/README.md +++ b/README.md @@ -166,20 +166,26 @@ Usable with cron as well. Run every 15 min. Compare torrents between two instances. Source instance and `compare` instance. Required flags: -* `--host` - Host url, without http -* `--port` - Host port / webui port +* `--addr` - Host url with http and port if needed * `--user` - Host user * `--pass` - Host pass -* `--compare-host` - url, without http -* `--compare-port` - port / webui port +* `--basic-user` - Host basic auth user +* `--basic-pass` - Host basic auth pass + + +* `--compare-addr` - url with http and port if needed * `--compare-user` - user * `--compare-pass` - pass +* `--compare-basic-user` - basic auth user +* `--compare-basic-pass` - basic auth pass Optional flags: -* `--tag` - Tag duplicates with `duplicate`, only on compare host +* `--dry-run` - Run without doing anything +* `--tag-duplicates` - Tag duplicates with `compare-dupe` tag, only on compare host +* `--tag` - Override the default tag `compare-dupe` ```bash -qbt compare --source url.com --port 10000 --user u --pass p --compare-host url.com --compare-port 10000 --compare-user u --compare-pass p +qbt compare --addr http://url.com:10000 --user u --pass p --compare-addr http://url.com:10000 --compare-user u --compare-pass p ``` ### Edit diff --git a/cmd/compare.go b/cmd/compare.go index 67c1d5f..0f029f7 100644 --- a/cmd/compare.go +++ b/cmd/compare.go @@ -16,17 +16,21 @@ import ( // RunCompare cmd to compare torrents between clients func RunCompare() *cobra.Command { var ( + dry bool tagDuplicates bool - - sourceHost string - sourcePort uint - sourceUser string - sourcePass string - - compareHost string - comparePort uint - compareUser string - comparePass string + tag string + + sourceAddr string + sourceUser string + sourcePass string + sourceBasicUser string + sourceBasicPass string + + compareAddr string + compareUser string + comparePass string + compareBasicUser string + compareBasicPass string ) var command = &cobra.Command{ @@ -41,25 +45,47 @@ func RunCompare() *cobra.Command { // return nil //}, } - command.Flags().BoolVar(&tagDuplicates, "tag", false, "tag duplicates on compare") + command.Flags().BoolVar(&dry, "dry-run", false, "dry run") + command.Flags().BoolVar(&tagDuplicates, "tag-duplicates", false, "tag duplicates on compare") + command.Flags().StringVar(&tag, "tag", "compare-dupe", "set a custom tag for duplicates on compare. default: compare-dupe") - command.Flags().StringVar(&sourceHost, "host", "", "Source host") - command.Flags().UintVar(&sourcePort, "port", 0, "Source host") + command.Flags().StringVar(&sourceAddr, "host", "", "Source host") command.Flags().StringVar(&sourceUser, "user", "", "Source user") command.Flags().StringVar(&sourcePass, "pass", "", "Source pass") + command.Flags().StringVar(&sourceBasicUser, "basic-user", "", "Source basic auth user") + command.Flags().StringVar(&sourceBasicPass, "basic-pass", "", "Source basic auth pass") - command.Flags().StringVar(&compareHost, "compare-host", "", "Secondary host") - command.Flags().UintVar(&comparePort, "compare-port", 0, "Secondary host") + command.Flags().StringVar(&compareAddr, "compare-host", "", "Secondary host") command.Flags().StringVar(&compareUser, "compare-user", "", "Secondary user") command.Flags().StringVar(&comparePass, "compare-pass", "", "Secondary pass") + command.Flags().StringVar(&compareBasicUser, "compare-basic-user", "", "Secondary basic auth user") + command.Flags().StringVar(&compareBasicPass, "compare-basic-pass", "", "Secondary basic auth pass") command.Run = func(cmd *cobra.Command, args []string) { config.InitConfig() + + if sourceAddr == "" { + sourceAddr = config.Qbit.Host + } + if sourceUser == "" { + sourceUser = config.Qbit.Login + } + if sourcePass == "" { + sourcePass = config.Qbit.Password + } + if sourceBasicUser == "" { + sourceBasicUser = config.Qbit.BasicUser + } + if sourceBasicPass == "" { + sourceBasicPass = config.Qbit.BasicPass + } + qbtSettings := qbittorrent.Settings{ - Hostname: sourceHost, - Port: sourcePort, - Username: sourceUser, - Password: sourcePass, + Addr: sourceAddr, + Username: sourceUser, + Password: sourcePass, + BasicUser: sourceBasicUser, + BasicPass: sourceBasicPass, } qb := qbittorrent.NewClient(qbtSettings) @@ -78,52 +104,70 @@ func RunCompare() *cobra.Command { fmt.Printf("Found: %d torrents on source\n", len(sourceData)) - qbtSettingsCompare := qbittorrent.Settings{ - Hostname: compareHost, - Port: comparePort, - Username: compareUser, - Password: comparePass, - } - qbCompare := qbittorrent.NewClient(qbtSettingsCompare) + // Start comparison + for _, compareConfig := range config.Compare { + compareAddr := compareConfig.Addr + compareUser := compareConfig.Login + comparePass := compareConfig.Password + compareBasicUser := compareConfig.BasicUser + compareBasicPass := compareConfig.BasicPass + + qbtSettingsCompare := qbittorrent.Settings{ + Addr: compareAddr, + Username: compareUser, + Password: comparePass, + BasicUser: compareBasicUser, + BasicPass: compareBasicPass, + } + qbCompare := qbittorrent.NewClient(qbtSettingsCompare) - if err = qbCompare.Login(ctx); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: connection failed to compare: %v\n", err) - os.Exit(1) - } + if err = qbCompare.Login(ctx); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: connection failed to compare: %v\n", err) + os.Exit(1) + } - compareData, err := qbCompare.GetTorrents(ctx) - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not get torrents from compare: %v\n", err) - os.Exit(1) - } + compareData, err := qbCompare.GetTorrents(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not get torrents from compare: %v\n", err) + os.Exit(1) + } - fmt.Printf("Found: %d torrents on compare\n", len(compareData)) + fmt.Printf("Found: %d torrents on compare\n", len(compareData)) - duplicateTorrents, err := compare(sourceData, compareData) - if err != nil { - os.Exit(1) - } + duplicateTorrents, err := compare(sourceData, compareData) + if err != nil { + os.Exit(1) + } - // --tag add tag duplicate - if tagDuplicates { - // Split the slice into batches of 20 items. - batch := 20 - for i := 0; i < len(duplicateTorrents); i += batch { - j := i + batch - if j > len(duplicateTorrents) { - j = len(duplicateTorrents) + // Process duplicate torrents + if tagDuplicates { + if !dry { + fmt.Printf("found: %d duplicate torrents from compare %s\n", len(duplicateTorrents), compareAddr) + + batch := 20 + for i := 0; i < len(duplicateTorrents); i += batch { + j := i + batch + if j > len(duplicateTorrents) { + j = len(duplicateTorrents) + } + + err = qbCompare.SetTag(ctx, duplicateTorrents[i:j], tag) + if err != nil { + fmt.Printf("ERROR: Failed to set tag: %v\n", err) + } + + // sleep before next request + time.Sleep(time.Second * 1) + } + } else { + fmt.Printf("dry-run: found: %d duplicate torrents from compare %s\n", len(duplicateTorrents), compareAddr) } - - qbCompare.SetTag(ctx, duplicateTorrents[i:j], "duplicate") - - // sleep before next request - time.Sleep(time.Second * 1) } - } - // --rm-duplicates + // --rm-duplicates - // --save save to file + // --save save to file + } } return command diff --git a/go.sum b/go.sum index e5e337c..05eaa8a 100644 --- a/go.sum +++ b/go.sum @@ -17,132 +17,211 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA= +cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= +crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c h1:wvzox0eLO6CKQAMcOqz7oH3UFqMpMmK7kwmwV+22HIs= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 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.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY= +github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0= +github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo= +github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM= +github.com/anacrolix/bargle v0.0.0-20220630015206-d7a4d433886a h1:KCP9QvHlLoUQBOaTf/YCuOzG91Ym1cPB6S68O4Q3puo= +github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U= github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 h1:8V0K09lrGoeT2KRJNOtspA7q+OMxGwQqK/Ug0IiaaRE= github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= +github.com/anacrolix/envpprof v1.2.1 h1:25TJe6t/i0AfzzldiGFKCpD+s+dk8lONBcacJZB2rdE= +github.com/anacrolix/fuse v0.2.0 h1:pc+To78kI2d/WUjIyrsdqeJQAesuwpGxlI3h1nAv3Do= +github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 h1:fyXlBfnlFzZSFckJ8QLb2lfmWfY++4RiUnae7ZMuv0A= +github.com/anacrolix/go-libutp v1.2.0 h1:sjxoB+/ARiKUR7IK/6wLWyADIBqGmu1fm0xo+8Yy7u0= 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.2-0.20230518105052-6aef2c4c91f1 h1:Yo4XQhmdmrkB4RGP7RWvl8U+og2rCBsNqoJFTew0plk= 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= github.com/anacrolix/missinggo v1.3.0 h1:06HlMsudotL7BAELRZs0yDZ4yVXsHXGi323QBjAVASw= github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H5fwlq/TeqMc= +github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy6BqESAJVw= github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY= github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA= github.com/anacrolix/missinggo/v2 v2.7.0 h1:4fzOAAn/VCvfWGviLmh64MPMttrlYew81JdPO7nSHvI= github.com/anacrolix/missinggo/v2 v2.7.0/go.mod h1:2IZIvmRTizALNYFYXsPR7ofXPzJgyBpKZ4kMqMEICkI= +github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg= +github.com/anacrolix/multiless v0.3.0 h1:5Bu0DZncjE4e06b9r1Ap2tUY4Au0NToBP5RpuEngSis= +github.com/anacrolix/squirrel v0.4.1-0.20220122230132-14b040773bac h1:eddZTnM9TIy3Z9ARLeDMlUpEjcs0ZdoFMXSG0ChAHvE= github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg= +github.com/anacrolix/stm v0.4.0 h1:tOGvuFwaBjeu1u9X1eIh9TX8OEedEiEQ1se1FjhFnXY= +github.com/anacrolix/sync v0.4.0 h1:T+MdO/u87ir/ijWsTFsPYw5jVm0SMm4kVpg8t4KF38o= 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/tagflag v1.3.0 h1:5NI+9CniDnEH0BWA4UcQbERyFPjKJqZnVkItGVIDy/s= github.com/anacrolix/torrent v1.51.1 h1:E48PcnNN3MFbqfFEinm+/E846ZNDVL0HKTY+XSsoEhM= github.com/anacrolix/torrent v1.51.1/go.mod h1:yCbMnw1IpltDstasqpZploAMnJrkXNjZFjo7XdDYUDY= +github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA= +github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/apache/thrift v0.12.0 h1:pODnxUFNcjP9UTLZGTdeh+j16A8lJbRvD3rOtrk/7bs= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= +github.com/benbjohnson/immutable v0.3.0 h1:TVRhuZx2wG9SZ0LRdqlbs9S5BZ6Y24hJEHTCgWHZEIw= 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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.2.2 h1:J5gbX05GpMdBjCvQ9MteIg2KKDExr7DrgK+Yc15FvIk= 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/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780 h1:tFh1tRc4CA31yP6qDcu+Trax5wW5GuMxvkIba07qVLY= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= +github.com/elliotchance/orderedmap v1.4.0 h1:wZtfeEONCbx6in1CZyE6bELEt/vFayMvsxqI5SgsR+A= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad h1:EmNYJhPYy0pOFjCx2PrgtaBXmee0iUX9hLlxE1xHOJE= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= +github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a h1:FQqoVvjbiUioBBFUL5up+h+GdCa/AnJsL/1bIs/veSI= github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= 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 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -158,6 +237,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 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/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -166,6 +246,7 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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= @@ -177,9 +258,12 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -190,22 +274,41 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2 h1:LR89qFljJ48s990kEKGsk213yIJDPI4205OKOzbURK8= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 h1:tlyzajkF3030q6M8SvmJSemC9DTHL/xaMa18b65+JM4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 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= github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= +github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= @@ -213,34 +316,50 @@ github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= 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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/lispad/go-generics-tools v1.1.0 h1:mbSgcxdFVmpoyso1X/MJHXbSbSL3dD+qhRryyxk+/XY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= @@ -249,30 +368,58 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= +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 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d3M= github.com/nwaples/rardecode v1.1.2/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/openzipkin/zipkin-go v0.1.6 h1:yXiysv1CSK7Q5yjGy1710zZGnsbMUIjluWBxtLXHPBo= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.12 h1:44l88ehTZAUGW4VlO1QC4zkilL99M6Y9MXNwEs0uzP8= github.com/pierrec/lz4/v4 v4.1.12/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E= +github.com/pion/dtls/v2 v2.2.4 h1:YSfYwDQgrxMYXLBc/m7PFY5BVtWlNm/DN4qoU2CbcWg= +github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig= +github.com/pion/interceptor v0.1.11 h1:00U6OlqxA3FFB50HSg25J/8cWi7P6FbSzw4eFn24Bvs= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= +github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= +github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U= +github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA= +github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA= +github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU= +github.com/pion/srtp/v2 v2.0.9 h1:JJq3jClmDFBPX/F5roEb0U19jSU7eUhyDqR/NZ34EKQ= +github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= +github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA= +github.com/pion/transport/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4= +github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw= +github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8= +github.com/pion/webrtc/v3 v3.1.42 h1:wJEQFIXVanptnQcHOLTuIo4AtGB2+mG2x4OhIhnITOA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -280,29 +427,41 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.35.0 h1:Eyr+Pw2VymWejHqCugNaQXkAi6KayVNxaHeu6khmFBE= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 h1:Lt9DzQALzHoDwMBGJ6v8ObDPR0dzr2a6sXTB1Fq7IHs= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= +github.com/sagikazarmark/crypt v0.9.0 h1:fipzMFW34hFUEc4D7fsLQFtE7yElkpgyS2zruedRdZk= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac h1:wbW+Bybf9pXxnCFAOWZTqkRjAc7rAIwo2e1ArUhiHxg= github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff h1:86HlEv0yBCry9syNuylzqznKXDK11p6D0DT596yNMys= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= @@ -319,6 +478,7 @@ github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jH github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -332,8 +492,10 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= 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 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= @@ -342,15 +504,22 @@ github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/bencode v1.0.0 h1:zgop0Wu1nu4IexAZeCZ5qbsjU4O1vMrfCrVgUjbHVuA= github.com/zeebo/bencode v1.0.0/go.mod h1:Ct7CkrWIQuLWAy9M3atFHYq4kG9Ao/SsY5cdtCXmp9Y= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/etcd/api/v3 v3.5.6 h1:Cy2qx3npLcYqTKqGJzMypnMv2tiRyifZJ17BlWIWA7A= +go.etcd.io/etcd/client/pkg/v3 v3.5.6 h1:TXQWYceBKqLp4sa87rcPs11SXxUA/mHwH975v+BDvLU= +go.etcd.io/etcd/client/v2 v2.305.6 h1:fIDR0p4KMjw01MJMfUIDWdQbjo06PD6CeYM5z4EHLi0= +go.etcd.io/etcd/client/v3 v3.5.6 h1:coLs69PWCXE9G4FKquzNaSHrRyMCAXwF+IX1tAPVO8E= 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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -359,6 +528,17 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0 h1:ao8CJIShCaIbaMsGxy+jp2YHSudketpDgDRcbirov78= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.8.0 h1:LrHL1A3KqIgAgi6mK7Q0aczmzU414AONAGT5xtnp+uo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.8.0 h1:00hCSGLIxdYK/Z7r8GkaX0QIlfvgU3tmnLlQvcnix6U= +go.opentelemetry.io/otel/sdk v1.8.0 h1:xwu69/fNuwbSHWe/0PGS888RmjWY181OmcXDQKu7ZQk= +go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY= +go.opentelemetry.io/proto/otlp v0.18.0 h1:W5hyXNComRa23tGpKwG+FRAc4rfF6ZUg1JReK+QHS80= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= 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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -367,6 +547,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -377,7 +558,9 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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= @@ -389,8 +572,10 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= @@ -400,6 +585,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= 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= @@ -446,6 +632,7 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -456,6 +643,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 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= @@ -501,6 +689,7 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -512,6 +701,7 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= 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= @@ -560,10 +750,12 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -584,12 +776,14 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.107.0 h1:I2SlFjD8ZWabaIFOfeEDg3pf0BHJDh6iYQ1ic3Yu/UU= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -627,6 +821,7 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -644,6 +839,7 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -654,19 +850,25 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -678,7 +880,11 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/config/config.go b/internal/config/config.go index 8dbd9cc..400b9ac 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,6 +14,7 @@ var ( CfgFile string Config domain.AppConfig Qbit domain.QbitConfig + Compare []domain.QbitConfig Reannounce domain.ReannounceSettings Rules domain.Rules ) @@ -52,6 +53,7 @@ func InitConfig() { } Qbit = Config.Qbit + Compare = Config.Compare Reannounce = Config.Reannounce Rules = Config.Rules } diff --git a/internal/domain/config.go b/internal/domain/config.go index d2301fb..016c579 100644 --- a/internal/domain/config.go +++ b/internal/domain/config.go @@ -26,4 +26,5 @@ type AppConfig struct { Qbit QbitConfig `mapstructure:"qbittorrent"` Reannounce ReannounceSettings `mapstructure:"reannounce"` Rules Rules `mapstructure:"rules"` + Compare []QbitConfig `mapstructure:"compare"` } From f8a1d8333efbdbac6bbe950f70449058424a1b18 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Sat, 19 Aug 2023 19:04:40 +0200 Subject: [PATCH 02/21] docs: explain export (#67) --- README.md | 16 +++- cmd/export.go | 2 +- go.sum | 208 -------------------------------------------------- 3 files changed, 16 insertions(+), 210 deletions(-) diff --git a/README.md b/README.md index 6154b51..f893add 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ Commands: - compare - edit - reannounce + - export Global flags: * `--config` - override config file @@ -172,7 +173,6 @@ Required flags: * `--basic-user` - Host basic auth user * `--basic-pass` - Host basic auth pass - * `--compare-addr` - url with http and port if needed * `--compare-user` - user * `--compare-pass` - pass @@ -260,3 +260,17 @@ Optional flags: * `--hash ` - Only look for torrents with the provided hash * `--interval ` - Override default interval of 7000 ms (7s) * `--attempts ` - Override default attempts of 50 + +### Export + +Export .torrent and .fastresume file baaed on categories. + + qbt export + +Required flags: +* `--source ` - qBittorrent state dir (~/.local/share/data/qBittorrent/BT_backup) +* `--export-dir ` - new dir or qBittorrent state dir (~/.local/share/data/qBittorrent/BT_backup) +* `--categories ` - Only look for torrents with the provided categories. Comma separated + +Optional flags: +* `--dry-run` - Run without doing anything diff --git a/cmd/export.go b/cmd/export.go index c933605..664849b 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -34,7 +34,7 @@ func RunExport() *cobra.Command { command.Flags().BoolVar(&dry, "dry-run", false, "dry run") command.Flags().StringVar(&sourceDir, "source", "", "Dir with torrent and fast-resume files") command.Flags().StringVar(&exportDir, "export-dir", "", "Dir to export files to") - command.Flags().StringSliceVar(&categories, "categories", []string{}, "Export torrents from categories") + command.Flags().StringSliceVar(&categories, "categories", []string{}, "Export torrents from categories. Comma separated") command.MarkFlagRequired("categories") command.RunE = func(cmd *cobra.Command, args []string) error { diff --git a/go.sum b/go.sum index 05eaa8a..98a8c13 100644 --- a/go.sum +++ b/go.sum @@ -17,211 +17,132 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA= -cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797 h1:yDf7ARQc637HoxDho7xjqdvO5ZA2Yb+xzv/fOnnvZzw= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= -crawshaw.io/sqlite v0.3.3-0.20220618202545-d1964889ea3c h1:wvzox0eLO6CKQAMcOqz7oH3UFqMpMmK7kwmwV+22HIs= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 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.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY= -github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0= -github.com/alecthomas/atomic v0.1.0-alpha2 h1:dqwXmax66gXvHhsOS4pGPZKqYOlTkapELkLb3MNdlH8= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo= -github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM= -github.com/anacrolix/bargle v0.0.0-20220630015206-d7a4d433886a h1:KCP9QvHlLoUQBOaTf/YCuOzG91Ym1cPB6S68O4Q3puo= -github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U= github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 h1:8V0K09lrGoeT2KRJNOtspA7q+OMxGwQqK/Ug0IiaaRE= github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= -github.com/anacrolix/envpprof v1.2.1 h1:25TJe6t/i0AfzzldiGFKCpD+s+dk8lONBcacJZB2rdE= -github.com/anacrolix/fuse v0.2.0 h1:pc+To78kI2d/WUjIyrsdqeJQAesuwpGxlI3h1nAv3Do= -github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 h1:fyXlBfnlFzZSFckJ8QLb2lfmWfY++4RiUnae7ZMuv0A= -github.com/anacrolix/go-libutp v1.2.0 h1:sjxoB+/ARiKUR7IK/6wLWyADIBqGmu1fm0xo+8Yy7u0= 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.2-0.20230518105052-6aef2c4c91f1 h1:Yo4XQhmdmrkB4RGP7RWvl8U+og2rCBsNqoJFTew0plk= 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= github.com/anacrolix/missinggo v1.3.0 h1:06HlMsudotL7BAELRZs0yDZ4yVXsHXGi323QBjAVASw= github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H5fwlq/TeqMc= -github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy6BqESAJVw= github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY= github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA= github.com/anacrolix/missinggo/v2 v2.7.0 h1:4fzOAAn/VCvfWGviLmh64MPMttrlYew81JdPO7nSHvI= github.com/anacrolix/missinggo/v2 v2.7.0/go.mod h1:2IZIvmRTizALNYFYXsPR7ofXPzJgyBpKZ4kMqMEICkI= -github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg= -github.com/anacrolix/multiless v0.3.0 h1:5Bu0DZncjE4e06b9r1Ap2tUY4Au0NToBP5RpuEngSis= -github.com/anacrolix/squirrel v0.4.1-0.20220122230132-14b040773bac h1:eddZTnM9TIy3Z9ARLeDMlUpEjcs0ZdoFMXSG0ChAHvE= github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg= -github.com/anacrolix/stm v0.4.0 h1:tOGvuFwaBjeu1u9X1eIh9TX8OEedEiEQ1se1FjhFnXY= -github.com/anacrolix/sync v0.4.0 h1:T+MdO/u87ir/ijWsTFsPYw5jVm0SMm4kVpg8t4KF38o= 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/tagflag v1.3.0 h1:5NI+9CniDnEH0BWA4UcQbERyFPjKJqZnVkItGVIDy/s= github.com/anacrolix/torrent v1.51.1 h1:E48PcnNN3MFbqfFEinm+/E846ZNDVL0HKTY+XSsoEhM= github.com/anacrolix/torrent v1.51.1/go.mod h1:yCbMnw1IpltDstasqpZploAMnJrkXNjZFjo7XdDYUDY= -github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA= -github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/apache/thrift v0.12.0 h1:pODnxUFNcjP9UTLZGTdeh+j16A8lJbRvD3rOtrk/7bs= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= -github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= -github.com/benbjohnson/immutable v0.3.0 h1:TVRhuZx2wG9SZ0LRdqlbs9S5BZ6Y24hJEHTCgWHZEIw= 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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.2.2 h1:J5gbX05GpMdBjCvQ9MteIg2KKDExr7DrgK+Yc15FvIk= 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/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= -github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780 h1:tFh1tRc4CA31yP6qDcu+Trax5wW5GuMxvkIba07qVLY= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= -github.com/elliotchance/orderedmap v1.4.0 h1:wZtfeEONCbx6in1CZyE6bELEt/vFayMvsxqI5SgsR+A= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad h1:EmNYJhPYy0pOFjCx2PrgtaBXmee0iUX9hLlxE1xHOJE= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= -github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a h1:FQqoVvjbiUioBBFUL5up+h+GdCa/AnJsL/1bIs/veSI= github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= 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 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -237,7 +158,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 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/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -246,7 +166,6 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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= @@ -258,12 +177,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -274,41 +190,22 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2 h1:LR89qFljJ48s990kEKGsk213yIJDPI4205OKOzbURK8= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 h1:tlyzajkF3030q6M8SvmJSemC9DTHL/xaMa18b65+JM4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 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= github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= -github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= @@ -316,50 +213,34 @@ github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= 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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/lispad/go-generics-tools v1.1.0 h1:mbSgcxdFVmpoyso1X/MJHXbSbSL3dD+qhRryyxk+/XY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= @@ -368,58 +249,30 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= -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 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d3M= github.com/nwaples/rardecode v1.1.2/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/openzipkin/zipkin-go v0.1.6 h1:yXiysv1CSK7Q5yjGy1710zZGnsbMUIjluWBxtLXHPBo= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.12 h1:44l88ehTZAUGW4VlO1QC4zkilL99M6Y9MXNwEs0uzP8= github.com/pierrec/lz4/v4 v4.1.12/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pion/datachannel v1.5.2 h1:piB93s8LGmbECrpO84DnkIVWasRMk3IimbcXkTQLE6E= -github.com/pion/dtls/v2 v2.2.4 h1:YSfYwDQgrxMYXLBc/m7PFY5BVtWlNm/DN4qoU2CbcWg= -github.com/pion/ice/v2 v2.2.6 h1:R/vaLlI1J2gCx141L5PEwtuGAGcyS6e7E0hDeJFq5Ig= -github.com/pion/interceptor v0.1.11 h1:00U6OlqxA3FFB50HSg25J/8cWi7P6FbSzw4eFn24Bvs= -github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= -github.com/pion/mdns v0.0.5 h1:Q2oj/JB3NqfzY9xGZ1fPzZzK7sDSD8rZPOvcIQ10BCw= -github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= -github.com/pion/rtcp v1.2.9 h1:1ujStwg++IOLIEoOiIQ2s+qBuJ1VN81KW+9pMPsif+U= -github.com/pion/rtp v1.7.13 h1:qcHwlmtiI50t1XivvoawdCGTP4Uiypzfrsap+bijcoA= -github.com/pion/sctp v1.8.2 h1:yBBCIrUMJ4yFICL3RIvR4eh/H2BTTvlligmSTy+3kiA= -github.com/pion/sdp/v3 v3.0.5 h1:ouvI7IgGl+V4CrqskVtr3AaTrPvPisEOxwgpdktctkU= -github.com/pion/srtp/v2 v2.0.9 h1:JJq3jClmDFBPX/F5roEb0U19jSU7eUhyDqR/NZ34EKQ= -github.com/pion/stun v0.3.5 h1:uLUCBCkQby4S1cf6CGuR9QrVOKcvUwFeemaC865QHDg= -github.com/pion/transport v0.13.1 h1:/UH5yLeQtwm2VZIPjxwnNFxjS4DFhyLfS4GlfuKUzfA= -github.com/pion/transport/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4= -github.com/pion/turn/v2 v2.0.8 h1:KEstL92OUN3k5k8qxsXHpr7WWfrdp7iJZHx99ud8muw= -github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8= -github.com/pion/webrtc/v3 v3.1.42 h1:wJEQFIXVanptnQcHOLTuIo4AtGB2+mG2x4OhIhnITOA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -427,41 +280,29 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.35.0 h1:Eyr+Pw2VymWejHqCugNaQXkAi6KayVNxaHeu6khmFBE= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 h1:Lt9DzQALzHoDwMBGJ6v8ObDPR0dzr2a6sXTB1Fq7IHs= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= -github.com/sagikazarmark/crypt v0.9.0 h1:fipzMFW34hFUEc4D7fsLQFtE7yElkpgyS2zruedRdZk= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac h1:wbW+Bybf9pXxnCFAOWZTqkRjAc7rAIwo2e1ArUhiHxg= github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= -github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff h1:86HlEv0yBCry9syNuylzqznKXDK11p6D0DT596yNMys= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= @@ -478,7 +319,6 @@ github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jH github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -492,34 +332,23 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= 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 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= -github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/bencode v1.0.0 h1:zgop0Wu1nu4IexAZeCZ5qbsjU4O1vMrfCrVgUjbHVuA= github.com/zeebo/bencode v1.0.0/go.mod h1:Ct7CkrWIQuLWAy9M3atFHYq4kG9Ao/SsY5cdtCXmp9Y= -go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= -go.etcd.io/etcd/api/v3 v3.5.6 h1:Cy2qx3npLcYqTKqGJzMypnMv2tiRyifZJ17BlWIWA7A= -go.etcd.io/etcd/client/pkg/v3 v3.5.6 h1:TXQWYceBKqLp4sa87rcPs11SXxUA/mHwH975v+BDvLU= -go.etcd.io/etcd/client/v2 v2.305.6 h1:fIDR0p4KMjw01MJMfUIDWdQbjo06PD6CeYM5z4EHLi0= -go.etcd.io/etcd/client/v3 v3.5.6 h1:coLs69PWCXE9G4FKquzNaSHrRyMCAXwF+IX1tAPVO8E= 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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -528,17 +357,6 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.8.0 h1:ao8CJIShCaIbaMsGxy+jp2YHSudketpDgDRcbirov78= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.8.0 h1:LrHL1A3KqIgAgi6mK7Q0aczmzU414AONAGT5xtnp+uo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.8.0 h1:00hCSGLIxdYK/Z7r8GkaX0QIlfvgU3tmnLlQvcnix6U= -go.opentelemetry.io/otel/sdk v1.8.0 h1:xwu69/fNuwbSHWe/0PGS888RmjWY181OmcXDQKu7ZQk= -go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY= -go.opentelemetry.io/proto/otlp v0.18.0 h1:W5hyXNComRa23tGpKwG+FRAc4rfF6ZUg1JReK+QHS80= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= 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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -547,7 +365,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -558,9 +375,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220613132600-b0d781184e0d h1:vtUKgx8dahOomfFzLREU8nSv25YHnTgLBn4rDnWZdU0= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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= @@ -572,10 +387,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= @@ -585,7 +398,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= 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= @@ -632,7 +444,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 h1:nt+Q6cXKz4MosCSpnbMtqiQ8Oz0pxTef2B4Vca2lvfk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -643,7 +454,6 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 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= @@ -689,7 +499,6 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -701,7 +510,6 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= 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= @@ -750,12 +558,10 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -776,14 +582,12 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.107.0 h1:I2SlFjD8ZWabaIFOfeEDg3pf0BHJDh6iYQ1ic3Yu/UU= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -821,7 +625,6 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -839,7 +642,6 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -850,25 +652,19 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= @@ -880,11 +676,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 3ecab1480b1241351a4a7d66f2c4462c124be55c Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Sat, 19 Aug 2023 19:42:28 +0200 Subject: [PATCH 03/21] fix(ci): update go version (#68) * fix(ci): update go version * fix(ci): run on pr * fix(ci): unify test and build pr --- .github/workflows/release.yml | 66 +++++++++++++++++++++++++++++++++-- .github/workflows/test.yml | 27 -------------- 2 files changed, 63 insertions(+), 30 deletions(-) delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2175d7d..905d26e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,12 +1,71 @@ name: goreleaser on: + pull_request: push: + branches: + - "master" + - "develop" tags: - - '*' + - 'v*' jobs: - goreleaser: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.21 + cache: true + + - name: Test + run: go test -v ./... + + build: + name: Build Go binaries + needs: [ test ] + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.21 + cache: true + + - name: Run GoReleaser build + uses: goreleaser/goreleaser-action@v4 + with: + distribution: goreleaser + version: latest + args: release --clean --skip-validate --skip-publish --parallelism 99 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload assets + uses: actions/upload-artifact@v3 + with: + name: qbittorrent-cli + path: | + dist/*.tar.gz + dist/*.zip + + release: + name: Build and publish binaries + needs: [ test ] + if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: - @@ -18,7 +77,8 @@ jobs: name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.17 + go-version: 1.21 + cache: true - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 829f4a4..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: test - -on: - push: - branches: - - "master" - - "develop" - pull_request: - -jobs: - test: - name: Test - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.20.3' - cache: true - - - name: Test - run: go test -v ./... From 20133e04544169299e907a8e664d3834bd72b2c1 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Fri, 25 Aug 2023 18:25:49 +0200 Subject: [PATCH 04/21] fix(export): improve matching (#69) * fix(export): always compare hashes lowercase * fix(export): improve ext compare and logs * fix(export): failing test * feat(export): create export dir if not exists * feat(export): rename test export funs * refactor(export): move dir exists check * refactor(export): process --- cmd/export.go | 150 ----------------------- cmd/export_test.go | 27 +++-- cmd/torrent_export.go | 276 ++++++++++++++++++++++++++++++++++++++++++ internal/fs/dir.go | 20 +++ 4 files changed, 311 insertions(+), 162 deletions(-) delete mode 100644 cmd/export.go create mode 100644 cmd/torrent_export.go create mode 100644 internal/fs/dir.go diff --git a/cmd/export.go b/cmd/export.go deleted file mode 100644 index 664849b..0000000 --- a/cmd/export.go +++ /dev/null @@ -1,150 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "io/fs" - "log" - "os" - "path/filepath" - "strings" - - "github.com/ludviglundgren/qbittorrent-cli/internal/config" - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" - "github.com/ludviglundgren/qbittorrent-cli/pkg/torrent" - - "github.com/pkg/errors" - "github.com/spf13/cobra" -) - -func RunExport() *cobra.Command { - var command = &cobra.Command{ - Use: "export", - Short: "export torrents", - Long: "Export torrents and fastresume by category", - } - - var ( - dry bool - sourceDir string - exportDir string - categories []string - ) - - command.Flags().BoolVar(&dry, "dry-run", false, "dry run") - command.Flags().StringVar(&sourceDir, "source", "", "Dir with torrent and fast-resume files") - command.Flags().StringVar(&exportDir, "export-dir", "", "Dir to export files to") - command.Flags().StringSliceVar(&categories, "categories", []string{}, "Export torrents from categories. Comma separated") - command.MarkFlagRequired("categories") - - command.RunE = func(cmd *cobra.Command, args []string) error { - // get torrents from client by categories - config.InitConfig() - - qbtSettings := qbittorrent.Settings{ - Addr: config.Qbit.Addr, - Hostname: config.Qbit.Host, - Port: config.Qbit.Port, - Username: config.Qbit.Login, - Password: config.Qbit.Password, - BasicUser: config.Qbit.BasicUser, - BasicPass: config.Qbit.BasicPass, - } - - qb := qbittorrent.NewClient(qbtSettings) - - ctx := context.Background() - - if err := qb.Login(ctx); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) - os.Exit(1) - } - hashes := map[string]struct{}{} - - for _, category := range categories { - torrents, err := qb.GetTorrentsWithFilters(ctx, &qbittorrent.GetTorrentsRequest{Category: category}) - if err != nil { - return errors.Wrapf(err, "could not get torrents for category: %s", category) - } - - for _, t := range torrents { - // only grab completed torrents - if t.Progress != 1 { - continue - } - - // append hash to map of hashes to gather - hashes[t.Hash] = struct{}{} - } - } - - if len(hashes) == 0 { - fmt.Printf("Could not find any matching torrents to export from (%s)\n", strings.Join(categories, ",")) - os.Exit(0) - } - - fmt.Printf("Found '%d' matching torrents\n", len(hashes)) - - if err := processHashes(sourceDir, exportDir, hashes, dry); err != nil { - return errors.Wrapf(err, "could not process torrents") - } - - fmt.Println("Successfully exported torrents!") - - return nil - } - - return command -} -func processHashes(sourceDir, exportDir string, hashes map[string]struct{}, dry bool) error { - exportCount := 0 - // check BT_backup dir, pick torrent and fastresume files by id - err := filepath.Walk(sourceDir, func(dirPath string, info fs.FileInfo, err error) error { - if err != nil { - return err - } - - if info.IsDir() { - return nil - } - - fileName := info.Name() - - if filepath.Ext(fileName) != ".torrent" || filepath.Ext(fileName) != ".fastresume" { - return nil - } - - torrentHash := fileNameTrimExt(fileName) - - // if filename not in hashes return and check next - _, ok := hashes[torrentHash] - if !ok { - return nil - } - - fmt.Printf("processing: %s\n", fileName) - - if !dry { - outFile := filepath.Join(exportDir, fileName) - if err := torrent.CopyFile(dirPath, outFile); err != nil { - return errors.Wrapf(err, "could not copy file: %s to %s", dirPath, outFile) - } - } - - exportCount++ - - return nil - }) - if err != nil { - log.Printf("error reading files: %q", err) - return err - } - - fmt.Printf("Found and exported '%d' torrents\n", exportCount) - - return nil -} - -func fileNameTrimExt(fileName string) string { - return strings.TrimSuffix(fileName, filepath.Ext(fileName)) -} diff --git a/cmd/export_test.go b/cmd/export_test.go index 1386ac2..63d06fa 100644 --- a/cmd/export_test.go +++ b/cmd/export_test.go @@ -2,33 +2,36 @@ package cmd import "testing" -func Test_processHashes(t *testing.T) { +func Test_export_processHashes(t *testing.T) { type args struct { sourceDir string exportDir string hashes map[string]struct{} - replace []string dry bool + verbose bool } tests := []struct { name string args args wantErr bool }{ - {name: "test_1", args: args{ - sourceDir: "../test/config/qBittorrent/BT_backup", - exportDir: "../test/export", - hashes: map[string]struct{}{ - "5ba4939a00a9b21629a0ad7d376898b768d997a3": {}, + { + name: "test_1", + args: args{ + sourceDir: "../test/config/qBittorrent/BT_backup", + exportDir: "../test/export", + hashes: map[string]struct{}{ + "5ba4939a00a9b21629a0ad7d376898b768d997a3": {}, + }, + dry: false, }, - replace: []string{"https://academictorrents.com/announce.php|https://test.com/announce.php?test"}, - dry: false, - }, wantErr: false}, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := processHashes(tt.args.sourceDir, tt.args.exportDir, tt.args.hashes, tt.args.dry); (err != nil) != tt.wantErr { - t.Errorf("processHashes() error = %v, wantErr %v", err, tt.wantErr) + if err := processExport(tt.args.sourceDir, tt.args.exportDir, tt.args.hashes, tt.args.dry, tt.args.verbose); (err != nil) != tt.wantErr { + t.Errorf("processExport() error = %v, wantErr %v", err, tt.wantErr) } }) } diff --git a/cmd/torrent_export.go b/cmd/torrent_export.go new file mode 100644 index 0000000..4cd7f60 --- /dev/null +++ b/cmd/torrent_export.go @@ -0,0 +1,276 @@ +package cmd + +import ( + "fmt" + "io/fs" + "log" + "os" + "path/filepath" + "strings" + + "github.com/ludviglundgren/qbittorrent-cli/internal/config" + fsutil "github.com/ludviglundgren/qbittorrent-cli/internal/fs" + "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func RunExport() *cobra.Command { + var command = &cobra.Command{ + Use: "export", + Short: "export torrents", + Long: "Export torrents and fastresume by category", + } + + f := export{ + dry: false, + verbose: false, + sourceDir: "", + exportDir: "", + categories: nil, + includeCategory: nil, + excludeCategory: nil, + includeTag: nil, + excludeTag: nil, + hashes: map[string]struct{}{}, + } + + command.Flags().BoolVar(&f.dry, "dry-run", false, "dry run") + command.Flags().BoolVarP(&f.verbose, "verbose", "v", false, "verbose output") + command.Flags().StringVar(&f.sourceDir, "source", "", "Dir with torrent and fast-resume files") + command.Flags().StringVar(&f.exportDir, "export-dir", "", "Dir to export files to") + command.Flags().StringSliceVar(&f.categories, "categories", []string{}, "Export torrents from categories. Comma separated") + command.Flags().StringSliceVar(&f.includeCategory, "include-category", []string{}, "Include categories. Comma separated") + + command.MarkFlagRequired("categories") + + command.RunE = func(cmd *cobra.Command, args []string) error { + // get torrents from client by categories + config.InitConfig() + + if _, err := os.Stat(f.sourceDir); err != nil { + if os.IsNotExist(err) { + return errors.Wrapf(err, "source dir %s does not exist", f.sourceDir) + } + + return err + } + + qbtSettings := qbittorrent.Settings{ + Addr: config.Qbit.Addr, + Hostname: config.Qbit.Host, + Port: config.Qbit.Port, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + if err := qb.Login(cmd.Context()); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) + os.Exit(1) + } + + if len(f.includeCategory) > 0 { + for _, category := range f.categories { + torrents, err := qb.GetTorrentsWithFilters(cmd.Context(), &qbittorrent.GetTorrentsRequest{Category: category}) + if err != nil { + return errors.Wrapf(err, "could not get torrents for category: %s", category) + } + + for _, t := range torrents { + // only grab completed torrents + if t.Progress != 1 { + continue + } + + // todo check tags + + // append hash to map of hashes to gather + f.hashes[strings.ToLower(t.Hash)] = struct{}{} + } + } + + } else { + torrents, err := qb.GetTorrents(cmd.Context()) + if err != nil { + return errors.Wrap(err, "could not get torrents") + } + + for _, t := range torrents { + // only grab completed torrents + if t.Progress != 1 { + continue + } + + // todo check tags and exclude categories + + // append hash to map of hashes to gather + f.hashes[strings.ToLower(t.Hash)] = struct{}{} + } + + } + + if len(f.hashes) == 0 { + fmt.Printf("Could not find any matching torrents to export from (%s)\n", strings.Join(f.categories, ",")) + os.Exit(1) + } + + fmt.Printf("Found '%d' matching torrents\n", len(f.hashes)) + + if err := processExport(f.sourceDir, f.exportDir, f.hashes, f.dry, f.verbose); err != nil { + return errors.Wrapf(err, "could not process torrents") + } + + fmt.Println("Successfully exported torrents!") + + return nil + } + + return command +} + +func processExport(sourceDir, exportDir string, hashes map[string]struct{}, dry, verbose bool) error { + exportCount := 0 + exportTorrentCount := 0 + exportFastresumeCount := 0 + + // check if export dir exists, if not then lets create it + if err := createDirIfNotExists(exportDir); err != nil { + fmt.Printf("could not check if dir %s exists. err: %q\n", exportDir, err) + return errors.Wrapf(err, "could not check if dir exists: %s", exportDir) + } + + // check BT_backup dir, pick torrent and fastresume files by id + err := filepath.Walk(sourceDir, func(dirPath string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + fileName := info.Name() + ext := filepath.Ext(fileName) + + if !isValidExt(ext) { + return nil + } + + torrentHash := fileNameTrimExt(fileName) + + // if filename not in hashes return and check next + _, ok := hashes[torrentHash] + if !ok { + return nil + } + + if dry { + if verbose { + fmt.Printf("processing: %s\n", fileName) + } + + exportCount++ + + //fmt.Printf("dry-run: (%d/%d) exported: %s '%s'\n", exportCount, len(hashes), torrentHash, fileName) + + switch ext { + case ".torrent": + exportTorrentCount++ + fmt.Printf("dry-run: (%d/%d) torrent exported: %s '%s'\n", exportTorrentCount, len(hashes), torrentHash, fileName) + case ".fastresume": + exportFastresumeCount++ + fmt.Printf("dry-run: (%d/%d) fastresume exported: %s '%s'\n", exportFastresumeCount, len(hashes), torrentHash, fileName) + } + + } else { + if verbose { + fmt.Printf("processing: %s\n", fileName) + } + + outFile := filepath.Join(exportDir, fileName) + if err := fsutil.CopyFile(dirPath, outFile); err != nil { + return errors.Wrapf(err, "could not copy file: %s to %s", dirPath, outFile) + } + + exportCount++ + + switch ext { + case ".torrent": + exportTorrentCount++ + fmt.Printf("(%d/%d) torrent exported: %s '%s'\n", exportTorrentCount, len(hashes), torrentHash, fileName) + case ".fastresume": + exportFastresumeCount++ + fmt.Printf("(%d/%d) fastresume exported: %s '%s'\n", exportFastresumeCount, len(hashes), torrentHash, fileName) + } + + //fmt.Printf("(%d/%d) exported: %s '%s'\n", exportCount, len(hashes), torrentHash, fileName) + } + + return nil + }) + if err != nil { + log.Printf("error reading files: %q\n", err) + return err + } + + fmt.Printf(` +found (%d) files in total +exported fastresume: %d +exported torrent %d +`, exportCount, exportFastresumeCount, exportTorrentCount) + + return nil +} + +func fileNameTrimExt(fileName string) string { + return strings.ToLower(strings.TrimSuffix(fileName, filepath.Ext(fileName))) +} + +// isValidExt check if the input ext is one of the ext we want +func isValidExt(filename string) bool { + valid := []string{".torrent", ".fastresume"} + + for _, s := range valid { + if s == filename { + return true + } + } + + return false +} + +// createDirIfNotExists check if export dir exists, if not then lets create it +func createDirIfNotExists(dir string) error { + if _, err := os.Stat(dir); err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + + return nil + } + + return err + } + + return nil +} + +type export struct { + dry bool + verbose bool + sourceDir string + exportDir string + categories []string + includeCategory []string + excludeCategory []string + includeTag []string + excludeTag []string + + hashes map[string]struct{} +} diff --git a/internal/fs/dir.go b/internal/fs/dir.go new file mode 100644 index 0000000..b68a74d --- /dev/null +++ b/internal/fs/dir.go @@ -0,0 +1,20 @@ +package fs + +import "os" + +// MkDirIfNotExists check if export dir exists, if not then lets create it +func MkDirIfNotExists(dir string) error { + if _, err := os.Stat(dir); err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + + return nil + } + + return err + } + + return nil +} From 610f226081809dc0cc3d73cc6efe8ff43d1be093 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Sun, 17 Sep 2023 16:46:05 +0200 Subject: [PATCH 05/21] refactor!: add top level commands and new sub commands (#70) * feat: new top level subcommands * setup top level subcommands and children * update docs * update flags and args * feat: change qbit client pkg * docs: update example config * feat(categories): list * feat(categories): add * feat(categories): delete * feat(categories): edit * feat(tags): list add delete * feat(torrent): category set unset * feat(app): get qbit version info * chore(deps): update go-qbittorrent to v1.4.0 * docs: update README --- .qbt.toml.example | 9 +- README.md | 699 ++++++++++++++++-- cmd/app.go | 82 ++ cmd/bencode.go | 137 ++++ cmd/category.go | 311 ++++++++ cmd/edit.go | 113 --- cmd/import.go | 114 --- cmd/list.go | 148 ---- cmd/move.go | 142 ---- cmd/pause.go | 112 --- cmd/qbt/main.go | 17 +- cmd/remove.go | 152 ---- cmd/resume.go | 112 --- cmd/tag.go | 236 ++++++ cmd/torrent.go | 28 + cmd/{add.go => torrent_add.go} | 136 ++-- cmd/torrent_category.go | 282 +++++++ cmd/{compare.go => torrent_compare.go} | 43 +- ...ompare_test.go => torrent_compare_test.go} | 3 +- cmd/torrent_export.go | 44 +- ...{export_test.go => torrent_export_test.go} | 0 cmd/{hash.go => torrent_hash.go} | 13 +- cmd/torrent_import.go | 140 ++++ cmd/torrent_list.go | 169 +++++ cmd/torrent_pause.go | 82 ++ cmd/{reannounce.go => torrent_reannounce.go} | 67 +- cmd/torrent_remove.go | 124 ++++ cmd/torrent_resume.go | 75 ++ cmd/version.go | 41 +- go.mod | 34 +- go.sum | 77 +- internal/importer/deluge.go | 86 ++- internal/importer/rtorrent.go | 69 +- pkg/torrent/torrent.go | 5 +- 34 files changed, 2650 insertions(+), 1252 deletions(-) create mode 100644 cmd/app.go create mode 100644 cmd/bencode.go create mode 100644 cmd/category.go delete mode 100644 cmd/edit.go delete mode 100644 cmd/import.go delete mode 100644 cmd/list.go delete mode 100644 cmd/move.go delete mode 100644 cmd/pause.go delete mode 100644 cmd/remove.go delete mode 100644 cmd/resume.go create mode 100644 cmd/tag.go create mode 100644 cmd/torrent.go rename cmd/{add.go => torrent_add.go} (62%) create mode 100644 cmd/torrent_category.go rename cmd/{compare.go => torrent_compare.go} (83%) rename cmd/{compare_test.go => torrent_compare_test.go} (95%) rename cmd/{export_test.go => torrent_export_test.go} (100%) rename cmd/{hash.go => torrent_hash.go} (83%) create mode 100644 cmd/torrent_import.go create mode 100644 cmd/torrent_list.go create mode 100644 cmd/torrent_pause.go rename cmd/{reannounce.go => torrent_reannounce.go} (69%) create mode 100644 cmd/torrent_remove.go create mode 100644 cmd/torrent_resume.go diff --git a/.qbt.toml.example b/.qbt.toml.example index 788d7ab..80fc627 100644 --- a/.qbt.toml.example +++ b/.qbt.toml.example @@ -1,8 +1,9 @@ [qbittorrent] -host = "127.0.0.1" # qbittorrent webui-api hostname/ip -port = 6776 # qbittorrent webui-api port -login = "user" # qbittorrent webui-api user -password = "password" # qbittorrent webui-api password +addr = "http://127.0.0.1:6776" # qbittorrent webui-api hostname/ip +login = "user" # qbittorrent webui-api user (optional) +password = "password" # qbittorrent webui-api password (optional) +#basicUser = "user" # qbittorrent webui-api basic auth user (optional) +#basicPass = "password" # qbittorrent webui-api basic auth password (optional) # some trackers are bugged and need to reannounce before torrent can start [reannounce] diff --git a/README.md b/README.md index f893add..16b1940 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,11 @@ A bare minimum config. Check [full example config](.qbt.toml.example). ```toml [qbittorrent] -host = "127.0.0.1" # qbittorrent webui-api hostname/ip -port = 6776 # qbittorrent webui-api port -login = "user" # qbittorrent webui-api user -password = "password" # qbittorrent webui-api password +addr = "http://127.0.0.1:6776" # qbittorrent webui-api hostname/ip +login = "user" # qbittorrent webui-api user +password = "password" # qbittorrent webui-api password +#basicUser = "user" # qbittorrent webui-api basic auth user +#basicPass = "password" # qbittorrent webui-api basic auth password [rules] enabled = true # enable or disable rules @@ -72,6 +73,9 @@ max_active_downloads = 2 # set max active downloads * If running on HDDs and 1Gbit - `max_active_downloads = 2` is a good setting to not overload the disks and gives as much bandwidth as possible to the torrents. * For SSDs and 1Gbit+ you can increase this value. +
+autodl-irssi setup + ### autodl-irssi setup Edit `autodl.cfg` @@ -106,51 +110,395 @@ Command: /usr/bin/qbt Arguments: add "$(TorrentPathName)" ``` +
+ ## Usage Use `qbt help` to find out more about how to use. -Commands: - - add - - list - - version - - help - - move - - compare - - edit - - reannounce - - export - -Global flags: - * `--config` - override config file - +```text +Usage: + qbt [command] + +Available Commands: + app app subcommand + bencode bencode subcommand + category category subcommand + completion Generate the autocompletion script for the specified shell + help Help about any command + tag tag subcommand + torrent torrent subcommand + version Print the version + +Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) + -h, --help help for qbt + Use "qbt [command] --help" for more information about a command. +``` + +Commands: +- app +- bencode +- category +- help +- tag +- torrent +- version + +### App + +``` +Get qBittorrent info + +Usage: + qbt app [command] + +Available Commands: + version Get application version + +Flags: + -h, --help help for app +``` + +#### Version + +``` +Get qBittorrent version info + +Usage: + qbt app version [flags] + +Flags: + -h, --help help for version +``` + +### Bencode + +``` +Do various bencode operations + +Usage: + qbt bencode [command] + +Available Commands: + edit edit bencode data + +Flags: + -h, --help help for bencode + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +#### Edit + +``` +Edit bencode files like .fastresume. Shut down client and make a backup of data before. + +Usage: + qbt bencode edit [flags] + +Examples: + qbt bencode edit --dir /home/user/.local/share/qBittorrent/BT_backup --pattern '/home/user01/torrents' --replace '/home/test/torrents' + +Flags: + --dir string Dir with fast-resume files (required) + --dry-run Dry run, don't write changes + -h, --help help for edit + --pattern string Pattern to change (required) + --replace string Text to replace pattern with (required) + -v, --verbose Verbose output + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +Edit fastresume data like save-path. Make sure to shut down the client and backup the data before running this. + +## Category + +``` +Do various category actions + +Usage: + qbt category [command] + +Available Commands: + add Add category + delete Delete category + edit Edit category + list List categories + +Flags: + -h, --help help for category + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) + +Use "qbt category [command] --help" for more information about a command. +``` + +#### Add + +``` +Add new category + +Usage: + qbt category add [flags] + +Examples: + qbt category add test-category + qbt category add test-category --save-path "/home/user/torrents/test-category" + +Flags: + --dry-run Run without doing anything + -h, --help help for add + --save-path string Category default save-path. Optional. Defaults to dir in default save dir. + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +#### Delete + +``` +Delete category + +Usage: + qbt category delete [flags] + +Examples: + qbt category delete test-category + +Flags: + --dry-run Run without doing anything + -h, --help help for delete + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +#### Edit + +``` +Edit category + +Usage: + qbt category edit [flags] + +Examples: + qbt category edit test-category --save-path "/home/user/new/path" + qbt category edit test-category --save-path "" + +Flags: + --dry-run Run without doing anything + -h, --help help for edit + --save-path string Edit category save-path + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +#### List + +``` +List categories + +Usage: + qbt category list [flags] + +Flags: + -h, --help help for list + --output string Print as [formatted text (default), json] + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +## Tag + +``` +Do various tag actions + +Usage: + qbt tag [command] + +Available Commands: + add Add tags + delete Delete tags + list List tags + +Flags: + -h, --help help for tag + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +#### Add + +``` +Add new tag + +Usage: + qbt tag add [flags] + +Examples: + qbt tag add tag1 + +Flags: + --dry-run Run without doing anything + -h, --help help for add + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +#### Delete + +``` +Usage: + qbt tag delete [flags] + +Examples: + qbt tag delete tag1 + +Flags: + --dry-run Run without doing anything + -h, --help help for delete + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +#### List + +``` +List tags. + +Usage: + qbt tag list [flags] + +Flags: + -h, --help help for list + --output string Print as [formatted text (default), json] + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +## Torrent + +```text +Do various torrent operations + +Usage: + qbt torrent [command] + +Available Commands: + add Add torrent(s) + category Torrent category subcommand + compare Compare torrents + export Export torrents + hash Print the hash of a torrent file or magnet + import Import torrents + list List torrents + pause Pause specified torrent(s) + reannounce Reannounce torrent(s) + remove Removes specified torrent(s) + resume Resume specified torrent(s) + +Flags: + -h, --help help for torrent + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) + +Use "qbt torrent [command] --help" for more information about a command. +``` ### Add -Add a new torrent to qBittorrent. +```text +Add new torrent(s) to qBittorrent from file or magnet. Supports glob pattern for files like: ./files/*.torrent + +Usage: + qbt torrent add [flags] + +Examples: + qbt torrent add my-file.torrent --category test --tags tag1 + qbt torrent add ./files/*.torrent --paused --skip-hash-check + qbt torrent add magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcad53426&dn=download + +Flags: + --category string Add torrent to the specified category + --dry-run Run without doing anything + -h, --help help for add + --ignore-rules Ignore rules from config + --limit-dl uint Set torrent download speed limit. Unit in bytes/second + --limit-ul uint Set torrent upload speed limit. Unit in bytes/second + --paused Add torrent in paused state + --remove-stalled Remove stalled torrents from re-announce + --save-path string Add torrent to the specified path + --skip-hash-check Skip hash check + --tags stringArray Add tags to torrent + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` - qbt add my-torrent-file.torrent +### Category -Optional flags: -* `--dry-run` - Run without doing anything -* `--magnet ` - Add magnet link instead of torrent file -* `--paused` - Add torrent in paused state -* `--skip-hash-check` - Skip hash check -* `--save-path ` - Add torrent to the specified path -* `--category ` - Add torrent to the specified category -* `--tags ` - Add tags to the torrent. Use multiple or comma-separate tags e.g. --tags linux,iso. Supported in 4.3.2+ -* `--ignore-rules` - Ignore rules set in config -* `--limit-ul ` - Set torrent upload speed limit. Unit in bytes/second -* `--limit-dl ` - Set torrent download speed limit. Unit in bytes/second - -### Move +```text +Do various torrent category operations + +Usage: + qbt torrent category [command] + +Available Commands: + move move torrents between categories + set Set torrent category + unset Unset torrent category + +Flags: + -h, --help help for category + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) + +Use "qbt torrent category [command] --help" for more information about a command. +``` + +#### Move + +```text +Move torrents from one category to another + +Usage: + qbt torrent category move [flags] + +Examples: + qbt torrent category move --from cat1 --to cat2 + +Flags: + --dry-run Run without doing anything + --exclude-tags strings Exclude torrents with provided tags + --from strings Move from categories (required) + -h, --help help for move + --include-tags strings Include torrents with provided tags + --min-seed-time int Minimum seed time in MINUTES before moving. + --to string Move to the specified category (required) + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` Move torrents from one or multiple categories to some other category. By using ATM (Automatic Torrent Mode) and the default behavior of categories mapped to folders this can be used to move from NVME to HDD storage after a `min-seed-time`. - qbt move --from categroy1 --to category2 --min-seed-time 45 + qbt torrent category move --from categroy1 --to category2 --min-seed-time 45 Optional flags: * `--dry-run` - Run without doing anything @@ -160,10 +508,81 @@ Optional flags: Usable with cron as well. Run every 15 min. - */15 * * * * /usr/bin/qbt move --from nvme --to hdd --min-seed-time 30 +```cronexp +*/15 * * * * /usr/bin/qbt move --from nvme --to hdd --min-seed-time 30 +``` + +#### Set + +```text +Set category for torrents via hashes + +Usage: + qbt torrent category set [flags] + +Examples: + qbt torrent category set test-category --hashes hash1,hash2 + +Flags: + --dry-run Run without doing anything + --hashes strings Torrent hashes, as comma separated list + -h, --help help for set + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +#### Unset + +```text +Unset category for torrents via hashes + +Usage: + qbt torrent category unset [flags] + +Examples: + qbt torrent category unset --hashes hash1,hash2 + +Flags: + --dry-run Run without doing anything + --hashes strings Torrent hashes, as comma separated list + -h, --help help for unset + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` ### Compare +```text +Compare torrents between clients + +Usage: + qbt torrent compare [flags] + +Examples: + qbt torrent compare --addr http://localhost:10000 --user u --pass p --compare-addr http://url.com:10000 --compare-user u --compare-pass p + +Flags: + --basic-pass string Source basic auth pass + --basic-user string Source basic auth user + --compare-basic-pass string Secondary basic auth pass + --compare-basic-user string Secondary basic auth user + --compare-host string Secondary host + --compare-pass string Secondary pass + --compare-user string Secondary user + --dry-run dry run + -h, --help help for compare + --host string Source host + --pass string Source pass + --tag string set a custom tag for duplicates on compare. default: compare-dupe (default "compare-dupe") + --tag-duplicates tag duplicates on compare + --user string Source user + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + Compare torrents between two instances. Source instance and `compare` instance. Required flags: @@ -184,49 +603,73 @@ Optional flags: * `--tag-duplicates` - Tag duplicates with `compare-dupe` tag, only on compare host * `--tag` - Override the default tag `compare-dupe` -```bash -qbt compare --addr http://url.com:10000 --user u --pass p --compare-addr http://url.com:10000 --compare-user u --compare-pass p +### Export + +```text +Export torrents and fastresume by category + +Usage: + qbt torrent export [flags] + +Examples: + qbt torrent export --source ~/.local/share/data/qBittorrent/BT_backup --export-dir ~/qbt-backup --include-category=movies,tv + +Flags: + --dry-run dry run + --export-dir string Dir to export files to (required) + -h, --help help for export + --include-category strings Export torrents from these categories. Comma separated + --source string Dir with torrent and fast-resume files (required) + -v, --verbose verbose output + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) ``` -### Edit +### Hash -Edit fastresume data like save-path. Make sure to shut down the client and backup the data before running this. +```text +Print the hash of a torrent file or magnet -Required flags: -* `--dir` - Dir to the fastresume data. Usually `~/.local/share/qBittorrent/BT_backup` -* `--pattern` - The pattern to replace. Eg. `/home/user01/torrents` -* `--replace` - The replacement text. Eg. `/home/test/torrents` +Usage: + qbt torrent hash [flags] -Optional flags: -* `--dry-run` - Dry run without editing files -* `--verbose, -v` - Verbose output +Examples: + qbt torrent hash file.torrent + +Flags: + -h, --help help for hash -```bash -qbt edit --dir /home/user/.local/share/qBittorrent/BT_backup --pattern '/home/user01/torrents' --replace '/home/test/torrents' +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) ``` ### Import -Import torrents from other client into qBittorrent, and keep state. +```text +Import torrents with state from other clients [rtorrent, deluge] -Required flags: -* `--source ` - Import from client [deluge, rtorrent] -* `--source-dir ` - State/session dir for client -* `--qbit-dir ` - qBittorrent dir (~/.local/share/data/qBittorrent/BT_backup) +Usage: + qbt torrent import {rtorrent | deluge} --source-dir dir --qbit-dir dir2 [--skip-backup] [--dry-run] [flags] -Supported clients: -* Deluge -* rTorrent +Examples: + qbt torrent import deluge --source-dir ~/.config/deluge/state/ --qbit-dir ~/.local/share/data/qBittorrent/BT_backup --dry-run + qbt torrent import rtorrent --source-dir ~/.sessions --qbit-dir ~/.local/share/data/qBittorrent/BT_backup --dry-run -Optional flags: -* `--dry-run` - don't write anything to disk -* `--skip-backup` - skip backup of state dirs before importing data +Flags: + --dry-run Run without importing anything + -h, --help help for import + --qbit-dir string qBittorrent BT_backup dir. Commonly ~/.local/share/qBittorrent/BT_backup (required) + --skip-backup Skip backup before import + --source-dir string source client state dir (required) -> WARNING: Make sure to stop both the source client and qBittorrent before importing. +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` -Example with Deluge. +Import torrents from other client into qBittorrent, and keep state. - qbt import --source deluge --source-dir ~/.config/deluge/state/ --qbit-dir ~/.local/share/data/qBittorrent/BT_backup --dry-run +> WARNING: Make sure to stop both the source client and qBittorrent before importing. After the import you will have to manually delete the torrents from the source client, but don't check the "also delete files" as currently the import DOES NOT move the actual data. @@ -242,35 +685,129 @@ After the import you will have to manually delete the torrents from the source c Torrents imported into qBittorrent does not have automatic management enabled, because it's default behavior is to move data. 1. Stop source client and qBittorrent. -2. Start with a dry run and see what it does `qbt import ..... --dry-run` +2. Start with a dry run and see what it does `qbt torrent import ..... --dry-run` 3. If it looks ok, run without `--dry-run` 4. Start clients again, go into the source client and stop everything. 5. Set categories/tags in batches. Start to add a category, then set "Automatic torrent management" for it to automatically move the files to the categories specified directory. +### List + +```text +List all torrents, or torrents with a specific filters. Get by filter, category, tag and hashes. Can be combined + +Usage: + qbt torrent list [flags] + +Examples: +qbt torrent list --filter=downloading --category=linux-iso + +Flags: + -c, --category string Filter by category. All categories by default. + -f, --filter string Filter by state. Available filters: all, downloading, seeding, completed, paused, active, inactive, resumed, + stalled, stalled_uploading, stalled_downloading, errored (default "all") + --hashes strings Filter by hashes. Separated by comma: "hash1,hash2". + -h, --help help for list + --output string Print as [formatted text (default), json] + -t, --tag string Filter by tag. Single tag: tag1 + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +### Pause + +```text +Pauses torrents indicated by hash, name or a prefix of either. Whitespace indicates next prefix unless argument is surrounded by quotes + +Usage: + qbt torrent pause [flags] + +Flags: + --all Pauses all torrents + --hashes strings Add hashes as comma separated list + -h, --help help for pause + --names Provided arguments will be read as torrent names + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + ### Reannounce -Reannounce torrents. +```text +Reannounce torrents with non-OK tracker status. - qbt reannounce +Usage: + qbt torrent reannounce [flags] -Optional flags: -* `--dry-run` - Run without doing anything -* `--category ` - Only look for torrents with the provided category -* `--tag ` - Only look for torrents with the provided tag -* `--hash ` - Only look for torrents with the provided hash -* `--interval ` - Override default interval of 7000 ms (7s) -* `--attempts ` - Override default attempts of 50 +Flags: + --attempts int Reannounce torrents X times (default 50) + --category string Reannounce torrents with category + --dry-run Run without doing anything + --hash string Reannounce torrent with hash + -h, --help help for reannounce + --interval int Reannounce torrents X times with interval Y. In MS (default 7000) + --tag string Reannounce torrents with tag -### Export +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` -Export .torrent and .fastresume file baaed on categories. +### Remove - qbt export +```text +Removes torrents indicated by hash, name or a prefix of either. Whitespace indicates next prefix unless argument is surrounded by quotes -Required flags: -* `--source ` - qBittorrent state dir (~/.local/share/data/qBittorrent/BT_backup) -* `--export-dir ` - new dir or qBittorrent state dir (~/.local/share/data/qBittorrent/BT_backup) -* `--categories ` - Only look for torrents with the provided categories. Comma separated +Usage: + qbt torrent remove [flags] + +Flags: + --all Removes all torrents + --delete-files Also delete downloaded files from torrent(s) + --dry-run Display what would be done without actually doing it + --hashes strings Add hashes as comma separated list + -h, --help help for remove + --paused Removes all paused torrents + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +### Resume + +```text +Resumes torrents indicated by hash, name or a prefix of either. Whitespace indicates next prefix unless argument is surrounded by quotes + +Usage: + qbt torrent resume [flags] + +Flags: + --all resumes all torrents + --hashes strings Add hashes as comma separated list + -h, --help help for resume + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) + +``` + +## Version + +```text +Print qbt version info + +Usage: + qbt version [flags] + +Examples: + qbt version + qbt version --output json + +Flags: + -h, --help help for version + --output string Print as [text, json] (default "text") + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` -Optional flags: -* `--dry-run` - Run without doing anything diff --git a/cmd/app.go b/cmd/app.go new file mode 100644 index 0000000..d3d212c --- /dev/null +++ b/cmd/app.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "fmt" + "log" + "os" + + "github.com/ludviglundgren/qbittorrent-cli/internal/config" + + "github.com/autobrr/go-qbittorrent" + "github.com/spf13/cobra" +) + +// RunApp cmd for application info +func RunApp() *cobra.Command { + var command = &cobra.Command{ + Use: "app", + Short: "App subcommand", + Long: "Do various app actions", + } + + command.AddCommand(RunAppVersion()) + + return command +} + +// RunAppVersion cmd to view application info +func RunAppVersion() *cobra.Command { + var command = &cobra.Command{ + Use: "version", + Short: "Get qBittorrent version info", + } + + var ( + output string + ) + + command.Flags().StringVar(&output, "output", "", "Print as [formatted text (default), json]") + + command.RunE = func(cmd *cobra.Command, args []string) error { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) + os.Exit(1) + } + + appVersion, err := qb.GetAppVersionCtx(ctx) + if err != nil { + log.Fatal("could not get app version") + } + + webapiVersion, err := qb.GetWebAPIVersionCtx(ctx) + if err != nil { + log.Fatal("could not get web api version") + } + + switch output { + case "json": + fmt.Printf(`{"app_version":"%s","api_version":"%s"}`, appVersion, webapiVersion) + + default: + fmt.Printf("qBittorrent version info:\nApp version: %s\nAPI version: %s\n", appVersion, webapiVersion) + } + + return nil + } + + return command +} diff --git a/cmd/bencode.go b/cmd/bencode.go new file mode 100644 index 0000000..162866e --- /dev/null +++ b/cmd/bencode.go @@ -0,0 +1,137 @@ +package cmd + +import ( + "fmt" + "io/fs" + "log" + "os" + "path/filepath" + "strings" + + "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/zeebo/bencode" +) + +// RunBencode cmd for bencode operations +func RunBencode() *cobra.Command { + var command = &cobra.Command{ + Use: "bencode", + Short: "Bencode subcommand", + Long: `Do various bencode operations`, + } + + command.AddCommand(RunBencodeEdit()) + + return command +} + +func RunBencodeEdit() *cobra.Command { + var command = &cobra.Command{ + Use: "edit", + Short: "edit bencode data", + Long: "Edit bencode files like .fastresume. Shut down client and make a backup of data before.", + Example: ` qbt bencode edit --dir /home/user/.local/share/qBittorrent/BT_backup --pattern '/home/user01/torrents' --replace '/home/test/torrents'`, + } + + var ( + dry bool + verbose bool + dir string + pattern string + replace string + ) + + command.Flags().BoolVar(&dry, "dry-run", false, "Dry run, don't write changes") + command.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output") + + command.Flags().StringVar(&dir, "dir", "", "Dir with fast-resume files (required)") + command.Flags().StringVar(&pattern, "pattern", "", "Pattern to change (required)") + command.Flags().StringVar(&replace, "replace", "", "Text to replace pattern with (required)") + + command.MarkFlagRequired("dir") + command.MarkFlagRequired("pattern") + command.MarkFlagRequired("replace") + + command.RunE = func(cmd *cobra.Command, args []string) error { + + // make sure dir exists before walk + _, err := os.Stat(dir) + if err != nil { + if os.IsNotExist(err) { + return errors.Wrapf(err, "Directory does not exist: %s\n", dir) + } + + return errors.Wrapf(err, "Directory error: %s\n", dir) + } + + matchedFiles := 0 + + err = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + matched, err := filepath.Match("*.fastresume", info.Name()) + if err != nil { + log.Fatalf("error matching files: %v", err) + } + + if matched { + matchedFiles++ + + if err := processFastResume(path, pattern, replace, verbose, dry); err != nil { + log.Fatalf("error processing file: %v", err) + } + } + + return nil + }) + if err != nil { + log.Fatalf("error reading files: %v", err) + } + + fmt.Printf("Found, matched and replaced in '%d' files\n", matchedFiles) + + return nil + } + + return command +} + +func processFastResume(path, pattern, replace string, verbose, dry bool) error { + if dry { + if verbose { + fmt.Printf("dry-run: replaced: '%s' with '%s' in %s\n", pattern, replace, path) + } + } else { + read, err := os.ReadFile(path) + if err != nil { + log.Fatalf("error reading file: %v - %v", path, err) + } + + var fastResume qbittorrent.Fastresume + if err := bencode.DecodeString(string(read), &fastResume); err != nil { + log.Printf("could not decode fastresume %v", path) + } + + fastResume.SavePath = strings.Replace(fastResume.SavePath, pattern, replace, -1) + + if err = fastResume.Encode(path); err != nil { + log.Printf("could not create qBittorrent fastresume file %s error: %q", path, err) + return err + } + + if verbose { + fmt.Printf("Replaced: '%s' with '%s' in %s\n", pattern, replace, path) + } + } + + return nil +} diff --git a/cmd/category.go b/cmd/category.go new file mode 100644 index 0000000..9444148 --- /dev/null +++ b/cmd/category.go @@ -0,0 +1,311 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "log" + "os" + "text/template" + + "github.com/ludviglundgren/qbittorrent-cli/internal/config" + + "github.com/autobrr/go-qbittorrent" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// RunCategory cmd for category actions +func RunCategory() *cobra.Command { + var command = &cobra.Command{ + Use: "category", + Short: "Category subcommand", + Long: `Do various category actions`, + } + + command.AddCommand(RunCategoryAdd()) + command.AddCommand(RunCategoryDelete()) + command.AddCommand(RunCategoryEdit()) + command.AddCommand(RunCategoryList()) + + return command +} + +// RunCategoryList cmd to add categories +func RunCategoryList() *cobra.Command { + var command = &cobra.Command{ + Use: "list", + Short: "List categories", + Long: `List categories`, + } + + var ( + output string + ) + + command.Flags().StringVar(&output, "output", "", "Print as [formatted text (default), json]") + + command.RunE = func(cmd *cobra.Command, args []string) error { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) + os.Exit(1) + } + + cats, err := qb.GetCategoriesCtx(ctx) + if err != nil { + log.Fatal("could not get categories") + } + + if len(cats) == 0 { + fmt.Println("No categories found") + return nil + } + + switch output { + case "json": + res, err := json.Marshal(cats) + if err != nil { + log.Fatalf("could not marshal categories, err: %q", err) + } + + fmt.Println(string(res)) + + default: + printCategoryList(cats) + + } + + return nil + } + + return command +} + +var categoryItemTemplate = `{{ range .}} +Name: {{.Name}} +Save path: {{.SavePath}} +{{end}} +` + +func printCategoryList(categories map[string]qbittorrent.Category) { + tmpl, err := template.New("category-list").Parse(categoryItemTemplate) + if err != nil { + log.Fatalf("error: %q", err) + } + + err = tmpl.Execute(os.Stdout, categories) + if err != nil { + log.Fatalf("could not generate template: %q", err) + } +} + +// RunCategoryAdd cmd to add categories +func RunCategoryAdd() *cobra.Command { + var ( + dry bool + savePath string + ) + + var command = &cobra.Command{ + Use: "add", + Short: "Add category", + Long: "Add new category", + Example: ` qbt category add test-category + qbt category add test-category --save-path "/home/user/torrents/test-category"`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires a category as first argument") + } + + return nil + }, + } + command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") + command.Flags().StringVar(&savePath, "save-path", "", "Category default save-path. Optional. Defaults to dir in default save dir.") + + command.RunE = func(cmd *cobra.Command, args []string) error { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) + os.Exit(1) + } + + // args + // first arg is path to torrent file + category := args[0] + + if dry { + log.Printf("dry-run: successfully added category: %s\n", category) + + return nil + + } else { + if err := qb.CreateCategoryCtx(ctx, category, savePath); err != nil { + log.Fatal("could not create category") + } + + log.Printf("successfully added category: %s\n", category) + } + + return nil + } + + return command +} + +// RunCategoryDelete cmd to delete categories +func RunCategoryDelete() *cobra.Command { + var ( + dry bool + ) + + var command = &cobra.Command{ + Use: "delete", + Short: "Delete category", + Long: "Delete category", + Example: ` qbt category delete test-category`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires a category as first argument") + } + + return nil + }, + } + command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") + + command.RunE = func(cmd *cobra.Command, args []string) error { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) + os.Exit(1) + } + + // args + // first arg is path to torrent file + category := args[0] + + if dry { + log.Printf("dry-run: successfully deleted category: %s\n", category) + + return nil + + } else { + if err := qb.RemoveCategoriesCtx(ctx, []string{category}); err != nil { + log.Fatal("could not delete category") + } + + log.Printf("successfully deleted category: %s\n", category) + } + + return nil + } + + return command +} + +// RunCategoryEdit cmd to edit category +func RunCategoryEdit() *cobra.Command { + var ( + dry bool + savePath string + ) + + var command = &cobra.Command{ + Use: "edit", + Short: "Edit category", + Long: "Edit category", + Example: ` qbt category edit test-category --save-path "/home/user/new/path" + qbt category edit test-category --save-path ""`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires a category as first argument") + } + + return nil + }, + } + command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") + command.Flags().StringVar(&savePath, "save-path", "", "Edit category save-path") + + command.RunE = func(cmd *cobra.Command, args []string) error { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) + os.Exit(1) + } + + // args + // first arg is path to torrent file + category := args[0] + + if dry { + log.Printf("dry-run: successfully edited category: %s\n", category) + + return nil + + } else { + if err := qb.EditCategoryCtx(ctx, category, savePath); err != nil { + log.Fatal("could not edit category") + } + + log.Printf("successfully edited category: %s\n", category) + } + + return nil + } + + return command +} diff --git a/cmd/edit.go b/cmd/edit.go deleted file mode 100644 index 972622a..0000000 --- a/cmd/edit.go +++ /dev/null @@ -1,113 +0,0 @@ -package cmd - -import ( - "fmt" - "io/fs" - "log" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - "github.com/zeebo/bencode" - - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" -) - -func RunEdit() *cobra.Command { - var command = &cobra.Command{ - Use: "edit", - Short: "edit fast resume data", - Long: "edit torrent fast resume data. Make sure to backup data before.", - } - - var ( - dry bool - verbose bool - dir string - pattern string - replace string - ) - - command.Flags().BoolVar(&dry, "dry-run", false, "Dry run, don't write changes") - command.Flags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output") - - command.Flags().StringVar(&dir, "dir", "", "Dir with fast-resume files") - command.Flags().StringVar(&pattern, "pattern", "", "Pattern to change") - command.Flags().StringVar(&replace, "replace", "", "Text to replace pattern with") - - command.Run = func(cmd *cobra.Command, args []string) { - if dir == "" { - log.Fatal("must have dir\n") - } - - matchedFiles := 0 - - err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { - if err != nil { - return err - } - - if info.IsDir() { - return nil - } - - if pattern == "" { - return errors.New("must have pattern") - } else if replace == "" { - return errors.New("must have replace") - } - - matched, err := filepath.Match("*.fastresume", info.Name()) - if err != nil { - log.Fatalf("error matching files: %v", err) - } - - if matched { - matchedFiles++ - - err := processFastResume(path, pattern, replace, verbose, dry) - if err != nil { - log.Fatalf("error processing file: %v", err) - } - } - - return nil - }) - if err != nil { - log.Fatalf("error reading files: %v", err) - } - - fmt.Printf("Found, matched and replaced in '%d' files\n", matchedFiles) - } - - return command -} - -func processFastResume(path, pattern, replace string, verbose, dry bool) error { - read, err := os.ReadFile(path) - if err != nil { - log.Fatalf("error reading file: %v - %v", path, err) - } - - var fastResume qbittorrent.Fastresume - if err := bencode.DecodeString(string(read), &fastResume); err != nil { - log.Printf("could not decode fastresume %v", path) - } - - if !dry { - fastResume.SavePath = strings.Replace(fastResume.SavePath, pattern, replace, -1) - - if err = fastResume.Encode(path); err != nil { - log.Printf("could not create qBittorrent fastresume file %v error: %v", path, err) - return err - } - } - - if verbose { - fmt.Printf("Replaced: '%v' with '%v' in %v\n", pattern, replace, path) - } - - return nil -} diff --git a/cmd/import.go b/cmd/import.go deleted file mode 100644 index e6bff68..0000000 --- a/cmd/import.go +++ /dev/null @@ -1,114 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - "path" - "time" - - "github.com/ludviglundgren/qbittorrent-cli/internal/importer" - - "github.com/mholt/archiver/v3" - "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" -) - -// RunImport cmd import torrents -func RunImport() *cobra.Command { - var command = &cobra.Command{ - Use: "import", - Short: "import torrents", - Long: `Import torrents with state from other clients [rtorrent, deluge]`, - } - - var ( - source string - sourceDir string - qbitDir string - dryRun bool - skipBackup bool - ) - - command.Flags().StringVar(&source, "source", "", "source client [deluge, rtorrent] (required)") - command.Flags().StringVar(&sourceDir, "source-dir", "", "source client state dir (required)") - command.Flags().StringVar(&qbitDir, "qbit-dir", "", "qbit dir (required)") - command.Flags().BoolVar(&dryRun, "dry-run", false, "Run without doing anything") - command.Flags().BoolVar(&skipBackup, "skip-backup", false, "Skip backup before import. Not advised") - - command.MarkFlagRequired("source") - command.MarkFlagRequired("source-dir") - command.MarkFlagRequired("qbit-dir") - - command.RunE = func(cmd *cobra.Command, args []string) error { - - // TODO check if program is running, if true exit - - // Backup data before running - if !skipBackup { - fmt.Print("Prepare to backup torrent data before import..\n") - - homeDir, err := homedir.Dir() - if err != nil { - fmt.Printf("could not find home directory: %q", err) - return err - } - - timeStamp := time.Now().Format("20060102150405") - sourceBackupArchive := path.Join(homeDir, "/qbt_backup/", source+"_backup_"+timeStamp+".tar.gz") - qbitBackupArchive := path.Join(homeDir, "/qbt_backup/", "qBittorrent_backup_"+timeStamp+".tar.gz") - - fmt.Printf("Creating %s backup of directory: %s to %s ...\n", source, sourceDir, sourceBackupArchive) - if err = archiver.Archive([]string{sourceDir}, sourceBackupArchive); err != nil { - log.Printf("could not backup directory: %q", err) - return err - } - - fmt.Printf("Creating qBittorrent backup of directory: %s to %s ...\n", qbitDir, qbitBackupArchive) - if err = archiver.Archive([]string{qbitDir}, qbitBackupArchive); err != nil { - log.Printf("could not backup directory: %q", err) - return err - } - - fmt.Print("Backup completed!\n") - } - - opts := importer.Options{ - SourceDir: sourceDir, - QbitDir: qbitDir, - DryRun: dryRun, - } - - start := time.Now() - - fmt.Printf("Preparing to import torrents from: %s dir: %s\n", source, sourceDir) - if dryRun { - fmt.Println("running with --dry-run, no data will be written") - } - - switch source { - case "deluge": - d := importer.NewDelugeImporter() - - if err := d.Import(opts); err != nil { - fmt.Printf("deluge import error: %q", err) - } - - case "rtorrent": - r := importer.NewRTorrentImporter() - - if err := r.Import(opts); err != nil { - fmt.Printf("rtorrent import error: %q", err) - } - - default: - fmt.Println("WARNING: Unsupported client!") - } - - elapsed := time.Since(start) - fmt.Printf("\nImport finished in: %s\n", elapsed) - - return nil - } - - return command -} diff --git a/cmd/list.go b/cmd/list.go deleted file mode 100644 index 368b7b8..0000000 --- a/cmd/list.go +++ /dev/null @@ -1,148 +0,0 @@ -package cmd - -import ( - "context" - "encoding/json" - "fmt" - "os" - "strings" - - "github.com/ludviglundgren/qbittorrent-cli/internal/config" - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" - - "github.com/dustin/go-humanize" - "github.com/spf13/cobra" -) - -// RunList cmd to list torrents -func RunList() *cobra.Command { - var ( - filter = "all" - category string - tag string - hashes string - outputJson bool - ) - - var command = &cobra.Command{ - Use: "list", - Short: "List torrents", - Long: `List all torrents, or torrents with a specific filters. Get by filter, category, tag and hashes. Can be combined`, - Example: `qbt list --filter=downloading --category=linux-iso`, - } - command.Flags().BoolVar(&outputJson, "json", false, "Print to json") - command.Flags().StringVarP(&filter, "filter", "f", "all", "Filter by state. Available filters: all, downloading, seeding, completed, paused, active, inactive, resumed, \nstalled, stalled_uploading, stalled_downloading, errored") - command.Flags().StringVarP(&category, "category", "c", "", "Filter by category. All categories by default.") - command.Flags().StringVarP(&tag, "tag", "t", "", "Filter by tag. Single tag: tag1") - command.Flags().StringVar(&hashes, "hashes", "", "Filter by hashes. Separated by | pipe: \"hash1|hash2\".") - - command.Run = func(cmd *cobra.Command, args []string) { - config.InitConfig() - qbtSettings := qbittorrent.Settings{ - Addr: config.Qbit.Addr, - Hostname: config.Qbit.Host, - Port: config.Qbit.Port, - Username: config.Qbit.Login, - Password: config.Qbit.Password, - BasicUser: config.Qbit.BasicUser, - BasicPass: config.Qbit.BasicPass, - } - - qb := qbittorrent.NewClient(qbtSettings) - - ctx := context.Background() - - if err := qb.Login(ctx); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) - os.Exit(1) - } - - req := qbittorrent.GetTorrentsRequest{ - Filter: qbittorrent.TorrentFilter(strings.ToLower(filter)), - Category: category, - Tag: tag, - Hashes: hashes, - } - - // get torrent list with default filter of all - torrents, err := qb.GetTorrentsWithFilters(ctx, &req) - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not get torrents %v\n", err) - os.Exit(1) - } - - if len(torrents) < 1 { - fmt.Printf("No torrents found with filter: %s\n", filter) - return - } - - if outputJson { - res, err := json.Marshal(torrents) - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not marshal torrents to json %v\n", err) - os.Exit(1) - } - fmt.Println(string(res)) - return - } - - printList(torrents) - } - - return command -} - -func printList(torrents []qbittorrent.Torrent) { - space := fmt.Sprintf("%*c", 4, ' ') - for _, torrent := range torrents { - fmt.Printf("[*] ") - fmt.Printf("%-80.80s%s[%s]\n", torrent.Name, space, torrent.State) - - fmt.Printf("%sDownloaded: ", space) - if torrent.AmountLeft <= 0 { - fmt.Printf("%s%s", humanize.Bytes(uint64(torrent.TotalSize)), space) - } else { - fmt.Printf( - "%s / %s%s", - humanize.Bytes(uint64(torrent.Completed)), - humanize.Bytes(uint64(torrent.TotalSize)), - space, - ) - } - - if torrent.Uploaded > 0 { - fmt.Printf("Uploaded: %s%s", humanize.Bytes(uint64(torrent.Uploaded)), space) - } - - if torrent.DlSpeed > 0 { - fmt.Printf( - "DL Speed: %s/s%s", - humanize.Bytes(uint64(torrent.DlSpeed)), - space, - ) - } else if torrent.UpSpeed > 0 { - fmt.Printf( - "UP Speed: %s/s%s", - humanize.Bytes(uint64(torrent.UpSpeed)), - space, - ) - } - - days := torrent.TimeActive / (60 * 60 * 24) - hours := (torrent.TimeActive / (60 * 60)) - (days * 24) - minutes := (torrent.TimeActive / 60) - ((days * 1440) + (hours * 60)) - - if days > 0 { - fmt.Printf("Time Active: %dd %dh %dm\n", days, hours, minutes) - } else if hours > 0 { - fmt.Printf("Time Active: %dh %dm\n", hours, minutes) - } else { - fmt.Printf("Time Active: %dm\n", minutes) - } - - fmt.Printf("%sSave Path: %s\n", space, torrent.SavePath) - fmt.Printf("%sHash: %s\n", space, torrent.Hash) - - fmt.Println() - } -} diff --git a/cmd/move.go b/cmd/move.go deleted file mode 100644 index b576180..0000000 --- a/cmd/move.go +++ /dev/null @@ -1,142 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "os" - "strings" - "time" - - "github.com/ludviglundgren/qbittorrent-cli/internal/config" - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" - - "github.com/spf13/cobra" -) - -// RunMove cmd to move torrents from some category to another -func RunMove() *cobra.Command { - var ( - dry bool - fromCategories []string - targetCategory string - includeTags []string - excludeTags []string - minSeedTime int - ) - - var command = &cobra.Command{ - Use: "move", - Short: "move torrents", - Long: `Move torrents from some category to another`, - } - - command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") - command.Flags().StringSliceVar(&fromCategories, "from", []string{}, "Move from categories") - command.Flags().StringVar(&targetCategory, "to", "", "Move to the specified category") - command.Flags().StringSliceVar(&includeTags, "include-tags", []string{}, "Include torrents with provided tags") - command.Flags().StringSliceVar(&excludeTags, "exclude-tags", []string{}, "Exclude torrents with provided tags") - command.Flags().IntVar(&minSeedTime, "min-seed-time", 0, "Minimum seed time in MINUTES before moving.") - command.MarkFlagRequired("from") - command.MarkFlagRequired("to") - - command.Run = func(cmd *cobra.Command, args []string) { - config.InitConfig() - qbtSettings := qbittorrent.Settings{ - Addr: config.Qbit.Addr, - Hostname: config.Qbit.Host, - Port: config.Qbit.Port, - Username: config.Qbit.Login, - Password: config.Qbit.Password, - BasicUser: config.Qbit.BasicUser, - BasicPass: config.Qbit.BasicPass, - } - - qb := qbittorrent.NewClient(qbtSettings) - - ctx := context.Background() - - if err := qb.Login(ctx); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) - os.Exit(1) - } - - var hashes []string - - for _, cat := range fromCategories { - torrents, err := qb.GetTorrentsWithFilters(ctx, &qbittorrent.GetTorrentsRequest{Category: cat}) - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not get torrents by category %v\n", err) - os.Exit(1) - } - - for _, torrent := range torrents { - // only grab completed torrents since we don't specify filter state - if torrent.Progress != 1 { - continue - } - - if len(includeTags) > 0 { - if _, validTag := validateTag(includeTags, torrent.Tags); !validTag { - continue - } - } - - if len(excludeTags) > 0 { - if tag, found := validateTag(excludeTags, torrent.Tags); found { - fmt.Printf("ignoring torrent %s %s containng tag: %s of tags: %s", torrent.Name, torrent.Hash, tag, excludeTags) - continue - } - } - - // check TimeActive (seconds), CompletionOn (epoch) SeenComplete - if minSeedTime > 0 { - completedTime := time.Unix(int64(torrent.CompletionOn), 0) - completedTimePlusMinSeedTime := completedTime.Add(time.Duration(minSeedTime) * time.Minute) - currentTime := time.Now() - - diff := currentTime.After(completedTimePlusMinSeedTime) - if !diff { - continue - } - } - - hashes = append(hashes, torrent.Hash) - } - } - - if len(hashes) == 0 { - fmt.Printf("Could not find any matching torrents to move from (%s) to (%s) with tags (%s) and min-seed-time %d minutes\n", strings.Join(fromCategories, ","), targetCategory, strings.Join(includeTags, ","), minSeedTime) - os.Exit(0) - } - - if !dry { - fmt.Printf("Found %d matching torrents to move from (%s) to (%s)\n", len(hashes), strings.Join(fromCategories, ","), targetCategory) - if err := qb.SetCategory(ctx, hashes, targetCategory); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not pause torrents %v\n", err) - os.Exit(1) - } - - fmt.Printf("Successfully moved %d torrents from (%s) to (%s)\n", len(hashes), strings.Join(fromCategories, ","), targetCategory) - } else { - fmt.Printf("DRY-RUN: Found %d matching torrents to move from (%s) to (%s)\n", len(hashes), strings.Join(fromCategories, ","), targetCategory) - fmt.Printf("DRY-RUN: Successfully moved %d torrents from (%s) to (%s)\n", len(hashes), strings.Join(fromCategories, ","), targetCategory) - return - } - } - - return command -} - -func validateTag(includeTags []string, torrentTags string) (string, bool) { - tagList := strings.Split(torrentTags, ", ") - - for _, includeTag := range includeTags { - for _, tag := range tagList { - if tag == includeTag { - return tag, true - } - } - } - - return "", false -} diff --git a/cmd/pause.go b/cmd/pause.go deleted file mode 100644 index 7ef522c..0000000 --- a/cmd/pause.go +++ /dev/null @@ -1,112 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "log" - "os" - "time" - - "github.com/ludviglundgren/qbittorrent-cli/internal/config" - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" - - "github.com/spf13/cobra" -) - -// RunPause cmd to pause torrents -func RunPause() *cobra.Command { - var ( - pauseAll bool - hashes bool - names bool - ) - - var command = &cobra.Command{ - Use: "pause", - Short: "Pause specified torrents", - Long: `Pauses torrents indicated by hash, name or a prefix of either; - whitespace indicates next prefix unless argument is surrounded by quotes`, - } - - command.Flags().BoolVar(&pauseAll, "all", false, "Pauses all torrents") - command.Flags().BoolVar(&hashes, "hashes", false, "Provided arguments will be read as torrent hashes") - command.Flags().BoolVar(&names, "names", false, "Provided arguments will be read as torrent names") - - command.Run = func(cmd *cobra.Command, args []string) { - if !pauseAll && len(args) < 1 { - log.Printf("Please provide atleast one torrent hash/name as an argument") - return - } - - if !pauseAll && !hashes && !names { - log.Printf("Please specifiy if arguments are to be read as hashes or names (--hashes / --names)") - return - } - - config.InitConfig() - qbtSettings := qbittorrent.Settings{ - Addr: config.Qbit.Addr, - Hostname: config.Qbit.Host, - Port: config.Qbit.Port, - Username: config.Qbit.Login, - Password: config.Qbit.Password, - BasicUser: config.Qbit.BasicUser, - BasicPass: config.Qbit.BasicPass, - } - - qb := qbittorrent.NewClient(qbtSettings) - - ctx := context.Background() - - if err := qb.Login(ctx); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) - os.Exit(1) - } - - if pauseAll { - if err := qb.Pause(ctx, []string{"all"}); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not pause torrents: %v\n", err) - os.Exit(1) - } - - log.Printf("All torrents paused successfully") - return - } - - foundTorrents, err := qb.GetTorrentsByPrefixes(ctx, args, hashes, names) - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: failed to retrieve torrents: %v\n", err) - os.Exit(1) - } - - hashesToPause := []string{} - for _, torrent := range foundTorrents { - hashesToPause = append(hashesToPause, torrent.Hash) - } - - if len(hashesToPause) < 1 { - log.Printf("No torrents found to pause with provided search terms") - return - } - - // Split the hashes to pause into groups of 20 to avoid flooding qbittorrent - batch := 20 - for i := 0; i < len(hashesToPause); i += batch { - j := i + batch - if j > len(hashesToPause) { - j = len(hashesToPause) - } - - if err := qb.Pause(ctx, hashesToPause[i:j]); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not pause torrents: %v\n", err) - os.Exit(1) - } - - time.Sleep(time.Second * 1) - } - - log.Printf("torrent(s) successfully paused") - } - - return command -} diff --git a/cmd/qbt/main.go b/cmd/qbt/main.go index 3cc3cf0..352ee24 100644 --- a/cmd/qbt/main.go +++ b/cmd/qbt/main.go @@ -31,19 +31,12 @@ Documentation is available at https://github.com/ludviglundgren/qbittorrent-cli` // override config rootCmd.PersistentFlags().StringVar(&config.CfgFile, "config", "", "config file (default is $HOME/.config/qbt/.qbt.toml)") + rootCmd.AddCommand(cmd.RunApp()) + rootCmd.AddCommand(cmd.RunBencode()) + rootCmd.AddCommand(cmd.RunTorrent()) + rootCmd.AddCommand(cmd.RunCategory()) + rootCmd.AddCommand(cmd.RunTag()) rootCmd.AddCommand(cmd.RunVersion(version, commit, date)) - rootCmd.AddCommand(cmd.RunAdd()) - rootCmd.AddCommand(cmd.RunCompare()) - rootCmd.AddCommand(cmd.RunEdit()) - rootCmd.AddCommand(cmd.RunExport()) - rootCmd.AddCommand(cmd.RunHash()) - rootCmd.AddCommand(cmd.RunImport()) - rootCmd.AddCommand(cmd.RunList()) - rootCmd.AddCommand(cmd.RunMove()) - rootCmd.AddCommand(cmd.RunPause()) - rootCmd.AddCommand(cmd.RunReannounce()) - rootCmd.AddCommand(cmd.RunRemove()) - rootCmd.AddCommand(cmd.RunResume()) if err := rootCmd.Execute(); err != nil { os.Exit(1) diff --git a/cmd/remove.go b/cmd/remove.go deleted file mode 100644 index e36c759..0000000 --- a/cmd/remove.go +++ /dev/null @@ -1,152 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "log" - "os" - "time" - - "github.com/ludviglundgren/qbittorrent-cli/internal/config" - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" - - "github.com/spf13/cobra" -) - -// RunRemove cmd to remove torrents -func RunRemove() *cobra.Command { - var ( - removeAll bool - removePaused bool - deleteFiles bool - hashes bool - names bool - dryRun bool - ) - - var command = &cobra.Command{ - Use: "remove", - Short: "Removes specified torrents", - Long: `Removes torrents indicated by hash, name or a prefix of either; - whitespace indicates next prefix unless argument is surrounded by quotes`, - } - - command.Flags().BoolVar(&removeAll, "all", false, "Removes all torrents") - command.Flags().BoolVar(&removePaused, "paused", false, "Removes all paused torrents") - command.Flags().BoolVar(&deleteFiles, "delete-files", false, "Also delete downloaded files from torrent(s)") - command.Flags().BoolVar(&hashes, "hashes", false, "Provided arguments will be read as torrent hashes") - command.Flags().BoolVar(&names, "names", false, "Provided arguments will be read as torrent names") - command.Flags().BoolVar(&dryRun, "dry-run", false, "Display what would be done without actually doing it") - - command.Run = func(cmd *cobra.Command, args []string) { - if !removeAll && !removePaused && len(args) < 1 { - log.Printf("Please provide at least one torrent hash/name as an argument") - return - } - - if !removeAll && !removePaused && !hashes && !names { - log.Printf("Please specify if arguments are to be read as hashes or names (--hashes / --names)") - return - } - - config.InitConfig() - qbtSettings := qbittorrent.Settings{ - Addr: config.Qbit.Addr, - Hostname: config.Qbit.Host, - Port: config.Qbit.Port, - Username: config.Qbit.Login, - Password: config.Qbit.Password, - BasicUser: config.Qbit.BasicUser, - BasicPass: config.Qbit.BasicPass, - } - - qb := qbittorrent.NewClient(qbtSettings) - - ctx := context.Background() - - if err := qb.Login(ctx); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) - os.Exit(1) - } - - if removeAll { - if dryRun { - log.Printf("Would remove all torrents") - } else { - if err := qb.DeleteTorrents(ctx, []string{"all"}, deleteFiles); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not delete torrents: %v\n", err) - os.Exit(1) - } - - log.Printf("All torrents removed successfully") - } - return - } - - if removePaused { - pausedTorrents, err := qb.GetTorrentsWithFilters(ctx, &qbittorrent.GetTorrentsRequest{Filter: qbittorrent.TorrentFilterPaused}) - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: failed to retrieve paused torrents: %v\n", err) - os.Exit(1) - } - - hashesToRemove := []string{} - for _, torrent := range pausedTorrents { - hashesToRemove = append(hashesToRemove, torrent.Hash) - } - - if len(hashesToRemove) < 1 { - log.Printf("No paused torrents found to remove") - return - } - - if dryRun { - log.Printf("Paused torrents to be removed: %v", hashesToRemove) - } else { - if err := qb.DeleteTorrents(ctx, hashesToRemove, deleteFiles); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not delete paused torrents: %v\n", err) - os.Exit(1) - } - - log.Print("Paused torrents removed successfully") - } - return - } - - foundTorrents, err := qb.GetTorrentsByPrefixes(ctx, args, hashes, names) - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: failed to retrieve torrents: %v\n", err) - os.Exit(1) - } - - hashesToRemove := []string{} - for _, torrent := range foundTorrents { - hashesToRemove = append(hashesToRemove, torrent.Hash) - } - - if len(hashesToRemove) < 1 { - log.Printf("No torrents found to remove with provided search terms") - return - } - - // Split the hashes to remove into groups of 20 to avoid flooding qbittorrent - batch := 20 - for i := 0; i < len(hashesToRemove); i += batch { - j := i + batch - if j > len(hashesToRemove) { - j = len(hashesToRemove) - } - - if err := qb.DeleteTorrents(ctx, hashesToRemove[i:j], deleteFiles); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not delete torrents: %v\n", err) - os.Exit(1) - } - - time.Sleep(time.Second * 1) - } - - log.Printf("torrent(s) successfully deleted") - } - - return command -} diff --git a/cmd/resume.go b/cmd/resume.go deleted file mode 100644 index 225c049..0000000 --- a/cmd/resume.go +++ /dev/null @@ -1,112 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "log" - "os" - "time" - - "github.com/ludviglundgren/qbittorrent-cli/internal/config" - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" - - "github.com/spf13/cobra" -) - -// RunResume cmd to resume torrents -func RunResume() *cobra.Command { - var ( - resumeAll bool - hashes bool - names bool - ) - - var command = &cobra.Command{ - Use: "resume", - Short: "resume specified torrents", - Long: `resumes torrents indicated by hash, name or a prefix of either; - whitespace indicates next prefix unless argument is surrounded by quotes`, - } - - command.Flags().BoolVar(&resumeAll, "all", false, "resumes all torrents") - command.Flags().BoolVar(&hashes, "hashes", false, "Provided arguments will be read as torrent hashes") - command.Flags().BoolVar(&names, "names", false, "Provided arguments will be read as torrent names") - - command.Run = func(cmd *cobra.Command, args []string) { - if !resumeAll && len(args) < 1 { - log.Printf("Please provide atleast one torrent hash/name as an argument") - return - } - - if !resumeAll && !hashes && !names { - log.Printf("Please specifiy if arguments are to be read as hashes or names (--hashes / --names)") - return - } - - config.InitConfig() - qbtSettings := qbittorrent.Settings{ - Addr: config.Qbit.Addr, - Hostname: config.Qbit.Host, - Port: config.Qbit.Port, - Username: config.Qbit.Login, - Password: config.Qbit.Password, - BasicUser: config.Qbit.BasicUser, - BasicPass: config.Qbit.BasicPass, - } - - qb := qbittorrent.NewClient(qbtSettings) - - ctx := context.Background() - - if err := qb.Login(ctx); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) - os.Exit(1) - } - - if resumeAll { - if err := qb.Resume(ctx, []string{"all"}); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not resume torrents: %v\n", err) - os.Exit(1) - } - - log.Printf("All torrents resumed successfully") - return - } - - foundTorrents, err := qb.GetTorrentsByPrefixes(ctx, args, hashes, names) - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: failed to retrieve torrents: %v\n", err) - os.Exit(1) - } - - hashesToResume := []string{} - for _, torrent := range foundTorrents { - hashesToResume = append(hashesToResume, torrent.Hash) - } - - if len(hashesToResume) < 1 { - log.Printf("No torrents found to resume with provided search terms") - return - } - - // Split the hashes to resume into groups of 20 to avoid flooding qbittorrent - batch := 20 - for i := 0; i < len(hashesToResume); i += batch { - j := i + batch - if j > len(hashesToResume) { - j = len(hashesToResume) - } - - if err := qb.Resume(ctx, hashesToResume[i:j]); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not resume torrents: %v\n", err) - os.Exit(1) - } - - time.Sleep(time.Second * 1) - } - - log.Printf("torrent(s) successfully resumed") - } - - return command -} diff --git a/cmd/tag.go b/cmd/tag.go new file mode 100644 index 0000000..88fef56 --- /dev/null +++ b/cmd/tag.go @@ -0,0 +1,236 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "log" + "os" + "text/template" + + "github.com/ludviglundgren/qbittorrent-cli/internal/config" + + "github.com/autobrr/go-qbittorrent" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// RunTag cmd for tag actions +func RunTag() *cobra.Command { + var command = &cobra.Command{ + Use: "tag", + Short: "Tag subcommand", + Long: `Do various tag actions`, + } + + command.AddCommand(RunTagAdd()) + command.AddCommand(RunTagDelete()) + command.AddCommand(RunTagList()) + + return command +} + +// RunTagList cmd to list tags +func RunTagList() *cobra.Command { + var command = &cobra.Command{ + Use: "list", + Short: "List tags", + Long: "List tags.", + } + + var ( + output string + ) + + command.Flags().StringVar(&output, "output", "", "Print as [formatted text (default), json]") + + command.RunE = func(cmd *cobra.Command, args []string) error { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) + os.Exit(1) + } + + tags, err := qb.GetTagsCtx(ctx) + if err != nil { + log.Fatal("could not get tags") + } + + if len(tags) == 0 { + fmt.Println("No tags found") + return nil + } + + switch output { + case "json": + res, err := json.Marshal(tags) + if err != nil { + log.Fatalf("could not marshal tags, err: %q", err) + } + + fmt.Println(string(res)) + + default: + printTagsList(tags) + + } + return nil + } + + return command +} + +var tagItemTemplate = `{{ range .}} +Name: {{.}} +{{end}} +` + +func printTagsList(tags []string) { + tmpl, err := template.New("tags-list").Parse(tagItemTemplate) + if err != nil { + log.Fatalf("error: %q", err) + } + + err = tmpl.Execute(os.Stdout, tags) + if err != nil { + log.Fatalf("could not generate template: %q", err) + } +} + +// RunTagAdd cmd to add tags +func RunTagAdd() *cobra.Command { + var ( + dry bool + ) + + var command = &cobra.Command{ + Use: "add", + Short: "Add tags", + Long: "Add tags", + Example: ` qbt tag add tag1`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires a tag as first argument") + } + + return nil + }, + } + command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") + + command.RunE = func(cmd *cobra.Command, args []string) error { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) + os.Exit(1) + } + + // args + // first arg is tag + tag := args[0] + + if dry { + log.Printf("dry-run: successfully created tag: %s\n", tag) + + return nil + + } else { + if err := qb.CreateTagsCtx(ctx, args); err != nil { + log.Fatal("could not create tag") + } + + log.Printf("successfully created tag: %s\n", tag) + } + return nil + } + + return command +} + +// RunTagDelete cmd to delete tags +func RunTagDelete() *cobra.Command { + var ( + dry bool + ) + + var command = &cobra.Command{ + Use: "delete", + Short: "Delete tags", + Long: "Delete tags.", + Example: ` qbt tag delete tag1`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires a tag as first argument") + } + + return nil + }, + } + command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") + + command.RunE = func(cmd *cobra.Command, args []string) error { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) + os.Exit(1) + } + + // args + // first arg is tag + tag := args[0] + + if dry { + log.Printf("dry-run: successfully deleted tag: %s\n", tag) + + return nil + + } else { + if err := qb.DeleteTagsCtx(ctx, []string{tag}); err != nil { + log.Fatal("could not delete tag") + } + + log.Printf("successfully deleted tag: %s\n", tag) + } + return nil + } + + return command +} diff --git a/cmd/torrent.go b/cmd/torrent.go new file mode 100644 index 0000000..437b257 --- /dev/null +++ b/cmd/torrent.go @@ -0,0 +1,28 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// RunTorrent cmd for torrent operations +func RunTorrent() *cobra.Command { + var command = &cobra.Command{ + Use: "torrent", + Short: "Torrent subcommand", + Long: `Do various torrent operations`, + } + + command.AddCommand(RunTorrentAdd()) + command.AddCommand(RunTorrentCategory()) + command.AddCommand(RunTorrentCompare()) + command.AddCommand(RunTorrentExport()) + command.AddCommand(RunTorrentHash()) + command.AddCommand(RunTorrentImport()) + command.AddCommand(RunTorrentList()) + command.AddCommand(RunTorrentPause()) + command.AddCommand(RunTorrentReannounce()) + command.AddCommand(RunTorrentRemove()) + command.AddCommand(RunTorrentResume()) + + return command +} diff --git a/cmd/add.go b/cmd/torrent_add.go similarity index 62% rename from cmd/add.go rename to cmd/torrent_add.go index fa96071..a4115dd 100644 --- a/cmd/add.go +++ b/cmd/torrent_add.go @@ -12,15 +12,15 @@ import ( "time" "github.com/ludviglundgren/qbittorrent-cli/internal/config" - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" + "github.com/anacrolix/torrent/metainfo" + "github.com/autobrr/go-qbittorrent" "github.com/spf13/cobra" ) -// RunAdd cmd to add torrents -func RunAdd() *cobra.Command { +// RunTorrentAdd cmd to add torrents +func RunTorrentAdd() *cobra.Command { var ( - magnet bool dry bool paused bool skipHashCheck bool @@ -37,15 +37,18 @@ func RunAdd() *cobra.Command { Use: "add", Short: "Add torrent(s)", Long: `Add new torrent(s) to qBittorrent from file or magnet. Supports glob pattern for files like: ./files/*.torrent`, + Example: ` qbt torrent add my-file.torrent --category test --tags tag1 + qbt torrent add ./files/*.torrent --paused --skip-hash-check + qbt torrent add magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcad53426&dn=download`, Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - return errors.New("requires a torrent file as first argument") + return errors.New("requires a torrent file, glob or magnet as first argument") } return nil }, } - command.Flags().BoolVar(&magnet, "magnet", false, "Add magnet link instead of torrent file") + command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") command.Flags().BoolVar(&paused, "paused", false, "Add torrent in paused state") command.Flags().BoolVar(&skipHashCheck, "skip-hash-check", false, "Skip hash check") @@ -63,10 +66,8 @@ func RunAdd() *cobra.Command { // first arg is path to torrent file filePath := args[0] - qbtSettings := qbittorrent.Settings{ - Addr: config.Qbit.Addr, - Hostname: config.Qbit.Host, - Port: config.Qbit.Port, + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, Username: config.Qbit.Login, Password: config.Qbit.Password, BasicUser: config.Qbit.BasicUser, @@ -75,15 +76,15 @@ func RunAdd() *cobra.Command { qb := qbittorrent.NewClient(qbtSettings) - ctx := context.Background() + ctx := cmd.Context() - if err := qb.Login(ctx); err != nil { + if err := qb.LoginCtx(ctx); err != nil { fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) os.Exit(1) } if config.Rules.Enabled && !ignoreRules { - activeDownloads, err := qb.GetTorrentsWithFilters(ctx, &qbittorrent.GetTorrentsRequest{Filter: qbittorrent.TorrentFilterDownloading}) + activeDownloads, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{Filter: qbittorrent.TorrentFilterDownloading}) if err != nil { log.Fatalf("could not fetch torrents: %q\n", err) } @@ -118,78 +119,99 @@ func RunAdd() *cobra.Command { options["dlLimit"] = strconv.FormatUint(uploadLimit, 10) } - if magnet || strings.HasPrefix(filePath, "magnet:") { + if strings.HasPrefix(filePath, "magnet:") { if dry { log.Printf("dry-run: successfully added torrent from magnet %s!\n", filePath) return } - hash, err := qb.AddTorrentFromMagnet(ctx, filePath, options) - if err != nil { + if err := qb.AddTorrentFromUrlCtx(ctx, filePath, options); err != nil { log.Fatalf("adding torrent failed: %q\n", err) } + hash := "" + // some trackers are bugged or slow, so we need to re-announce the torrent until it works if config.Reannounce.Enabled && !paused { - if err := checkTrackerStatus(ctx, qb, removeStalled, hash); err != nil { - log.Fatalf("could not get tracker status for torrent: %q\n", err) + magnet, err := metainfo.ParseMagnetUri(filePath) + if err != nil { + fmt.Printf("could not parse magnet URI: %s\n", filePath) } + + hash := magnet.InfoHash.String() + + go func() { + if err := checkTrackerStatus(ctx, qb, removeStalled, hash); err != nil { + log.Fatalf("could not get tracker status for torrent: %q\n", err) + } + }() } log.Printf("successfully added torrent from magnet: %s %s\n", filePath, hash) return - } + } else { + files, err := filepath.Glob(filePath) + if err != nil { + log.Fatalf("could not find files matching: %s err: %q\n", filePath, err) + } - files, err := filepath.Glob(filePath) - if err != nil { - log.Fatalf("could not find files matching: %s err: %q\n", filePath, err) - } + if len(files) == 0 { + log.Printf("found 0 torrents matching %s\n", filePath) + return + } - if len(files) == 0 { - log.Printf("found 0 torrents matching %s\n", filePath) - return - } + log.Printf("found (%d) torrent(s) to add\n", len(files)) - log.Printf("found (%d) torrent(s) to add\n", len(files)) + success := 0 + for _, file := range files { + if dry { + log.Printf("dry-run: torrent %s successfully added!\n", file) - success := 0 - for _, file := range files { - if dry { - log.Printf("dry-run: torrent %s successfully added!\n", file) + continue + } - continue - } + // set savePath again + options["savepath"] = savePath - // set savePath again - options["savepath"] = savePath + if err := qb.AddTorrentFromFileCtx(ctx, file, options); err != nil { + log.Fatalf("adding torrent failed: %q\n", err) + } - hash, err := qb.AddTorrentFromFile(ctx, file, options) - if err != nil { - log.Fatalf("adding torrent failed: %q\n", err) - } + // Get meta info from file to find out the hash for later use + t, err := metainfo.LoadFromFile(file) + if err != nil { + fmt.Printf("could not open file: %s", file) + continue + } - // some trackers are bugged or slow, so we need to re-announce the torrent until it works - if config.Reannounce.Enabled && !paused { - if err := checkTrackerStatus(ctx, qb, removeStalled, hash); err != nil { - log.Printf("could not get tracker status for torrent: %s err: %q\n", hash, err) + hash := t.HashInfoBytes().String() + + // some trackers are bugged or slow, so we need to re-announce the torrent until it works + if config.Reannounce.Enabled && !paused { + go func() { + if err := checkTrackerStatus(ctx, qb, removeStalled, hash); err != nil { + log.Printf("could not get tracker status for torrent: %s err: %q\n", hash, err) + } + }() } - } - success++ + success++ - log.Printf("successfully added torrent: %s\n", hash) + log.Printf("successfully added torrent: %s\n", hash) - if len(files) > 1 { - log.Println("sleeping 2 seconds before adding next torrent...") + if len(files) > 1 { + log.Println("sleeping 2 seconds before adding next torrent...") - time.Sleep(2 * time.Second) + time.Sleep(2 * time.Second) + + continue + } - continue } - } - log.Printf("successfully added %d torrent(s)\n", success) + log.Printf("successfully added %d torrent(s)\n", success) + } } return command @@ -202,7 +224,7 @@ func checkTrackerStatus(ctx context.Context, qb *qbittorrent.Client, removeStall time.Sleep(time.Duration(config.Reannounce.Interval) * time.Millisecond) for attempts < config.Reannounce.Attempts { - trackers, err := qb.GetTorrentTrackers(ctx, hash) + trackers, err := qb.GetTorrentTrackersCtx(ctx, hash) if err != nil { log.Fatalf("could not get trackers of torrent: %v %v", hash, err) } @@ -214,7 +236,7 @@ func checkTrackerStatus(ctx context.Context, qb *qbittorrent.Client, removeStall break } - if err := qb.ReAnnounceTorrents(ctx, []string{hash}); err != nil { + if err := qb.ReAnnounceTorrentsCtx(ctx, []string{hash}); err != nil { return err } @@ -227,7 +249,7 @@ func checkTrackerStatus(ctx context.Context, qb *qbittorrent.Client, removeStall if removeStalled { log.Println("Announce not ok, deleting torrent") - if err := qb.DeleteTorrents(ctx, []string{hash}, false); err != nil { + if err := qb.DeleteTorrentsCtx(ctx, []string{hash}, false); err != nil { return err } } @@ -246,7 +268,7 @@ func checkTrackerStatus(ctx context.Context, qb *qbittorrent.Client, removeStall // 4 Tracker has been contacted, but it is not working (or doesn't send proper replies) func findTrackerStatus(slice []qbittorrent.TorrentTracker, val int) (int, bool) { for i, item := range slice { - if item.Status == val { + if int(item.Status) == val { return i, true } } diff --git a/cmd/torrent_category.go b/cmd/torrent_category.go new file mode 100644 index 0000000..b5d1331 --- /dev/null +++ b/cmd/torrent_category.go @@ -0,0 +1,282 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "strings" + "time" + + "github.com/ludviglundgren/qbittorrent-cli/internal/config" + + "github.com/autobrr/go-qbittorrent" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// RunTorrentCategory cmd for torrent category operations +func RunTorrentCategory() *cobra.Command { + var command = &cobra.Command{ + Use: "category", + Short: "Torrent category subcommand", + Long: `Do various torrent category operations`, + } + + command.AddCommand(RunTorrentCategoryChange()) + command.AddCommand(RunTorrentCategorySet()) + command.AddCommand(RunTorrentCategoryUnSet()) + + return command +} + +// RunTorrentCategorySet cmd for torrent category operations +func RunTorrentCategorySet() *cobra.Command { + var command = &cobra.Command{ + Use: "set", + Short: "Set torrent category", + Long: `Set category for torrents via hashes`, + Example: ` qbt torrent category set test-category --hashes hash1,hash2`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires a category as first argument") + } + + return nil + }, + } + + var ( + dry bool + hashes []string + ) + + command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") + command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Torrent hashes, as comma separated list") + + command.RunE = func(cmd *cobra.Command, args []string) error { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) + os.Exit(1) + } + + // args + // first arg is path to torrent file + category := args[0] + + if dry { + log.Printf("dry-run: successfully set category %s on torrents: %v\n", category, hashes) + + return nil + + } else { + if err := qb.SetCategoryCtx(ctx, hashes, category); err != nil { + log.Fatal("could not set category on torrents") + } + + log.Printf("successfully set category %s on torrents: %v\n", category, hashes) + } + + return nil + } + + return command +} + +// RunTorrentCategoryUnSet cmd for torrent category operations +func RunTorrentCategoryUnSet() *cobra.Command { + var command = &cobra.Command{ + Use: "unset", + Short: "Unset torrent category", + Long: `Unset category for torrents via hashes`, + Example: ` qbt torrent category unset --hashes hash1,hash2`, + } + + var ( + dry bool + hashes []string + ) + + command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") + command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Torrent hashes, as comma separated list") + + command.RunE = func(cmd *cobra.Command, args []string) error { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) + os.Exit(1) + } + + if dry { + log.Printf("dry-run: successfully unset category on torrents: %v\n", hashes) + + return nil + + } else { + if err := qb.SetCategoryCtx(ctx, hashes, ""); err != nil { + log.Fatal("could not unset category on torrents") + } + + log.Printf("successfully unset category on torrents: %v\n", hashes) + } + + return nil + } + + return command +} + +// RunTorrentCategoryChange cmd to move torrents from some category to another +func RunTorrentCategoryChange() *cobra.Command { + var ( + dry bool + fromCategories []string + targetCategory string + includeTags []string + excludeTags []string + minSeedTime int + ) + + var command = &cobra.Command{ + Use: "move", + Short: "move torrents between categories", + Long: `Move torrents from one category to another`, + Example: ` qbt torrent category move --from cat1 --to cat2`, + } + + command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") + command.Flags().StringSliceVar(&fromCategories, "from", []string{}, "Move from categories (required)") + command.Flags().StringVar(&targetCategory, "to", "", "Move to the specified category (required)") + command.Flags().StringSliceVar(&includeTags, "include-tags", []string{}, "Include torrents with provided tags") + command.Flags().StringSliceVar(&excludeTags, "exclude-tags", []string{}, "Exclude torrents with provided tags") + command.Flags().IntVar(&minSeedTime, "min-seed-time", 0, "Minimum seed time in MINUTES before moving.") + + command.MarkFlagRequired("from") + command.MarkFlagRequired("to") + + command.Run = func(cmd *cobra.Command, args []string) { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) + os.Exit(1) + } + + var hashes []string + + for _, cat := range fromCategories { + torrents, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{Category: cat}) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not get torrents by category %v\n", err) + os.Exit(1) + } + + for _, torrent := range torrents { + // only grab completed torrents since we don't specify filter state + if torrent.Progress != 1 { + continue + } + + if len(includeTags) > 0 { + if _, validTag := validateTag(includeTags, torrent.Tags); !validTag { + continue + } + } + + if len(excludeTags) > 0 { + if tag, found := validateTag(excludeTags, torrent.Tags); found { + fmt.Printf("ignoring torrent %s %s containng tag: %s of tags: %s", torrent.Name, torrent.Hash, tag, excludeTags) + continue + } + } + + // check TimeActive (seconds), CompletionOn (epoch) SeenComplete + if minSeedTime > 0 { + completedTime := time.Unix(torrent.CompletionOn, 0) + completedTimePlusMinSeedTime := completedTime.Add(time.Duration(minSeedTime) * time.Minute) + currentTime := time.Now() + + diff := currentTime.After(completedTimePlusMinSeedTime) + if !diff { + continue + } + } + + hashes = append(hashes, torrent.Hash) + } + } + + if len(hashes) == 0 { + fmt.Printf("Could not find any matching torrents to move from (%s) to (%s) with tags (%s) and min-seed-time %d minutes\n", strings.Join(fromCategories, ","), targetCategory, strings.Join(includeTags, ","), minSeedTime) + os.Exit(0) + } + + if dry { + fmt.Printf("dry-run: Found %d matching torrents to move from (%s) to (%s)\n", len(hashes), strings.Join(fromCategories, ","), targetCategory) + fmt.Printf("dry-run: Successfully moved %d torrents from (%s) to (%s)\n", len(hashes), strings.Join(fromCategories, ","), targetCategory) + } else { + fmt.Printf("Found %d matching torrents to move from (%s) to (%s)\n", len(hashes), strings.Join(fromCategories, ","), targetCategory) + + if err := qb.SetCategoryCtx(ctx, hashes, targetCategory); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not pause torrents %v\n", err) + os.Exit(1) + } + + fmt.Printf("Successfully moved %d torrents from (%s) to (%s)\n", len(hashes), strings.Join(fromCategories, ","), targetCategory) + } + } + + return command +} + +func validateTag(includeTags []string, torrentTags string) (string, bool) { + tagList := strings.Split(torrentTags, ", ") + + for _, includeTag := range includeTags { + for _, tag := range tagList { + if tag == includeTag { + return tag, true + } + } + } + + return "", false +} diff --git a/cmd/compare.go b/cmd/torrent_compare.go similarity index 83% rename from cmd/compare.go rename to cmd/torrent_compare.go index 0f029f7..c0bee95 100644 --- a/cmd/compare.go +++ b/cmd/torrent_compare.go @@ -1,20 +1,19 @@ package cmd import ( - "context" "fmt" "os" "time" "github.com/ludviglundgren/qbittorrent-cli/internal/config" - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" + "github.com/autobrr/go-qbittorrent" "github.com/dustin/go-humanize" "github.com/spf13/cobra" ) -// RunCompare cmd to compare torrents between clients -func RunCompare() *cobra.Command { +// RunTorrentCompare cmd to compare torrents between clients +func RunTorrentCompare() *cobra.Command { var ( dry bool tagDuplicates bool @@ -34,9 +33,10 @@ func RunCompare() *cobra.Command { ) var command = &cobra.Command{ - Use: "compare", - Short: "Compare torrents", - Long: `Compare torrents between clients`, + Use: "compare", + Short: "Compare torrents", + Long: `Compare torrents between clients`, + Example: ` qbt torrent compare --addr http://localhost:10000 --user u --pass p --compare-addr http://url.com:10000 --compare-user u --compare-pass p`, //Args: func(cmd *cobra.Command, args []string) error { // if len(args) < 1 { // return errors.New("requires a torrent file as first argument") @@ -80,8 +80,8 @@ func RunCompare() *cobra.Command { sourceBasicPass = config.Qbit.BasicPass } - qbtSettings := qbittorrent.Settings{ - Addr: sourceAddr, + qbtSettings := qbittorrent.Config{ + Host: sourceAddr, Username: sourceUser, Password: sourcePass, BasicUser: sourceBasicUser, @@ -89,14 +89,14 @@ func RunCompare() *cobra.Command { } qb := qbittorrent.NewClient(qbtSettings) - ctx := context.Background() + ctx := cmd.Context() - if err := qb.Login(ctx); err != nil { + if err := qb.LoginCtx(ctx); err != nil { fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) os.Exit(1) } - sourceData, err := qb.GetTorrents(ctx) + sourceData, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{}) if err != nil { fmt.Fprintf(os.Stderr, "ERROR: could not get torrents %v\n", err) os.Exit(1) @@ -112,8 +112,8 @@ func RunCompare() *cobra.Command { compareBasicUser := compareConfig.BasicUser compareBasicPass := compareConfig.BasicPass - qbtSettingsCompare := qbittorrent.Settings{ - Addr: compareAddr, + qbtSettingsCompare := qbittorrent.Config{ + Host: compareAddr, Username: compareUser, Password: comparePass, BasicUser: compareBasicUser, @@ -121,12 +121,12 @@ func RunCompare() *cobra.Command { } qbCompare := qbittorrent.NewClient(qbtSettingsCompare) - if err = qbCompare.Login(ctx); err != nil { + if err = qbCompare.LoginCtx(ctx); err != nil { fmt.Fprintf(os.Stderr, "ERROR: connection failed to compare: %v\n", err) os.Exit(1) } - compareData, err := qbCompare.GetTorrents(ctx) + compareData, err := qbCompare.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{}) if err != nil { fmt.Fprintf(os.Stderr, "ERROR: could not get torrents from compare: %v\n", err) os.Exit(1) @@ -151,8 +151,7 @@ func RunCompare() *cobra.Command { j = len(duplicateTorrents) } - err = qbCompare.SetTag(ctx, duplicateTorrents[i:j], tag) - if err != nil { + if err := qbCompare.AddTagsCtx(ctx, duplicateTorrents[i:j], tag); err != nil { fmt.Printf("ERROR: Failed to set tag: %v\n", err) } @@ -174,10 +173,10 @@ func RunCompare() *cobra.Command { } func compare(source, compare []qbittorrent.Torrent) ([]string, error) { - sourceTorrents := make(map[string]qbittorrent.TorrentBasic, 0) + sourceTorrents := make(map[string]qbittorrent.Torrent, 0) for _, s := range source { - sourceTorrents[s.Hash] = qbittorrent.TorrentBasic{ + sourceTorrents[s.Hash] = qbittorrent.Torrent{ Category: s.Category, Downloaded: s.Downloaded, Hash: s.Hash, @@ -193,7 +192,7 @@ func compare(source, compare []qbittorrent.Torrent) ([]string, error) { } duplicateTorrentIDs := make([]string, 0) - duplicateTorrentsSlice := make([]qbittorrent.TorrentBasic, 0) + duplicateTorrentsSlice := make([]qbittorrent.Torrent, 0) var totalSize uint64 @@ -203,7 +202,7 @@ func compare(source, compare []qbittorrent.Torrent) ([]string, error) { totalSize += uint64(c.Size) - duplicateTorrentsSlice = append(duplicateTorrentsSlice, qbittorrent.TorrentBasic{ + duplicateTorrentsSlice = append(duplicateTorrentsSlice, qbittorrent.Torrent{ Category: c.Category, Downloaded: c.Downloaded, Hash: c.Hash, diff --git a/cmd/compare_test.go b/cmd/torrent_compare_test.go similarity index 95% rename from cmd/compare_test.go rename to cmd/torrent_compare_test.go index 9d16a1d..d1de32a 100644 --- a/cmd/compare_test.go +++ b/cmd/torrent_compare_test.go @@ -3,9 +3,8 @@ package cmd import ( "testing" + "github.com/autobrr/go-qbittorrent" "github.com/magiconair/properties/assert" - - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" ) func Test_compare(t *testing.T) { diff --git a/cmd/torrent_export.go b/cmd/torrent_export.go index 4cd7f60..6a36184 100644 --- a/cmd/torrent_export.go +++ b/cmd/torrent_export.go @@ -10,17 +10,18 @@ import ( "github.com/ludviglundgren/qbittorrent-cli/internal/config" fsutil "github.com/ludviglundgren/qbittorrent-cli/internal/fs" - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" + "github.com/autobrr/go-qbittorrent" "github.com/pkg/errors" "github.com/spf13/cobra" ) -func RunExport() *cobra.Command { +func RunTorrentExport() *cobra.Command { var command = &cobra.Command{ - Use: "export", - Short: "export torrents", - Long: "Export torrents and fastresume by category", + Use: "export", + Short: "Export torrents", + Long: "Export torrents and fastresume by category", + Example: ` qbt torrent export --source ~/.local/share/data/qBittorrent/BT_backup --export-dir ~/qbt-backup --include-category=movies,tv`, } f := export{ @@ -28,7 +29,6 @@ func RunExport() *cobra.Command { verbose: false, sourceDir: "", exportDir: "", - categories: nil, includeCategory: nil, excludeCategory: nil, includeTag: nil, @@ -38,12 +38,17 @@ func RunExport() *cobra.Command { command.Flags().BoolVar(&f.dry, "dry-run", false, "dry run") command.Flags().BoolVarP(&f.verbose, "verbose", "v", false, "verbose output") - command.Flags().StringVar(&f.sourceDir, "source", "", "Dir with torrent and fast-resume files") - command.Flags().StringVar(&f.exportDir, "export-dir", "", "Dir to export files to") - command.Flags().StringSliceVar(&f.categories, "categories", []string{}, "Export torrents from categories. Comma separated") - command.Flags().StringSliceVar(&f.includeCategory, "include-category", []string{}, "Include categories. Comma separated") + command.Flags().StringVar(&f.sourceDir, "source", "", "Dir with torrent and fast-resume files (required)") + command.Flags().StringVar(&f.exportDir, "export-dir", "", "Dir to export files to (required)") + command.Flags().StringSliceVar(&f.includeCategory, "include-category", []string{}, "Export torrents from these categories. Comma separated") command.MarkFlagRequired("categories") + command.MarkFlagRequired("source") + command.MarkFlagRequired("export-dir") + + //command.Flags().StringSliceVar(&f.excludeCategory, "exclude-category", []string{}, "Exclude categories. Comma separated") + //command.Flags().StringSliceVar(&f.includeTag, "include-tag", []string{}, "Include tags. Comma separated") + //command.Flags().StringSliceVar(&f.excludeTag, "exclude-tag", []string{}, "Exclude tags. Comma separated") command.RunE = func(cmd *cobra.Command, args []string) error { // get torrents from client by categories @@ -57,10 +62,8 @@ func RunExport() *cobra.Command { return err } - qbtSettings := qbittorrent.Settings{ - Addr: config.Qbit.Addr, - Hostname: config.Qbit.Host, - Port: config.Qbit.Port, + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, Username: config.Qbit.Login, Password: config.Qbit.Password, BasicUser: config.Qbit.BasicUser, @@ -69,14 +72,16 @@ func RunExport() *cobra.Command { qb := qbittorrent.NewClient(qbtSettings) - if err := qb.Login(cmd.Context()); err != nil { + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) os.Exit(1) } if len(f.includeCategory) > 0 { - for _, category := range f.categories { - torrents, err := qb.GetTorrentsWithFilters(cmd.Context(), &qbittorrent.GetTorrentsRequest{Category: category}) + for _, category := range f.includeCategory { + torrents, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{Category: category}) if err != nil { return errors.Wrapf(err, "could not get torrents for category: %s", category) } @@ -95,7 +100,7 @@ func RunExport() *cobra.Command { } } else { - torrents, err := qb.GetTorrents(cmd.Context()) + torrents, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{}) if err != nil { return errors.Wrap(err, "could not get torrents") } @@ -115,7 +120,7 @@ func RunExport() *cobra.Command { } if len(f.hashes) == 0 { - fmt.Printf("Could not find any matching torrents to export from (%s)\n", strings.Join(f.categories, ",")) + fmt.Printf("Could not find any matching torrents to export from (%s)\n", strings.Join(f.includeCategory, ",")) os.Exit(1) } @@ -266,7 +271,6 @@ type export struct { verbose bool sourceDir string exportDir string - categories []string includeCategory []string excludeCategory []string includeTag []string diff --git a/cmd/export_test.go b/cmd/torrent_export_test.go similarity index 100% rename from cmd/export_test.go rename to cmd/torrent_export_test.go diff --git a/cmd/hash.go b/cmd/torrent_hash.go similarity index 83% rename from cmd/hash.go rename to cmd/torrent_hash.go index 8a07a29..7794ce9 100644 --- a/cmd/hash.go +++ b/cmd/torrent_hash.go @@ -10,12 +10,12 @@ import ( "github.com/spf13/cobra" ) -// RunHash cmd to add torrents -func RunHash() *cobra.Command { +// RunTorrentHash cmd to add torrents +func RunTorrentHash() *cobra.Command { var command = &cobra.Command{ Use: "hash", - Short: "Print the hash of a torrent/magnet", - Example: ` qbt hash file.torrent`, + Short: "Print the hash of a torrent file or magnet", + Example: ` qbt torrent hash file.torrent`, Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { return errors.New("requires a torrent file or magnet URI as first argument") @@ -28,19 +28,24 @@ func RunHash() *cobra.Command { command.Run = func(cmd *cobra.Command, args []string) { filePath := args[0] hash := "" + if strings.HasPrefix(filePath, "magnet:") { metadata, err := metainfo.ParseMagnetUri(filePath) if err != nil { log.Fatalf("could not parse magnet URI. error: %v", err) } + hash = metadata.InfoHash.HexString() + } else { metadata, err := metainfo.LoadFromFile(filePath) if err != nil { log.Fatalf("could not parse torrent file. error: %v", err) } + hash = metadata.HashInfoBytes().HexString() } + fmt.Println(hash) } return command diff --git a/cmd/torrent_import.go b/cmd/torrent_import.go new file mode 100644 index 0000000..d07f91b --- /dev/null +++ b/cmd/torrent_import.go @@ -0,0 +1,140 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + "github.com/ludviglundgren/qbittorrent-cli/internal/importer" + + "github.com/mholt/archiver/v3" + "github.com/mitchellh/go-homedir" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// RunTorrentImport cmd import torrents +func RunTorrentImport() *cobra.Command { + var command = &cobra.Command{ + Use: "import {rtorrent | deluge} --source-dir dir --qbit-dir dir2 [--skip-backup] [--dry-run]", + Short: "Import torrents", + Long: `Import torrents with state from other clients [rtorrent, deluge]`, + Example: ` qbt torrent import deluge --source-dir ~/.config/deluge/state/ --qbit-dir ~/.local/share/data/qBittorrent/BT_backup --dry-run + qbt torrent import rtorrent --source-dir ~/.sessions --qbit-dir ~/.local/share/data/qBittorrent/BT_backup --dry-run`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("requires a source client [rtorrent, deluge] as first argument") + } + + return cobra.OnlyValidArgs(cmd, args) + }, + ValidArgs: []string{"rtorrent", "deluge"}, + } + + var ( + //source string + sourceDir string + qbitDir string + dryRun bool + skipBackup bool + ) + + command.Flags().BoolVar(&dryRun, "dry-run", false, "Run without importing anything") + //command.Flags().StringVar(&source, "source", "", "source client [deluge, rtorrent] (required)") + command.Flags().StringVar(&sourceDir, "source-dir", "", "source client state dir (required)") + command.Flags().StringVar(&qbitDir, "qbit-dir", "", "qBittorrent BT_backup dir. Commonly ~/.local/share/qBittorrent/BT_backup (required)") + command.Flags().BoolVar(&skipBackup, "skip-backup", false, "Skip backup before import") + + //command.MarkFlagRequired("source") + command.MarkFlagRequired("source-dir") + command.MarkFlagRequired("qbit-dir") + + command.RunE = func(cmd *cobra.Command, args []string) error { + source := args[0] + + var imp importer.Importer + + switch source { + case "deluge": + imp = importer.NewDelugeImporter() + + case "rtorrent": + imp = importer.NewRTorrentImporter() + + default: + return fmt.Errorf("error: unsupported client: %s\n", source) + } + + // TODO check if program is running, if true exit + + // Backup data before running + if !skipBackup { + fmt.Print("prepare to backup torrent data before import..\n") + + homeDir, err := homedir.Dir() + if err != nil { + fmt.Printf("could not find home directory: %q", err) + return err + } + + timeStamp := time.Now().Format("20060102150405") + + sourceBackupArchive := filepath.Join(homeDir, "qbt_backup", source+"_backup_"+timeStamp+".tar.gz") + qbitBackupArchive := filepath.Join(homeDir, "qbt_backup", "qBittorrent_backup_"+timeStamp+".tar.gz") + + if dryRun { + fmt.Printf("dry-run: creating %s backup of directory: %s to %s ...\n", source, sourceDir, sourceBackupArchive) + } else { + fmt.Printf("creating %s backup of directory: %s to %s ...\n", source, sourceDir, sourceBackupArchive) + + if err := archiver.Archive([]string{sourceDir}, sourceBackupArchive); err != nil { + log.Printf("could not backup directory: %q", err) + return err + } + } + + if dryRun { + fmt.Printf("dry-run: creating qBittorrent backup of directory: %s to %s ...\n", qbitDir, qbitBackupArchive) + } else { + fmt.Printf("creating qBittorrent backup of directory: %s to %s ...\n", qbitDir, qbitBackupArchive) + + if err := archiver.Archive([]string{qbitDir}, qbitBackupArchive); err != nil { + log.Printf("could not backup directory: %q", err) + return err + } + } + + fmt.Print("Backup completed!\n") + } + + start := time.Now() + + if dryRun { + fmt.Printf("dry-run: preparing to import torrents from: %s dir: %s\n", source, sourceDir) + fmt.Println("dry-run: no data will be written") + } else { + fmt.Printf("preparing to import torrents from: %s dir: %s\n", source, sourceDir) + } + + opts := importer.Options{ + SourceDir: sourceDir, + QbitDir: qbitDir, + DryRun: dryRun, + } + + if err := imp.Import(opts); err != nil { + fmt.Printf("%s import error: %q\n", source, err) + os.Exit(1) + } + + elapsed := time.Since(start) + + fmt.Printf("\nImport finished in: %s\n", elapsed) + + return nil + } + + return command +} diff --git a/cmd/torrent_list.go b/cmd/torrent_list.go new file mode 100644 index 0000000..5156c93 --- /dev/null +++ b/cmd/torrent_list.go @@ -0,0 +1,169 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "log" + "os" + "strings" + "text/template" + "time" + + "github.com/ludviglundgren/qbittorrent-cli/internal/config" + + "github.com/autobrr/go-qbittorrent" + "github.com/dustin/go-humanize" + "github.com/spf13/cobra" +) + +// RunTorrentList cmd to list torrents +func RunTorrentList() *cobra.Command { + var ( + filter = "all" + category string + tag string + hashes []string + output string + ) + + var command = &cobra.Command{ + Use: "list", + Short: "List torrents", + Long: `List all torrents, or torrents with a specific filters. Get by filter, category, tag and hashes. Can be combined`, + Example: `qbt torrent list --filter=downloading --category=linux-iso`, + } + command.Flags().StringVar(&output, "output", "", "Print as [formatted text (default), json]") + command.Flags().StringVarP(&filter, "filter", "f", "all", "Filter by state. Available filters: all, downloading, seeding, completed, paused, active, inactive, resumed, \nstalled, stalled_uploading, stalled_downloading, errored") + command.Flags().StringVarP(&category, "category", "c", "", "Filter by category. All categories by default.") + command.Flags().StringVarP(&tag, "tag", "t", "", "Filter by tag. Single tag: tag1") + command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Filter by hashes. Separated by comma: \"hash1,hash2\".") + + command.Run = func(cmd *cobra.Command, args []string) { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) + os.Exit(1) + } + + req := qbittorrent.TorrentFilterOptions{ + Filter: qbittorrent.TorrentFilter(strings.ToLower(filter)), + Category: category, + Tag: tag, + Hashes: hashes, + } + + // get torrent list with default filter of all + torrents, err := qb.GetTorrentsCtx(ctx, req) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not get torrents %v\n", err) + os.Exit(1) + } + + if len(torrents) == 0 { + fmt.Printf("No torrents found with filter: %s\n", filter) + return + } + + switch output { + case "json": + res, err := json.Marshal(torrents) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not marshal torrents to json %v\n", err) + os.Exit(1) + } + fmt.Println(string(res)) + + default: + printList(torrents) + } + + } + + return command +} + +var torrentItemTemplate = `{{ range .}} +[*] {{.Name}} + Hash: {{.Hash}} State: {{.State}} + Category: {{.Category}} Tags: [{{.Tags}}] + Size: {{.Size}} Downloaded: {{.Completed}} / {{.TotalSize}} Uploaded: {{.Uploaded}} + Added: {{.Added}} Time Active: {{.TimeActive}} + Save path: {{.SavePath}} +{{end}} +` + +type ItemData struct { + Hash string + Name string + State string + Category string + Tags string + SavePath string + Added string + TimeActive string + Size string + TotalSize string + Completed string + Uploaded string +} + +func printList(torrents []qbittorrent.Torrent) { + tmpl, err := template.New("item").Parse(torrentItemTemplate) + if err != nil { + fmt.Errorf("error") + } + + var data []ItemData + + for _, torrent := range torrents { + + //if torrent.DlSpeed > 0 { + // fmt.Printf( + // "DL Speed: %s/s%s", + // humanize.Bytes(uint64(torrent.DlSpeed)), + // space, + // ) + //} else if torrent.UpSpeed > 0 { + // fmt.Printf( + // "UP Speed: %s/s%s", + // humanize.Bytes(uint64(torrent.UpSpeed)), + // space, + // ) + //} + + d := ItemData{ + Hash: torrent.Hash, + Name: torrent.Name, + State: string(torrent.State), + Size: humanize.Bytes(uint64(torrent.Size)), + TotalSize: humanize.Bytes(uint64(torrent.TotalSize)), + Category: torrent.Category, + Tags: torrent.Tags, + SavePath: torrent.SavePath, + Added: humanize.RelTime(time.Unix(0, 0), time.Unix(int64(torrent.AddedOn), 0), "ago", ""), + TimeActive: humanize.RelTime(time.Unix(0, 0), time.Unix(int64(torrent.TimeActive), 0), "", ""), + Completed: humanize.Bytes(uint64(torrent.Completed)), + Uploaded: humanize.Bytes(uint64(torrent.Uploaded)), + } + + data = append(data, d) + } + + err = tmpl.Execute(os.Stdout, data) + if err != nil { + log.Fatal(err) + } +} diff --git a/cmd/torrent_pause.go b/cmd/torrent_pause.go new file mode 100644 index 0000000..5db0291 --- /dev/null +++ b/cmd/torrent_pause.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/ludviglundgren/qbittorrent-cli/internal/config" + + "github.com/autobrr/go-qbittorrent" + "github.com/spf13/cobra" +) + +// RunTorrentPause cmd to pause torrents +func RunTorrentPause() *cobra.Command { + var ( + pauseAll bool + names bool + hashes []string + ) + + var command = &cobra.Command{ + Use: "pause", + Short: "Pause specified torrent(s)", + Long: `Pauses torrents indicated by hash, name or a prefix of either. Whitespace indicates next prefix unless argument is surrounded by quotes`, + } + + command.Flags().BoolVar(&pauseAll, "all", false, "Pauses all torrents") + command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Add hashes as comma separated list") + command.Flags().BoolVar(&names, "names", false, "Provided arguments will be read as torrent names") + + command.Run = func(cmd *cobra.Command, args []string) { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) + os.Exit(1) + } + + if pauseAll { + hashes = []string{"all"} + } + + if len(hashes) == 0 { + log.Printf("No torrents found to pause with provided search terms") + return + } + + // Split the hashes into groups of 20 to avoid flooding qbittorrent + batch := 20 + for i := 0; i < len(hashes); i += batch { + j := i + batch + if j > len(hashes) { + j = len(hashes) + } + + if err := qb.PauseCtx(ctx, hashes[i:j]); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not pause torrents: %v\n", err) + os.Exit(1) + } + + time.Sleep(time.Second * 1) + } + + log.Printf("torrent(s) successfully paused") + } + + return command +} diff --git a/cmd/reannounce.go b/cmd/torrent_reannounce.go similarity index 69% rename from cmd/reannounce.go rename to cmd/torrent_reannounce.go index 5d4e892..c473c7a 100644 --- a/cmd/reannounce.go +++ b/cmd/torrent_reannounce.go @@ -9,13 +9,13 @@ import ( "time" "github.com/ludviglundgren/qbittorrent-cli/internal/config" - "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" + "github.com/autobrr/go-qbittorrent" "github.com/spf13/cobra" ) -// RunReannounce cmd to reannounce torrents -func RunReannounce() *cobra.Command { +// RunTorrentReannounce cmd to reannounce torrents +func RunTorrentReannounce() *cobra.Command { var ( dry bool hash string @@ -28,7 +28,7 @@ func RunReannounce() *cobra.Command { var command = &cobra.Command{ Use: "reannounce", Short: "Reannounce torrent(s)", - Long: `Reannounce torrents if needed.`, + Long: `Reannounce torrents with non-OK tracker status.`, } command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") @@ -41,38 +41,39 @@ func RunReannounce() *cobra.Command { command.Run = func(cmd *cobra.Command, args []string) { config.InitConfig() - if !dry { - qbtSettings := qbittorrent.Settings{ - Addr: config.Qbit.Addr, - Hostname: config.Qbit.Host, - Port: config.Qbit.Port, - Username: config.Qbit.Login, - Password: config.Qbit.Password, - BasicUser: config.Qbit.BasicUser, - BasicPass: config.Qbit.BasicPass, - } + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } - qb := qbittorrent.NewClient(qbtSettings) + qb := qbittorrent.NewClient(qbtSettings) - ctx := context.Background() + ctx := cmd.Context() - if err := qb.Login(ctx); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) - os.Exit(1) - } + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) + os.Exit(1) + } - req := &qbittorrent.GetTorrentsRequest{ - Filter: qbittorrent.TorrentFilterDownloading, - Category: category, - Tag: tag, - Hashes: hash, - } + req := qbittorrent.TorrentFilterOptions{ + //Filter: qbittorrent.TorrentFilterDownloading, + Category: category, + Tag: tag, + Hashes: []string{hash}, + } - activeDownloads, err := qb.GetTorrentsWithFilters(ctx, req) - if err != nil { - log.Fatalf("could not fetch torrents: err: %q", err) - } + activeDownloads, err := qb.GetTorrentsCtx(ctx, req) + if err != nil { + log.Fatalf("could not fetch torrents: err: %q", err) + } + + if dry { + log.Println("dry-run: torrents successfully re-announced!") + } else { for _, torrent := range activeDownloads { if torrent.Progress == 0 && torrent.TimeActive < 120 { go func(torrent qbittorrent.Torrent) { @@ -90,8 +91,6 @@ func RunReannounce() *cobra.Command { } log.Println("torrents successfully re-announced") - } else { - log.Println("dry-run: torrents successfully re-announced!") } } @@ -105,7 +104,7 @@ func reannounceTorrent(ctx context.Context, qb *qbittorrent.Client, interval, at time.Sleep(time.Duration(interval) * time.Millisecond) for attempt < attempts { - trackers, err := qb.GetTorrentTrackers(ctx, hash) + trackers, err := qb.GetTorrentTrackersCtx(ctx, hash) if err != nil { log.Fatalf("could not get trackers of torrent: %s err: %q", hash, err) } @@ -117,7 +116,7 @@ func reannounceTorrent(ctx context.Context, qb *qbittorrent.Client, interval, at break } - if err = qb.ReAnnounceTorrents(ctx, []string{hash}); err != nil { + if err = qb.ReAnnounceTorrentsCtx(ctx, []string{hash}); err != nil { return err } diff --git a/cmd/torrent_remove.go b/cmd/torrent_remove.go new file mode 100644 index 0000000..c2cdd9d --- /dev/null +++ b/cmd/torrent_remove.go @@ -0,0 +1,124 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/ludviglundgren/qbittorrent-cli/internal/config" + + "github.com/autobrr/go-qbittorrent" + "github.com/spf13/cobra" +) + +// RunTorrentRemove cmd to remove torrents +func RunTorrentRemove() *cobra.Command { + var ( + dryRun bool + removeAll bool + removePaused bool + deleteFiles bool + hashes []string + ) + + var command = &cobra.Command{ + Use: "remove", + Short: "Removes specified torrent(s)", + Long: `Removes torrents indicated by hash, name or a prefix of either. Whitespace indicates next prefix unless argument is surrounded by quotes`, + } + + command.Flags().BoolVar(&dryRun, "dry-run", false, "Display what would be done without actually doing it") + command.Flags().BoolVar(&removeAll, "all", false, "Removes all torrents") + command.Flags().BoolVar(&removePaused, "paused", false, "Removes all paused torrents") + command.Flags().BoolVar(&deleteFiles, "delete-files", false, "Also delete downloaded files from torrent(s)") + command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Add hashes as comma separated list") + + command.Run = func(cmd *cobra.Command, args []string) { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) + os.Exit(1) + } + + if removeAll { + hashes = []string{"all"} + } + + if removePaused { + pausedTorrents, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{Filter: qbittorrent.TorrentFilterPaused}) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: failed to retrieve paused torrents: %v\n", err) + os.Exit(1) + } + + for _, torrent := range pausedTorrents { + hashes = append(hashes, torrent.Hash) + } + + if dryRun { + log.Printf("dry-run: found (%d) paused torrents to be removed\n", len(hashes)) + } else { + log.Printf("found (%d) paused torrents to be removed\n", len(hashes)) + } + } + + if len(hashes) == 0 { + log.Println("No torrents found to remove") + return + } + + if dryRun { + if hashes[0] == "all" { + log.Println("dry-run: all torrents to be removed") + } else { + log.Printf("dry-run: (%d) torrents to be removed\n", len(hashes)) + } + } else { + if hashes[0] == "all" { + log.Println("dry-run: all torrents to be removed") + } else { + log.Printf("dry-run: (%d) torrents to be removed\n", len(hashes)) + } + + // Split the hashes into groups of 20 to avoid flooding qbittorrent + batch := 20 + for i := 0; i < len(hashes); i += batch { + j := i + batch + if j > len(hashes) { + j = len(hashes) + } + + if err := qb.DeleteTorrentsCtx(ctx, hashes[i:j], deleteFiles); err != nil { + fmt.Fprintf(os.Stderr, "could not delete torrents: %v\n", err) + os.Exit(1) + } + + time.Sleep(time.Second * 1) + } + + if hashes[0] == "all" { + log.Println("successfully removed all torrents") + } else { + log.Printf("successfully removed (%d) torrents\n", len(hashes)) + } + } + + log.Printf("torrent(s) successfully deleted\n") + } + + return command +} diff --git a/cmd/torrent_resume.go b/cmd/torrent_resume.go new file mode 100644 index 0000000..9066feb --- /dev/null +++ b/cmd/torrent_resume.go @@ -0,0 +1,75 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/ludviglundgren/qbittorrent-cli/internal/config" + + "github.com/autobrr/go-qbittorrent" + "github.com/spf13/cobra" +) + +// RunTorrentResume cmd to resume torrents +func RunTorrentResume() *cobra.Command { + var ( + resumeAll bool + hashes []string + ) + + var command = &cobra.Command{ + Use: "resume", + Short: "Resume specified torrent(s)", + Long: `Resumes torrents indicated by hash, name or a prefix of either. Whitespace indicates next prefix unless argument is surrounded by quotes`, + } + + command.Flags().BoolVar(&resumeAll, "all", false, "resumes all torrents") + command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Add hashes as comma separated list") + + command.Run = func(cmd *cobra.Command, args []string) { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "connection failed: %v\n", err) + os.Exit(1) + } + + if resumeAll { + hashes = []string{"all"} + } + + // Split the hashes into groups of 20 to avoid flooding qbittorrent + batch := 20 + for i := 0; i < len(hashes); i += batch { + j := i + batch + if j > len(hashes) { + j = len(hashes) + } + + if err := qb.ResumeCtx(ctx, hashes[i:j]); err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not resume torrents: %v\n", err) + os.Exit(1) + } + + time.Sleep(time.Second * 1) + } + + log.Printf("torrent(s) successfully resumed") + } + + return command +} diff --git a/cmd/version.go b/cmd/version.go index 71739e0..0cadaa2 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,22 +1,51 @@ package cmd import ( + "encoding/json" "fmt" + "os" "github.com/spf13/cobra" ) func RunVersion(version, commit, date string) *cobra.Command { + var output string var command = &cobra.Command{ - Use: "version", - Short: "Print the version", - Example: ` qbt version`, + Use: "version", + Short: "Print qbt version info", + Example: ` qbt version + qbt version --output json`, SilenceUsage: false, } + + command.Flags().StringVar(&output, "output", "text", "Print as [text, json]") + command.Run = func(cmd *cobra.Command, args []string) { - fmt.Println("Version:", version) - fmt.Println("Commit:", commit) - fmt.Println("Date:", date) + switch output { + case "text": + fmt.Printf(`qbt - qbitttorrent cli +Version: %s +Commit: %s +Date: %s +`, version, commit, date) + return + + case "json": + res, err := json.Marshal(versionInfo{Version: version, Commit: commit, Date: date}) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not marshal version info to json %v\n", err) + os.Exit(1) + } + fmt.Println(string(res)) + + } } + return command } + +type versionInfo struct { + Version string `json:"version"` + Commit string `json:"commit"` + Date string `json:"date"` +} diff --git a/go.mod b/go.mod index a5e6ee2..68696fc 100644 --- a/go.mod +++ b/go.mod @@ -3,44 +3,46 @@ module github.com/ludviglundgren/qbittorrent-cli go 1.20 require ( - github.com/anacrolix/torrent v1.51.1 + github.com/anacrolix/torrent v1.52.5 + github.com/autobrr/go-qbittorrent v1.4.0 github.com/dustin/go-humanize v1.0.1 github.com/magiconair/properties v1.8.7 github.com/mholt/archiver/v3 v3.5.1 github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.7.0 - github.com/spf13/viper v1.15.0 + github.com/spf13/viper v1.16.0 github.com/zeebo/bencode v1.0.0 - golang.org/x/net v0.10.0 + golang.org/x/net v0.15.0 ) require ( github.com/anacrolix/missinggo v1.3.0 // indirect - github.com/anacrolix/missinggo/v2 v2.7.0 // indirect - github.com/andybalholm/brotli v1.0.4 // indirect + github.com/anacrolix/missinggo/v2 v2.7.2 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/huandu/xstrings v1.3.2 // indirect + github.com/huandu/xstrings v1.4.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.13.6 // indirect - github.com/klauspost/pgzip v1.2.5 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/nwaples/rardecode v1.1.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect - github.com/pierrec/lz4/v4 v4.1.12 // indirect - github.com/spf13/afero v1.9.3 // indirect - github.com/spf13/cast v1.5.0 // indirect + github.com/nwaples/rardecode v1.1.3 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pierrec/lz4/v4 v4.1.18 // indirect + github.com/spf13/afero v1.9.5 // indirect + github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.4.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 98a8c13..b97e719 100644 --- a/go.sum +++ b/go.sum @@ -63,18 +63,22 @@ github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY= github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA= -github.com/anacrolix/missinggo/v2 v2.7.0 h1:4fzOAAn/VCvfWGviLmh64MPMttrlYew81JdPO7nSHvI= -github.com/anacrolix/missinggo/v2 v2.7.0/go.mod h1:2IZIvmRTizALNYFYXsPR7ofXPzJgyBpKZ4kMqMEICkI= +github.com/anacrolix/missinggo/v2 v2.7.2 h1:XGia0kZVC8DDY6XVl15fjtdEyUF39tWkdtsH1VjuAHg= +github.com/anacrolix/missinggo/v2 v2.7.2/go.mod h1:mIEtp9pgaXqt8VQ3NQxFOod/eQ1H0D1XsZzKUQfwtac= github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg= github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= -github.com/anacrolix/torrent v1.51.1 h1:E48PcnNN3MFbqfFEinm+/E846ZNDVL0HKTY+XSsoEhM= -github.com/anacrolix/torrent v1.51.1/go.mod h1:yCbMnw1IpltDstasqpZploAMnJrkXNjZFjo7XdDYUDY= +github.com/anacrolix/torrent v1.52.5 h1:jWowdx+EU6zFVfBwmnL0d3H4J6vTFEGOrHI35YdfIT8= +github.com/anacrolix/torrent v1.52.5/go.mod h1:CcM8oPMYye5J42cSqJrmUpqwRFgSsJQ1jCEHwygqnqQ= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/autobrr/go-qbittorrent v1.4.0 h1:f8FhLB5IVprQ7qATVrJkQxT88gi1Soqz2wa+YCxmI88= +github.com/autobrr/go-qbittorrent v1.4.0/go.mod h1:z88B3+O/1/3doQABErvIOOxE4hjpmIpulu6XzDG/q78= +github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -210,8 +214,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -226,11 +230,12 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -256,19 +261,19 @@ github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE 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/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d3M= -github.com/nwaples/rardecode v1.1.2/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= +github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 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.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +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/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/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pierrec/lz4/v4 v4.1.12 h1:44l88ehTZAUGW4VlO1QC4zkilL99M6Y9MXNwEs0uzP8= -github.com/pierrec/lz4/v4 v4.1.12/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= +github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -304,18 +309,18 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= -github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= +github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= -github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -328,10 +333,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 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= @@ -364,7 +369,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -433,8 +438,9 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 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/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -493,11 +499,12 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -505,8 +512,10 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/internal/importer/deluge.go b/internal/importer/deluge.go index cb3ba58..27cc5e1 100644 --- a/internal/importer/deluge.go +++ b/internal/importer/deluge.go @@ -3,11 +3,10 @@ package importer import ( "log" "os" - "path" "path/filepath" + "github.com/ludviglundgren/qbittorrent-cli/internal/fs" "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" - "github.com/ludviglundgren/qbittorrent-cli/pkg/torrent" "github.com/anacrolix/torrent/metainfo" "github.com/pkg/errors" @@ -15,10 +14,9 @@ import ( ) type Options struct { - SourceDir string - QbitDir string - RTorrentDir string - DryRun bool + SourceDir string + QbitDir string + DryRun bool } type Importer interface { @@ -34,38 +32,47 @@ func NewDelugeImporter() Importer { func (di *DelugeImport) Import(opts Options) error { sourceDir := opts.SourceDir - info, err := os.Stat(sourceDir) - if os.IsNotExist(err) { - return errors.Wrapf(err, "Directory does not exist: %s\n", sourceDir) - } + sourceDirInfo, err := os.Stat(sourceDir) if err != nil { - return errors.Wrapf(err, "Directory error: %s\n", sourceDir) + if os.IsNotExist(err) { + return errors.Errorf("source directory does not exist: %s", sourceDir) + } + + return errors.Wrapf(err, "source directory error: %s\n", sourceDir) + } + if !sourceDirInfo.IsDir() { + return errors.Errorf("source is a file, not a directory: %s\n", sourceDir) } - if !info.IsDir() { - return errors.Errorf("Directory is a file, not a directory: %s\n", sourceDir) + + if err := fs.MkDirIfNotExists(opts.QbitDir); err != nil { + return errors.Wrapf(err, "qbit directory error: %s\n", opts.QbitDir) } - resumeFilePath := path.Join(sourceDir, "torrents.fastresume") + resumeFilePath := filepath.Join(sourceDir, "torrents.fastresume") if _, err := os.Stat(resumeFilePath); os.IsNotExist(err) { - log.Println("Could not find deluge fastresume file") + log.Printf("Could not find deluge fastresume file: %s\n", resumeFilePath) return err } fastresumeFile, err := decodeFastresumeFile(resumeFilePath) if err != nil { - log.Println("Could not decode deluge fastresume file") + log.Printf("Could not decode deluge fastresume file: %s\n", resumeFilePath) return err } - matches, _ := filepath.Glob(path.Join(sourceDir, "*.torrent")) + matches, err := filepath.Glob(filepath.Join(sourceDir, "*.torrent")) + if err != nil { + return errors.Wrapf(err, "glob error: %s", matches) + } totalJobs := len(matches) + log.Printf("Total torrents to process: %d\n", totalJobs) positionNum := 0 for torrentID, value := range fastresumeFile { - torrentNamePath := path.Join(sourceDir, torrentID+".torrent") + torrentNamePath := filepath.Join(sourceDir, torrentID+".torrent") // If a file exist in fastresume data but no .torrent file, skip if _, err = os.Stat(torrentNamePath); os.IsNotExist(err) { @@ -74,24 +81,29 @@ func (di *DelugeImport) Import(opts Options) error { positionNum++ - torrentOutFile := path.Join(opts.QbitDir, torrentID+".torrent") + if opts.DryRun { + log.Printf("dry-run: (%d/%d) successfully imported: %s\n", positionNum, totalJobs, torrentID) + continue + } + + torrentOutFile := filepath.Join(opts.QbitDir, torrentID+".torrent") // If file already exists, skip if _, err = os.Stat(torrentOutFile); err == nil { - log.Printf("%d/%d %s Torrent already exists, skipping", positionNum, totalJobs, torrentID) + log.Printf("(%d/%d) %s Torrent already exists, skipping\n", positionNum, totalJobs, torrentID) continue } var fastResume qbittorrent.Fastresume if err := bencode.DecodeString(value.(string), &fastResume); err != nil { - log.Printf("Could not decode row %s. Continue", torrentID) + log.Printf("Could not decode row %s. Continue\n", torrentID) continue } fastResume.TorrentFilePath = torrentNamePath if _, err = os.Stat(fastResume.TorrentFilePath); os.IsNotExist(err) { - log.Printf("Could not find torrent file %s for %s", fastResume.TorrentFilePath, torrentID) + log.Printf("Could not find torrent file %s for %s\n", fastResume.TorrentFilePath, torrentID) return err } @@ -134,22 +146,22 @@ func (di *DelugeImport) Import(opts Options) error { // TODO handle replace paths - if opts.DryRun != true { - fastResumeOutFile := path.Join(opts.QbitDir, torrentID+".fastresume") - if err = fastResume.Encode(fastResumeOutFile); err != nil { - log.Printf("Could not create qBittorrent fastresume file %s error: %q", fastResumeOutFile, err) - return err - } - - if err = torrent.CopyFile(fastResume.TorrentFilePath, torrentOutFile); err != nil { - log.Printf("Could not copy qBittorrent torrent file %s error %q", torrentOutFile, err) - return err - } + fastResumeOutFile := filepath.Join(opts.QbitDir, torrentID+".fastresume") + if err = fastResume.Encode(fastResumeOutFile); err != nil { + log.Printf("Could not create qBittorrent fastresume file %s error: %q\n", fastResumeOutFile, err) + return err + } + + if err = fs.CopyFile(fastResume.TorrentFilePath, torrentOutFile); err != nil { + log.Printf("Could not copy qBittorrent torrent file %s error %q\n", torrentOutFile, err) + return err } - log.Printf("%d/%d %s Successfully imported: %s", positionNum, totalJobs, torrentID, metaInfo.Name) + log.Printf("(%d/%d) successfully imported: %s %s\n", positionNum, totalJobs, torrentID, metaInfo.Name) } + log.Printf("(%d/%d) successfully imported torrents!\n", positionNum, totalJobs) + return nil } @@ -159,10 +171,10 @@ func decodeFastresumeFile(path string) (map[string]interface{}, error) { return nil, err } - var torrent map[string]interface{} - if err := bencode.DecodeBytes(dat, &torrent); err != nil { + var fastresumeFile map[string]interface{} + if err := bencode.DecodeBytes(dat, &fastresumeFile); err != nil { return nil, err } - return torrent, nil + return fastresumeFile, nil } diff --git a/internal/importer/rtorrent.go b/internal/importer/rtorrent.go index 889ef23..4b12cbf 100644 --- a/internal/importer/rtorrent.go +++ b/internal/importer/rtorrent.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/ludviglundgren/qbittorrent-cli/internal/fs" "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" "github.com/ludviglundgren/qbittorrent-cli/pkg/torrent" @@ -31,22 +32,32 @@ var ( func (i *RTorrentImport) Import(opts Options) error { torrentsSessionDir := opts.SourceDir - info, err := os.Stat(torrentsSessionDir) - if os.IsNotExist(err) { - return errors.Wrapf(err, "Directory does not exist: %s", torrentsSessionDir) - } + sourceDirInfo, err := os.Stat(torrentsSessionDir) if err != nil { - return errors.Wrapf(err, "Directory error: %s", torrentsSessionDir) + if os.IsNotExist(err) { + return errors.Errorf("source directory does not exist: %s", torrentsSessionDir) + } + + return errors.Wrapf(err, "source directory error: %s", torrentsSessionDir) + } + if !sourceDirInfo.IsDir() { + return errors.Errorf("source is a file, not a directory: %s", torrentsSessionDir) } - if !info.IsDir() { - return errors.Errorf("Directory is a file, not a directory: %s", torrentsSessionDir) + + matches, err := filepath.Glob(filepath.Join(torrentsSessionDir, "*.torrent")) + if err != nil { + return errors.Wrapf(err, "glob error: %s", torrentsSessionDir) } - matches, _ := filepath.Glob(path.Join(torrentsSessionDir, "*.torrent")) + if len(matches) == 0 { + log.Printf("Found 0 files to process in: %s\n", torrentsSessionDir) + return nil + } totalJobs := len(matches) - log.Printf("Total torrents to process: %v", totalJobs) + + log.Printf("Total torrents to process: %d\n", totalJobs) positionNum := 0 for _, match := range matches { @@ -54,17 +65,22 @@ func (i *RTorrentImport) Import(opts Options) error { torrentID := getTorrentFileName(match) - torrentOutFile := path.Join(opts.QbitDir, torrentID+".torrent") + torrentOutFile := filepath.Join(opts.QbitDir, torrentID+".torrent") // If file already exists, skip if _, err = os.Stat(torrentOutFile); err == nil { - log.Printf("%d/%d %s Torrent already exists, skipping", positionNum, totalJobs, torrentOutFile) + log.Printf("(%d/%d) %s Torrent already exists, skipping\n", positionNum, totalJobs, torrentOutFile) + continue + } + + if opts.DryRun { + log.Printf("dry-run: (%d/%d) successfully imported: %s\n", positionNum, totalJobs, torrentID) continue } torrentFile, err := torrent.OpenDecodeRaw(match) if err != nil { - log.Printf("Could not decode torrent file %s. Could not decode string %s. Continue", match, torrentID) + log.Printf("Could not decode torrent file %s. Could not decode string %s. Continue\n", match, torrentID) continue } @@ -154,7 +170,7 @@ func (i *RTorrentImport) Import(opts Options) error { // Fix savepath for torrents with subfolder // directory contains the whole torrent path, which gives error in qBit. - // remove file.info.name from full path in id.rtorrent directory + // remove file.sourceDirInfo.name from full path in id.rtorrent directory newPath := strings.ReplaceAll(rtorrentFile.Directory, metaInfo.Name, "") newFastResume.Path = newPath @@ -179,24 +195,23 @@ func (i *RTorrentImport) Import(opts Options) error { // Set 20 byte SHA1 hash newFastResume.InfoHash = newFastResume.GetInfoHashSHA1() - // only run if not dry-run - if opts.DryRun != true { - // copy torrent file - fastResumeOutFile := path.Join(opts.QbitDir, torrentID+".fastresume") - if err = newFastResume.Encode(fastResumeOutFile); err != nil { - log.Printf("Could not create qBittorrent fastresume file %s error: %q", fastResumeOutFile, err) - return err - } - - if err = torrent.CopyFile(match, torrentOutFile); err != nil { - log.Printf("Could copy qBittorrent torrent file %v error %v", torrentOutFile, err) - return err - } + // copy torrent file + fastResumeOutFile := filepath.Join(opts.QbitDir, torrentID+".fastresume") + if err = newFastResume.Encode(fastResumeOutFile); err != nil { + log.Printf("Could not create qBittorrent fastresume file %s error: %q\n", fastResumeOutFile, err) + return err + } + + if err = fs.CopyFile(match, torrentOutFile); err != nil { + log.Printf("Could copy qBittorrent torrent file %s error %q\n", torrentOutFile, err) + return err } - log.Printf("%d/%d %s Sucessfully imported: %s", positionNum, totalJobs, torrentID, metaInfo.Name) + log.Printf("(%d/%d) successfully imported: %s %s\n", positionNum, totalJobs, torrentID, metaInfo.Name) } + log.Printf("(%d/%d) successfully imported torrents!\n", positionNum, totalJobs) + return nil } diff --git a/pkg/torrent/torrent.go b/pkg/torrent/torrent.go index ea26c3d..a1e4af0 100644 --- a/pkg/torrent/torrent.go +++ b/pkg/torrent/torrent.go @@ -4,7 +4,6 @@ import ( "crypto/sha1" "encoding/hex" "io" - "io/ioutil" "os" "github.com/zeebo/bencode" @@ -33,7 +32,7 @@ type TorrentInfoFile struct { } func Decode(path string) (*TorrentInfo, error) { - dat, err := ioutil.ReadFile(path) + dat, err := os.ReadFile(path) if err != nil { return nil, err } @@ -47,7 +46,7 @@ func Decode(path string) (*TorrentInfo, error) { } func OpenDecodeRaw(path string) (map[string]interface{}, error) { - dat, err := ioutil.ReadFile(path) + dat, err := os.ReadFile(path) if err != nil { return nil, err } From 2cf39f27bbf848511b24b07fb3394c8849039e0c Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Sun, 17 Sep 2023 21:20:25 +0200 Subject: [PATCH 06/21] feat: add self-update command (#72) --- cmd/qbt/main.go | 1 + cmd/update.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 11 +++++++++++ go.sum | 33 ++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+) create mode 100644 cmd/update.go diff --git a/cmd/qbt/main.go b/cmd/qbt/main.go index 352ee24..681b010 100644 --- a/cmd/qbt/main.go +++ b/cmd/qbt/main.go @@ -37,6 +37,7 @@ Documentation is available at https://github.com/ludviglundgren/qbittorrent-cli` rootCmd.AddCommand(cmd.RunCategory()) rootCmd.AddCommand(cmd.RunTag()) rootCmd.AddCommand(cmd.RunVersion(version, commit, date)) + rootCmd.AddCommand(cmd.RunUpdate(version)) if err := rootCmd.Execute(); err != nil { os.Exit(1) diff --git a/cmd/update.go b/cmd/update.go new file mode 100644 index 0000000..795c53a --- /dev/null +++ b/cmd/update.go @@ -0,0 +1,51 @@ +package cmd + +import ( + "log" + + "github.com/blang/semver" + "github.com/rhysd/go-github-selfupdate/selfupdate" + "github.com/spf13/cobra" +) + +func RunUpdate(version string) *cobra.Command { + var command = &cobra.Command{ + Use: "update", + Short: "Update qbittorrent-cli to latest version", + Example: ` qbt update`, + SilenceUsage: false, + } + + var verbose bool + + command.Flags().BoolVar(&verbose, "verbose", false, "Verbose output: Print changelog") + + command.Run = func(cmd *cobra.Command, args []string) { + v, err := semver.ParseTolerant(version) + if err != nil { + log.Println("could not parse version:", err) + return + } + + latest, err := selfupdate.UpdateSelf(v, "ludviglundgren/qbittorrent-cli") + if err != nil { + log.Println("Binary update failed:", err) + return + } + + if latest.Version.Equals(v) { + // latest version is the same as current version. It means current binary is up-to-date. + log.Println("Current binary is the latest version", version) + } else { + log.Println("Successfully updated to version: ", latest.Version) + + if verbose { + log.Println("Release note:\n", latest.ReleaseNotes) + } + } + + return + } + + return command +} diff --git a/go.mod b/go.mod index 68696fc..a989990 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,13 @@ go 1.20 require ( github.com/anacrolix/torrent v1.52.5 github.com/autobrr/go-qbittorrent v1.4.0 + github.com/blang/semver v3.5.1+incompatible github.com/dustin/go-humanize v1.0.1 github.com/magiconair/properties v1.8.7 github.com/mholt/archiver/v3 v3.5.1 github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 + github.com/rhysd/go-github-selfupdate v1.2.3 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.16.0 github.com/zeebo/bencode v1.0.0 @@ -24,9 +26,13 @@ require ( github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-github/v30 v30.1.0 // indirect + github.com/google/go-querystring v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect + github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/pgzip v1.2.6 // indirect @@ -39,10 +45,15 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/tcnksm/go-gitconfig v0.1.2 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/text v0.13.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b97e719..3b6aab1 100644 --- a/go.sum +++ b/go.sum @@ -83,6 +83,8 @@ github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylH 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/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 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= @@ -162,6 +164,9 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -181,6 +186,10 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= +github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -218,6 +227,8 @@ github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8= +github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -265,6 +276,8 @@ github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9l github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 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.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= 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.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= @@ -299,6 +312,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rhysd/go-github-selfupdate v1.2.3 h1:iaa+J202f+Nc+A8zi75uccC8Wg3omaM7HDeimXA22Ag= +github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -337,6 +352,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw= +github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE= 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= @@ -368,8 +385,11 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -442,6 +462,7 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -450,6 +471,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -475,6 +498,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -505,6 +529,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -512,6 +537,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= @@ -592,11 +618,13 @@ google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -661,6 +689,10 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= @@ -675,6 +707,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= From 970e736de42188cbbc695cfacc30ece2e5752c1d Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Sun, 17 Sep 2023 21:29:39 +0200 Subject: [PATCH 07/21] docs: self-update command (#73) --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 16b1940..3bcf846 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![goreleaser](https://github.com/ludviglundgren/qbittorrent-cli/actions/workflows/release.yml/badge.svg)](https://github.com/ludviglundgren/qbittorrent-cli/actions/workflows/release.yml) -A cli to manage qBittorrent. Add torrents, reannounce and import from other clients. +A cli to manage qBittorrent. Add torrents, categories, tags, reannounce and import from other clients. ## Features @@ -10,6 +10,9 @@ A cli to manage qBittorrent. Add torrents, reannounce and import from other clie * Reannounce torrents for troublesome trackers * Set limits on how many simultaneously active downloads are allowed * Import torrents with state from Deluge and rTorrent +* Manage categories +* Manage tags +* Self updater ## Install @@ -128,6 +131,7 @@ Available Commands: help Help about any command tag tag subcommand torrent torrent subcommand + update Update qbittorrent-cli to latest version version Print the version Flags: @@ -144,6 +148,7 @@ Commands: - help - tag - torrent +- update - version ### App @@ -811,3 +816,18 @@ Global Flags: --config string config file (default is $HOME/.config/qbt/.qbt.toml) ``` +## Update + +```text +Update qbittorrent-cli to latest version + +Usage: + qbt update [flags] + +Examples: + qbt update + +Flags: + -h, --help help for update + --verbose Verbose output: Print changelog +``` From 73412724bb6519ab78d5923a4d46fd76d65dc01b Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Sun, 24 Sep 2023 14:52:04 +0200 Subject: [PATCH 08/21] feat: batch pause resume remove (#75) --- cmd/torrent_pause.go | 22 +++++++--------------- cmd/torrent_remove.go | 22 +++++++--------------- cmd/torrent_resume.go | 42 ++++++++++++++++++++++++++++-------------- 3 files changed, 42 insertions(+), 44 deletions(-) diff --git a/cmd/torrent_pause.go b/cmd/torrent_pause.go index 5db0291..90709c6 100644 --- a/cmd/torrent_pause.go +++ b/cmd/torrent_pause.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "os" - "time" "github.com/ludviglundgren/qbittorrent-cli/internal/config" @@ -59,20 +58,13 @@ func RunTorrentPause() *cobra.Command { return } - // Split the hashes into groups of 20 to avoid flooding qbittorrent - batch := 20 - for i := 0; i < len(hashes); i += batch { - j := i + batch - if j > len(hashes) { - j = len(hashes) - } - - if err := qb.PauseCtx(ctx, hashes[i:j]); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not pause torrents: %v\n", err) - os.Exit(1) - } - - time.Sleep(time.Second * 1) + err := batchRequests(hashes, func(start, end int) error { + return qb.PauseCtx(ctx, hashes[start:end]) + }) + if err != nil { + fmt.Fprintf(os.Stderr, "could not pause torrents: %v\n", err) + os.Exit(1) + return } log.Printf("torrent(s) successfully paused") diff --git a/cmd/torrent_remove.go b/cmd/torrent_remove.go index c2cdd9d..e66f631 100644 --- a/cmd/torrent_remove.go +++ b/cmd/torrent_remove.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "os" - "time" "github.com/ludviglundgren/qbittorrent-cli/internal/config" @@ -94,20 +93,13 @@ func RunTorrentRemove() *cobra.Command { log.Printf("dry-run: (%d) torrents to be removed\n", len(hashes)) } - // Split the hashes into groups of 20 to avoid flooding qbittorrent - batch := 20 - for i := 0; i < len(hashes); i += batch { - j := i + batch - if j > len(hashes) { - j = len(hashes) - } - - if err := qb.DeleteTorrentsCtx(ctx, hashes[i:j], deleteFiles); err != nil { - fmt.Fprintf(os.Stderr, "could not delete torrents: %v\n", err) - os.Exit(1) - } - - time.Sleep(time.Second * 1) + err := batchRequests(hashes, func(start, end int) error { + return qb.DeleteTorrentsCtx(ctx, hashes[start:end], deleteFiles) + }) + if err != nil { + fmt.Fprintf(os.Stderr, "could not delete torrents: %v\n", err) + os.Exit(1) + return } if hashes[0] == "all" { diff --git a/cmd/torrent_resume.go b/cmd/torrent_resume.go index 9066feb..c6a91f0 100644 --- a/cmd/torrent_resume.go +++ b/cmd/torrent_resume.go @@ -52,20 +52,13 @@ func RunTorrentResume() *cobra.Command { hashes = []string{"all"} } - // Split the hashes into groups of 20 to avoid flooding qbittorrent - batch := 20 - for i := 0; i < len(hashes); i += batch { - j := i + batch - if j > len(hashes) { - j = len(hashes) - } - - if err := qb.ResumeCtx(ctx, hashes[i:j]); err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not resume torrents: %v\n", err) - os.Exit(1) - } - - time.Sleep(time.Second * 1) + err := batchRequests(hashes, func(start, end int) error { + return qb.ResumeCtx(ctx, hashes[start:end]) + }) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not resume torrents: %v\n", err) + os.Exit(1) + return } log.Printf("torrent(s) successfully resumed") @@ -73,3 +66,24 @@ func RunTorrentResume() *cobra.Command { return command } + +// batchRequests split into multiple requests because qbit uses application/x-www-form-urlencoded +// which might lead to too big requests +func batchRequests(hashes []string, fn func(start, end int) error) error { + // Split the hashes into groups of 20 to avoid flooding qbittorrent + batch := 25 + for i := 0; i < len(hashes); i += batch { + j := i + batch + if j > len(hashes) { + j = len(hashes) + } + + if err := fn(i, j); err != nil { + return err + } + + time.Sleep(time.Second * 1) + } + + return nil +} From 60d407a0aab9391837b67a9d182167eb2b9d4395 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Sun, 24 Sep 2023 15:10:02 +0200 Subject: [PATCH 09/21] feat(remove): improve filtering (#77) --- cmd/torrent_remove.go | 64 ++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/cmd/torrent_remove.go b/cmd/torrent_remove.go index e66f631..dc024f1 100644 --- a/cmd/torrent_remove.go +++ b/cmd/torrent_remove.go @@ -14,11 +14,14 @@ import ( // RunTorrentRemove cmd to remove torrents func RunTorrentRemove() *cobra.Command { var ( - dryRun bool - removeAll bool - removePaused bool - deleteFiles bool - hashes []string + dryRun bool + removeAll bool + deleteFiles bool + hashes []string + includeCategory []string + includeTags []string + excludeTags []string + filter string ) var command = &cobra.Command{ @@ -29,9 +32,12 @@ func RunTorrentRemove() *cobra.Command { command.Flags().BoolVar(&dryRun, "dry-run", false, "Display what would be done without actually doing it") command.Flags().BoolVar(&removeAll, "all", false, "Removes all torrents") - command.Flags().BoolVar(&removePaused, "paused", false, "Removes all paused torrents") command.Flags().BoolVar(&deleteFiles, "delete-files", false, "Also delete downloaded files from torrent(s)") + command.Flags().StringVar(&filter, "filter", "", "Filter by state: all, active, paused, completed, stalled, errored") command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Add hashes as comma separated list") + command.Flags().StringSliceVar(&includeCategory, "include-category", []string{}, "Remove torrents from these categories. Comma separated") + command.Flags().StringSliceVar(&includeTags, "include-tags", []string{}, "Include torrents with provided tags") + command.Flags().StringSliceVar(&excludeTags, "exclude-tags", []string{}, "Exclude torrents with provided tags") command.Run = func(cmd *cobra.Command, args []string) { config.InitConfig() @@ -57,21 +63,35 @@ func RunTorrentRemove() *cobra.Command { hashes = []string{"all"} } - if removePaused { - pausedTorrents, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{Filter: qbittorrent.TorrentFilterPaused}) - if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: failed to retrieve paused torrents: %v\n", err) - os.Exit(1) - } - - for _, torrent := range pausedTorrents { - hashes = append(hashes, torrent.Hash) - } + options := qbittorrent.TorrentFilterOptions{} + if filter != "" { + options.Filter = qbittorrent.TorrentFilter(filter) + } - if dryRun { - log.Printf("dry-run: found (%d) paused torrents to be removed\n", len(hashes)) - } else { - log.Printf("found (%d) paused torrents to be removed\n", len(hashes)) + if len(includeCategory) > 0 { + for _, category := range includeCategory { + options.Category = category + + torrents, err := qb.GetTorrentsCtx(ctx, options) + if err != nil { + log.Fatalf("could not get torrents for category: %s err: %q\n", category, err) + } + + for _, torrent := range torrents { + if len(includeTags) > 0 { + if _, validTag := validateTag(includeTags, torrent.Tags); !validTag { + continue + } + } + + if len(excludeTags) > 0 { + if _, found := validateTag(excludeTags, torrent.Tags); found { + continue + } + } + + hashes = append(hashes, torrent.Hash) + } } } @@ -88,9 +108,9 @@ func RunTorrentRemove() *cobra.Command { } } else { if hashes[0] == "all" { - log.Println("dry-run: all torrents to be removed") + log.Println("all torrents to be removed") } else { - log.Printf("dry-run: (%d) torrents to be removed\n", len(hashes)) + log.Printf("(%d) torrents to be removed\n", len(hashes)) } err := batchRequests(hashes, func(start, end int) error { From a5afb933c040f98270293a041b238432ddad1199 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Sun, 24 Sep 2023 18:18:22 +0200 Subject: [PATCH 10/21] feat(export): write export manifest (#76) * feat(export): write export manifest * feat: batch pause resume remove (#75) * feat(remove): improve filtering (#77) * feat(export): write export manifest * fix(export): tests --- cmd/torrent_export.go | 229 +++++++++++++++++++++++++++++++++---- cmd/torrent_export_test.go | 9 +- 2 files changed, 213 insertions(+), 25 deletions(-) diff --git a/cmd/torrent_export.go b/cmd/torrent_export.go index 6a36184..8bdc5f1 100644 --- a/cmd/torrent_export.go +++ b/cmd/torrent_export.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/json" "fmt" "io/fs" "log" @@ -33,23 +34,28 @@ func RunTorrentExport() *cobra.Command { excludeCategory: nil, includeTag: nil, excludeTag: nil, - hashes: map[string]struct{}{}, + tags: map[string]struct{}{}, + category: map[string]qbittorrent.Category{}, + hashes: map[string]qbittorrent.Torrent{}, } + var skipManifest bool command.Flags().BoolVar(&f.dry, "dry-run", false, "dry run") command.Flags().BoolVarP(&f.verbose, "verbose", "v", false, "verbose output") + command.Flags().BoolVar(&skipManifest, "skip-manifest", false, "Do not export all used tags and categories into manifest") + command.Flags().StringVar(&f.sourceDir, "source", "", "Dir with torrent and fast-resume files (required)") command.Flags().StringVar(&f.exportDir, "export-dir", "", "Dir to export files to (required)") + command.Flags().StringSliceVar(&f.includeCategory, "include-category", []string{}, "Export torrents from these categories. Comma separated") + command.Flags().StringSliceVar(&f.excludeCategory, "exclude-category", []string{}, "Exclude categories. Comma separated") + + command.Flags().StringSliceVar(&f.includeTag, "include-tag", []string{}, "Include tags. Comma separated") + command.Flags().StringSliceVar(&f.excludeTag, "exclude-tag", []string{}, "Exclude tags. Comma separated") - command.MarkFlagRequired("categories") command.MarkFlagRequired("source") command.MarkFlagRequired("export-dir") - //command.Flags().StringSliceVar(&f.excludeCategory, "exclude-category", []string{}, "Exclude categories. Comma separated") - //command.Flags().StringSliceVar(&f.includeTag, "include-tag", []string{}, "Include tags. Comma separated") - //command.Flags().StringSliceVar(&f.excludeTag, "exclude-tag", []string{}, "Exclude tags. Comma separated") - command.RunE = func(cmd *cobra.Command, args []string) error { // get torrents from client by categories config.InitConfig() @@ -86,16 +92,42 @@ func RunTorrentExport() *cobra.Command { return errors.Wrapf(err, "could not get torrents for category: %s", category) } - for _, t := range torrents { + for _, tor := range torrents { // only grab completed torrents - if t.Progress != 1 { - continue + //if tor.Progress != 1 { + // continue + //} + + if tor.Tags != "" { + tags := strings.Split(tor.Tags, ", ") + + // check tags and exclude categories + if len(f.includeTag) > 0 && !containsTag(f.includeTag, tags) { + continue + } + + if len(f.excludeTag) > 0 && containsTag(f.excludeTag, tags) { + continue + } + + for _, tag := range tags { + _, ok := f.tags[tag] + if !ok { + f.tags[tag] = struct{}{} + } + } + } - // todo check tags + if tor.Category != "" { + f.category[tor.Category] = qbittorrent.Category{ + Name: tor.Category, + SavePath: "", + } + } // append hash to map of hashes to gather - f.hashes[strings.ToLower(t.Hash)] = struct{}{} + f.hashes[strings.ToLower(tor.Hash)] = tor } } @@ -105,18 +137,46 @@ func RunTorrentExport() *cobra.Command { return errors.Wrap(err, "could not get torrents") } - for _, t := range torrents { + for _, tor := range torrents { // only grab completed torrents - if t.Progress != 1 { + //if tor.Progress != 1 { + // continue + //} + + if len(f.excludeCategory) > 0 && containsCategory(f.excludeCategory, tor.Category) { continue } - // todo check tags and exclude categories + if tor.Tags != "" { + tags := strings.Split(tor.Tags, ", ") + + // check tags and exclude categories + if len(f.includeTag) > 0 && !containsTag(f.includeTag, tags) { + continue + } + + if len(f.excludeTag) > 0 && containsTag(f.excludeTag, tags) { + continue + } + + for _, tag := range tags { + _, ok := f.tags[tag] + if !ok { + f.tags[tag] = struct{}{} + } + } + } + + if tor.Category != "" { + f.category[tor.Category] = qbittorrent.Category{ + Name: tor.Category, + SavePath: "", + } + } // append hash to map of hashes to gather - f.hashes[strings.ToLower(t.Hash)] = struct{}{} + f.hashes[strings.ToLower(tor.Hash)] = tor } - } if len(f.hashes) == 0 { @@ -130,6 +190,38 @@ func RunTorrentExport() *cobra.Command { return errors.Wrapf(err, "could not process torrents") } + // write export manifest with categories and tags + // can be used for import later on + if !skipManifest { + // get categories + if len(f.category) > 0 { + cats, err := qb.GetCategoriesCtx(ctx) + if err != nil { + return errors.Wrapf(err, "could not get categories from qbit") + } + + for name, category := range cats { + _, ok := f.category[name] + if !ok { + continue + } + + f.category[name] = category + } + } + + if f.dry { + fmt.Println("dry-run: successfully wrote manifest to file") + } else { + if err := exportManifest(f.hashes, f.tags, f.category); err != nil { + fmt.Printf("could not export manifest: %q\n", err) + os.Exit(1) + } + + fmt.Println("successfully wrote manifest to file") + } + } + fmt.Println("Successfully exported torrents!") return nil @@ -138,7 +230,60 @@ func RunTorrentExport() *cobra.Command { return command } -func processExport(sourceDir, exportDir string, hashes map[string]struct{}, dry, verbose bool) error { +func exportManifest(hashes map[string]qbittorrent.Torrent, tags map[string]struct{}, categories map[string]qbittorrent.Category) error { + data := Manifest{ + Tags: make([]string, 0), + Categories: []qbittorrent.Category{}, + Torrents: make([]basicTorrent, 0), + } + + for tag, _ := range tags { + data.Tags = append(data.Tags, tag) + } + + for _, category := range categories { + data.Categories = append(data.Categories, category) + } + + for _, torrent := range hashes { + data.Torrents = append(data.Torrents, basicTorrent{ + Hash: torrent.Hash, + Name: torrent.Name, + Tags: torrent.Tags, + Category: torrent.Category, + Tracker: torrent.Tracker, + }) + } + + res, err := json.Marshal(data) + if err != nil { + return errors.Wrap(err, "could not marshal manifest to json") + } + + currentWorkingDirectory, err := os.Getwd() + if err != nil { + return err + } + + // Create a new file in the current working directory. + fileName := "export-manifest.json" + + file, err := os.Create(filepath.Join(currentWorkingDirectory, fileName)) + if err != nil { + return err + } + defer file.Close() + + // Write the string to the file. + _, err = file.WriteString(string(res)) + if err != nil { + return err + } + + return nil +} + +func processExport(sourceDir, exportDir string, hashes map[string]qbittorrent.Torrent, dry, verbose bool) error { exportCount := 0 exportTorrentCount := 0 exportFastresumeCount := 0 @@ -186,10 +331,10 @@ func processExport(sourceDir, exportDir string, hashes map[string]struct{}, dry, switch ext { case ".torrent": exportTorrentCount++ - fmt.Printf("dry-run: (%d/%d) torrent exported: %s '%s'\n", exportTorrentCount, len(hashes), torrentHash, fileName) + fmt.Printf("dry-run: (%d/%d) exported: %s\n", exportTorrentCount, len(hashes), fileName) case ".fastresume": exportFastresumeCount++ - fmt.Printf("dry-run: (%d/%d) fastresume exported: %s '%s'\n", exportFastresumeCount, len(hashes), torrentHash, fileName) + fmt.Printf("dry-run: (%d/%d) exported: %s\n", exportFastresumeCount, len(hashes), fileName) } } else { @@ -207,10 +352,10 @@ func processExport(sourceDir, exportDir string, hashes map[string]struct{}, dry, switch ext { case ".torrent": exportTorrentCount++ - fmt.Printf("(%d/%d) torrent exported: %s '%s'\n", exportTorrentCount, len(hashes), torrentHash, fileName) + fmt.Printf("(%d/%d) exported: %s\n", exportTorrentCount, len(hashes), fileName) case ".fastresume": exportFastresumeCount++ - fmt.Printf("(%d/%d) fastresume exported: %s '%s'\n", exportFastresumeCount, len(hashes), torrentHash, fileName) + fmt.Printf("(%d/%d) exported: %s\n", exportFastresumeCount, len(hashes), fileName) } //fmt.Printf("(%d/%d) exported: %s '%s'\n", exportCount, len(hashes), torrentHash, fileName) @@ -276,5 +421,45 @@ type export struct { includeTag []string excludeTag []string - hashes map[string]struct{} + tags map[string]struct{} + category map[string]qbittorrent.Category + + hashes map[string]qbittorrent.Torrent +} + +func containsTag(contains []string, tags []string) bool { + for _, s := range tags { + s = strings.ToLower(s) + for _, contain := range contains { + if s == strings.ToLower(contain) { + return true + } + } + } + + return false +} + +func containsCategory(contains []string, category string) bool { + for _, cat := range contains { + if strings.ToLower(category) == strings.ToLower(cat) { + return true + } + } + + return false +} + +type basicTorrent struct { + Hash string `json:"hash"` + Name string `json:"name"` + Tags string `json:"tags"` + Category string `json:"category"` + Tracker string `json:"tracker"` +} + +type Manifest struct { + Tags []string `json:"tags"` + Categories []qbittorrent.Category `json:"categories"` + Torrents []basicTorrent `json:"torrents"` } diff --git a/cmd/torrent_export_test.go b/cmd/torrent_export_test.go index 63d06fa..bd7e240 100644 --- a/cmd/torrent_export_test.go +++ b/cmd/torrent_export_test.go @@ -1,12 +1,15 @@ package cmd -import "testing" +import ( + "github.com/autobrr/go-qbittorrent" + "testing" +) func Test_export_processHashes(t *testing.T) { type args struct { sourceDir string exportDir string - hashes map[string]struct{} + hashes map[string]qbittorrent.Torrent dry bool verbose bool } @@ -20,7 +23,7 @@ func Test_export_processHashes(t *testing.T) { args: args{ sourceDir: "../test/config/qBittorrent/BT_backup", exportDir: "../test/export", - hashes: map[string]struct{}{ + hashes: map[string]qbittorrent.Torrent{ "5ba4939a00a9b21629a0ad7d376898b768d997a3": {}, }, dry: false, From 023e947159711b0fe379b4eb4a9a2ec7441f4eb1 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Sun, 24 Sep 2023 18:34:43 +0200 Subject: [PATCH 11/21] feat(torrent): edit trackers (#79) * chore(deps): update go-qbittorrent to v1.5.0 * feat(torrent): edit tracker * feat(torrent): update readme --- README.md | 41 ++++++++++++++++++ cmd/torrent.go | 1 + cmd/torrent_tracker.go | 96 ++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 2 + 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 cmd/torrent_tracker.go diff --git a/README.md b/README.md index 3bcf846..750698f 100644 --- a/README.md +++ b/README.md @@ -413,6 +413,7 @@ Available Commands: reannounce Reannounce torrent(s) remove Removes specified torrent(s) resume Resume specified torrent(s) + tracker Torrent tracker subcommand Flags: -h, --help help for torrent @@ -793,7 +794,47 @@ Flags: Global Flags: --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + +### Tracker + +```text +Do various torrent category operations + +Usage: + qbt torrent tracker [command] +Available Commands: + edit Edit torrent tracker + +Flags: + -h, --help help for tracker + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) + +Use "qbt torrent tracker [command] --help" for more information about a command. +``` + +#### Tracker edit + +```text +Edit tracker for torrents via hashes + +Usage: + qbt torrent tracker edit [flags] + +Examples: + qbt torrent tracker edit --old url.old/test --new url.com/test + +Flags: + --dry-run Run without doing anything + -h, --help help for edit + --new string New tracker URL + --old string Old tracker URL to replace + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) ``` ## Version diff --git a/cmd/torrent.go b/cmd/torrent.go index 437b257..cb5c656 100644 --- a/cmd/torrent.go +++ b/cmd/torrent.go @@ -23,6 +23,7 @@ func RunTorrent() *cobra.Command { command.AddCommand(RunTorrentReannounce()) command.AddCommand(RunTorrentRemove()) command.AddCommand(RunTorrentResume()) + command.AddCommand(RunTorrentTracker()) return command } diff --git a/cmd/torrent_tracker.go b/cmd/torrent_tracker.go new file mode 100644 index 0000000..2213303 --- /dev/null +++ b/cmd/torrent_tracker.go @@ -0,0 +1,96 @@ +package cmd + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/ludviglundgren/qbittorrent-cli/internal/config" + + "github.com/autobrr/go-qbittorrent" + "github.com/spf13/cobra" +) + +// RunTorrentTracker cmd for torrent tracker operations +func RunTorrentTracker() *cobra.Command { + var command = &cobra.Command{ + Use: "tracker", + Short: "Torrent tracker subcommand", + Long: `Do various torrent category operations`, + } + + command.AddCommand(RunTorrentTrackerEdit()) + + return command +} + +// RunTorrentTrackerEdit cmd for torrent tracker operations +func RunTorrentTrackerEdit() *cobra.Command { + var command = &cobra.Command{ + Use: "edit", + Short: "Edit torrent tracker", + Long: `Edit tracker for torrents via hashes`, + Example: ` qbt torrent tracker edit --old url.old/test --new url.com/test`, + } + + var ( + dry bool + oldURL string + newURL string + ) + + command.Flags().BoolVar(&dry, "dry-run", false, "Run without doing anything") + command.Flags().StringVar(&oldURL, "old", "", "Old tracker URL to replace") + command.Flags().StringVar(&newURL, "new", "", "New tracker URL") + + command.RunE = func(cmd *cobra.Command, args []string) error { + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "could not login to qbit: %q\n", err) + os.Exit(1) + } + + if dry { + log.Printf("dry-run: successfully updated tracker on torrents\n") + + return nil + } else { + torrents, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{}) + if err != nil { + log.Fatalf("could not get torrents err: %q\n", err) + } + + matches := 0 + + for _, torrent := range torrents { + if strings.Contains(torrent.Tracker, oldURL) { + if err := qb.EditTrackerCtx(ctx, torrent.Hash, torrent.Tracker, newURL); err != nil { + log.Fatalf("could not edit tracker for torrent: %s\n", torrent.Hash) + } + + matches++ + } + } + + log.Printf("successfully updated tracker for (%d) torrents\n", matches) + } + + return nil + } + + return command +} diff --git a/go.mod b/go.mod index a989990..79a27bb 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/anacrolix/torrent v1.52.5 - github.com/autobrr/go-qbittorrent v1.4.0 + github.com/autobrr/go-qbittorrent v1.5.0 github.com/blang/semver v3.5.1+incompatible github.com/dustin/go-humanize v1.0.1 github.com/magiconair/properties v1.8.7 diff --git a/go.sum b/go.sum index 3b6aab1..fc71f24 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/autobrr/go-qbittorrent v1.4.0 h1:f8FhLB5IVprQ7qATVrJkQxT88gi1Soqz2wa+YCxmI88= github.com/autobrr/go-qbittorrent v1.4.0/go.mod h1:z88B3+O/1/3doQABErvIOOxE4hjpmIpulu6XzDG/q78= +github.com/autobrr/go-qbittorrent v1.5.0 h1:UBl6w/bxxT5v1d9sGKp5Be4js4YNNumXw4hZNXXxPd8= +github.com/autobrr/go-qbittorrent v1.5.0/go.mod h1:z88B3+O/1/3doQABErvIOOxE4hjpmIpulu6XzDG/q78= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= From f46c1b5edf8ecf1ad181ef05c6742922af07cbd3 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Sun, 24 Sep 2023 18:36:39 +0200 Subject: [PATCH 12/21] docs: update export command (#80) --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 750698f..4ff95fb 100644 --- a/README.md +++ b/README.md @@ -622,9 +622,13 @@ Examples: Flags: --dry-run dry run + --exclude-category strings Exclude categories. Comma separated + --exclude-tag strings Exclude tags. Comma separated --export-dir string Dir to export files to (required) -h, --help help for export --include-category strings Export torrents from these categories. Comma separated + --include-tag strings Include tags. Comma separated + --skip-manifest Do not export all used tags and categories into manifest --source string Dir with torrent and fast-resume files (required) -v, --verbose verbose output From 729570267718ec3625ac89d1c1b6acfba324b4a0 Mon Sep 17 00:00:00 2001 From: Bas van den Wollenberg Date: Fri, 5 Apr 2024 08:33:13 +0200 Subject: [PATCH 13/21] feat(add): make sleep time configurable (#90) --- cmd/torrent_add.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/torrent_add.go b/cmd/torrent_add.go index a4115dd..b11d7b9 100644 --- a/cmd/torrent_add.go +++ b/cmd/torrent_add.go @@ -31,6 +31,7 @@ func RunTorrentAdd() *cobra.Command { ignoreRules bool uploadLimit uint64 downloadLimit uint64 + sleep time.Duration ) var command = &cobra.Command{ @@ -58,6 +59,7 @@ func RunTorrentAdd() *cobra.Command { command.Flags().StringVar(&category, "category", "", "Add torrent to the specified category") command.Flags().Uint64Var(&uploadLimit, "limit-ul", 0, "Set torrent upload speed limit. Unit in bytes/second") command.Flags().Uint64Var(&downloadLimit, "limit-dl", 0, "Set torrent download speed limit. Unit in bytes/second") + command.Flags().DurationVar(&sleep, "sleep", 200*time.Millisecond, "Set the amount of time to wait between adding torrents in seconds") command.Flags().StringArrayVar(&tags, "tags", []string{}, "Add tags to torrent") command.Run = func(cmd *cobra.Command, args []string) { @@ -201,9 +203,9 @@ func RunTorrentAdd() *cobra.Command { log.Printf("successfully added torrent: %s\n", hash) if len(files) > 1 { - log.Println("sleeping 2 seconds before adding next torrent...") + log.Printf("sleeping %v before adding next torrent...\n", sleep) - time.Sleep(2 * time.Second) + time.Sleep(sleep) continue } From d029c02699408d2ee4f13cee841eb9ea5cc7b2b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Mechnich?= Date: Fri, 5 Apr 2024 08:59:13 +0200 Subject: [PATCH 14/21] fix(torrent): remove with only filter (#82) * Add short cmdline options for filter and include-category * Also remove torrents if only filter was set * Update README to reflect new goreleaser cmdline interface --- README.md | 2 +- cmd/torrent_remove.go | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4ff95fb..aae5013 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Or with only go Builds with `goreleaser` will also include version info. - goreleaser release --snapshot --skip-publish --clean + goreleaser release --snapshot --skip=publish --clean ## Configuration diff --git a/cmd/torrent_remove.go b/cmd/torrent_remove.go index dc024f1..195c846 100644 --- a/cmd/torrent_remove.go +++ b/cmd/torrent_remove.go @@ -33,9 +33,9 @@ func RunTorrentRemove() *cobra.Command { command.Flags().BoolVar(&dryRun, "dry-run", false, "Display what would be done without actually doing it") command.Flags().BoolVar(&removeAll, "all", false, "Removes all torrents") command.Flags().BoolVar(&deleteFiles, "delete-files", false, "Also delete downloaded files from torrent(s)") - command.Flags().StringVar(&filter, "filter", "", "Filter by state: all, active, paused, completed, stalled, errored") + command.Flags().StringVarP(&filter, "filter", "f", "", "Filter by state: all, active, paused, completed, stalled, errored") command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Add hashes as comma separated list") - command.Flags().StringSliceVar(&includeCategory, "include-category", []string{}, "Remove torrents from these categories. Comma separated") + command.Flags().StringSliceVarP(&includeCategory, "include-category", "c", []string{}, "Remove torrents from these categories. Comma separated") command.Flags().StringSliceVar(&includeTags, "include-tags", []string{}, "Include torrents with provided tags") command.Flags().StringSliceVar(&excludeTags, "exclude-tags", []string{}, "Exclude torrents with provided tags") @@ -66,6 +66,9 @@ func RunTorrentRemove() *cobra.Command { options := qbittorrent.TorrentFilterOptions{} if filter != "" { options.Filter = qbittorrent.TorrentFilter(filter) + if len(includeCategory) == 0 { + includeCategory = []string{""} + } } if len(includeCategory) > 0 { From 928e38d59ce07673e23ddccc6aba47613b4265b6 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Mon, 19 Aug 2024 16:04:42 +0200 Subject: [PATCH 15/21] feat(ci): update workflows (#97) * feat(ci): update workflows * feat(ci): set goreleaser config version --- .github/workflows/release.yml | 26 +++++++++++++------------- .goreleaser.yml | 2 ++ go.mod | 2 +- go.sum | 9 +++++++-- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 905d26e..02b6041 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,14 +15,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.22 cache: true - name: Test @@ -35,27 +35,27 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.22 cache: true - name: Run GoReleaser build - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser version: latest - args: release --clean --skip-validate --skip-publish --parallelism 99 + args: release --clean --skip=validate --skip=publish --parallelism 5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload assets - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: qbittorrent-cli path: | @@ -70,18 +70,18 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: 1.22 cache: true - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4 + uses: goreleaser/goreleaser-action@v6 with: version: latest args: release --clean diff --git a/.goreleaser.yml b/.goreleaser.yml index 473c470..5ede2e8 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,5 @@ +version: 2 + env: - GO111MODULE=on diff --git a/go.mod b/go.mod index 79a27bb..7258cca 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ludviglundgren/qbittorrent-cli -go 1.20 +go 1.22 require ( github.com/anacrolix/torrent v1.52.5 diff --git a/go.sum b/go.sum index fc71f24..ea45e8f 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 h1:8V0K09lrGoeT2KRJNOtspA7q+OMxGwQqK/Ug0IiaaRE= +github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444/go.mod h1:MctKM1HS5YYDb3F30NGJxLE+QPuqWoT5ReW/4jt8xew= github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= @@ -75,8 +76,6 @@ github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/autobrr/go-qbittorrent v1.4.0 h1:f8FhLB5IVprQ7qATVrJkQxT88gi1Soqz2wa+YCxmI88= -github.com/autobrr/go-qbittorrent v1.4.0/go.mod h1:z88B3+O/1/3doQABErvIOOxE4hjpmIpulu6XzDG/q78= github.com/autobrr/go-qbittorrent v1.5.0 h1:UBl6w/bxxT5v1d9sGKp5Be4js4YNNumXw4hZNXXxPd8= github.com/autobrr/go-qbittorrent v1.5.0/go.mod h1:z88B3+O/1/3doQABErvIOOxE4hjpmIpulu6XzDG/q78= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= @@ -122,6 +121,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= @@ -188,6 +188,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= @@ -254,9 +255,11 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -318,6 +321,7 @@ github.com/rhysd/go-github-selfupdate v1.2.3 h1:iaa+J202f+Nc+A8zi75uccC8Wg3omaM7 github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -710,6 +714,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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= From a4d9415638a732c753dc42c2a58b05ca302d5099 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Mon, 19 Aug 2024 16:08:40 +0200 Subject: [PATCH 16/21] docs: add table of contents (#96) --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index aae5013..0af1f39 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,50 @@ A cli to manage qBittorrent. Add torrents, categories, tags, reannounce and impo * Manage tags * Self updater +## Table of contents + +* [Features](#features) +* [Install](#install) +* [Build from source](#build-from-source) + * [Multi-platform with `goreleaser`](#multi-platform-with-goreleaser) +* [Configuration](#configuration) +* [Usage](#usage) + * [App](#app) + * [Version](#version) + * [Bencode](#bencode) + * [Edit](#edit) + * [Category](#category) + * [Add](#add) + * [Delete](#delete) + * [Edit](#edit-1) + * [List](#list) + * [Tag](#tag) + * [Add](#add-1) + * [Delete](#delete-1) + * [List](#list-1) + * [Torrent](#torrent) + * [Add](#add-2) + * [Category](#category-1) + * [Move](#move) + * [Set](#set) + * [Unset](#unset) + * [Compare](#compare) + * [Export](#export) + * [Hash](#hash) + * [Import](#import) + * [Caveats](#caveats) + * [Workflow](#workflow) + * [List](#list-2) + * [Pause](#pause) + * [Reannounce](#reannounce) + * [Remove](#remove) + * [Resume](#resume) + * [Tracker](#tracker) + * [Tracker edit](#tracker-edit) + * [Version](#version-1) + * [Update](#update) + + ## Install Download the [latest binary](https://github.com/ludviglundgren/qbittorrent-cli/releases/latest) and put somewhere in $PATH. @@ -142,14 +186,13 @@ Use "qbt [command] --help" for more information about a command. ``` Commands: -- app -- bencode -- category -- help -- tag -- torrent -- update -- version +* [app](#app) +* [bencode](#bencode) +* [category](#category) +* [tag](#tag) +* [torrent](#torrent) +* [version](#version-1) +* [update](#update) ### App From e7d804bb2c199fe91c5f62e4b9a5fcd04cd611f0 Mon Sep 17 00:00:00 2001 From: soup Date: Mon, 19 Aug 2024 16:22:48 +0200 Subject: [PATCH 17/21] chore(deps): update x/* and protobuf (#91) * chore(deps): update x/* and protobuf medium and high alerts * chore: update deps --------- Co-authored-by: Ludvig Lundgren --- go.mod | 61 ++++--- go.sum | 492 ++++++++++++--------------------------------------------- 2 files changed, 139 insertions(+), 414 deletions(-) diff --git a/go.mod b/go.mod index 7258cca..f59d81b 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/ludviglundgren/qbittorrent-cli -go 1.22 +go 1.22.6 require ( - github.com/anacrolix/torrent v1.52.5 - github.com/autobrr/go-qbittorrent v1.5.0 + github.com/anacrolix/torrent v1.56.1 + github.com/autobrr/go-qbittorrent v1.9.0 github.com/blang/semver v3.5.1+incompatible github.com/dustin/go-humanize v1.0.1 github.com/magiconair/properties v1.8.7 @@ -12,48 +12,59 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 github.com/rhysd/go-github-selfupdate v1.2.3 - github.com/spf13/cobra v1.7.0 - github.com/spf13/viper v1.16.0 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 github.com/zeebo/bencode v1.0.0 - golang.org/x/net v0.15.0 + golang.org/x/net v0.28.0 ) require ( + github.com/anacrolix/dht/v2 v2.21.1 // indirect + github.com/anacrolix/generics v0.0.2 // indirect github.com/anacrolix/missinggo v1.3.0 // indirect - github.com/anacrolix/missinggo/v2 v2.7.2 // indirect - github.com/andybalholm/brotli v1.0.5 // indirect + github.com/anacrolix/missinggo/v2 v2.7.3 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/go-github/v30 v30.1.0 // indirect - github.com/google/go-querystring v1.0.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/huandu/xstrings v1.4.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/klauspost/pgzip v1.2.6 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mr-tron/base58 v1.2.0 // indirect + github.com/multiformats/go-multihash v0.2.3 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect github.com/nwaples/rardecode v1.1.3 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/pierrec/lz4/v4 v4.1.18 // indirect - github.com/spf13/afero v1.9.5 // indirect - github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/onsi/gomega v1.17.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tcnksm/go-gitconfig v0.1.2 // indirect - github.com/ulikunitz/xz v0.5.11 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/text v0.13.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.30.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/blake3 v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index ea45e8f..b9da154 100644 --- a/go.sum +++ b/go.sum @@ -1,45 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 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= @@ -49,11 +12,13 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 h1:8V0K09lrGoeT2KRJNOtspA7q+OMxGwQqK/Ug0IiaaRE= -github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444/go.mod h1:MctKM1HS5YYDb3F30NGJxLE+QPuqWoT5ReW/4jt8xew= +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/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= +github.com/anacrolix/generics v0.0.2 h1:UbtD+KntUGxeGYMC4RwhsETieL9ixGdSptJQRhdy7No= +github.com/anacrolix/generics v0.0.2/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/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= @@ -64,20 +29,22 @@ github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY= github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA= -github.com/anacrolix/missinggo/v2 v2.7.2 h1:XGia0kZVC8DDY6XVl15fjtdEyUF39tWkdtsH1VjuAHg= -github.com/anacrolix/missinggo/v2 v2.7.2/go.mod h1:mIEtp9pgaXqt8VQ3NQxFOod/eQ1H0D1XsZzKUQfwtac= +github.com/anacrolix/missinggo/v2 v2.7.3 h1:Ee//CmZBMadeNiYB/hHo9ly2PFOEZ4Fhsbnug3rDAIE= +github.com/anacrolix/missinggo/v2 v2.7.3/go.mod h1:mIEtp9pgaXqt8VQ3NQxFOod/eQ1H0D1XsZzKUQfwtac= +github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7 h1:lOtCD+LzoD1g7bowhYJNR++uV+FyY5bTZXKwnPex9S8= +github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7/go.mod h1:zJv1JF9AqdZiHwxqPgjuOZDGWER6nyE48WBCi/OOrMM= github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg= github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= -github.com/anacrolix/torrent v1.52.5 h1:jWowdx+EU6zFVfBwmnL0d3H4J6vTFEGOrHI35YdfIT8= -github.com/anacrolix/torrent v1.52.5/go.mod h1:CcM8oPMYye5J42cSqJrmUpqwRFgSsJQ1jCEHwygqnqQ= +github.com/anacrolix/torrent v1.56.1 h1:QeJMOP0NuhpQ5dATsOqEL0vUO85aPMNMGP2FACNt0Eg= +github.com/anacrolix/torrent v1.56.1/go.mod h1:5DMHbeIM1TuC5wTQ99XieKKLiYZYz6iB2lyZpKZEr6w= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= -github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/autobrr/go-qbittorrent v1.5.0 h1:UBl6w/bxxT5v1d9sGKp5Be4js4YNNumXw4hZNXXxPd8= -github.com/autobrr/go-qbittorrent v1.5.0/go.mod h1:z88B3+O/1/3doQABErvIOOxE4hjpmIpulu6XzDG/q78= +github.com/autobrr/go-qbittorrent v1.9.0 h1:HaLueJ99D3G1cQ2r5ADVbtfwyEhekt2eQoEZ7yhAwYs= +github.com/autobrr/go-qbittorrent v1.9.0/go.mod h1:z88B3+O/1/3doQABErvIOOxE4hjpmIpulu6XzDG/q78= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/benbjohnson/immutable v0.2.0/go.mod h1:uc6OHo6PN2++n98KHLxW8ef4W42ylHiQSENghE1ezxI= @@ -90,19 +57,13 @@ github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2w github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= @@ -114,104 +75,62 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/go-unsnap-stream v0.0.0-20190901134440-81cf024a9e0a/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 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.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/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 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -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/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 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= github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -219,39 +138,35 @@ github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORR github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= -github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8= github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -265,6 +180,8 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -273,32 +190,45 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= +github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= +github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 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/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -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.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 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/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= -github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -306,7 +236,6 @@ github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3O github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -319,43 +248,49 @@ github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rhysd/go-github-selfupdate v1.2.3 h1:iaa+J202f+Nc+A8zi75uccC8Wg3omaM7HDeimXA22Ag= github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= -github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= -github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= -github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw= @@ -365,70 +300,35 @@ github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDW github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= -github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 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/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/bencode v1.0.0 h1:zgop0Wu1nu4IexAZeCZ5qbsjU4O1vMrfCrVgUjbHVuA= github.com/zeebo/bencode v1.0.0/go.mod h1:Ct7CkrWIQuLWAy9M3atFHYq4kG9Ao/SsY5cdtCXmp9Y= 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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= +golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= 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= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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= @@ -438,273 +338,93 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -713,19 +433,13 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +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.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= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= +lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= From 81a4c06a12fb0b0229a25672ca116a5f1014ebd8 Mon Sep 17 00:00:00 2001 From: Aviad Gafni <40899785+Acamol@users.noreply.github.com> Date: Mon, 19 Aug 2024 17:26:06 +0300 Subject: [PATCH 18/21] fix(config): add Windows home dir path (#88) * fix(config): add Windows home dir path On PowerShell / CMD the HOME environment variable is often not defined, so $HOME/.config/qbt will evaluate to C:/.config/qbt instead of C://.config/qbt as one would expect. * fix: make config path lookup windows compatible --------- Co-authored-by: Ludvig Lundgren --- internal/config/config.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 400b9ac..957aa1f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,8 +1,10 @@ package config import ( + "errors" "fmt" "os" + "path/filepath" "github.com/ludviglundgren/qbittorrent-cli/internal/domain" @@ -32,18 +34,21 @@ func InitConfig() { os.Exit(1) } - // Search config in directories viper.SetConfigName(".qbt") + // Search config in directories + // call multiple times to add many search paths viper.AddConfigPath(".") // optionally look for config in the working directory viper.AddConfigPath(home) - viper.AddConfigPath("$HOME/.config/qbt") // call multiple times to add many search paths + viper.AddConfigPath(filepath.Join(home, ".config", "qbt")) // windows path + viper.AddConfigPath("$HOME/.config/qbt") } if err := viper.ReadInConfig(); err != nil { - if ferr, ok := err.(*viper.ConfigFileNotFoundError); ok { + var ferr *viper.ConfigFileNotFoundError + if errors.As(err, &ferr) { fmt.Printf("config file not found: err %q\n", ferr) } else { - fmt.Println("Could not read config file:", err) + fmt.Printf("could not read config: err %q\n", err) } os.Exit(1) } From 19bd7296f06e404f4c658dbd76e6efaabfcf9702 Mon Sep 17 00:00:00 2001 From: rok Date: Tue, 20 Aug 2024 00:54:47 +0900 Subject: [PATCH 19/21] feat: add recheck command (#93) * feat: add recheck command * check if file exists before globbing fixes #89 * fix: revert changes on torrent_add * fix: command helper text * docs: add recheck command --------- Co-authored-by: Ludvig Lundgren --- README.md | 22 ++++++++++++++ cmd/torrent.go | 1 + cmd/torrent_recheck.go | 69 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 cmd/torrent_recheck.go diff --git a/README.md b/README.md index 0af1f39..fa3abbe 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ A cli to manage qBittorrent. Add torrents, categories, tags, reannounce and impo * [List](#list-2) * [Pause](#pause) * [Reannounce](#reannounce) + * [Recheck](#recheck) * [Remove](#remove) * [Resume](#resume) * [Tracker](#tracker) @@ -806,6 +807,27 @@ Global Flags: --config string config file (default is $HOME/.config/qbt/.qbt.toml) ``` +### Recheck + +```text +Rechecks torrents indicated by hash(es). + +Usage: + qbt torrent recheck [flags] + +Examples: + qbt torrent recheck --hashes HASH + qbt torrent recheck --hashes HASH1,HASH2 + + +Flags: + --hashes strings Add hashes as comma separated list + -h, --help help for recheck + +Global Flags: + --config string config file (default is $HOME/.config/qbt/.qbt.toml) +``` + ### Remove ```text diff --git a/cmd/torrent.go b/cmd/torrent.go index cb5c656..a85f50a 100644 --- a/cmd/torrent.go +++ b/cmd/torrent.go @@ -21,6 +21,7 @@ func RunTorrent() *cobra.Command { command.AddCommand(RunTorrentList()) command.AddCommand(RunTorrentPause()) command.AddCommand(RunTorrentReannounce()) + command.AddCommand(RunTorrentRecheck()) command.AddCommand(RunTorrentRemove()) command.AddCommand(RunTorrentResume()) command.AddCommand(RunTorrentTracker()) diff --git a/cmd/torrent_recheck.go b/cmd/torrent_recheck.go new file mode 100644 index 0000000..82b23c8 --- /dev/null +++ b/cmd/torrent_recheck.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "fmt" + "log" + "os" + + "github.com/ludviglundgren/qbittorrent-cli/internal/config" + + "github.com/autobrr/go-qbittorrent" + "github.com/spf13/cobra" +) + +// RunTorrentRecheck cmd to recheck torrents +func RunTorrentRecheck() *cobra.Command { + var ( + hashes []string + ) + + var command = &cobra.Command{ + Use: "recheck", + Short: "Recheck specified torrent(s)", + Long: `Rechecks torrents indicated by hash(es).`, + Example: ` qbt torrent recheck --hashes HASH + qbt torrent recheck --hashes HASH1,HASH2 +`, + } + + command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Add hashes as comma separated list") + + command.Run = func(cmd *cobra.Command, args []string) { + if len(hashes) == 0 { + log.Println("No torrents found to recheck") + return + } + + config.InitConfig() + + qbtSettings := qbittorrent.Config{ + Host: config.Qbit.Addr, + Username: config.Qbit.Login, + Password: config.Qbit.Password, + BasicUser: config.Qbit.BasicUser, + BasicPass: config.Qbit.BasicPass, + } + + qb := qbittorrent.NewClient(qbtSettings) + + ctx := cmd.Context() + + if err := qb.LoginCtx(ctx); err != nil { + fmt.Fprintf(os.Stderr, "connection failed: %v\n", err) + os.Exit(1) + } + + err := batchRequests(hashes, func(start, end int) error { + return qb.RecheckCtx(ctx, hashes[start:end]) + }) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not recheck torrents: %v\n", err) + os.Exit(1) + return + } + + log.Printf("torrent(s) successfully recheckd") + } + + return command +} From 09a160a7292d997d97b8a8478633f36cfcaac42b Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Mon, 19 Aug 2024 19:55:04 +0200 Subject: [PATCH 20/21] feat: validate hashes (#98) * feat: validate hashes * chore: update .gitignore --- .gitignore | 3 ++- cmd/torrent_category.go | 10 ++++++++++ cmd/torrent_list.go | 8 ++++++++ cmd/torrent_pause.go | 10 +++++++++- cmd/torrent_recheck.go | 8 +++++++- cmd/torrent_remove.go | 8 ++++++++ cmd/torrent_resume.go | 8 ++++++++ cmd/torrent_tracker.go | 43 ++++++++++++++++++++++++++--------------- pkg/utils/utils.go | 25 ++++++++++++++++++++++++ pkg/utils/utils_test.go | 28 +++++++++++++++++++++++++++ 10 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 pkg/utils/utils.go create mode 100644 pkg/utils/utils_test.go diff --git a/.gitignore b/.gitignore index 20b2e2a..57ba287 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ .DS_Store .qbt.toml dist -bin \ No newline at end of file +bin +testdata \ No newline at end of file diff --git a/cmd/torrent_category.go b/cmd/torrent_category.go index b5d1331..f536a3e 100644 --- a/cmd/torrent_category.go +++ b/cmd/torrent_category.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" "strings" @@ -54,6 +55,15 @@ func RunTorrentCategorySet() *cobra.Command { command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Torrent hashes, as comma separated list") command.RunE = func(cmd *cobra.Command, args []string) error { + if len(hashes) == 0 { + log.Println("No hashes supplied!") + } + + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + config.InitConfig() qbtSettings := qbittorrent.Config{ diff --git a/cmd/torrent_list.go b/cmd/torrent_list.go index 5156c93..1566367 100644 --- a/cmd/torrent_list.go +++ b/cmd/torrent_list.go @@ -3,6 +3,7 @@ package cmd import ( "encoding/json" "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" "strings" @@ -39,6 +40,13 @@ func RunTorrentList() *cobra.Command { command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Filter by hashes. Separated by comma: \"hash1,hash2\".") command.Run = func(cmd *cobra.Command, args []string) { + if len(hashes) > 0 { + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + } + config.InitConfig() qbtSettings := qbittorrent.Config{ diff --git a/cmd/torrent_pause.go b/cmd/torrent_pause.go index 90709c6..5436b92 100644 --- a/cmd/torrent_pause.go +++ b/cmd/torrent_pause.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" @@ -30,6 +31,13 @@ func RunTorrentPause() *cobra.Command { command.Flags().BoolVar(&names, "names", false, "Provided arguments will be read as torrent names") command.Run = func(cmd *cobra.Command, args []string) { + if len(hashes) > 0 { + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + } + config.InitConfig() qbtSettings := qbittorrent.Config{ @@ -54,7 +62,7 @@ func RunTorrentPause() *cobra.Command { } if len(hashes) == 0 { - log.Printf("No torrents found to pause with provided search terms") + log.Printf("No torrents found to pause with provided hashes. Use --all to pause all torrents.") return } diff --git a/cmd/torrent_recheck.go b/cmd/torrent_recheck.go index 82b23c8..801e34a 100644 --- a/cmd/torrent_recheck.go +++ b/cmd/torrent_recheck.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" @@ -34,6 +35,11 @@ func RunTorrentRecheck() *cobra.Command { return } + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + config.InitConfig() qbtSettings := qbittorrent.Config{ @@ -53,7 +59,7 @@ func RunTorrentRecheck() *cobra.Command { os.Exit(1) } - err := batchRequests(hashes, func(start, end int) error { + err = batchRequests(hashes, func(start, end int) error { return qb.RecheckCtx(ctx, hashes[start:end]) }) if err != nil { diff --git a/cmd/torrent_remove.go b/cmd/torrent_remove.go index 195c846..350fecd 100644 --- a/cmd/torrent_remove.go +++ b/cmd/torrent_remove.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" @@ -40,6 +41,13 @@ func RunTorrentRemove() *cobra.Command { command.Flags().StringSliceVar(&excludeTags, "exclude-tags", []string{}, "Exclude torrents with provided tags") command.Run = func(cmd *cobra.Command, args []string) { + if len(hashes) > 0 { + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + } + config.InitConfig() qbtSettings := qbittorrent.Config{ diff --git a/cmd/torrent_resume.go b/cmd/torrent_resume.go index c6a91f0..9d3e3c4 100644 --- a/cmd/torrent_resume.go +++ b/cmd/torrent_resume.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" "time" @@ -29,6 +30,13 @@ func RunTorrentResume() *cobra.Command { command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Add hashes as comma separated list") command.Run = func(cmd *cobra.Command, args []string) { + if len(hashes) > 0 { + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + } + config.InitConfig() qbtSettings := qbittorrent.Config{ diff --git a/cmd/torrent_tracker.go b/cmd/torrent_tracker.go index 2213303..d2ccaa5 100644 --- a/cmd/torrent_tracker.go +++ b/cmd/torrent_tracker.go @@ -44,6 +44,9 @@ func RunTorrentTrackerEdit() *cobra.Command { command.Flags().StringVar(&oldURL, "old", "", "Old tracker URL to replace") command.Flags().StringVar(&newURL, "new", "", "New tracker URL") + command.MarkFlagRequired("old") + command.MarkFlagRequired("new") + command.RunE = func(cmd *cobra.Command, args []string) error { config.InitConfig() @@ -64,31 +67,39 @@ func RunTorrentTrackerEdit() *cobra.Command { os.Exit(1) } - if dry { - log.Printf("dry-run: successfully updated tracker on torrents\n") + torrents, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{}) + if err != nil { + log.Fatalf("could not get torrents err: %q\n", err) + } - return nil - } else { - torrents, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{}) - if err != nil { - log.Fatalf("could not get torrents err: %q\n", err) + var torrentsToUpdate []qbittorrent.Torrent + + for _, torrent := range torrents { + if strings.Contains(torrent.Tracker, oldURL) { + torrentsToUpdate = append(torrentsToUpdate, torrent) } + } - matches := 0 + if len(torrentsToUpdate) == 0 { + log.Printf("found no torrents with tracker %q\n", oldURL) + return nil + } + + for i, torrent := range torrentsToUpdate { + if dry { + log.Printf("dry-run: [%d/%d] updating tracker for torrent %s %q\n", i+1, len(torrentsToUpdate), torrent.Hash, torrent.Name) - for _, torrent := range torrents { - if strings.Contains(torrent.Tracker, oldURL) { - if err := qb.EditTrackerCtx(ctx, torrent.Hash, torrent.Tracker, newURL); err != nil { - log.Fatalf("could not edit tracker for torrent: %s\n", torrent.Hash) - } + } else { + log.Printf("[%d/%d] updating tracker for torrent %s %q\n", i+1, len(torrentsToUpdate), torrent.Hash, torrent.Name) - matches++ + if err := qb.EditTrackerCtx(ctx, torrent.Hash, torrent.Tracker, newURL); err != nil { + log.Fatalf("could not edit tracker for torrent: %s\n", torrent.Hash) } } - - log.Printf("successfully updated tracker for (%d) torrents\n", matches) } + log.Printf("successfully updated tracker for (%d) torrents\n", len(torrentsToUpdate)) + return nil } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 0000000..eb14570 --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,25 @@ +package utils + +import ( + "fmt" + "regexp" + "strings" +) + +var hashRegex = regexp.MustCompile("^[a-fA-F0-9]{40}$") + +func ValidateHash(hashes []string) error { + var invalid []string + + for _, hash := range hashes { + if !hashRegex.MatchString(hash) { + invalid = append(invalid, hash) + } + } + + if len(invalid) > 0 { + return fmt.Errorf("invalid hashes: %s", strings.Join(invalid, ",")) + } + + return nil +} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go new file mode 100644 index 0000000..331759a --- /dev/null +++ b/pkg/utils/utils_test.go @@ -0,0 +1,28 @@ +package utils + +import "testing" + +func TestValidateHash(t *testing.T) { + type args struct { + hashes []string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + {name: "ok", args: args{hashes: []string{"6957bf5272f5b994132458a557864e3ea747489f"}}, want: true, wantErr: false}, + {name: "invalid", args: args{hashes: []string{"6957bf5272f5b994132458a557864e3ea747489"}}, want: false, wantErr: true}, + {name: "invalid_2", args: args{hashes: []string{"11111"}}, want: false, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateHash(tt.args.hashes) + if (err != nil) != tt.wantErr { + t.Errorf("ValidateHash() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} From 4631210bf1a7b3b82398872ea21dbb5108fee9a3 Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Mon, 19 Aug 2024 23:04:54 +0200 Subject: [PATCH 21/21] fix(torrent): improve glob detection (#99) * fix: check if file exists before glob * fix: check if arg is glob pattern --- cmd/torrent_add.go | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/cmd/torrent_add.go b/cmd/torrent_add.go index b11d7b9..02bd468 100644 --- a/cmd/torrent_add.go +++ b/cmd/torrent_add.go @@ -7,6 +7,7 @@ import ( "log" "os" "path/filepath" + "runtime" "strconv" "strings" "time" @@ -153,9 +154,21 @@ func RunTorrentAdd() *cobra.Command { log.Printf("successfully added torrent from magnet: %s %s\n", filePath, hash) return } else { - files, err := filepath.Glob(filePath) - if err != nil { - log.Fatalf("could not find files matching: %s err: %q\n", filePath, err) + var files []string + var err error + + if IsGlobPattern(filePath) { + files, err = filepath.Glob(filePath) + if err != nil { + log.Fatalf("could not find files matching: %s err: %q\n", filePath, err) + } + } else { + _, err := os.Lstat(filePath) + if err != nil { + log.Fatalf("could not stat file: %q\n", err) + } + + files = []string{filePath} } if len(files) == 0 { @@ -219,6 +232,16 @@ func RunTorrentAdd() *cobra.Command { return command } +// IsGlobPattern reports whether path contains any of the magic characters +// recognized by Match. +func IsGlobPattern(path string) bool { + magicChars := `*?[` + if runtime.GOOS != "windows" { + magicChars = `*?[\` + } + return strings.ContainsAny(path, magicChars) +} + func checkTrackerStatus(ctx context.Context, qb *qbittorrent.Client, removeStalled bool, hash string) error { announceOK := false attempts := 0