Skip to content

Commit

Permalink
experimental relabel cross-seed support with hardlinks
Browse files Browse the repository at this point in the history
  • Loading branch information
SweetMnM committed Dec 10, 2022
1 parent 2cecd67 commit 604e7a8
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 15 deletions.
16 changes: 15 additions & 1 deletion client/deluge.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ func (c *Deluge) Connect() error {
return nil
}

func (c *Deluge) LoadLabelPathMap() error {
// @TODO: implement
return nil
}

func (c *Deluge) LabelPathMap() map[string]string {
return nil
}

func (c *Deluge) GetTorrents() (map[string]config.Torrent, error) {
// retrieve torrents from client
c.log.Tracef("Retrieving torrents...")
Expand Down Expand Up @@ -227,7 +236,12 @@ func (c *Deluge) RemoveTorrent(hash string, deleteData bool) (bool, error) {
return true, nil
}

func (c *Deluge) SetTorrentLabel(hash string, label string) error {
func (c *Deluge) SetTorrentLabel(hash string, label string, hardlink bool) error {
// hardlink behaviour currently not tested for deluge
if hardlink {
return fmt.Errorf("hardlink relabeling not supported for deluge (yet)")
}

// set label
if err := c.client.SetTorrentLabel(hash, label); err != nil {
return fmt.Errorf("set torrent label: %v: %w", label, err)
Expand Down
4 changes: 3 additions & 1 deletion client/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ type Interface interface {
Connect() error
GetTorrents() (map[string]config.Torrent, error)
RemoveTorrent(string, bool) (bool, error)
SetTorrentLabel(string, string) error
SetTorrentLabel(hash string, label string, hardlink bool) error
GetCurrentFreeSpace(string) (int64, error)
AddFreeSpace(int64)
GetFreeSpace() float64
LoadLabelPathMap() error
LabelPathMap() map[string]string

ShouldIgnore(*config.Torrent) (bool, error)
ShouldRemove(*config.Torrent) (bool, error)
Expand Down
87 changes: 85 additions & 2 deletions client/qbittorrent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package client
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
Expand Down Expand Up @@ -31,6 +32,9 @@ type QBittorrent struct {
clientType string
client *qbittorrent.Client

// need to be loaded by LoadLabelPathMap
labelPathMap map[string]string

// set by cmd handler
freeSpaceGB float64
freeSpaceSet bool
Expand Down Expand Up @@ -90,6 +94,33 @@ func (c *QBittorrent) Connect() error {
return nil
}

func (c *QBittorrent) LoadLabelPathMap() error {
p, err := c.client.Application.GetAppPreferences()
if err != nil {
return fmt.Errorf("get app preferences: %w", err)
}

cats, err := c.client.Torrent.GetCategories()
if err != nil {
return fmt.Errorf("get categories: %w", err)
}

c.labelPathMap = make(map[string]string)
for _, cat := range cats {
if cat.SavePath != "" {
c.labelPathMap[cat.Name] = cat.SavePath
} else {
c.labelPathMap[cat.Name] = filepath.Join(p.SavePath, cat.Name)
}
}

return nil
}

func (c *QBittorrent) LabelPathMap() map[string]string {
return c.labelPathMap
}

func (c *QBittorrent) GetTorrents() (map[string]config.Torrent, error) {
// retrieve torrents from client
c.log.Tracef("Retrieving torrents...")
Expand Down Expand Up @@ -228,13 +259,65 @@ func (c *QBittorrent) RemoveTorrent(hash string, deleteData bool) (bool, error)
return true, nil
}

func (c *QBittorrent) SetTorrentLabel(hash string, label string) error {
func (c *QBittorrent) SetTorrentLabel(hash string, label string, hardlink bool) error {
if hardlink {
// get label path
lp := c.labelPathMap[label]
if lp == "" {
return fmt.Errorf("label path not found for label %v", label)
}

// get torrent details
td, err := c.client.Torrent.GetProperties(hash)
if err != nil {
return fmt.Errorf("get torrent properties: %w", err)
}

if filepath.Clean(td.SavePath) != filepath.Clean(lp) {
// get torrent files
tf, err := c.client.Torrent.GetContents(hash)
if err != nil {
return fmt.Errorf("get torrent files: %w", err)
}

for _, f := range tf {
source := filepath.Join(td.SavePath, f.Name)
target := filepath.Join(lp, f.Name)
if _, err := os.Stat(source); err != nil {
return fmt.Errorf("stat file '%v': %w", target, err)
}

// create target directory
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return fmt.Errorf("create target directory: %w", err)
}

// link
if err := os.Link(source, target); err != nil {
return fmt.Errorf("create hardlink for '%v': %w", f.Name, err)
}
}
}

// if just setting category and letting autotmm move
// qbit force moves the files, overwriting existing files
// manually settings location, and then setting category works
// and causes qbit to recheck instead of move
if err := c.client.Torrent.SetAutomaticManagement([]string{hash}, false); err != nil {
return fmt.Errorf("set automatic management: %w", err)
}
if err := c.client.Torrent.SetLocations([]string{hash}, lp); err != nil {
return fmt.Errorf("set location: %w", err)
}
}

// set label
if err := c.client.Torrent.SetCategories([]string{hash}, label); err != nil {
return fmt.Errorf("set torrent label: %v: %w", label, err)
}

if c.EnableAutoTmmAfterRelabel {
// enable autotmm
if c.EnableAutoTmmAfterRelabel && !hardlink {
if err := c.client.Torrent.SetAutomaticManagement([]string{hash}, true); err != nil {
return fmt.Errorf("enable autotmm: %w", err)
}
Expand Down
23 changes: 16 additions & 7 deletions cmd/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,22 +111,31 @@ func relabelEligibleTorrents(log *logrus.Entry, c client.Interface, torrents map
continue
}

hardlink := false
if !tfm.IsUnique(t) {
// torrent file is not unique, files are contained within another torrent
// so we cannot safely change the label in-case of auto move
nonUniqueTorrents++
log.Warnf("Skipping non unique torrent | Name: %s / Label: %s / Tags: %s / Tracker: %s", t.Name, t.Label, strings.Join(t.Tags, ", "), t.TrackerName)
continue
if !flagExperimentalRelabelForCrossSeeds {
// torrent file is not unique, files are contained within another torrent
// so we cannot safely change the label in-case of auto move
nonUniqueTorrents++
log.Warnf("Skipping non unique torrent | Name: %s / Label: %s / Tags: %s / Tracker: %s", t.Name, t.Label, strings.Join(t.Tags, ", "), t.TrackerName)
continue
}

hardlink = true
}

// relabel
log.Info("-----")
log.Infof("Relabeling: %q - %s", t.Name, label)
if hardlink {
log.Infof("Relabeling: %q - %s | with hardlinks to: %q", t.Name, label, c.LabelPathMap()[label])
} else {
log.Infof("Relabeling: %q - %s", t.Name, label)
}
log.Infof("Ratio: %.3f / Seed days: %.3f / Seeds: %d / Label: %s / Tags: %s / Tracker: %s / "+
"Tracker Status: %q", t.Ratio, t.SeedingDays, t.Seeds, t.Label, strings.Join(t.Tags, ", "), t.TrackerName, t.TrackerStatus)

if !flagDryRun {
if err := c.SetTorrentLabel(t.Hash, label); err != nil {
if err := c.SetTorrentLabel(t.Hash, label, hardlink); err != nil {
log.WithError(err).Fatalf("Failed relabeling torrent: %+v", t)
errorRelabelTorrents++
continue
Expand Down
5 changes: 5 additions & 0 deletions cmd/relabel.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ var relabelCmd = &cobra.Command{
}
}

// load client label path map
if err := c.LoadLabelPathMap(); err != nil {
log.WithError(err).Fatal("Failed loading label path map")
}

// retrieve torrents
torrents, err := c.GetTorrents()
if err != nil {
Expand Down
11 changes: 7 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package cmd

import (
"fmt"
"os"
"path/filepath"

"github.com/l3uddz/tqm/runtime"
"github.com/l3uddz/tqm/stringutils"
"github.com/l3uddz/tqm/tracker"
"os"
"path/filepath"

"github.com/l3uddz/tqm/config"
"github.com/l3uddz/tqm/logger"
Expand All @@ -22,8 +23,9 @@ var (
flagConfigFolder = config.GetDefaultConfigDirectory("tqm", flagConfigFile)
flagLogFile = "activity.log"

flagFilterName string
flagDryRun bool
flagFilterName string
flagDryRun bool
flagExperimentalRelabelForCrossSeeds bool

// Global vars
log *logrus.Entry
Expand Down Expand Up @@ -52,6 +54,7 @@ func init() {
rootCmd.PersistentFlags().CountVarP(&flagLogLevel, "verbose", "v", "Verbose level")

rootCmd.PersistentFlags().BoolVar(&flagDryRun, "dry-run", false, "Dry run mode")
rootCmd.PersistentFlags().BoolVar(&flagExperimentalRelabelForCrossSeeds, "experimental-relabel", false, "Enable experimental relabeling for cross-seeded torrents, using hardlinks (only qbit for now")
}

func initCore(showAppInfo bool) {
Expand Down

0 comments on commit 604e7a8

Please sign in to comment.