diff --git a/internal/torrent/fixer.go b/internal/torrent/fixer.go index 2d858e4..a540ebf 100644 --- a/internal/torrent/fixer.go +++ b/internal/torrent/fixer.go @@ -3,6 +3,7 @@ package torrent import ( "io" "os" + "strings" "github.com/debridmediamanager/zurg/pkg/realdebrid" cmap "github.com/orcaman/concurrent-map/v2" @@ -36,19 +37,51 @@ func (t *TorrentManager) processFixers(instances []realdebrid.Torrent) { 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, "repaired") // requeue the fixer + 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 _, instance := range instances { + if instance.ID == otherId { + if instance.Progress != 100 { + t.fixers.Set(id, command) // requeue the fixer + break + } + torrent := t.getMoreInfo(instance) + 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 } } diff --git a/internal/torrent/repair.go b/internal/torrent/repair.go index 4561729..1eea89e 100644 --- a/internal/torrent/repair.go +++ b/internal/torrent/repair.go @@ -163,7 +163,7 @@ func (t *TorrentManager) repair(torrent *Torrent) { } // get all broken files - brokenFiles := getBrokenFiles(torrent) + brokenFiles, allBroken := getBrokenFiles(torrent) brokenFileIDs := getFileIDs(brokenFiles) t.log.Debugf("Torrent %s has %d broken files (ids=%s; total is %d), repairing by redownloading", t.GetKey(torrent), len(brokenFiles), brokenFileIDs, torrent.SelectedFiles.Count()) @@ -198,25 +198,50 @@ func (t *TorrentManager) repair(torrent *Torrent) { return } - if len(brokenFiles) == torrent.SelectedFiles.Count() { - // all files are broken, nothing we can do - t.log.Warnf("Torrent %s has broken cached files (cached but cannot be downloaded), you can repair it manually, marking as unfixable", t.GetKey(torrent)) - t.markAsUnfixable(torrent, "broken cache") - return - } - // second step: download the broken files if len(brokenFiles) > 0 { t.log.Infof("Repairing by downloading only the %d broken out of %d files of torrent %s", len(brokenFiles), torrent.SelectedFiles.Count(), t.GetKey(torrent)) - redownloadedInfo, err := t.redownloadTorrent(torrent, brokenFileIDs) - if err != nil { - t.log.Warnf("Cannot repair torrent %s by downloading broken files (error=%s) giving up", t.GetKey(torrent), err.Error()) + if !allBroken { + redownloadedInfo, err := t.redownloadTorrent(torrent, brokenFileIDs) + if err != nil { + t.log.Warnf("Cannot repair torrent %s by downloading broken files (error=%s) giving up", t.GetKey(torrent), err.Error()) + return + } + if redownloadedInfo != nil { + t.fixerAddCommand(redownloadedInfo.ID, "repaired") + return + } + } + + // divide the broken files into 2 groups + group1 := make([]*File, 0) + group2 := make([]*File, 0) + for idx, file := range brokenFiles { + if idx%2 == 0 { + group1 = append(group1, file) + } else { + group2 = append(group2, file) + } + } + brokenFileIDs1 := getFileIDs(group1) + redownloadedInfo1, err1 := t.redownloadTorrent(torrent, brokenFileIDs1) + if err1 != nil { + t.log.Warnf("Cannot repair torrent %s by downloading broken files (error=%s) giving up", t.GetKey(torrent), err1.Error()) return } - if redownloadedInfo != nil { - t.fixerAddCommand(redownloadedInfo.ID, "repaired") - return + if redownloadedInfo1 != nil { + brokenFileIDs2 := getFileIDs(group2) + redownloadedInfo2, err2 := t.redownloadTorrent(torrent, brokenFileIDs2) + if err2 != nil { + t.log.Warnf("Cannot repair torrent %s by downloading broken files (error=%s) giving up", t.GetKey(torrent), err2.Error()) + return + } + if redownloadedInfo2 != nil { + t.fixerAddCommand(redownloadedInfo2.ID, fmt.Sprintf("repaired_with:%s", redownloadedInfo1.ID)) + return + } } + } else { t.log.Infof("Torrent %s has no broken files to repair", t.GetKey(torrent)) } @@ -447,14 +472,17 @@ func (t *TorrentManager) markAsUnfixable(torrent *Torrent, reason string) { } // getBrokenFiles returns the files that are not http links and not deleted -func getBrokenFiles(torrent *Torrent) []*File { +func getBrokenFiles(torrent *Torrent) ([]*File, bool) { var brokenFiles []*File + allBroken := true torrent.SelectedFiles.IterCb(func(_ string, file *File) { if file.IsBroken && !file.IsDeleted { brokenFiles = append(brokenFiles, file) + } else { + allBroken = false } }) - return brokenFiles + return brokenFiles, allBroken } // isStillBroken checks if the torrent is still broken