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
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)
}

View File

@@ -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
}
}
}

View File

@@ -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