From fbd5ccbf4aebd01eb9cea353b3128e83029a380f Mon Sep 17 00:00:00 2001 From: Ben Sarmiento Date: Fri, 24 May 2024 17:38:39 +0200 Subject: [PATCH] Fix repair fsm --- internal/torrent/refresh.go | 5 +-- internal/torrent/repair.go | 75 +++++++++++++++++--------------- internal/universal/downloader.go | 32 ++++---------- 3 files changed, 48 insertions(+), 64 deletions(-) diff --git a/internal/torrent/refresh.go b/internal/torrent/refresh.go index 12ff71a..86a2467 100644 --- a/internal/torrent/refresh.go +++ b/internal/torrent/refresh.go @@ -352,18 +352,15 @@ func (t *TorrentManager) mergeTorrents(existing, toMerge *Torrent) *Torrent { brokenCount := 0 okCount := 0 - wtfCount := 0 mergedTorrent.SelectedFiles.IterCb(func(key string, file *File) { if file.State.Is("broken_file") { brokenCount++ } else if file.State.Is("ok_file") { okCount++ - } else { - wtfCount++ } }) - if brokenCount == 0 && okCount > 0 && mergedTorrent.State.Can("mark_as_repaired") { + if brokenCount == 0 && okCount > 0 { if err := mergedTorrent.State.Event(context.Background(), "mark_as_repaired"); err != nil { t.log.Errorf("Cannot repair torrent %s: %v", t, t.GetKey(mergedTorrent), err) } diff --git a/internal/torrent/repair.go b/internal/torrent/repair.go index 622c391..4e08a55 100644 --- a/internal/torrent/repair.go +++ b/internal/torrent/repair.go @@ -77,6 +77,16 @@ func (t *TorrentManager) invokeRepair(torrent *Torrent) { // TriggerRepair allows an on-demand repair to be initiated. func (t *TorrentManager) TriggerRepair(torrent *Torrent) { + if torrent != nil { + if err := torrent.State.Event(context.Background(), "break_torrent"); err != nil { + t.log.Errorf("Failed to mark torrent %s as broken: %v", t.GetKey(torrent), err) + return + } + if !t.Config.EnableRepair() { + t.log.Warnf("Repair is disabled, skipping repair for torrent %s", t.GetKey(torrent)) + return + } + } t.repairTrigger <- torrent } @@ -155,17 +165,14 @@ func (t *TorrentManager) Repair(torrent *Torrent, wg *sync.WaitGroup) { }) } +// repairman func (t *TorrentManager) repair(torrent *Torrent) { - torrentIDs := torrent.DownloadedIDs.ToSlice() - t.log.Infof("Started repair process for torrent %s (ids=%v)", t.GetKey(torrent), torrentIDs) + t.log.Infof("Started repair process for torrent %s (ids=%v)", t.GetKey(torrent), torrent.DownloadedIDs.ToSlice()) - // handle torrents with incomplete links for selected files - // torrent can be rar'ed by RD, so we need to check for that - if torrent.UnassignedLinks.Cardinality() > 0 && !t.assignUnassignedLinks(torrent) { + if torrent.UnassignedLinks.Cardinality() > 0 && !t.assignLinks(torrent) { return } - // get all broken files brokenFiles, allBroken := getBrokenFiles(torrent) // first step: redownload the whole torrent @@ -186,6 +193,7 @@ func (t *TorrentManager) repair(torrent *Torrent) { } return false }) + t.log.Infof("Successfully repaired torrent %s by redownloading all files", t.GetKey(torrent)) return } else if info != nil && info.Progress != 100 { @@ -256,7 +264,7 @@ func (t *TorrentManager) repair(torrent *Torrent) { } } -func (t *TorrentManager) assignUnassignedLinks(torrent *Torrent) bool { +func (t *TorrentManager) assignLinks(torrent *Torrent) bool { unassignedTotal := torrent.UnassignedLinks.Cardinality() t.log.Infof("Trying to assign %d links to the %d selected of incomplete torrent %s", unassignedTotal, torrent.SelectedFiles.Count(), t.GetKey(torrent)) @@ -386,26 +394,29 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection []string) } else if strings.Contains(err.Error(), "allowed") { t.markAsUnfixable(torrent, "torrent not allowed") } - return nil, fmt.Errorf("cannot redownload torrent: %v", err) + return nil, fmt.Errorf("cannot add magnet of torrent %s (hash=%s): %v", t.GetKey(torrent), torrent.Hash, err) } newTorrentID := resp.ID - // sleep for 1 second to let RD process the magnet - time.Sleep(1 * time.Second) + time.Sleep(2 * time.Second) var info *realdebrid.TorrentInfo + retries := 0 for { - // select files + retries++ + if retries > 10 { + t.setToBinImmediately(newTorrentID) + return nil, fmt.Errorf("cannot start redownloading torrent %s (id=%s): too many retries", t.GetKey(torrent), newTorrentID) + } + err = t.api.SelectTorrentFiles(newTorrentID, finalSelection) if err != nil { t.setToBinImmediately(newTorrentID) return nil, fmt.Errorf("cannot start redownloading torrent %s (id=%s): %v", t.GetKey(torrent), newTorrentID, err) } - // sleep for 2 second to let RD process the magnet time.Sleep(2 * time.Second) - // see if the torrent is ready info, err = t.api.GetTorrentInfo(newTorrentID) if err != nil { t.setToBinImmediately(newTorrentID) @@ -415,9 +426,6 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection []string) if info.Status == "magnet_conversion" { time.Sleep(60 * time.Second) continue - } else if info.Status == "waiting_files_selection" { - time.Sleep(10 * time.Second) - return nil, fmt.Errorf("torrent %s (id=%s) is stuck on waiting_files_selection", t.GetKey(torrent), newTorrentID) } break } @@ -434,7 +442,7 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection []string) } if !isOkStatus { - t.setToBinImmediately(info.ID) + t.setToBinImmediately(newTorrentID) return nil, fmt.Errorf("the redownloaded torrent %s is in a non-OK state: %s", t.GetKey(torrent), info.Status) } @@ -540,23 +548,21 @@ func (t *TorrentManager) isStillBroken(info *realdebrid.TorrentInfo, brokenFiles State: NewFileState("broken_file"), }) } - if len(selectedFiles) == len(info.Links) { - // all links are still intact! good! - for i, file := range selectedFiles { - file.Link = info.Links[i] - if strings.HasPrefix(file.Link, "https://real-debrid.com/d/") { - file.Link = file.Link[0:39] - } - if err := file.State.Event(context.Background(), "repair_file"); err != nil { - t.log.Errorf("Failed to mark file %s as repaired: %v", file.Path, err) - return true - } - } - } else { - // if we can't assign links then it's still broken + if len(selectedFiles) != len(info.Links) { return true } + for i, file := range selectedFiles { + file.Link = info.Links[i] + if strings.HasPrefix(file.Link, "https://real-debrid.com/d/") { + file.Link = file.Link[0:39] + } + if err := file.State.Event(context.Background(), "repair_file"); err != nil { + t.log.Errorf("Failed to mark file %s as repaired: %v", file.Path, err) + return true + } + } + if len(brokenFiles) == 0 { // just check for the last file brokenFiles = append(brokenFiles, selectedFiles[len(selectedFiles)-1]) @@ -565,11 +571,8 @@ func (t *TorrentManager) isStillBroken(info *realdebrid.TorrentInfo, brokenFiles // check if the broken files can now be unrestricted for _, oldFile := range brokenFiles { for idx, newFile := range selectedFiles { - if oldFile.Bytes == newFile.Bytes { - unrestrict := t.UnrestrictFileUntilOk(selectedFiles[idx]) - if unrestrict == nil || oldFile.Bytes != unrestrict.Filesize { - return true - } + if oldFile.ID == newFile.ID && t.UnrestrictFileUntilOk(selectedFiles[idx]) == nil { + return true } } } diff --git a/internal/universal/downloader.go b/internal/universal/downloader.go index a99599e..9015011 100644 --- a/internal/universal/downloader.go +++ b/internal/universal/downloader.go @@ -63,17 +63,13 @@ func (dl *Downloader) DownloadFile( unrestrict := torMgr.UnrestrictFileUntilOk(file) if unrestrict == nil { + log.Warnf("File %s cannot be unrestricted (link=%s)", fileName, file.Link) if err := file.State.Event(context.Background(), "break_file"); err != nil { log.Errorf("File %s is stale: %v", fileName, err) http.Error(resp, "File is stale, please try again", http.StatusLocked) return } - if cfg.EnableRepair() { - log.Warnf("File %s cannot be unrestricted (link=%s) (repairing...)", fileName, file.Link) - torMgr.TriggerRepair(torrent) - } else { - log.Warnf("Repair is disabled, skipping repair for unavailable file %s (link=%s)", fileName, file.Link) - } + torMgr.TriggerRepair(torrent) http.Error(resp, "File is not available", http.StatusNotFound) return } else { @@ -152,20 +148,14 @@ func (dl *Downloader) streamFileToResponse( // Perform the request downloadResp, err := dl.client.Do(dlReq) if err != nil { - if file != nil && unrestrict.Streamable == 1 { + log.Warnf("Cannot download file %s: %v", unrestrict.Download, err) + if file != nil { if err := file.State.Event(context.Background(), "break_file"); err != nil { log.Errorf("File %s is stale: %v", file.Path, err) http.Error(resp, "File is stale, please try again", http.StatusLocked) return } - if cfg.EnableRepair() && torrent != nil { - log.Warnf("Cannot download file %s: %v (repairing...)", unrestrict.Download, err) - torMgr.TriggerRepair(torrent) - } else { - log.Warnf("Repair is disabled, skipping repair for unavailable file %s (link=%s)", file.Path, file.Link) - } - } else { - log.Warnf("Cannot download file %s: %v", unrestrict.Download, err) + torMgr.TriggerRepair(torrent) } http.Error(resp, "File is not available", http.StatusNotFound) return @@ -174,20 +164,14 @@ func (dl *Downloader) streamFileToResponse( // Check if the download was not successful if downloadResp.StatusCode/100 != 2 { - if file != nil && unrestrict.Streamable == 1 { + log.Warnf("Received a %s status code for file %s", downloadResp.Status, unrestrict.Filename) + if file != nil { if err := file.State.Event(context.Background(), "break_file"); err != nil { log.Errorf("File %s is stale: %v", file.Path, err) http.Error(resp, "File is stale, please try again", http.StatusLocked) return } - if cfg.EnableRepair() && torrent != nil { - log.Warnf("Received a %s status code for file %s (repairing...)", downloadResp.Status, file.Path) - torMgr.TriggerRepair(torrent) - } else { - log.Warnf("Repair is disabled, skipping repair for unavailable file %s (link=%s)", file.Path, file.Link) - } - } else { - log.Warnf("Received a %s status code for file %s", downloadResp.Status, unrestrict.Download) + torMgr.TriggerRepair(torrent) } http.Error(resp, "File is not available", http.StatusNotFound) return