From 8827aca1926505b967d319032ade1d3744dcc9eb Mon Sep 17 00:00:00 2001 From: celogeek <65178+celogeek@users.noreply.github.com> Date: Tue, 15 Aug 2023 20:17:10 +0200 Subject: [PATCH] rsync with progress --- main.go | 62 ++++++++++++++++++++++++----------- qbittorent.go | 18 +++++++++-- rsync.go | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 21 deletions(-) create mode 100644 rsync.go diff --git a/main.go b/main.go index 8ead52b..98ca1aa 100644 --- a/main.go +++ b/main.go @@ -2,31 +2,49 @@ package main import ( "flag" + "fmt" "log" - "time" ) -type Options struct { - Qbittorent QBitTorrentOptions +type RsyncOptions struct { + Username string + Hostname string +} + +func (r *RsyncOptions) Uri(path string) string { + result := fmt.Sprintf("%s:%s", r.Hostname, path) + if r.Username == "" { + return result + } + return fmt.Sprintf("%s@%s", r.Username, result) } func main() { - options := &Options{} - flag.StringVar(&options.Qbittorent.Uri, "qbittorrent-uri", "http://localhost:8080", "URI of qbittorrent") - flag.StringVar(&options.Qbittorent.Username, "qbittorrent-username", "", "Username of qbittorrent") - flag.StringVar(&options.Qbittorent.Password, "qbittorrent-password", "", "Password of qbittorrent") - flag.StringVar(&options.Qbittorent.SyncTag, "qbittorrent-sync-tag", "Sync", "Tag of qbittorrent to copy") - flag.StringVar(&options.Qbittorent.SyncedTag, "qbittorrent-synced-tag", "", "Tag of qbittorrent when copy finished") + qbitoptions := &QBitTorrentOptions{} + rsyncoptions := &RsyncOptions{} + dest := "" + flag.StringVar(&qbitoptions.Uri, "qbittorrent-uri", "http://localhost:8080", "URI of qbittorrent") + flag.StringVar(&qbitoptions.Username, "qbittorrent-username", "", "Username of qbittorrent") + flag.StringVar(&qbitoptions.Password, "qbittorrent-password", "", "Password of qbittorrent") + flag.StringVar(&qbitoptions.SyncTag, "qbittorrent-sync-tag", "Sync", "Tag of qbittorrent to copy") + flag.StringVar(&qbitoptions.SyncedTag, "qbittorrent-synced-tag", "", "Tag of qbittorrent when copy finished") + flag.StringVar(&rsyncoptions.Hostname, "rsync-hostname", "", "Rsync host") + flag.StringVar(&rsyncoptions.Username, "rsync-username", "", "Rsync username") + flag.StringVar(&dest, "dest", ".", "Destination directory") flag.Parse() - if options.Qbittorent.Uri == "" || - options.Qbittorent.Username == "" || - options.Qbittorent.Password == "" || - options.Qbittorent.SyncTag == "" { + if qbitoptions.Uri == "" || + qbitoptions.Username == "" || + qbitoptions.Password == "" || + qbitoptions.SyncTag == "" { log.Fatal("missing qbittorrent parameters") } - qcli, err := NewQBittorrentCli(&options.Qbittorent) + if rsyncoptions.Hostname == "" { + log.Fatal("missing rsync parameters") + } + + qcli, err := NewQBittorrentCli(qbitoptions) if err != nil { log.Fatal(err) } @@ -38,12 +56,20 @@ func main() { } for _, t := range torrents { - for p := 0; p <= 100; p += 20 { + rtask := NewRsync( + rsyncoptions.Uri(t.Path), + dest, + func(p int) { + qcli.SetProgress(t, p) + }, + ) + + if err := rtask.Run(); err != nil { qcli.ClearTags() - qcli.SetProgress(&t, p) - time.Sleep(time.Second) + log.Fatal(err) } - qcli.SetDone(&t) + + qcli.SetDone(t) } } diff --git a/qbittorent.go b/qbittorent.go index c68ed26..2f299b6 100644 --- a/qbittorent.go +++ b/qbittorent.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "log" "strings" "github.com/go-resty/resty/v2" @@ -60,8 +61,8 @@ func (c *QBittorrentCli) Logout() error { return err } -func (c *QBittorrentCli) List() ([]Torrent, error) { - result := make([]Torrent, 0) +func (c *QBittorrentCli) List() ([]*Torrent, error) { + result := make([]*Torrent, 0) _, err := c.cli.R(). SetQueryParam("filter", "completed"). @@ -152,8 +153,19 @@ func (c *QBittorrentCli) SetProgress(t *Torrent, p int) error { return nil } + err := c.ClearTags() + if err != nil { + return err + } + + c.SetTag(t, fmt.Sprintf("Progress:%d%%", p)) + if err != nil { + return err + } + t.Progress = p - return c.SetTag(t, fmt.Sprintf("Progress:%d%%", p)) + log.Printf("Downloading [%s]: %d%%", t.Name, p) + return nil } func (c *QBittorrentCli) SetDone(t *Torrent) error { diff --git a/rsync.go b/rsync.go new file mode 100644 index 0000000..515a218 --- /dev/null +++ b/rsync.go @@ -0,0 +1,89 @@ +package main + +import ( + "bufio" + "bytes" + "os/exec" + "regexp" + "strconv" + "sync" +) + +type Rsync struct { + Source string + Destination string + OnProgress func(p int) + + progress int +} + +func NewRsync(source, destination string, onProgress func(p int)) *Rsync { + return &Rsync{ + Source: source, + Destination: destination, + OnProgress: onProgress, + progress: -1, + } +} + +func ScanCR(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + if i := bytes.IndexByte(data, '\r'); i >= 0 { + // We have a full newline-terminated line. + return i + 1, data[0:i], nil + } + // If we're at EOF, we have a final, non-terminated line. Return it. + if atEOF { + return len(data), data, nil + } + // Request more data. + return 0, nil, nil +} + +func (r *Rsync) Run() error { + cmd := exec.Command( + "rsync", + "--archive", + "--partial", + "--inplace", + "--no-inc-recursive", + "--info=progress2", + r.Source, + r.Destination, + ) + out, err := cmd.StdoutPipe() + if err != nil { + return err + } + defer out.Close() + + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + scanner := bufio.NewScanner(out) + scanner.Split(ScanCR) + progressMatch := regexp.MustCompile(`(\d+)%`) + for scanner.Scan() { + progress := scanner.Text() + if progressMatch.MatchString(progress) { + m := progressMatch.FindStringSubmatch(progress) + if p, err := strconv.Atoi(m[1]); err == nil { + r.OnProgress(p) + } + } + } + }() + + if err := cmd.Start(); err != nil { + return err + } + err = cmd.Wait() + if err != nil { + return err + } + wg.Wait() + return nil +}