Refactor for hotfix release
This commit is contained in:
210
internal/torrent/repair.go
Normal file
210
internal/torrent/repair.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package torrent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (t *TorrentManager) RepairAll() {
|
||||
_ = t.repairWorker.Submit(func() {
|
||||
t.log.Info("Checking for torrents to repair")
|
||||
t.repairAll()
|
||||
t.log.Info("Finished checking for torrents to repair")
|
||||
})
|
||||
}
|
||||
|
||||
func (t *TorrentManager) repairAll() {
|
||||
allTorrents, _ := t.DirectoryMap.Get(INT_ALL)
|
||||
var toRepair []*Torrent
|
||||
allTorrents.IterCb(func(_ string, torrent *Torrent) {
|
||||
if torrent.AnyInProgress() && torrent.ForRepair {
|
||||
t.log.Warnf("Skipping %s for repairs because it is in progress", torrent.AccessKey)
|
||||
return
|
||||
} else if torrent.ForRepair {
|
||||
toRepair = append(toRepair, torrent)
|
||||
}
|
||||
})
|
||||
t.log.Debugf("Found %d torrents to repair", len(toRepair))
|
||||
for i := range toRepair {
|
||||
t.log.Infof("Repairing %s", toRepair[i].AccessKey)
|
||||
t.repair(toRepair[i])
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TorrentManager) Repair(torrent *Torrent) {
|
||||
_ = t.repairWorker.Submit(func() {
|
||||
t.log.Info("Repairing torrent %s", torrent.AccessKey)
|
||||
t.repair(torrent)
|
||||
t.log.Info("Finished repairing torrent %s", torrent.AccessKey)
|
||||
})
|
||||
|
||||
var updatedPaths []string
|
||||
t.assignedDirectoryCb(torrent, func(directory string) {
|
||||
updatedPaths = append(updatedPaths, fmt.Sprintf("%s/%s", directory, torrent.AccessKey))
|
||||
})
|
||||
t.TriggerHookOnLibraryUpdate(updatedPaths)
|
||||
}
|
||||
|
||||
func (t *TorrentManager) repair(torrent *Torrent) {
|
||||
if torrent.AllInProgress() {
|
||||
t.log.Infof("Torrent %s is in progress, skipping repair until download is done", torrent.AccessKey)
|
||||
return
|
||||
}
|
||||
|
||||
proceed := t.canCapacityHandle() // blocks for approx 45 minutes if active torrents are full
|
||||
if !proceed {
|
||||
t.log.Error("Reached the max number of active torrents, cannot continue with the repair")
|
||||
return
|
||||
}
|
||||
|
||||
// first solution: reinsert with same selection
|
||||
if t.reinsertTorrent(torrent, "") {
|
||||
t.log.Infof("Successfully downloaded torrent %s to repair it", torrent.AccessKey)
|
||||
return
|
||||
}
|
||||
|
||||
// second solution: add only the missing files
|
||||
var missingFiles []File
|
||||
torrent.SelectedFiles.IterCb(func(_ string, file *File) {
|
||||
if !strings.HasPrefix(file.Link, "http") {
|
||||
missingFiles = append(missingFiles, *file)
|
||||
}
|
||||
})
|
||||
|
||||
// if we download a single file, it will be named differently
|
||||
// so we need to download 1 extra file to preserve the name
|
||||
// this is only relevant if we enable retain_rd_torrent_name
|
||||
if len(missingFiles) == 1 && torrent.SelectedFiles.Count() > 1 {
|
||||
// add the first file link encountered with a prefix of http
|
||||
for _, file := range torrent.SelectedFiles.Items() {
|
||||
if strings.HasPrefix(file.Link, "http") {
|
||||
missingFiles = append(missingFiles, *file)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(missingFiles) > 0 {
|
||||
t.log.Infof("Redownloading in multiple batches the %d missing files for torrent %s", len(missingFiles), torrent.AccessKey)
|
||||
// if not, last resort: add only the missing files but do it in 2 batches
|
||||
half := len(missingFiles) / 2
|
||||
missingFiles1 := strings.Join(getFileIDs(missingFiles[:half]), ",")
|
||||
missingFiles2 := strings.Join(getFileIDs(missingFiles[half:]), ",")
|
||||
if missingFiles1 != "" {
|
||||
t.reinsertTorrent(torrent, missingFiles1)
|
||||
}
|
||||
if missingFiles2 != "" {
|
||||
t.reinsertTorrent(torrent, missingFiles2)
|
||||
}
|
||||
} else {
|
||||
t.log.Warnf("Torrent %s has no missing files to repair", torrent.AccessKey)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TorrentManager) reinsertTorrent(torrent *Torrent, missingFiles string) bool {
|
||||
// missing files means missing links
|
||||
// if missingFiles is not provided
|
||||
if missingFiles == "" {
|
||||
tmpSelection := ""
|
||||
torrent.SelectedFiles.IterCb(func(_ string, file *File) {
|
||||
tmpSelection += fmt.Sprintf("%d,", file.ID) // select all files
|
||||
})
|
||||
if tmpSelection == "" {
|
||||
return true // nothing to repair
|
||||
} else {
|
||||
missingFiles = tmpSelection[:len(tmpSelection)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// redownload torrent
|
||||
resp, err := t.Api.AddMagnetHash(torrent.Hash)
|
||||
if err != nil {
|
||||
t.log.Warnf("Cannot redownload torrent: %v", err)
|
||||
return false
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// select files
|
||||
newTorrentID := resp.ID
|
||||
err = t.Api.SelectTorrentFiles(newTorrentID, missingFiles)
|
||||
if err != nil {
|
||||
t.log.Warnf("Cannot start redownloading: %v", err)
|
||||
t.Api.DeleteTorrent(newTorrentID)
|
||||
return false
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// see if the torrent is ready
|
||||
info, err := t.Api.GetTorrentInfo(newTorrentID)
|
||||
if err != nil {
|
||||
t.log.Warnf("Cannot get info on redownloaded torrent id=%s : %v", newTorrentID, err)
|
||||
t.Api.DeleteTorrent(newTorrentID)
|
||||
return false
|
||||
}
|
||||
|
||||
if info.Status == "magnet_error" || info.Status == "error" || info.Status == "virus" || info.Status == "dead" {
|
||||
t.log.Warnf("The redownloaded torrent id=%s is in error state: %s", newTorrentID, info.Status)
|
||||
t.Api.DeleteTorrent(newTorrentID)
|
||||
return false
|
||||
}
|
||||
|
||||
if info.Progress != 100 {
|
||||
t.log.Infof("Torrent id=%s is not cached anymore so we have to wait until completion (this should fix the issue already)", info.ID)
|
||||
return true
|
||||
}
|
||||
|
||||
missingCount := len(strings.Split(missingFiles, ","))
|
||||
if len(info.Links) != missingCount {
|
||||
t.log.Infof("It did not fix the issue for id=%s, only got %d files but we need %d, undoing", info.ID, len(info.Links), missingCount)
|
||||
t.Api.DeleteTorrent(newTorrentID)
|
||||
return false
|
||||
}
|
||||
|
||||
t.log.Infof("Repair successful id=%s", newTorrentID)
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *TorrentManager) canCapacityHandle() bool {
|
||||
// max waiting time is 45 minutes
|
||||
const maxRetries = 50
|
||||
const baseDelay = 1 * time.Second
|
||||
const maxDelay = 60 * time.Second
|
||||
retryCount := 0
|
||||
for {
|
||||
count, err := t.Api.GetActiveTorrentCount()
|
||||
if err != nil {
|
||||
t.log.Warnf("Cannot get active downloads count: %v", err)
|
||||
if retryCount >= maxRetries {
|
||||
t.log.Error("Max retries reached. Exiting.")
|
||||
return false
|
||||
}
|
||||
delay := time.Duration(math.Pow(2, float64(retryCount))) * baseDelay
|
||||
if delay > maxDelay {
|
||||
delay = maxDelay
|
||||
}
|
||||
time.Sleep(delay)
|
||||
retryCount++
|
||||
continue
|
||||
}
|
||||
|
||||
if count.DownloadingCount < count.MaxNumberOfTorrents {
|
||||
// t.log.Infof("We can still add a new torrent, we have capacity for %d more", count.MaxNumberOfTorrents-count.DownloadingCount)
|
||||
return true
|
||||
}
|
||||
|
||||
delay := time.Duration(math.Pow(2, float64(retryCount))) * baseDelay
|
||||
if delay > maxDelay {
|
||||
delay = maxDelay
|
||||
}
|
||||
t.log.Infof("We have reached the max number of active torrents, waiting for %s seconds before retrying", delay)
|
||||
|
||||
if retryCount >= maxRetries {
|
||||
t.log.Error("Max retries reached. Exiting.")
|
||||
return false
|
||||
}
|
||||
|
||||
time.Sleep(delay)
|
||||
retryCount++
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user