package torrent import ( "fmt" "os" "strings" mapset "github.com/deckarep/golang-set/v2" ) const BINS_FILE = "data/bins.json" // initializeBins reads from bins.json and assigns values to t.trashBin and t.repairBin func (t *TorrentManager) initializeBins() { if _, err := os.Stat(BINS_FILE); os.IsNotExist(err) { t.repairLog.Info("data/bins.json does not exist. Initializing empty bins.") t.ImmediateBin = mapset.NewSet[string]() t.OnceDoneBin = mapset.NewSet[string]() return } fileData, err := os.ReadFile(BINS_FILE) if err != nil { t.repairLog.Errorf("Failed to read bins.json file: %v", err) t.ImmediateBin = mapset.NewSet[string]() t.OnceDoneBin = mapset.NewSet[string]() return } data := map[string][]string{} err = json.Unmarshal(fileData, &data) if err != nil { t.repairLog.Errorf("Failed to unmarshal bin data: %v", err) t.ImmediateBin = mapset.NewSet[string]() t.OnceDoneBin = mapset.NewSet[string]() return } t.ImmediateBin = mapset.NewSet[string](data["trash_bin"]...) t.OnceDoneBin = mapset.NewSet[string](data["repair_bin"]...) t.repairLog.Debugf("Bin immediately: %v", t.ImmediateBin.ToSlice()) t.repairLog.Debugf("Bin once done: %v", t.OnceDoneBin.ToSlice()) } func (t *TorrentManager) persistBins() { data := map[string]interface{}{ "trash_bin": t.ImmediateBin.ToSlice(), // Assuming trashBin is a mapset.Set[string] "repair_bin": t.OnceDoneBin.ToSlice(), // Assuming repairBin is a mapset.Set[string] } jsonData, err := json.Marshal(data) if err != nil { t.repairLog.Errorf("Failed to marshal bin data: %v", err) return } file, err := os.Create(BINS_FILE) if err != nil { t.repairLog.Errorf("Failed to create bins.json file: %v", err) return } defer file.Close() _, err = file.Write(jsonData) if err != nil { t.repairLog.Errorf("Failed to write to bins.json file: %v", err) } } func (t *TorrentManager) setToBinImmediately(torrentId string) { t.repairLog.Debugf("id=%s set to delete immediately", torrentId) t.ImmediateBin.Add(torrentId) t.persistBins() } func (t *TorrentManager) setToBinOnceDone(torrentId string) { t.repairLog.Debugf("id=%s set to delete once it completes", torrentId) t.OnceDoneBin.Add(torrentId) t.persistBins() } func (t *TorrentManager) setXToBinOnceYDone(deleteId, completeId string) { t.repairLog.Debugf("id=%s set to delete once id=%s completes", deleteId, completeId) t.OnceDoneBin.Add(fmt.Sprintf("%s-", completeId)) t.OnceDoneBin.Add(fmt.Sprintf("%s-%s", completeId, deleteId)) t.persistBins() } func (t *TorrentManager) cleanupBins(freshIDs mapset.Set[string]) { t.ImmediateBin.Clone().Each(func(entry string) bool { if !freshIDs.Contains(entry) { t.ImmediateBin.Remove(entry) } return false }) t.OnceDoneBin.Clone().Each(func(entry string) bool { // check if the entry is a special case if strings.Contains(entry, "-") { // format is: id1-id2 or id1- // if either id1 or id2 is not fresh, remove the entry ids := strings.Split(entry, "-") if !freshIDs.Contains(ids[0]) || (ids[1] != "" && !freshIDs.Contains(ids[1])) { t.OnceDoneBin.Remove(entry) } return false } if !freshIDs.Contains(entry) { t.OnceDoneBin.Remove(entry) } return false }) t.persistBins() } // binImmediatelyErrorCheck checks if the torrent is in the ImmediateBin and deletes it if it is. // returns true if the torrent was in the bin and was deleted, false otherwise func (t *TorrentManager) binImmediately(torrentId string) bool { if t.ImmediateBin.Contains(torrentId) { if err := t.api.DeleteTorrent(torrentId); err != nil { t.repairLog.Warnf("Failed to delete torrent %s: %v", torrentId, err) } t.ImmediateBin.Remove(torrentId) t.repairLog.Debugf("Bin: immediate deletion of torrent %s", torrentId) t.persistBins() return true } return false } // binOnceDoneErrorCheck checks if the torrent is in error states and then checks if it should be deleted func (t *TorrentManager) binOnceDoneErrorCheck(torrentId, status string) bool { if status == "downloading" || status == "downloaded" || status == "uploading" || status == "queued" || status == "compressing" || status == "waiting_files_selection" { return false } t.repairLog.Infof("Bin: error status=%s, checking if %s should be deleted", status, torrentId) return t.binOnceDone(torrentId, true) } // binOnceDone checks if the torrent is in the OnceDoneBin and deletes it if it is. // returns true if the torrent was in the bin and was deleted, false otherwise func (t *TorrentManager) binOnceDone(completedTorrentId string, errorCheck bool) bool { if t.OnceDoneBin.Contains(completedTorrentId) { if err := t.api.DeleteTorrent(completedTorrentId); err != nil { t.repairLog.Warnf("Failed to delete torrent %s: %v", completedTorrentId, err) } t.deleteInfoFile(completedTorrentId) t.OnceDoneBin.Remove(completedTorrentId) if errorCheck { t.repairLog.Errorf("Bin: error deletion of torrent %s", completedTorrentId) } else { t.repairLog.Debugf("Bin: done deletion of torrent %s", completedTorrentId) } t.persistBins() return true } // special case: yyy-xxx means if yyy is done, delete xxx specialCase := fmt.Sprintf("%s-", completedTorrentId) if !t.OnceDoneBin.Contains(specialCase) { return false } t.deleteInfoFile(completedTorrentId) t.OnceDoneBin.Remove(specialCase) t.OnceDoneBin.Clone().Each(func(entry string) bool { if strings.Contains(entry, specialCase) { if errorCheck { if err := t.api.DeleteTorrent(completedTorrentId); err != nil { t.repairLog.Warnf("Failed to delete torrent %s: %v", completedTorrentId, err) } t.OnceDoneBin.Remove(entry) t.repairLog.Errorf("Bin: error deletion of torrent %s", completedTorrentId) } else { idToDelete := strings.Split(entry, "-")[1] if err := t.api.DeleteTorrent(idToDelete); err != nil { t.repairLog.Warnf("Failed to delete torrent %s: %v", idToDelete, err) } t.OnceDoneBin.Remove(entry) t.repairLog.Debugf("Bin: %s completed, done deletion of torrent %s", completedTorrentId, idToDelete) } } return false }) t.persistBins() return true }