package torrent import ( "io" "os" 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) handleFixers() { var toDelete []string allTorrents, _ := t.DirectoryMap.Get(INT_ALL) infoCache, _ := t.DirectoryMap.Get(INT_INFO_CACHE) infoCache.IterCb(func(id string, torrent *Torrent) { if !t.fixers.Has(id) || torrent.AnyInProgress() { return } 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, t.GetKey(torrent)) toDelete = append(toDelete, id) case "download_failed": // id is failed fixer id t.log.Debugf("Deleting failed fixer id=%s of torrent %s", id, t.GetKey(torrent)) toDelete = append(toDelete, id) case "repaired": // id is fixer id t.log.Debugf("Repairing torrent %s again now that fixer id=%s is done", t.GetKey(torrent), id) repairMe, _ := allTorrents.Get(t.GetKey(torrent)) repairMe.DownloadedIDs.Remove(id) toDelete = append(toDelete, id) t.TriggerRepair(repairMe) } }) for _, id := range toDelete { t.Api.DeleteTorrent(id) infoCache.Remove(id) t.deleteTorrentFile(id) } // remove expired fixers var expired []string t.fixers.IterCb(func(trigger string, command string) { if infoCache.Has(trigger) { return } expired = append(expired, trigger) }) for _, trigger := range expired { t.log.Debugf("Removing expired fixer id=%s", trigger) t.fixers.Remove(trigger) } t.writeFixersToFile() t.log.Debugf("Finished handling fixers") } 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 }