diff --git a/internal/app.go b/internal/app.go index 39d415e..4cbf59c 100644 --- a/internal/app.go +++ b/internal/app.go @@ -64,7 +64,7 @@ func MainApp(configPath string) { downloadClient := http.NewHTTPClient(config.GetToken(), config.GetRetriesUntilFailed(), config.GetDownloadTimeoutSecs(), true, config, log.Named("download_client")) - api := realdebrid.NewRealDebrid(apiClient, unrestrictClient, downloadClient, log.Named("realdebrid")) + api := realdebrid.NewRealDebrid(apiClient, unrestrictClient, downloadClient, config, log.Named("realdebrid")) premium.MonitorPremiumStatus(api, zurglog) diff --git a/internal/config/types.go b/internal/config/types.go index 3f3375f..b693b71 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -30,6 +30,7 @@ type ConfigInterface interface { ShouldDeleteRarFiles() bool GetDownloadsEveryMins() int GetPlayableExtensions() []string + GetTorrentsCount() int } type ZurgConfig struct { @@ -60,6 +61,7 @@ type ZurgConfig struct { RetriesUntilFailed int `yaml:"retries_until_failed" json:"retries_until_failed"` ServeFromRclone bool `yaml:"serve_from_rclone" json:"serve_from_rclone"` Username string `yaml:"username" json:"username"` + TorrentsCount int `yaml:"get_torrents_count" json:"get_torrents_count"` } func (z *ZurgConfig) GetConfig() ZurgConfig { @@ -201,3 +203,10 @@ func (z *ZurgConfig) GetPlayableExtensions() []string { } return z.PlayableExtensions } + +func (z *ZurgConfig) GetTorrentsCount() int { + if z.TorrentsCount == 0 { + return 100 + } + return z.TorrentsCount +} diff --git a/internal/torrent/delete.go b/internal/torrent/delete.go index 4275588..463c22e 100644 --- a/internal/torrent/delete.go +++ b/internal/torrent/delete.go @@ -40,7 +40,6 @@ func (t *TorrentManager) Delete(accessKey string, deleteInRD bool) { }) } } - t.allAccessKeys.Remove(accessKey) t.log.Infof("Removing torrent %s from zurg database (not real-debrid)", accessKey) t.DirectoryMap.IterCb(func(directory string, torrents cmap.ConcurrentMap[string, *Torrent]) { torrents.Remove(accessKey) diff --git a/internal/torrent/manager.go b/internal/torrent/manager.go index 4220189..71af32f 100644 --- a/internal/torrent/manager.go +++ b/internal/torrent/manager.go @@ -29,6 +29,7 @@ type TorrentManager struct { DownloadMap cmap.ConcurrentMap[string, *realdebrid.Download] fixers cmap.ConcurrentMap[string, string] // trigger -> [command, id] allAccessKeys mapset.Set[string] + allIDs mapset.Set[string] latestState *LibraryState requiredVersion string workerPool *ants.Pool @@ -55,6 +56,7 @@ func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, w RepairKillSwitch: make(chan struct{}, 1), RemountTrigger: make(chan struct{}, 1), allAccessKeys: mapset.NewSet[string](), + allIDs: mapset.NewSet[string](), latestState: &LibraryState{}, requiredVersion: "0.9.3-hotfix.10", workerPool: workerPool, diff --git a/internal/torrent/refresh.go b/internal/torrent/refresh.go index aa8d5fd..1418950 100644 --- a/internal/torrent/refresh.go +++ b/internal/torrent/refresh.go @@ -36,60 +36,60 @@ func (t *TorrentManager) refreshTorrents(isInitialRun bool) []string { close(infoChan) t.log.Infof("Fetched info for %d torrents", len(instances)) - newlyFetchedKeys := mapset.NewSet[string]() + var updatedPaths []string noInfoCount := 0 allTorrents, _ := t.DirectoryMap.Get(INT_ALL) + freshAccessKeys := mapset.NewSet[string]() + deletedIDs := t.allIDs.Clone() for info := range infoChan { if info == nil { noInfoCount++ continue } - accessKey := t.GetKey(info) - if !info.AnyInProgress() { // since it's single instance info, Any == All - newlyFetchedKeys.Add(accessKey) - } - torrent, exists := allTorrents.Get(accessKey) + infoID, _ := info.DownloadedIDs.Clone().Pop() + deletedIDs.Remove(infoID) + accessKey := t.GetKey(info) + freshAccessKeys.Add(accessKey) + + // update allTorrents + mainTorrent, exists := allTorrents.Get(accessKey) if !exists { allTorrents.Set(accessKey, info) - continue + } else { + if !mainTorrent.DownloadedIDs.Contains(infoID) { + merged := t.mergeToMain(mainTorrent, info) + allTorrents.Set(accessKey, merged) + } } - newIDs := info.DownloadedIDs.Union(info.InProgressIDs) - oldIDs := torrent.DownloadedIDs.Union(torrent.InProgressIDs) - if !newIDs.Difference(oldIDs).IsEmpty() { - mainTorrent := t.mergeToMain(torrent, info) - allTorrents.Set(accessKey, &mainTorrent) + + // check for newly finished torrents for assigning to directories + isDone := info.DownloadedIDs.Cardinality() > 0 && info.InProgressIDs.IsEmpty() + if isDone && !t.allIDs.Contains(infoID) { + var directories []string + mainTor, _ := allTorrents.Get(accessKey) + t.assignedDirectoryCb(mainTor, func(directory string) { + listing, _ := t.DirectoryMap.Get(directory) + listing.Set(accessKey, mainTor) + updatedPaths = append(updatedPaths, fmt.Sprintf("%s/%s", directory, accessKey)) + // this is just for the logs + if directory != config.ALL_TORRENTS { + directories = append(directories, directory) + } + }) + t.allIDs.Add(infoID) } } + t.allIDs.RemoveAll(deletedIDs.ToSlice()...) t.log.Infof("Compiled into %d torrents, %d were missing info", allTorrents.Count(), noInfoCount) - var updatedPaths []string - // torrents yet to be assigned in a directory - newlyFetchedKeys.Difference(t.allAccessKeys).Each(func(accessKey string) bool { - // assign to directories - tor, ok := allTorrents.Get(accessKey) - if !ok { - return false - } - var directories []string - t.assignedDirectoryCb(tor, func(directory string) { - torrents, _ := t.DirectoryMap.Get(directory) - torrents.Set(accessKey, tor) - updatedPaths = append(updatedPaths, fmt.Sprintf("%s/%s", directory, accessKey)) - // this is just for the logs - if directory != config.ALL_TORRENTS { - directories = append(directories, directory) - } - }) - // t.log.Debugf("Added %s to %v", accessKey, directories) - t.allAccessKeys.Add(accessKey) - return false - }) // removed torrents - t.allAccessKeys.Difference(newlyFetchedKeys).Each(func(accessKey string) bool { + t.allAccessKeys.Difference(freshAccessKeys).Each(func(accessKey string) bool { t.Delete(accessKey, false) + t.allAccessKeys.Remove(accessKey) return false }) + t.allAccessKeys.Append(freshAccessKeys.ToSlice()...) if t.Config.EnableRepair() { if isInitialRun { @@ -138,33 +138,17 @@ func (t *TorrentManager) StartRefreshJob() { func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent { infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE) - if torrentFromCache, exists := infoCache.Get(rdTorrent.ID); exists && - !torrentFromCache.AnyInProgress() && - torrentFromCache.SelectedFiles.Count() == len(rdTorrent.Links) { + if cachedTor, exists := infoCache.Get(rdTorrent.ID); exists && + cachedTor.SelectedFiles.Count() == len(rdTorrent.Links) { - return torrentFromCache + return cachedTor - } else if !exists { + } else if diskTor := t.readTorrentFromFile(rdTorrent.ID); diskTor != nil && + diskTor.SelectedFiles.Count() == len(rdTorrent.Links) { - torrentFromFile := t.readTorrentFromFile(rdTorrent.ID) - - if torrentFromFile != nil && - torrentFromFile.SelectedFiles.Count() == len(rdTorrent.Links) { - - hasBrokenFiles := false - torrentFromFile.SelectedFiles.IterCb(func(filepath string, file *File) { - if file.IsBroken && !file.IsDeleted { - hasBrokenFiles = true - } - }) - - if !hasBrokenFiles { - infoCache.Set(rdTorrent.ID, torrentFromFile) - t.ResetSelectedFiles(torrentFromFile) - return torrentFromFile - - } - } + infoCache.Set(rdTorrent.ID, diskTor) + t.ResetSelectedFiles(diskTor) + return diskTor } info, err := t.Api.GetTorrentInfo(rdTorrent.ID) @@ -184,7 +168,6 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent { // if it is empty, it means the file is no longer available // Files+Links together are the same as SelectedFiles var selectedFiles []*File - // if some Links are empty, we need to repair it for _, file := range info.Files { if file.Selected == 0 { continue @@ -192,7 +175,7 @@ func (t *TorrentManager) getMoreInfo(rdTorrent realdebrid.Torrent) *Torrent { selectedFiles = append(selectedFiles, &File{ File: file, Ended: info.Ended, - Link: "", // no link yet + Link: "", // no link yet, consider it broken IsBroken: true, }) } @@ -253,7 +236,7 @@ func (t *TorrentManager) ResetSelectedFiles(torrent *Torrent) { torrent.SelectedFiles = newSelectedFiles } -func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) Torrent { +func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) *Torrent { var newer, older *Torrent if existing.Added < toMerge.Added { newer = toMerge @@ -322,7 +305,7 @@ func (t *TorrentManager) mergeToMain(existing, toMerge *Torrent) Torrent { t.CheckDeletedStatus(&mainTorrent) } - return mainTorrent + return &mainTorrent } func (t *TorrentManager) assignedDirectoryCb(tor *Torrent, cb func(string)) { diff --git a/pkg/realdebrid/api.go b/pkg/realdebrid/api.go index a791e5a..3182bca 100644 --- a/pkg/realdebrid/api.go +++ b/pkg/realdebrid/api.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/debridmediamanager/zurg/internal/config" zurghttp "github.com/debridmediamanager/zurg/pkg/http" "github.com/debridmediamanager/zurg/pkg/logutil" ) @@ -16,14 +17,16 @@ type RealDebrid struct { client *zurghttp.HTTPClient unrestrictClient *zurghttp.HTTPClient downloadClient *zurghttp.HTTPClient + cfg config.ConfigInterface log *logutil.Logger } -func NewRealDebrid(client, unrestrictClient, downloadClient *zurghttp.HTTPClient, log *logutil.Logger) *RealDebrid { +func NewRealDebrid(client, unrestrictClient, downloadClient *zurghttp.HTTPClient, cfg config.ConfigInterface, log *logutil.Logger) *RealDebrid { return &RealDebrid{ client: client, unrestrictClient: unrestrictClient, downloadClient: downloadClient, + cfg: cfg, log: log, } } @@ -119,7 +122,7 @@ func (rd *RealDebrid) GetTorrents(customLimit int, active bool) ([]Torrent, int, page := 1 limit := customLimit if limit == 0 { - limit = 100 + limit = rd.cfg.GetTorrentsCount() } totalCount := 0