Fix repair fsm

This commit is contained in:
Ben Sarmiento
2024-05-24 17:38:39 +02:00
parent 9ecbb5d892
commit fbd5ccbf4a
3 changed files with 48 additions and 64 deletions

View File

@@ -352,18 +352,15 @@ func (t *TorrentManager) mergeTorrents(existing, toMerge *Torrent) *Torrent {
brokenCount := 0 brokenCount := 0
okCount := 0 okCount := 0
wtfCount := 0
mergedTorrent.SelectedFiles.IterCb(func(key string, file *File) { mergedTorrent.SelectedFiles.IterCb(func(key string, file *File) {
if file.State.Is("broken_file") { if file.State.Is("broken_file") {
brokenCount++ brokenCount++
} else if file.State.Is("ok_file") { } else if file.State.Is("ok_file") {
okCount++ 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 { 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) t.log.Errorf("Cannot repair torrent %s: %v", t, t.GetKey(mergedTorrent), err)
} }

View File

@@ -77,6 +77,16 @@ func (t *TorrentManager) invokeRepair(torrent *Torrent) {
// TriggerRepair allows an on-demand repair to be initiated. // TriggerRepair allows an on-demand repair to be initiated.
func (t *TorrentManager) TriggerRepair(torrent *Torrent) { 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 t.repairTrigger <- torrent
} }
@@ -155,17 +165,14 @@ func (t *TorrentManager) Repair(torrent *Torrent, wg *sync.WaitGroup) {
}) })
} }
// repairman
func (t *TorrentManager) repair(torrent *Torrent) { func (t *TorrentManager) repair(torrent *Torrent) {
torrentIDs := torrent.DownloadedIDs.ToSlice() t.log.Infof("Started repair process for torrent %s (ids=%v)", t.GetKey(torrent), torrent.DownloadedIDs.ToSlice())
t.log.Infof("Started repair process for torrent %s (ids=%v)", t.GetKey(torrent), torrentIDs)
// handle torrents with incomplete links for selected files if torrent.UnassignedLinks.Cardinality() > 0 && !t.assignLinks(torrent) {
// torrent can be rar'ed by RD, so we need to check for that
if torrent.UnassignedLinks.Cardinality() > 0 && !t.assignUnassignedLinks(torrent) {
return return
} }
// get all broken files
brokenFiles, allBroken := getBrokenFiles(torrent) brokenFiles, allBroken := getBrokenFiles(torrent)
// first step: redownload the whole torrent // first step: redownload the whole torrent
@@ -186,6 +193,7 @@ func (t *TorrentManager) repair(torrent *Torrent) {
} }
return false return false
}) })
t.log.Infof("Successfully repaired torrent %s by redownloading all files", t.GetKey(torrent)) t.log.Infof("Successfully repaired torrent %s by redownloading all files", t.GetKey(torrent))
return return
} else if info != nil && info.Progress != 100 { } 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() 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)) 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") { } else if strings.Contains(err.Error(), "allowed") {
t.markAsUnfixable(torrent, "torrent not 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 newTorrentID := resp.ID
// sleep for 1 second to let RD process the magnet time.Sleep(2 * time.Second)
time.Sleep(1 * time.Second)
var info *realdebrid.TorrentInfo var info *realdebrid.TorrentInfo
retries := 0
for { 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) err = t.api.SelectTorrentFiles(newTorrentID, finalSelection)
if err != nil { if err != nil {
t.setToBinImmediately(newTorrentID) t.setToBinImmediately(newTorrentID)
return nil, fmt.Errorf("cannot start redownloading torrent %s (id=%s): %v", t.GetKey(torrent), newTorrentID, err) 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) time.Sleep(2 * time.Second)
// see if the torrent is ready
info, err = t.api.GetTorrentInfo(newTorrentID) info, err = t.api.GetTorrentInfo(newTorrentID)
if err != nil { if err != nil {
t.setToBinImmediately(newTorrentID) t.setToBinImmediately(newTorrentID)
@@ -415,9 +426,6 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection []string)
if info.Status == "magnet_conversion" { if info.Status == "magnet_conversion" {
time.Sleep(60 * time.Second) time.Sleep(60 * time.Second)
continue 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 break
} }
@@ -434,7 +442,7 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection []string)
} }
if !isOkStatus { 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) 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"), State: NewFileState("broken_file"),
}) })
} }
if len(selectedFiles) == len(info.Links) { 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
return true 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 { if len(brokenFiles) == 0 {
// just check for the last file // just check for the last file
brokenFiles = append(brokenFiles, selectedFiles[len(selectedFiles)-1]) 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 // check if the broken files can now be unrestricted
for _, oldFile := range brokenFiles { for _, oldFile := range brokenFiles {
for idx, newFile := range selectedFiles { for idx, newFile := range selectedFiles {
if oldFile.Bytes == newFile.Bytes { if oldFile.ID == newFile.ID && t.UnrestrictFileUntilOk(selectedFiles[idx]) == nil {
unrestrict := t.UnrestrictFileUntilOk(selectedFiles[idx]) return true
if unrestrict == nil || oldFile.Bytes != unrestrict.Filesize {
return true
}
} }
} }
} }

View File

@@ -63,17 +63,13 @@ func (dl *Downloader) DownloadFile(
unrestrict := torMgr.UnrestrictFileUntilOk(file) unrestrict := torMgr.UnrestrictFileUntilOk(file)
if unrestrict == nil { 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 { if err := file.State.Event(context.Background(), "break_file"); err != nil {
log.Errorf("File %s is stale: %v", fileName, err) log.Errorf("File %s is stale: %v", fileName, err)
http.Error(resp, "File is stale, please try again", http.StatusLocked) http.Error(resp, "File is stale, please try again", http.StatusLocked)
return return
} }
if cfg.EnableRepair() { torMgr.TriggerRepair(torrent)
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)
}
http.Error(resp, "File is not available", http.StatusNotFound) http.Error(resp, "File is not available", http.StatusNotFound)
return return
} else { } else {
@@ -152,20 +148,14 @@ func (dl *Downloader) streamFileToResponse(
// Perform the request // Perform the request
downloadResp, err := dl.client.Do(dlReq) downloadResp, err := dl.client.Do(dlReq)
if err != nil { 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 { if err := file.State.Event(context.Background(), "break_file"); err != nil {
log.Errorf("File %s is stale: %v", file.Path, err) log.Errorf("File %s is stale: %v", file.Path, err)
http.Error(resp, "File is stale, please try again", http.StatusLocked) http.Error(resp, "File is stale, please try again", http.StatusLocked)
return return
} }
if cfg.EnableRepair() && torrent != nil { torMgr.TriggerRepair(torrent)
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)
} }
http.Error(resp, "File is not available", http.StatusNotFound) http.Error(resp, "File is not available", http.StatusNotFound)
return return
@@ -174,20 +164,14 @@ func (dl *Downloader) streamFileToResponse(
// Check if the download was not successful // Check if the download was not successful
if downloadResp.StatusCode/100 != 2 { 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 { if err := file.State.Event(context.Background(), "break_file"); err != nil {
log.Errorf("File %s is stale: %v", file.Path, err) log.Errorf("File %s is stale: %v", file.Path, err)
http.Error(resp, "File is stale, please try again", http.StatusLocked) http.Error(resp, "File is stale, please try again", http.StatusLocked)
return return
} }
if cfg.EnableRepair() && torrent != nil { torMgr.TriggerRepair(torrent)
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)
} }
http.Error(resp, "File is not available", http.StatusNotFound) http.Error(resp, "File is not available", http.StatusNotFound)
return return