Files
polaris/pkg/qbittorrent/qbittorrent.go
2025-05-07 18:16:10 +08:00

267 lines
5.2 KiB
Go

package qbittorrent
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"polaris/pkg"
"polaris/pkg/go-qbittorrent/qbt"
"polaris/pkg/utils"
"strings"
"github.com/pkg/errors"
)
const btCategory = "polaris"
type Info struct {
URL string
User string
Password string
}
type Client struct {
c *qbt.Client
category string
Info
}
func NewClient(url, user, pass string) (*Client, error) {
// connect to qbittorrent client
qb := qbt.NewClient(url)
// login to the client
loginOpts := qbt.LoginOptions{
Username: user,
Password: pass,
}
err := qb.Login(loginOpts)
if err != nil {
return nil, err
}
return &Client{c: qb, category: btCategory, Info: Info{URL: url, User: user, Password: pass}}, nil
}
func (c *Client) GetAll() ([]pkg.Torrent, error) {
tt, err := c.c.Torrents(qbt.TorrentsOptions{Category: &c.category})
if err != nil {
return nil, errors.Wrap(err, "get torrents")
}
var res []pkg.Torrent
for _, t := range tt {
t1 := &Torrent{
c: c.c,
hash: t.Hash,
//Info: c.Info,
}
res = append(res, t1)
}
return res, nil
}
func (c *Client) GetListenPort() (int, error) {
pref, err := c.c.Preferences()
if err != nil {
return 0, errors.Wrap(err, "get preferences")
}
return pref.ListenPort, nil
}
func (c *Client) SetListenPort(port int) error {
ok, err := c.c.SetPreferences(map[string]any{"listen_port": port})
if !ok || err != nil {
return errors.Wrap(err, "set preferences")
}
return nil
}
func (c *Client) Download(link, hash, dir string) (pkg.Torrent, error) {
err := c.c.DownloadLinks([]string{link}, qbt.DownloadOptions{Savepath: &dir, Category: &c.category})
if err != nil {
return nil, errors.Wrap(err, "qbt download")
}
return &Torrent{hash: hash, c: c.c}, nil
}
func NewTorrentHash(info Info, hash string) (*Torrent, error) {
c, err := NewClient(info.URL, info.User, info.Password)
if err != nil {
return nil, err
}
t := &Torrent{
c: c.c,
hash: hash,
}
if !t.Exists() {
return nil, errors.Errorf("torrent not exist: %v", hash)
}
return t, nil
}
func NewTorrent(info Info, link string) (*Torrent, error) {
magnet, err := utils.Link2Magnet(link)
if err != nil {
return nil, errors.Errorf("converting link to magnet error, link: %v, error: %v", link, err)
}
hash, err := utils.MagnetHash(magnet)
if err != nil {
return nil, err
}
return NewTorrentHash(info, hash)
}
type Torrent struct {
c *qbt.Client
hash string
//info Info
}
func (t *Torrent) GetHash() string {
return t.hash
}
func (t *Torrent) getTorrent() (*qbt.TorrentInfo, error) {
all, err := t.c.Torrents(qbt.TorrentsOptions{Hashes: []string{t.hash}})
if err != nil {
return nil, err
}
if len(all) == 0 {
return nil, fmt.Errorf("no such torrent: %v", t.hash)
}
return &all[0], nil
}
func (t *Torrent) Name() (string, error) {
dir, err := t.getTorrentBaseNameOrDir()
if err != nil { //use torrent name
qb, err := t.getTorrent()
if err != nil {
return "", err
}
return qb.Name, nil
}
return dir, nil
}
// https://github.com/qbittorrent/qBittorrent/issues/13572
func (t *Torrent) getTorrentBaseNameOrDir() (string, error) {
files, err := t.c.TorrentFiles(t.hash)
if err != nil {
return "", err
}
if len(files) == 0 {
return "", errors.Wrap(err, "no file")
}
name := files[0].Name
dir := strings.Split(name, string(os.PathSeparator))[0]
return dir, nil
}
func (t *Torrent) Progress() (int, error) {
qb, err := t.getTorrent()
if err != nil {
return 0, err
}
p := qb.Progress * 100
if p >= 100 {
return 100, nil
}
if int(p) == 100 {
return 99, nil
}
return int(p), nil
}
func (t *Torrent) Stop() error {
return t.c.Pause([]string{t.hash})
}
func (t *Torrent) Start() error {
ok, err := t.c.Resume([]string{t.hash})
if err != nil {
return err
}
if !ok {
return fmt.Errorf("status not 200")
}
return nil
}
func (t *Torrent) Remove() error {
ok, err := t.c.Delete([]string{t.hash}, true)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("status not 200")
}
return nil
}
func (t *Torrent) Exists() bool {
_, err := t.getTorrent()
return err == nil
}
func (t *Torrent) SeedRatio() (float64, error) {
qb, err := t.getTorrent()
if err != nil {
return 0, err
}
return qb.Ratio, nil
}
func (t *Torrent) Walk(f func(string) error) error {
files, err := t.c.TorrentFiles(t.hash)
if err != nil {
return err
}
for _, file := range files {
if err := f(file.Name); err != nil {
return errors.Errorf("proccess file (%s) error: %v", file.Name, err)
}
}
return nil
}
func (t *Torrent) WalkFunc() func(fn func(path string, info fs.FileInfo) error) error {
files, err := t.c.TorrentFiles(t.hash)
if err != nil {
return func(fn func(path string, info fs.FileInfo) error) error {
return err
}
}
path, err := t.c.DefaultSavePath()
if err != nil {
return func(fn func(path string, info fs.FileInfo) error) error {
return err
}
}
return func(fn func(path string, info fs.FileInfo) error) error {
for _, file := range files {
name := filepath.Join(path, file.Name)
info, err := os.Stat(name)
if err != nil {
return err
}
if err := fn(name, info); err != nil {
return errors.Errorf("proccess file (%s) error: %v", file.Name, err)
}
}
return nil
}
}