-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(export): export torrents by category (#35)
* feat(export): export torrents by category * feat: use pipe as replace separator * fix(import): traling slash path handling (#41) * chore: update go mod version * fix: trailing slash in paths * feat(export): export torrents by category * feat: use pipe as replace separator * chore: improve error handling and logs * feat: check both torrent and fastresume
- Loading branch information
1 parent
868f3d2
commit d1332f6
Showing
5 changed files
with
185 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package cmd | ||
|
||
import ( | ||
"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") | ||
command.MarkFlagRequired("categories") | ||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error { | ||
// get torrents from client by categories | ||
config.InitConfig() | ||
|
||
qbtSettings := qbittorrent.Settings{ | ||
Hostname: config.Qbit.Host, | ||
Port: config.Qbit.Port, | ||
Username: config.Qbit.Login, | ||
Password: config.Qbit.Password, | ||
} | ||
|
||
qb := qbittorrent.NewClient(qbtSettings) | ||
if err := qb.Login(); err != nil { | ||
return errors.Wrapf(err, "connection failed") | ||
} | ||
|
||
hashes := map[string]struct{}{} | ||
|
||
for _, category := range categories { | ||
torrents, err := qb.GetTorrentsByCategory(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)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package cmd | ||
|
||
import "testing" | ||
|
||
func Test_processHashes(t *testing.T) { | ||
type args struct { | ||
sourceDir string | ||
exportDir string | ||
hashes map[string]struct{} | ||
replace []string | ||
dry 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": {}, | ||
}, | ||
replace: []string{"https://academictorrents.com/announce.php|https://test.com/announce.php?test"}, | ||
dry: 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.replace, tt.args.dry); (err != nil) != tt.wantErr { | ||
t.Errorf("processHashes() error = %v, wantErr %v", err, tt.wantErr) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+1.4 KB
test/config/qBittorrent/BT_backup/5ba4939a00a9b21629a0ad7d376898b768d997a3.fastresume
Binary file not shown.
1 change: 1 addition & 0 deletions
1
test/config/qBittorrent/BT_backup/5ba4939a00a9b21629a0ad7d376898b768d997a3.torrent
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
d8:announce41:https://academictorrents.com/announce.php13:announce-listll41:https://academictorrents.com/announce.phpel34:udp://tracker.coppersurfer.tk:6969el42:udp://tracker.opentrackr.org:1337/announceel44:udp://tracker.openbittorrent.com:80/announceee10:created by23:py3createtorrent v0.9.513:creation datei1398649389e4:infod6:lengthi42310e4:name44:Scikit-learn: Machine Learning in Python.pdf12:piece lengthi16384e6:pieces60:l�3��M]d=��"T[]�,*ƫ9ˢ:��ho7Bd?v�C��٩`Ƨ��%}��h��e8:url-listl65:http://www.jmlr.org/papers/volume12/pedregosa11a/pedregosa11a.pdf93:https://web.archive.org/web/http://www.jmlr.org/papers/volume12/pedregosa11a/pedregosa11a.pdfee |