diff --git a/internal/torrent/bins.go b/internal/torrent/bins.go new file mode 100644 index 0000000..fbec0dd --- /dev/null +++ b/internal/torrent/bins.go @@ -0,0 +1,132 @@ +package torrent + +import ( + "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.log.Warn("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.log.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.log.Errorf("Failed to unmarshal bin data: %v", err) + return + } + + t.ImmediateBin = mapset.NewSet[string](data["trash_bin"]...) + t.OnceDoneBin = mapset.NewSet[string](data["repair_bin"]...) + + t.log.Debugf("Bin immediately: %v", t.ImmediateBin.ToSlice()) + t.log.Debugf("Bin once done: %v", t.OnceDoneBin.ToSlice()) +} +func (t *TorrentManager) setToBinImmediately(torrentId string) { + t.log.Debugf("Set to delete immediately: %s", torrentId) + t.ImmediateBin.Add(torrentId) + t.persistBins() +} + +func (t *TorrentManager) setToBinOnceDone(torrentId string) { + t.log.Debugf("Set to delete once completed: %s", torrentId) + t.OnceDoneBin.Add(torrentId) + t.persistBins() +} + +func (t *TorrentManager) binImmediately(torrentId string) bool { + if t.ImmediateBin.Contains(torrentId) { + if err := t.api.DeleteTorrent(torrentId); err != nil { + t.log.Errorf("Failed to delete torrent %s: %v", torrentId, err) + return false + } + t.ImmediateBin.Remove(torrentId) + return true + } + return false +} + +func (t *TorrentManager) binOnceDone(torrentId string) bool { + if t.OnceDoneBin.Contains(torrentId) { + if err := t.api.DeleteTorrent(torrentId); err != nil { + t.log.Errorf("Failed to delete torrent %s: %v", torrentId, err) + return false + } + t.OnceDoneBin.Remove(torrentId) + return true + } + + // special case: xxx-yyy means if xxx is done, delete yyy + found := false + specialCases := t.OnceDoneBin.ToSlice() + for _, specialCase := range specialCases { + if strings.Contains(specialCase, "-") { + parts := strings.Split(specialCase, "-") + if parts[0] == torrentId { + if err := t.api.DeleteTorrent(parts[1]); err != nil { + t.log.Errorf("Failed to delete torrent %s: %v", parts[1], err) + continue + } + t.OnceDoneBin.Remove(specialCase) + found = true + } + } + } + + return found +} + +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.log.Errorf("Failed to marshal bin data: %v", err) + return + } + + file, err := os.Create(BINS_FILE) + if err != nil { + t.log.Errorf("Failed to create bins.json file: %v", err) + return + } + defer file.Close() + + _, err = file.Write(jsonData) + if err != nil { + t.log.Errorf("Failed to write to bins.json file: %v", err) + } +} + +func (t *TorrentManager) cleanupBins(freshIDs mapset.Set[string]) { + t.ImmediateBin.Difference(freshIDs).Each(func(id string) bool { + t.ImmediateBin.Remove(id) + return false + }) + t.OnceDoneBin.Difference(freshIDs).Each(func(id string) bool { + t.OnceDoneBin.Remove(id) + return false + }) + t.persistBins() +} diff --git a/internal/torrent/manager.go b/internal/torrent/manager.go index aba4fd2..c2dbccc 100644 --- a/internal/torrent/manager.go +++ b/internal/torrent/manager.go @@ -209,7 +209,7 @@ func (t *TorrentManager) deleteTorrentFile(hash string) { /// info functions func (t *TorrentManager) getInfoFiles() mapset.Set[string] { - files, err := filepath.Glob("data/*.info_zurg") + files, err := filepath.Glob("data/*.zurginfo") if err != nil { t.log.Warnf("Cannot get files in data directory: %v", err) return nil @@ -218,7 +218,7 @@ func (t *TorrentManager) getInfoFiles() mapset.Set[string] { } func (t *TorrentManager) writeInfoToFile(info *realdebrid.TorrentInfo) { - filePath := "data/" + info.ID + ".info_zurg" + filePath := "data/" + info.ID + ".zurginfo" file, err := os.Create(filePath) if err != nil { t.log.Warnf("Cannot create info file %s: %v", filePath, err) @@ -241,7 +241,7 @@ func (t *TorrentManager) writeInfoToFile(info *realdebrid.TorrentInfo) { } func (t *TorrentManager) readInfoFromFile(torrentID string) *realdebrid.TorrentInfo { - filePath := "data/" + torrentID + ".info_zurg" + filePath := "data/" + torrentID + ".zurginfo" file, err := os.Open(filePath) if err != nil { if os.IsNotExist(err) { @@ -262,7 +262,7 @@ func (t *TorrentManager) readInfoFromFile(torrentID string) *realdebrid.TorrentI } func (t *TorrentManager) deleteInfoFile(torrentID string) { - filePath := "data/" + torrentID + ".info_zurg" + filePath := "data/" + torrentID + ".zurginfo" _ = os.Remove(filePath) } diff --git a/internal/torrent/refresh.go b/internal/torrent/refresh.go index c42fec8..bb715f3 100644 --- a/internal/torrent/refresh.go +++ b/internal/torrent/refresh.go @@ -3,7 +3,6 @@ package torrent import ( "context" "fmt" - "os" "path/filepath" "strings" "sync" @@ -152,7 +151,7 @@ func (t *TorrentManager) refreshTorrents() []string { existingIDs := mapset.NewSet[string]() t.getInfoFiles().Each(func(path string) bool { path = filepath.Base(path) - torrentID := strings.TrimSuffix(path, ".info_zurg") + torrentID := strings.TrimSuffix(path, ".zurginfo") if !t.binOnceDone(torrentID) { existingIDs.Add(torrentID) } @@ -407,108 +406,3 @@ func (t *TorrentManager) IsPlayable(filePath string) bool { } return false } - -// initializeBins reads from bins.json and assigns values to t.trashBin and t.repairBin -func (t *TorrentManager) initializeBins() { - if _, err := os.Stat("data/bins.json"); os.IsNotExist(err) { - t.log.Warn("data/bins.json does not exist. Initializing empty bins.") - t.ImmediateBin = mapset.NewSet[string]() - t.OnceDoneBin = mapset.NewSet[string]() - return - } - - fileData, err := os.ReadFile("data/bins.json") - if err != nil { - t.log.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.log.Errorf("Failed to unmarshal bin data: %v", err) - return - } - - t.ImmediateBin = mapset.NewSet[string](data["trash_bin"]...) - t.OnceDoneBin = mapset.NewSet[string](data["repair_bin"]...) - - t.log.Debug("Successfully read bins from bins.json") - t.log.Debugf("Bin immediately: %v", t.ImmediateBin.ToSlice()) - t.log.Debugf("Bin once done: %v", t.OnceDoneBin.ToSlice()) -} -func (t *TorrentManager) setToBinImmediately(torrentId string) { - t.log.Debugf("Set to delete immediately: %s", torrentId) - t.ImmediateBin.Add(torrentId) - t.persistBins() -} - -func (t *TorrentManager) setToBinOnceDone(torrentId string) { - t.log.Debugf("Set to delete once completed: %s", torrentId) - t.OnceDoneBin.Add(torrentId) - t.persistBins() -} - -func (t *TorrentManager) binImmediately(torrentId string) bool { - if t.ImmediateBin.Contains(torrentId) { - if err := t.api.DeleteTorrent(torrentId); err != nil { - t.log.Errorf("Failed to delete torrent %s: %v", torrentId, err) - return false - } - t.ImmediateBin.Remove(torrentId) - return true - } - return false -} - -func (t *TorrentManager) binOnceDone(torrentId string) bool { - if t.OnceDoneBin.Contains(torrentId) { - if err := t.api.DeleteTorrent(torrentId); err != nil { - t.log.Errorf("Failed to delete torrent %s: %v", torrentId, err) - return false - } - t.OnceDoneBin.Remove(torrentId) - return true - } - return false -} - -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.log.Errorf("Failed to marshal bin data: %v", err) - return - } - - file, err := os.Create("data/bins.json") - if err != nil { - t.log.Errorf("Failed to create bins.json file: %v", err) - return - } - defer file.Close() - - _, err = file.Write(jsonData) - if err != nil { - t.log.Errorf("Failed to write to bins.json file: %v", err) - } -} - -func (t *TorrentManager) cleanupBins(freshIDs mapset.Set[string]) { - t.ImmediateBin.Difference(freshIDs).Each(func(id string) bool { - t.ImmediateBin.Remove(id) - return false - }) - t.OnceDoneBin.Difference(freshIDs).Each(func(id string) bool { - t.OnceDoneBin.Remove(id) - return false - }) - t.persistBins() -} diff --git a/internal/torrent/repair.go b/internal/torrent/repair.go index 165492a..8508eb7 100644 --- a/internal/torrent/repair.go +++ b/internal/torrent/repair.go @@ -192,18 +192,24 @@ func (t *TorrentManager) repair(torrent *Torrent) { if info != nil && info.Progress == 100 && !t.isStillBroken(info, brokenFiles) { // successful repair torrent.State.Event(context.Background(), "mark_as_repaired") - // delete old torrents + t.repairLog.Infof("Successfully repaired torrent %s by redownloading all files", t.GetKey(torrent)) + // delete the torrents it replaced torrent.DownloadedIDs.Each(func(torrentID string) bool { if torrentID != info.ID { t.setToBinImmediately(torrentID) } return false }) - - t.repairLog.Infof("Successfully repaired torrent %s by redownloading all files", t.GetKey(torrent)) return } else if info != nil && info.Progress != 100 { t.repairLog.Infof("Torrent %s is still in progress after redownloading but it should be repaired once done", t.GetKey(torrent)) + // once info.ID is done, we can delete the old torrent + torrent.DownloadedIDs.Each(func(torrentID string) bool { + if torrentID != info.ID { + t.setToBinOnceDone(fmt.Sprintf("%s-%s", info.ID, torrentID)) + } + return false + }) return }