WIP
This commit is contained in:
@@ -12,6 +12,8 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type QBittorrentClient struct {
|
||||
@@ -54,7 +56,7 @@ func (c *QBittorrentClient) apiURL(path string) string {
|
||||
|
||||
func (c *QBittorrentClient) mapState(state string) TorrentState {
|
||||
switch state {
|
||||
case "downloading", "forcedDL", "metaDL", "allocating":
|
||||
case "downloading", "forcedDL", "metaDL", "allocating", "stalledDL":
|
||||
return StateDownloading
|
||||
case "uploading", "forcedUP", "stalledUP":
|
||||
return StateSeeding
|
||||
@@ -209,6 +211,8 @@ func (c *QBittorrentClient) AddTorrentURL(ctx context.Context, torrentURL string
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug().Str("url", torrentURL).Msg("[QBITTORRENT] adding torrent URL")
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := multipart.NewWriter(&buf)
|
||||
w.WriteField("urls", torrentURL)
|
||||
@@ -225,15 +229,27 @@ func (c *QBittorrentClient) AddTorrentURL(ctx context.Context, torrentURL string
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("[QBITTORRENT] request failed")
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
bodyStr := strings.TrimSpace(string(body))
|
||||
|
||||
log.Debug().Int("status", resp.StatusCode).Str("body", bodyStr).Msg("[QBITTORRENT] add torrent response")
|
||||
|
||||
if !statusOK(resp.StatusCode) {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("%w: %s", ErrInvalidRequest, string(body))
|
||||
log.Error().Int("status", resp.StatusCode).Str("body", bodyStr).Msg("[QBITTORRENT] add torrent failed")
|
||||
return fmt.Errorf("%w: %s", ErrInvalidRequest, bodyStr)
|
||||
}
|
||||
|
||||
if bodyStr == "Fails." {
|
||||
log.Error().Str("url", torrentURL).Msg("[QBITTORRENT] torrent add rejected")
|
||||
return fmt.Errorf("qBittorrent rejected torrent: %s", torrentURL)
|
||||
}
|
||||
|
||||
log.Info().Msg("[QBITTORRENT] torrent added successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+145
-8
@@ -2,6 +2,8 @@ package torrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
@@ -11,19 +13,25 @@ import (
|
||||
type StubClient struct {
|
||||
logPath string
|
||||
savePath string
|
||||
mu sync.Mutex
|
||||
mu sync.RWMutex
|
||||
logMu sync.Mutex
|
||||
torrents map[string]*TorrentInfo
|
||||
}
|
||||
|
||||
func NewStubClient(logPath, savePath string) *StubClient {
|
||||
return &StubClient{
|
||||
logPath: logPath,
|
||||
savePath: savePath,
|
||||
torrents: make(map[string]*TorrentInfo),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *StubClient) log(format string, args ...any) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.logPath == "" {
|
||||
return
|
||||
}
|
||||
c.logMu.Lock()
|
||||
defer c.logMu.Unlock()
|
||||
|
||||
f, err := os.OpenFile(c.logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
@@ -47,13 +55,29 @@ func (c *StubClient) Disconnect(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (c *StubClient) ListTorrents(ctx context.Context) ([]TorrentInfo, error) {
|
||||
c.log("LIST_TORRENTS")
|
||||
return []TorrentInfo{}, nil
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
c.log("LIST_TORRENTS count=%d", len(c.torrents))
|
||||
|
||||
result := make([]TorrentInfo, 0, len(c.torrents))
|
||||
for _, t := range c.torrents {
|
||||
result = append(result, *t)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *StubClient) GetTorrent(ctx context.Context, hash string) (*TorrentInfo, error) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
c.log("GET_TORRENT hash=%s", hash)
|
||||
return nil, ErrTorrentNotFound
|
||||
|
||||
t, ok := c.torrents[hash]
|
||||
if !ok {
|
||||
return nil, ErrTorrentNotFound
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (c *StubClient) AddTorrentURL(ctx context.Context, url string, savePath *string) error {
|
||||
@@ -61,7 +85,24 @@ func (c *StubClient) AddTorrentURL(ctx context.Context, url string, savePath *st
|
||||
if savePath != nil {
|
||||
path = *savePath
|
||||
}
|
||||
c.log("ADD_TORRENT_URL url=%s save_path=%s", url, path)
|
||||
|
||||
hash := generateHashFromURL(url)
|
||||
name := "Torrent-" + hash[:8]
|
||||
|
||||
c.mu.Lock()
|
||||
c.torrents[hash] = &TorrentInfo{
|
||||
Hash: hash,
|
||||
Name: name,
|
||||
Size: 500 * 1024 * 1024,
|
||||
Progress: 0,
|
||||
DownloadSpeed: 0,
|
||||
UploadSpeed: 0,
|
||||
State: StateQueued,
|
||||
SavePath: path,
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
c.log("ADD_TORRENT_URL url=%s hash=%s save_path=%s", url, hash, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -70,21 +111,117 @@ func (c *StubClient) AddTorrentFile(ctx context.Context, data []byte, savePath *
|
||||
if savePath != nil {
|
||||
path = *savePath
|
||||
}
|
||||
c.log("ADD_TORRENT_FILE size=%d save_path=%s", len(data), path)
|
||||
|
||||
hash := generateHashFromData(data)
|
||||
name := "Torrent-" + hash[:8]
|
||||
|
||||
c.mu.Lock()
|
||||
c.torrents[hash] = &TorrentInfo{
|
||||
Hash: hash,
|
||||
Name: name,
|
||||
Size: uint64(len(data) * 100),
|
||||
Progress: 0,
|
||||
DownloadSpeed: 0,
|
||||
UploadSpeed: 0,
|
||||
State: StateQueued,
|
||||
SavePath: path,
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
c.log("ADD_TORRENT_FILE size=%d hash=%s save_path=%s", len(data), hash, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *StubClient) RemoveTorrent(ctx context.Context, hash string, deleteFiles bool) error {
|
||||
c.mu.Lock()
|
||||
delete(c.torrents, hash)
|
||||
c.mu.Unlock()
|
||||
|
||||
c.log("REMOVE_TORRENT hash=%s delete_files=%t", hash, deleteFiles)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *StubClient) PauseTorrent(ctx context.Context, hash string) error {
|
||||
c.mu.Lock()
|
||||
if t, ok := c.torrents[hash]; ok {
|
||||
t.State = StatePaused
|
||||
t.DownloadSpeed = 0
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
c.log("PAUSE_TORRENT hash=%s", hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *StubClient) ResumeTorrent(ctx context.Context, hash string) error {
|
||||
c.mu.Lock()
|
||||
if t, ok := c.torrents[hash]; ok {
|
||||
if t.Progress < 1.0 {
|
||||
t.State = StateDownloading
|
||||
} else {
|
||||
t.State = StateSeeding
|
||||
}
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
c.log("RESUME_TORRENT hash=%s", hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *StubClient) SetTorrentState(hash string, state TorrentState, progress float64) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if t, ok := c.torrents[hash]; ok {
|
||||
t.State = state
|
||||
t.Progress = progress
|
||||
if state == StateSeeding {
|
||||
t.Progress = 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *StubClient) SetTorrentName(hash, name string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if t, ok := c.torrents[hash]; ok {
|
||||
t.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
func (c *StubClient) AddTorrentDirect(info TorrentInfo) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.torrents[info.Hash] = &info
|
||||
}
|
||||
|
||||
func (c *StubClient) Clear() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.torrents = make(map[string]*TorrentInfo)
|
||||
}
|
||||
|
||||
func (c *StubClient) GetAllTorrents() map[string]*TorrentInfo {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
result := make(map[string]*TorrentInfo, len(c.torrents))
|
||||
for k, v := range c.torrents {
|
||||
copy := *v
|
||||
result[k] = ©
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func generateHashFromURL(url string) string {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(url))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func generateHashFromData(data []byte) string {
|
||||
h := sha1.New()
|
||||
h.Write(data)
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user