From 3c4c6b4ea38fd7c434c3fd56f976d9368c9daf69 Mon Sep 17 00:00:00 2001 From: xel86 Date: Mon, 7 Feb 2022 13:51:25 -0500 Subject: [PATCH] refactor: add and batch pause resume (#33) --- cmd/pause.go | 72 +++++++++++++++++++++++++++++++++++--- cmd/remove.go | 67 ++++++++++++++++++----------------- cmd/resume.go | 72 +++++++++++++++++++++++++++++++++++--- pkg/qbittorrent/methods.go | 62 ++++++++++++++++++++++++-------- 4 files changed, 218 insertions(+), 55 deletions(-) diff --git a/cmd/pause.go b/cmd/pause.go index 4258a7f..442376c 100644 --- a/cmd/pause.go +++ b/cmd/pause.go @@ -3,6 +3,8 @@ package cmd import ( "fmt" "os" + "log" + "time" "github.com/ludviglundgren/qbittorrent-cli/internal/config" "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" @@ -12,12 +14,34 @@ import ( // RunPause cmd to pause torrents func RunPause() *cobra.Command { + var ( + pauseAll bool + hashes bool + names bool + ) + var command = &cobra.Command{ Use: "pause", - Short: "Pause all torrents", - Long: `Pause all torrents`, + 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{ Hostname: config.Qbit.Host, @@ -33,11 +57,51 @@ func RunPause() *cobra.Command { os.Exit(1) } - err = qb.Pause(nil) + if pauseAll { + qb.Pause([]string{"all"}) + if 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(args, hashes, names) if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not pause torrents %v\n", err) + 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) + } + + qb.Pause(hashesToPause[i:j]) + if 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/remove.go b/cmd/remove.go index 8851a6d..c3412f1 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -3,8 +3,8 @@ package cmd import ( "fmt" "os" - "strings" "log" + "time" "github.com/ludviglundgren/qbittorrent-cli/internal/config" "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" @@ -58,48 +58,49 @@ func RunRemove() *cobra.Command { fmt.Fprintf(os.Stderr, "ERROR: connection failed: %v\n", err) os.Exit(1) } + + if removeAll { + qb.DeleteTorrents([]string{"all"}, deleteFiles) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not delete torrents: %v\n", err) + os.Exit(1) + } + + log.Printf("All torrents removed successfully") + return + } - torrents, err := qb.GetTorrents() + foundTorrents, err := qb.GetTorrentsByPrefixes(args, hashes, names) if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not retrieve torrents: %v\n", err) + fmt.Fprintf(os.Stderr, "ERROR: failed to retrieve torrents: %v\n", err) os.Exit(1) } - foundHashes := map[string]bool{} - for _, torrent := range torrents { - if removeAll { - foundHashes[torrent.Hash] = true - continue - } + hashesToRemove := []string{} + for _, torrent := range foundTorrents { + hashesToRemove = append(hashesToRemove, torrent.Hash) + } - if hashes { - for _, targetHash := range args { - if strings.HasPrefix(torrent.Hash, targetHash) { - foundHashes[torrent.Hash] = true - break - } - } - } + if len(hashesToRemove) < 1 { + log.Printf("No torrents found to remove with provided search terms") + return + } - if names { - for _, targetName := range args { - if strings.HasPrefix(torrent.Name, targetName) { - foundHashes[torrent.Hash] = true - break - } - } + // 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) } - } - hashesToRemove := []string{} - for hash := range foundHashes { - hashesToRemove = append(hashesToRemove, hash) - } + qb.DeleteTorrents(hashesToRemove[i:j], deleteFiles) + if err != nil { + fmt.Fprintf(os.Stderr, "ERROR: could not delete torrents: %v\n", err) + os.Exit(1) + } - err = qb.DeleteTorrents(hashesToRemove, deleteFiles) - if 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") diff --git a/cmd/resume.go b/cmd/resume.go index f96c088..92cb40d 100644 --- a/cmd/resume.go +++ b/cmd/resume.go @@ -3,6 +3,8 @@ package cmd import ( "fmt" "os" + "log" + "time" "github.com/ludviglundgren/qbittorrent-cli/internal/config" "github.com/ludviglundgren/qbittorrent-cli/pkg/qbittorrent" @@ -12,13 +14,34 @@ import ( // RunResume cmd to resume torrents func RunResume() *cobra.Command { + var ( + resumeAll bool + hashes bool + names bool + ) + var command = &cobra.Command{ Use: "resume", - Short: "Resume all torrents", - Long: `Resume all torrents`, + 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{ Hostname: config.Qbit.Host, @@ -34,11 +57,52 @@ func RunResume() *cobra.Command { os.Exit(1) } - err = qb.Resume(nil) + + if resumeAll { + qb.Resume([]string{"all"}) + if 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(args, hashes, names) if err != nil { - fmt.Fprintf(os.Stderr, "ERROR: could not resume torrents %v\n", err) + 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) + } + + qb.Resume(hashesToResume[i:j]) + if 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/pkg/qbittorrent/methods.go b/pkg/qbittorrent/methods.go index 0b2e023..4ee99af 100644 --- a/pkg/qbittorrent/methods.go +++ b/pkg/qbittorrent/methods.go @@ -141,6 +141,46 @@ func (c *Client) GetTorrentByHash(hash string) (string, error) { return string(data), nil } +// Search for torrents using provided prefixes; checks against either hashes, names, or both +func (c *Client) GetTorrentsByPrefixes(terms []string, hashes bool, names bool) ([]Torrent, error) { + torrents, err := c.GetTorrents() + if err != nil { + log.Fatalf("ERROR: could not retrieve torrents: %v\n", err) + } + + matchedTorrents := map[Torrent]bool{} + for _, torrent := range torrents { + if hashes { + for _, targetHash := range terms { + if strings.HasPrefix(torrent.Hash, targetHash) { + matchedTorrents[torrent] = true + break + } + } + + if matchedTorrents[torrent] { + continue + } + } + + if names { + for _, targetName := range terms { + if strings.HasPrefix(torrent.Name, targetName) { + matchedTorrents[torrent] = true + break + } + } + } + } + + var foundTorrents []Torrent + for torrent := range matchedTorrents { + foundTorrents = append(foundTorrents, torrent) + } + + return foundTorrents, nil +} + func (c *Client) GetTorrentTrackers(hash string) ([]TorrentTracker, error) { var trackers []TorrentTracker @@ -253,15 +293,12 @@ func (c *Client) ReAnnounceTorrents(hashes []string) error { func (c *Client) Pause(hashes []string) error { v := url.Values{} - encodedHashes := "all" - if len(hashes) > 0 { - // Add hashes together with | separator - encodedHashes = strings.Join(hashes, "|") - } + // Add hashes together with | separator + hv := strings.Join(hashes, "|") + v.Add("hashes", hv) - v.Add("hashes", encodedHashes) - encodedHashes = v.Encode() + encodedHashes := v.Encode() resp, err := c.get("torrents/pause?"+encodedHashes, nil) if err != nil { @@ -277,15 +314,12 @@ func (c *Client) Pause(hashes []string) error { func (c *Client) Resume(hashes []string) error { v := url.Values{} - encodedHashes := "all" - if len(hashes) > 0 { - // Add hashes together with | separator - encodedHashes = strings.Join(hashes, "|") - } + // Add hashes together with | separator + hv := strings.Join(hashes, "|") + v.Add("hashes", hv) - v.Add("hashes", encodedHashes) - encodedHashes = v.Encode() + encodedHashes := v.Encode() resp, err := c.get("torrents/resume?"+encodedHashes, nil) if err != nil {