mirror of
https://github.com/rumanzo/bt2qbt.git
synced 2024-11-08 20:02:30 +01:00
Modern qBittorrent doesn't save tags to config file. It uses categories.json instead.
Rewritted handleLabels function. Tests
This commit is contained in:
parent
46103167ef
commit
e166194b56
@ -52,8 +52,8 @@ func main() {
|
||||
}
|
||||
|
||||
color.Green("It will be performed processing from directory %v to directory %v\n", opts.BitDir, opts.QBitDir)
|
||||
color.HiRed("Check that the qBittorrent is turned off and the directory %v and config %v is backed up.\n",
|
||||
opts.QBitDir, opts.Config)
|
||||
color.HiRed("Check that the qBittorrent is turned off and the directory %v and %v is backed up.\n",
|
||||
opts.QBitDir, opts.Categories)
|
||||
color.HiRed("Check that you previously disable option \"Append .!ut/.!bt to incomplete files\" in preferences of uTorrent/Bittorrent \n\n")
|
||||
fmt.Println("Press Enter to start")
|
||||
fmt.Scanln()
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
type Opts struct {
|
||||
BitDir string `short:"s" long:"source" description:"Source directory that contains resume.dat and torrents files"`
|
||||
QBitDir string `short:"d" long:"destination" description:"Destination directory BT_backup (as default)"`
|
||||
Config string `short:"c" long:"config" description:"qBittorrent config file (for write tags)"`
|
||||
Categories string `short:"c" long:"categories" description:"path to qBittorrent categories.json file (for write tags)"`
|
||||
WithoutLabels bool `long:"without-labels" description:"Do not export/import labels"`
|
||||
WithoutTags bool `long:"without-tags" description:"Do not export/import tags"`
|
||||
SearchPaths []string `short:"t" long:"search" description:"Additional search path for torrents files\n Example: --search='/mnt/olddisk/savedtorrents' --search='/mnt/olddisk/workstorrents'"`
|
||||
@ -29,7 +29,7 @@ func PrepareOpts() *Opts {
|
||||
switch OS := runtime.GOOS; OS {
|
||||
case "windows":
|
||||
opts.BitDir = filepath.Join(os.Getenv("APPDATA"), "uTorrent")
|
||||
opts.Config = filepath.Join(os.Getenv("APPDATA"), "qBittorrent", "qBittorrent.ini")
|
||||
opts.Categories = filepath.Join(os.Getenv("APPDATA"), "qBittorrent", "categories.json")
|
||||
opts.QBitDir = filepath.Join(os.Getenv("LOCALAPPDATA"), "qBittorrent", "BT_backup")
|
||||
case "linux":
|
||||
usr, err := user.Current()
|
||||
@ -37,7 +37,7 @@ func PrepareOpts() *Opts {
|
||||
panic(err)
|
||||
}
|
||||
opts.BitDir = "/mnt/uTorrent/"
|
||||
opts.Config = filepath.Join(usr.HomeDir, ".config", "qBittorrent", "qBittorrent.conf")
|
||||
opts.Categories = filepath.Join(usr.HomeDir, ".config", "qBittorrent", "categories.json")
|
||||
opts.QBitDir = filepath.Join(usr.HomeDir, ".local", "share", "data", "qBittorrent", "BT_backup")
|
||||
case "darwin":
|
||||
usr, err := user.Current()
|
||||
@ -45,7 +45,7 @@ func PrepareOpts() *Opts {
|
||||
panic(err)
|
||||
}
|
||||
opts.BitDir = filepath.Join(usr.HomeDir, "Library", "Application Support", "uTorrent")
|
||||
opts.Config = filepath.Join(usr.HomeDir, ".config", "qBittorrent", "qbittorrent.ini")
|
||||
opts.Categories = filepath.Join(usr.HomeDir, ".config", "qBittorrent", "categories.json")
|
||||
opts.QBitDir = filepath.Join(usr.HomeDir, "Library", "Application Support", "QBittorrent", "BT_backup")
|
||||
}
|
||||
return opts
|
||||
@ -86,12 +86,6 @@ func OptsCheck(opts *Opts) error {
|
||||
return fmt.Errorf("can't find qBittorrent folder")
|
||||
}
|
||||
|
||||
if opts.WithoutTags == false {
|
||||
if _, err := os.Stat(opts.Config); os.IsNotExist(err) {
|
||||
return fmt.Errorf("can not read qBittorrent config file. Try run and close qBittorrent if you have not done" +
|
||||
" so already, or specify the path explicitly or do not import tags")
|
||||
}
|
||||
}
|
||||
if runtime.GOOS == "linux" {
|
||||
if opts.SearchPaths == nil {
|
||||
return fmt.Errorf("on linux systems you must define search path for torrents")
|
||||
|
@ -33,7 +33,7 @@ func TestOptionsArgs(t *testing.T) {
|
||||
args: []string{
|
||||
"-s", "/dir",
|
||||
"-d", "/dir",
|
||||
"-c", "/dir/q.conf",
|
||||
"-c", "/dir/q.json",
|
||||
"-r", "dir1,dir2", "-r", "dir3,dir4",
|
||||
"--sep", "/",
|
||||
"-t", "/dir5", "-t", "/dir6/",
|
||||
@ -42,7 +42,7 @@ func TestOptionsArgs(t *testing.T) {
|
||||
expected: &Opts{
|
||||
BitDir: "/dir",
|
||||
QBitDir: "/dir",
|
||||
Config: "/dir/q.conf",
|
||||
Categories: "/dir/q.json",
|
||||
Replaces: []string{"dir1,dir2", "dir3,dir4"},
|
||||
PathSeparator: "/",
|
||||
SearchPaths: []string{"/dir5", "/dir6/"},
|
||||
@ -54,7 +54,7 @@ func TestOptionsArgs(t *testing.T) {
|
||||
args: []string{
|
||||
"--source", "/dir",
|
||||
"--destination", "/dir",
|
||||
"--config", "/dir/q.conf",
|
||||
"--categories", "/dir/q.json",
|
||||
"--replace", "dir1,dir2", "-r", "dir3,dir4",
|
||||
"--sep", "/",
|
||||
"--search", "/dir5", "-t", "/dir6/",
|
||||
@ -63,7 +63,7 @@ func TestOptionsArgs(t *testing.T) {
|
||||
expected: &Opts{
|
||||
BitDir: "/dir",
|
||||
QBitDir: "/dir",
|
||||
Config: "/dir/q.conf",
|
||||
Categories: "/dir/q.json",
|
||||
Replaces: []string{"dir1,dir2", "dir3,dir4"},
|
||||
PathSeparator: "/",
|
||||
SearchPaths: []string{"/dir5", "/dir6/"},
|
||||
@ -96,7 +96,7 @@ func TestOptionsHandle(t *testing.T) {
|
||||
opts: &Opts{
|
||||
BitDir: "/dir",
|
||||
QBitDir: "/dir",
|
||||
Config: "/dir/q.conf",
|
||||
Categories: "/dir/q.json",
|
||||
Replaces: []string{"dir1,dir2", "dir3,dir4"},
|
||||
PathSeparator: "/",
|
||||
SearchPaths: []string{"/dir5", "/dir6/"},
|
||||
@ -142,7 +142,7 @@ func TestOptionsChecks(t *testing.T) {
|
||||
opts: &Opts{
|
||||
BitDir: "/dir",
|
||||
QBitDir: "/dir",
|
||||
Config: "/dir/q.conf",
|
||||
Categories: "/dir/q.json",
|
||||
Replaces: []string{"dir1,dir2", "dir3,dir4"},
|
||||
PathSeparator: "/",
|
||||
SearchPaths: []string{"/dir5", "/dir6/"},
|
||||
@ -155,7 +155,6 @@ func TestOptionsChecks(t *testing.T) {
|
||||
opts: &Opts{
|
||||
BitDir: "../../test/data",
|
||||
QBitDir: "../../test/data",
|
||||
Config: "../../test/data/testfileset.torrent",
|
||||
SearchPaths: []string{},
|
||||
},
|
||||
mustFail: false,
|
||||
@ -165,23 +164,14 @@ func TestOptionsChecks(t *testing.T) {
|
||||
opts: &Opts{
|
||||
BitDir: "/dir",
|
||||
QBitDir: "/dir",
|
||||
Config: "/dir/q.conf",
|
||||
Categories: "/dir/q.json",
|
||||
Replaces: []string{"dir1,dir2,dir4", "dir4"},
|
||||
SearchPaths: []string{"/dir5", "/dir6/"},
|
||||
},
|
||||
mustFail: true,
|
||||
},
|
||||
{
|
||||
name: "004 Must fail do not exists config test",
|
||||
opts: &Opts{
|
||||
BitDir: "../../test/data",
|
||||
QBitDir: "../../test/data",
|
||||
Config: "/dir/q.conf",
|
||||
},
|
||||
mustFail: true,
|
||||
},
|
||||
{
|
||||
name: "005 Must fail do not exists qbitdir test",
|
||||
name: "004 Must fail do not exists qbitdir test",
|
||||
opts: &Opts{
|
||||
BitDir: "../../test/data",
|
||||
QBitDir: "/dir",
|
||||
|
@ -1,50 +1,65 @@
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-ini/ini"
|
||||
"github.com/rumanzo/bt2qbt/internal/options"
|
||||
"github.com/rumanzo/bt2qbt/pkg/helpers"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ProcessLabels(opts *options.Opts, newtags []string) {
|
||||
var oldtags string
|
||||
cfg, err := ini.Load(opts.Config)
|
||||
ini.PrettyFormat = false
|
||||
ini.PrettySection = false
|
||||
if err != nil {
|
||||
fmt.Println("Can not read qBittorrent config file. Try to specify the path explicitly or do not import tags")
|
||||
time.Sleep(30 * time.Second)
|
||||
os.Exit(1)
|
||||
}
|
||||
if _, err := cfg.GetSection("BitTorrent"); err != nil {
|
||||
cfg.NewSection("BitTorrent")
|
||||
func ProcessLabels(opts *options.Opts, newtags []string) error {
|
||||
categories := map[string]map[string]string{}
|
||||
|
||||
//Dirty hack for section order. Sorry
|
||||
kv := cfg.Section("Network").KeysHash()
|
||||
cfg.DeleteSection("Network")
|
||||
cfg.NewSection("Network")
|
||||
for key, value := range kv {
|
||||
cfg.Section("Network").NewKey(key, value)
|
||||
}
|
||||
//End of dirty hack
|
||||
// check if categories is new file. If it exists it must be unmarshaled. Default categories file contains only {}
|
||||
var categoriesIsNew bool
|
||||
file, err := os.OpenFile(opts.Categories, os.O_RDWR, 0644)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
categoriesIsNew = true
|
||||
} else if err != nil {
|
||||
return errors.New(fmt.Sprintf("Unexpected error while open categories.json. Error:\n%v\n", err))
|
||||
}
|
||||
if cfg.Section("BitTorrent").HasKey("Session\\Tags") {
|
||||
oldtags = cfg.Section("BitTorrent").Key("Session\\Tags").String()
|
||||
for _, tag := range strings.Split(oldtags, ", ") {
|
||||
if exists, t := helpers.CheckExists(tag, newtags); !exists {
|
||||
newtags = append(newtags, t)
|
||||
}
|
||||
|
||||
if !categoriesIsNew {
|
||||
dataRaw, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Unexpected error while read categories.json. Error:\n%v\n", err))
|
||||
}
|
||||
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Can't close categories.json. Error:\n%v\n", err))
|
||||
}
|
||||
|
||||
err = json.Unmarshal(dataRaw, &categories)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Unexpected error while unmarshaling categories.json. Error:\n%v\n", err))
|
||||
}
|
||||
cfg.Section("BitTorrent").Key("Session\\Tags").SetValue(strings.Join(newtags, ", "))
|
||||
} else {
|
||||
cfg.Section("BitTorrent").NewKey("Session\\Tags", strings.Join(newtags, ", "))
|
||||
}
|
||||
err = cfg.SaveTo(opts.Config)
|
||||
|
||||
for _, tag := range newtags {
|
||||
if _, ok := categories[tag]; !ok { // append only if key doesn't already exist
|
||||
categories[tag] = map[string]string{"save_path": ""}
|
||||
}
|
||||
}
|
||||
|
||||
if !categoriesIsNew {
|
||||
err = os.Rename(opts.Categories, opts.Categories+".bak")
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Can't move categories.json to categories.bak. Error:\n%v\n", err))
|
||||
}
|
||||
}
|
||||
|
||||
newCategories, err := json.Marshal(categories)
|
||||
if err != nil {
|
||||
fmt.Printf("Unexpected error while save qBittorrent config.ini. Error:\n%v\n", err)
|
||||
return errors.New(fmt.Sprintf("Can't marshal categories. Error:\n%v\n", err))
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(opts.Categories, newCategories, 0644)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Can't write categories.json. Error:\n%v\n", err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
39
internal/transfer/labels_test.go
Normal file
39
internal/transfer/labels_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package transfer
|
||||
|
||||
import (
|
||||
"github.com/rumanzo/bt2qbt/internal/options"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProcessLabelsExisting(t *testing.T) {
|
||||
err := ioutil.WriteFile("../../test/categories_existing.json", []byte("{}"), 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Can't write empty categories test file. Err: %v", err.Error())
|
||||
}
|
||||
|
||||
opts := &options.Opts{Categories: "../../test/categories_existing.json"}
|
||||
err = ProcessLabels(opts, []string{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpecter error with handle categories. Err: %v", err.Error())
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
err = os.Remove(opts.Categories + ".bak")
|
||||
if err != nil {
|
||||
t.Fatalf("It must exists bak file. Err: %v", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestProcessLabelsNotExisting(t *testing.T) {
|
||||
opts := &options.Opts{Categories: "../../test/categories_not_existing.json"}
|
||||
os.Remove(opts.Categories)
|
||||
err := ProcessLabels(opts, []string{})
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
os.Remove(opts.Categories)
|
||||
})
|
||||
}
|
@ -95,7 +95,7 @@ func HandleResumeItems(opts *options.Opts, resumeItems map[string]*utorrentStruc
|
||||
if opts.WithoutTags == false {
|
||||
if resumeItem.Labels != nil {
|
||||
for _, label := range resumeItem.Labels {
|
||||
if exists, tag := helpers.CheckExists(helpers.ASCIIConvert(label), newTags); !exists {
|
||||
if exists, tag := helpers.CheckExists(label, newTags); !exists {
|
||||
newTags = append(newTags, tag)
|
||||
}
|
||||
}
|
||||
@ -125,7 +125,10 @@ func HandleResumeItems(opts *options.Opts, resumeItems map[string]*utorrentStruc
|
||||
numJob++
|
||||
}
|
||||
if opts.WithoutTags == false {
|
||||
ProcessLabels(opts, newTags)
|
||||
err := ProcessLabels(opts, newTags)
|
||||
if err != nil {
|
||||
fmt.Printf("Can't handle labels with error:\n%v\n", err)
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
log.Println("Ended")
|
||||
|
1
test/categories_existing.json
Normal file
1
test/categories_existing.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
Loading…
Reference in New Issue
Block a user