package torrent import ( "io" "os" "strings" "github.com/debridmediamanager/zurg/pkg/realdebrid" cmap "github.com/orcaman/concurrent-map/v2" ) // fixers are commands that will be run on the next refresh // they are stored in a file so that they can be run on startup // they follow the format of: // key: value: // id_trigger: this means a specific torrent id's completion // commands: delete | repair func (t *TorrentManager) fixerAddCommand(trigger, command string) { t.log.Debugf("Adding fixer command: %s %s", trigger, command) t.fixers.Set(trigger, command) t.writeFixersToFile() } func (t *TorrentManager) processFixers(instances []realdebrid.Torrent) { t.log.Debugf("Processing fixers (%d left: %v)", t.fixers.Count(), t.fixers.Keys()) var toDelete []string var toRedownload []*Torrent allTorrents, _ := t.DirectoryMap.Get(INT_ALL) for _, instance := range instances { id := instance.ID if !t.fixers.Has(id) { continue } command, _ := t.fixers.Pop(id) // delete the fixer if it's done switch command { case "replaced": // id is old torrent id t.log.Debugf("Deleting old id=%s because it's redundant to fixed %s ", id, instance.Name) toDelete = append(toDelete, id) continue case "download_failed": // id is failed fixer id t.log.Debugf("Deleting failed fixer id=%s of torrent %s", id, instance.Name) toDelete = append(toDelete, id) continue case "repaired": // this torrent contains broken files if instance.Progress != 100 { t.fixers.Set(id, command) // requeue the fixer continue } torrent := t.getMoreInfo(instance) t.log.Debugf("Repairing torrent %s again now that fixer id=%s is done", t.GetKey(torrent), id) repairMe, _ := allTorrents.Get(t.GetKey(torrent)) toRedownload = append(toRedownload, repairMe) toDelete = append(toDelete, id) continue } // a new case: repaired_with: if strings.HasPrefix(command, "repaired_with:") { if instance.Progress != 100 { t.fixers.Set(id, command) // requeue the fixer continue } otherId := strings.TrimPrefix(command, "repaired_with:") for _, instance2 := range instances { if instance2.ID == otherId { if instance2.Progress != 100 { t.fixers.Set(id, command) // requeue the fixer break } torrent := t.getMoreInfo(instance2) t.log.Debugf("Repairing torrent %s again now that fixers ids=%s and %s are done", t.GetKey(torrent), id, otherId) repairMe, _ := allTorrents.Get(t.GetKey(torrent)) toRedownload = append(toRedownload, repairMe) toDelete = append(toDelete, id, otherId) break } } continue } } infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE) for _, id := range toDelete { t.Api.DeleteTorrent(id) infoCache.Remove(id) t.deleteTorrentFile(id) } for _, torrent := range toRedownload { t.redownloadTorrent(torrent, "") } t.writeFixersToFile() t.log.Debugf("Finished processing fixers") } func (t *TorrentManager) removeExpiredFixers(instances []realdebrid.Torrent) { fixers := t.fixers.Keys() if len(fixers) == 0 { return } for _, fixerID := range fixers { found := false for _, instance := range instances { if instance.ID == fixerID { found = true break } } if !found { t.log.Debugf("Removing expired fixer id=%s", fixerID) t.fixers.Remove(fixerID) } } } func (t *TorrentManager) writeFixersToFile() { filePath := "data/fixers.json" file, err := os.Create(filePath) if err != nil { t.log.Warnf("Cannot create fixer file %s: %v", filePath, err) return } defer file.Close() fileData, err := t.fixers.MarshalJSON() if err != nil { t.log.Warnf("Cannot marshal fixers: %v", err) return } _, err = file.Write(fileData) if err != nil { t.log.Warnf("Cannot write to fixer file %s: %v", filePath, err) return } } func (t *TorrentManager) readFixersFromFile() (ret cmap.ConcurrentMap[string, string]) { ret = cmap.New[string]() filePath := "data/fixers.json" file, err := os.Open(filePath) if err != nil { if os.IsNotExist(err) { return } t.log.Warnf("Cannot open fixer file %s: %v", filePath, err) return } defer file.Close() fileData, err := io.ReadAll(file) if err != nil { t.log.Warnf("Cannot read fixer file %s: %v", filePath, err) return } err = ret.UnmarshalJSON(fileData) if err != nil { t.log.Warnf("Cannot unmarshal fixer file %s: %v", filePath, err) return } return }