diff --git a/CHANGELOG.md b/CHANGELOG.md index c2c5de5..691ac7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,4 +67,9 @@ - Add file download support(Sequential Download) - Fix http handler error - Fix *arrs map failing concurrently -- Fix cache not being updated \ No newline at end of file +- Fix cache not being updated + +#### 0.2.5 +- Fix ContentPath not being set prior +- Rewrote Readme +- Cleaned up the code \ No newline at end of file diff --git a/README.md b/README.md index 8bad8ea..48da9c4 100644 --- a/README.md +++ b/README.md @@ -68,16 +68,19 @@ Download the binary from the releases page and run it with the config file. "max_cache_size": 1000, "qbittorrent": { "port": "8282", - "username": "admin", // deprecated - "password": "admin", // deprecated "download_folder": "/media/symlinks/", "categories": ["sonarr", "radarr"], - "refresh_interval": 5 // in seconds + "refresh_interval": 5 } } ``` #### Config Notes +##### Max Cache Size +- The `max_cache_size` key is used to set the maximum number of infohashes that can be stored in the availability cache. This is used to prevent round trip to the debrid provider when using the proxy/Qbittorrent +- The default value is `1000` +- The cache is stored in memory and is not persisted on restart + ##### Debrid Config - This config key is important as it's used for both Blackhole and Proxy @@ -93,6 +96,7 @@ Download the binary from the releases page and run it with the config file. - The `port` key is the port the qBittorrent will listen on - The `download_folder` is the folder where the torrents will be downloaded. e.g `/media/symlinks/` - The `categories` key is used to filter out torrents based on the category. e.g `sonarr`, `radarr` +- The `refresh_interval` key is used to set the interval in minutes to refresh the Arrs Monitored Downloads(it's in seconds). The default value is `5` seconds ### Proxy diff --git a/pkg/debrid/torrent.go b/pkg/debrid/torrent.go index 46a66e9..124bf15 100644 --- a/pkg/debrid/torrent.go +++ b/pkg/debrid/torrent.go @@ -36,6 +36,7 @@ type Torrent struct { Magnet *common.Magnet `json:"magnet"` Files []TorrentFile `json:"files"` Status string `json:"status"` + Added string `json:"added"` Progress float64 `json:"progress"` Speed int64 `json:"speed"` Seeders int `json:"seeders"` diff --git a/pkg/qbit/downloader.go b/pkg/qbit/downloader.go index 7b2704a..ec57835 100644 --- a/pkg/qbit/downloader.go +++ b/pkg/qbit/downloader.go @@ -125,7 +125,7 @@ func (q *QBit) createSymLink(path string, torrentMountPath string, file debrid.T torrentFilePath := filepath.Join(torrentMountPath, file.Name) // debridFolder/MyTVShow/MyTVShow.S01E01.720p.mkv err := os.Symlink(torrentFilePath, fullPath) if err != nil { - q.logger.Printf("Failed to create symlink: %s\n", fullPath) + q.logger.Printf("Failed to create symlink: %s: %v\n", fullPath, err) } // Check if the file exists if !common.FileReady(fullPath) { diff --git a/pkg/qbit/handlers.go b/pkg/qbit/handlers.go index b143227..6db62ba 100644 --- a/pkg/qbit/handlers.go +++ b/pkg/qbit/handlers.go @@ -24,6 +24,7 @@ func (q *QBit) AddRoutes(r chi.Router) http.Handler { r.Get("/resume", q.handleTorrentsResume) r.Get("/recheck", q.handleTorrentRecheck) r.Get("/properties", q.handleTorrentProperties) + r.Get("/files", q.handleTorrentFiles) }) r.Route("/app", func(r chi.Router) { diff --git a/pkg/qbit/handlers_auth.go b/pkg/qbit/handlers_auth.go index e6ec14c..1c376f3 100644 --- a/pkg/qbit/handlers_auth.go +++ b/pkg/qbit/handlers_auth.go @@ -5,6 +5,5 @@ import ( ) func (q *QBit) handleLogin(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("Ok.")) + _, _ = w.Write([]byte("Ok.")) } diff --git a/pkg/qbit/handlers_torrent.go b/pkg/qbit/handlers_torrent.go index 891115d..5fea3e9 100644 --- a/pkg/qbit/handlers_torrent.go +++ b/pkg/qbit/handlers_torrent.go @@ -163,3 +163,13 @@ func (q *QBit) handleTorrentProperties(w http.ResponseWriter, r *http.Request) { properties := q.GetTorrentProperties(torrent) JSONResponse(w, properties, http.StatusOK) } + +func (q *QBit) handleTorrentFiles(w http.ResponseWriter, r *http.Request) { + hash := r.URL.Query().Get("hash") + torrent := q.storage.Get(hash) + if torrent == nil { + return + } + files := q.GetTorrentFiles(torrent) + JSONResponse(w, files, http.StatusOK) +} diff --git a/pkg/qbit/qbit.go b/pkg/qbit/qbit.go index cb980eb..4ddf551 100644 --- a/pkg/qbit/qbit.go +++ b/pkg/qbit/qbit.go @@ -58,9 +58,7 @@ func (q *QBit) Process(ctx context.Context, magnet *common.Magnet, category stri } return err } - torrent.ID = debridTorrent.Id - torrent.DebridTorrent = debridTorrent - torrent.Name = debridTorrent.Name + torrent = q.UpdateTorrentMin(torrent, debridTorrent) q.storage.AddOrUpdate(torrent) go q.processFiles(torrent, debridTorrent, arr, isSymlink) // We can send async for file processing not to delay the response return nil @@ -74,23 +72,14 @@ func (q *QBit) CreateTorrentFromMagnet(magnet *common.Magnet, category string) * Size: magnet.Size, Category: category, State: "downloading", - AddedOn: time.Now().Unix(), MagnetUri: magnet.Link, - Tracker: "udp://tracker.opentrackr.org:1337", - UpLimit: -1, - DlLimit: -1, - FlPiecePrio: false, - ForceStart: false, - AutoTmm: false, - Availability: 2, - MaxRatio: -1, - MaxSeedingTime: -1, - NumComplete: 10, - NumIncomplete: 0, - NumLeechs: 1, - Ratio: 1, - RatioLimit: 1, + Tracker: "udp://tracker.opentrackr.org:1337", + UpLimit: -1, + DlLimit: -1, + AutoTmm: false, + Ratio: 1, + RatioLimit: 1, } return torrent } @@ -98,15 +87,17 @@ func (q *QBit) CreateTorrentFromMagnet(magnet *common.Magnet, category string) * func (q *QBit) processFiles(torrent *Torrent, debridTorrent *debrid.Torrent, arr *debrid.Arr, isSymlink bool) { for debridTorrent.Status != "downloaded" { progress := debridTorrent.Progress - q.logger.Printf("Progress: %.2f%%", progress) + q.logger.Printf("RD Download Progress: %.2f%%", progress) time.Sleep(5 * time.Second) dbT, err := q.debrid.CheckStatus(debridTorrent, isSymlink) if err != nil { q.logger.Printf("Error checking status: %v", err) q.MarkAsFailed(torrent) + q.RefreshArr(arr) return } debridTorrent = dbT + torrent = q.UpdateTorrentMin(torrent, debridTorrent) } if isSymlink { q.processSymlink(torrent, debridTorrent, arr) diff --git a/pkg/qbit/structs.go b/pkg/qbit/structs.go index 36f9c10..c93906e 100644 --- a/pkg/qbit/structs.go +++ b/pkg/qbit/structs.go @@ -174,20 +174,20 @@ type Torrent struct { TorrentPath string `json:"-"` AddedOn int64 `json:"added_on,omitempty"` - AmountLeft int64 `json:"amount_left,omitempty"` + AmountLeft int64 `json:"amount_left"` AutoTmm bool `json:"auto_tmm"` - Availability float64 `json:"availability"` + Availability float64 `json:"availability,omitempty"` Category string `json:"category,omitempty"` - Completed int64 `json:"completed,omitempty"` + Completed int64 `json:"completed"` CompletionOn int64 `json:"completion_on,omitempty"` - ContentPath string `json:"content_path,omitempty"` - DlLimit int64 `json:"dl_limit,omitempty"` - Dlspeed int64 `json:"dlspeed,omitempty"` - Downloaded int64 `json:"downloaded,omitempty"` - DownloadedSession int64 `json:"downloaded_session,omitempty"` - Eta int64 `json:"eta,omitempty"` - FlPiecePrio bool `json:"f_l_piece_prio"` - ForceStart bool `json:"force_start"` + ContentPath string `json:"content_path"` + DlLimit int64 `json:"dl_limit"` + Dlspeed int64 `json:"dlspeed"` + Downloaded int64 `json:"downloaded"` + DownloadedSession int64 `json:"downloaded_session"` + Eta int64 `json:"eta"` + FlPiecePrio bool `json:"f_l_piece_prio,omitempty"` + ForceStart bool `json:"force_start,omitempty"` Hash string `json:"hash"` LastActivity int64 `json:"last_activity,omitempty"` MagnetUri string `json:"magnet_uri,omitempty"` @@ -202,7 +202,7 @@ type Torrent struct { Progress float32 `json:"progress"` Ratio int64 `json:"ratio,omitempty"` RatioLimit int64 `json:"ratio_limit,omitempty"` - SavePath string `json:"save_path,omitempty"` + SavePath string `json:"save_path"` SeedingTimeLimit int64 `json:"seeding_time_limit,omitempty"` SeenComplete int64 `json:"seen_complete,omitempty"` SeqDl bool `json:"seq_dl"` @@ -259,6 +259,17 @@ type TorrentProperties struct { UpSpeedAvg int64 `json:"up_speed_avg,omitempty"` } +type TorrentFile struct { + Index int `json:"index,omitempty"` + Name string `json:"name,omitempty"` + Size int64 `json:"size,omitempty"` + Progress int64 `json:"progress,omitempty"` + Priority int64 `json:"priority,omitempty"` + IsSeed bool `json:"is_seed,omitempty"` + PieceRange []int64 `json:"piece_range,omitempty"` + Availability float64 `json:"availability,omitempty"` +} + func NewAppPreferences() *AppPreferences { preferences := &AppPreferences{ AddTrackers: "", diff --git a/pkg/qbit/torrent.go b/pkg/qbit/torrent.go index b94c662..881f0ec 100644 --- a/pkg/qbit/torrent.go +++ b/pkg/qbit/torrent.go @@ -16,31 +16,19 @@ func (q *QBit) MarkAsFailed(t *Torrent) *Torrent { return t } -func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent { - rcLoneMount := q.debrid.GetMountPath() - if debridTorrent == nil && t.ID != "" { - debridTorrent, _ = q.debrid.GetTorrent(t.ID) - } +func (q *QBit) UpdateTorrentMin(t *Torrent, debridTorrent *debrid.Torrent) *Torrent { if debridTorrent == nil { - q.logger.Printf("Torrent with ID %s not found in %s", t.ID, q.debrid.GetName()) return t } - if debridTorrent.Status != "downloaded" { - debridTorrent, _ = q.debrid.GetTorrent(t.ID) - } - if t.TorrentPath == "" { - t.TorrentPath = filepath.Base(debridTorrent.GetMountFolder(rcLoneMount)) + addedOn, err := time.Parse(time.RFC3339, debridTorrent.Added) + if err != nil { + addedOn = time.Now() } - - totalSize := float64(cmp.Or(debridTorrent.Bytes, 1.0)) + totalSize := float64(debridTorrent.Bytes) progress := cmp.Or(debridTorrent.Progress, 100.0) progress = progress / 100.0 - var sizeCompleted int64 - - sizeCompleted = int64(totalSize * progress) - savePath := filepath.Join(q.DownloadFolder, t.Category) + string(os.PathSeparator) - torrentPath := filepath.Join(savePath, t.TorrentPath) + string(os.PathSeparator) + sizeCompleted := int64(totalSize * progress) var speed int64 if debridTorrent.Speed != 0 { @@ -50,9 +38,11 @@ func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent if speed != 0 { eta = int64((totalSize - float64(sizeCompleted)) / float64(speed)) } - - t.Size = debridTorrent.Bytes + t.ID = debridTorrent.Id + t.Name = debridTorrent.Name + t.AddedOn = addedOn.Unix() t.DebridTorrent = debridTorrent + t.Size = int64(totalSize) t.Completed = sizeCompleted t.Downloaded = sizeCompleted t.DownloadedSession = sizeCompleted @@ -60,29 +50,57 @@ func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent t.UploadedSession = sizeCompleted t.AmountLeft = int64(totalSize) - sizeCompleted t.Progress = float32(progress) - t.SavePath = savePath - t.ContentPath = torrentPath t.Eta = eta t.Dlspeed = speed t.Upspeed = speed + t.SavePath = filepath.Join(q.DownloadFolder, t.Category) + string(os.PathSeparator) + t.ContentPath = filepath.Join(t.SavePath, t.Name) + string(os.PathSeparator) + return t +} + +func (q *QBit) UpdateTorrent(t *Torrent, debridTorrent *debrid.Torrent) *Torrent { + rcLoneMount := q.debrid.GetMountPath() + if debridTorrent == nil && t.ID != "" { + debridTorrent, _ = q.debrid.GetTorrent(t.ID) + } + if debridTorrent == nil { + q.logger.Printf("Torrent with ID %s not found in %s", t.ID, q.debrid.GetName()) + return t + } + if debridTorrent.Status != "downloaded" { + debridTorrent, _ = q.debrid.GetTorrent(t.ID) + } + + if t.TorrentPath == "" { + t.TorrentPath = filepath.Base(debridTorrent.GetMountFolder(rcLoneMount)) + } + savePath := filepath.Join(q.DownloadFolder, t.Category) + string(os.PathSeparator) + torrentPath := filepath.Join(savePath, t.TorrentPath) + string(os.PathSeparator) + t = q.UpdateTorrentMin(t, debridTorrent) + t.ContentPath = torrentPath if t.IsReady() { t.State = "pausedUP" - q.storage.AddOrUpdate(t) + q.storage.Update(t) return t } - ticker := time.NewTicker(3 * time.Second) + + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + for { select { case <-ticker.C: if t.IsReady() { t.State = "pausedUP" - q.storage.AddOrUpdate(t) - ticker.Stop() + q.storage.Update(t) return t - } else { - return q.UpdateTorrent(t, debridTorrent) } + updatedT := q.UpdateTorrent(t, debridTorrent) + t = updatedT + + case <-time.After(10 * time.Minute): // Add a timeout + return t } } } @@ -123,3 +141,18 @@ func (q *QBit) GetTorrentProperties(t *Torrent) *TorrentProperties { ShareRatio: 100, } } + +func (q *QBit) GetTorrentFiles(t *Torrent) []*TorrentFile { + files := make([]*TorrentFile, 0) + if t.DebridTorrent == nil { + return files + } + for index, file := range t.DebridTorrent.Files { + files = append(files, &TorrentFile{ + Index: index, + Name: file.Path, + Size: file.Size, + }) + } + return files +}