Repair enqueque
This commit is contained in:
@@ -49,8 +49,8 @@ type TorrentManager struct {
|
||||
latestState *LibraryState
|
||||
inProgressHashes mapset.Set[string]
|
||||
|
||||
repairTrigger chan *Torrent
|
||||
repairQueue mapset.Set[*Torrent]
|
||||
repairChan chan *Torrent
|
||||
RepairQueue mapset.Set[*Torrent]
|
||||
repairRunning bool
|
||||
repairRunningMu sync.Mutex
|
||||
|
||||
@@ -129,7 +129,7 @@ func NewTorrentManager(cfg config.ConfigInterface, api *realdebrid.RealDebrid, w
|
||||
|
||||
t.setNewLatestState(t.getCurrentState())
|
||||
|
||||
t.TriggerRepair(nil)
|
||||
t.EnqueueForRepair(nil)
|
||||
})
|
||||
|
||||
return t
|
||||
|
||||
@@ -21,13 +21,14 @@ const (
|
||||
|
||||
func (t *TorrentManager) StartRepairJob() {
|
||||
if !t.Config.EnableRepair() {
|
||||
t.repairLog.Debug("Repair is disabled, skipping repair job")
|
||||
t.repairLog.Warn("Repair is disabled, skipping repair job")
|
||||
return
|
||||
}
|
||||
t.repairTrigger = make(chan *Torrent)
|
||||
t.repairQueue = mapset.NewSet[*Torrent]()
|
||||
|
||||
t.repairChan = make(chan *Torrent)
|
||||
t.RepairQueue = mapset.NewSet[*Torrent]()
|
||||
t.RepairAllTrigger = make(chan struct{})
|
||||
// there is 1 repair worker, with max 1 blocking task
|
||||
|
||||
t.workerPool.Submit(func() {
|
||||
t.repairLog.Debug("Starting periodic repair job")
|
||||
repairTicker := time.NewTicker(time.Duration(t.Config.GetRepairEveryMins()) * time.Minute)
|
||||
@@ -36,11 +37,20 @@ func (t *TorrentManager) StartRepairJob() {
|
||||
for {
|
||||
select {
|
||||
case <-repairTicker.C:
|
||||
t.invokeRepair(nil)
|
||||
t.repairLog.Debug("Periodic repair started; searching for broken torrents")
|
||||
t.EnqueueForRepair(nil)
|
||||
case <-t.RepairAllTrigger:
|
||||
t.invokeRepair(nil)
|
||||
case torrent := <-t.repairTrigger:
|
||||
// On-demand trigger with a specific torrent
|
||||
t.repairLog.Debug("Manual repair of all torrents triggered; searching for broken torrents")
|
||||
t.EnqueueForRepair(nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// there is 1 repair worker, with max 1 blocking task
|
||||
t.workerPool.Submit(func() {
|
||||
for {
|
||||
select {
|
||||
case torrent := <-t.repairChan:
|
||||
t.invokeRepair(torrent)
|
||||
case <-t.RepairWorkerKillSwitch:
|
||||
t.repairLog.Info("Stopping periodic repair job")
|
||||
@@ -50,64 +60,61 @@ func (t *TorrentManager) StartRepairJob() {
|
||||
})
|
||||
}
|
||||
|
||||
// EnqueueForRepair allows an on-demand repair to be initiated.
|
||||
func (t *TorrentManager) EnqueueForRepair(torrent *Torrent) {
|
||||
if !t.Config.EnableRepair() {
|
||||
if torrent != nil {
|
||||
t.repairLog.Warnf("Repair is disabled, skipping repair for torrent %s", t.GetKey(torrent))
|
||||
}
|
||||
return
|
||||
}
|
||||
if torrent != nil && torrent.State.Event(context.Background(), "break_torrent") != nil {
|
||||
// t.repairLog.Errorf("Failed to mark torrent %s as broken: %v", t.GetKey(torrent), err)
|
||||
return
|
||||
}
|
||||
t.workerPool.Submit(func() {
|
||||
t.invokeRepair(torrent)
|
||||
})
|
||||
}
|
||||
|
||||
// invokeRepair runs a sync repair job
|
||||
func (t *TorrentManager) invokeRepair(torrent *Torrent) {
|
||||
t.repairRunningMu.Lock()
|
||||
if t.repairRunning {
|
||||
t.repairRunningMu.Unlock()
|
||||
t.repairQueue.Add(torrent)
|
||||
t.RepairQueue.Add(torrent)
|
||||
// don't do anything if repair is already running
|
||||
return
|
||||
}
|
||||
|
||||
t.repairRunning = true
|
||||
t.repairRunningMu.Unlock()
|
||||
|
||||
// Execute the repair job
|
||||
t.repairAll(torrent)
|
||||
time.Sleep(10 * time.Second)
|
||||
t.executeRepairJob(torrent)
|
||||
|
||||
// After repair is done
|
||||
t.repairRunningMu.Lock()
|
||||
t.repairRunning = false
|
||||
t.repairRunningMu.Unlock()
|
||||
|
||||
// before we let go, let's check repairQueue
|
||||
queuedTorrent, exists := t.repairQueue.Pop()
|
||||
if exists {
|
||||
t.TriggerRepair(queuedTorrent)
|
||||
if queuedTorrent, exists := t.RepairQueue.Pop(); exists {
|
||||
t.workerPool.Submit(func() {
|
||||
t.invokeRepair(queuedTorrent)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TriggerRepair allows an on-demand repair to be initiated.
|
||||
func (t *TorrentManager) TriggerRepair(torrent *Torrent) {
|
||||
if !t.Config.EnableRepair() {
|
||||
if torrent != nil {
|
||||
t.repairLog.Warnf("Repair is disabled, skipping repair for torrent %s", t.GetKey(torrent))
|
||||
} else {
|
||||
t.repairLog.Warn("Repair is disabled, skipping repair")
|
||||
}
|
||||
return
|
||||
}
|
||||
if torrent != nil {
|
||||
if err := torrent.State.Event(context.Background(), "break_torrent"); err != nil {
|
||||
// t.repairLog.Errorf("Failed to mark torrent %s as broken: %v", t.GetKey(torrent), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
t.repairTrigger <- torrent
|
||||
}
|
||||
|
||||
func (t *TorrentManager) repairAll(torrent *Torrent) {
|
||||
// todo: a more elegant way to do this
|
||||
func (t *TorrentManager) executeRepairJob(torrent *Torrent) {
|
||||
var haystack cmap.ConcurrentMap[string, *Torrent]
|
||||
if torrent == nil {
|
||||
haystack, _ = t.DirectoryMap.Get(INT_ALL)
|
||||
t.repairLog.Debug("Periodic repair started; searching for broken torrents")
|
||||
} else {
|
||||
haystack = cmap.New[*Torrent]()
|
||||
haystack.Set("", torrent)
|
||||
}
|
||||
|
||||
// collect all torrents that need to be repaired
|
||||
// collect all torrents that need to be repaired asynchronously
|
||||
toRepair := mapset.NewSet[*Torrent]()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
@@ -115,7 +122,8 @@ func (t *TorrentManager) repairAll(torrent *Torrent) {
|
||||
wg.Add(1)
|
||||
t.workerPool.Submit(func() {
|
||||
defer wg.Done()
|
||||
if torrent.UnrepairableReason != "" {
|
||||
canExtract := t.Config.GetRarAction() == "extract" && strings.Contains(torrent.UnrepairableReason, "rar")
|
||||
if torrent.UnrepairableReason != "" || !canExtract {
|
||||
return
|
||||
}
|
||||
// check 1: for broken files
|
||||
@@ -144,7 +152,7 @@ func (t *TorrentManager) repairAll(torrent *Torrent) {
|
||||
|
||||
toRepair.Each(func(torrent *Torrent) bool {
|
||||
wg.Add(1)
|
||||
t.Repair(torrent, &wg)
|
||||
t.repair(torrent, &wg)
|
||||
return false
|
||||
})
|
||||
wg.Wait()
|
||||
@@ -152,24 +160,21 @@ func (t *TorrentManager) repairAll(torrent *Torrent) {
|
||||
t.repairLog.Infof("Finished periodic repair sequence for %d broken torrent(s)", toRepair.Cardinality())
|
||||
}
|
||||
|
||||
func (t *TorrentManager) Repair(torrent *Torrent, wg *sync.WaitGroup) {
|
||||
// repairman
|
||||
func (t *TorrentManager) repair(torrent *Torrent, wg *sync.WaitGroup) {
|
||||
defer wg.Done()
|
||||
|
||||
if err := torrent.State.Event(context.Background(), "repair_torrent"); err != nil && t.inProgressHashes.Contains(torrent.Hash) {
|
||||
// t.repairLog.Errorf("Failed to mark torrent %s as under repair: %v", t.GetKey(torrent), err)
|
||||
return
|
||||
}
|
||||
|
||||
// blocks for approx 45 minutes if active torrents are full
|
||||
if !t.canCapacityHandle() {
|
||||
t.repairLog.Error("Blocked for too long due to limit of active torrents, cannot continue with the repair")
|
||||
return
|
||||
}
|
||||
|
||||
if err := torrent.State.Event(context.Background(), "repair_torrent"); err != nil && t.inProgressHashes.Contains(torrent.Hash) {
|
||||
// t.repairLog.Errorf("Failed to mark torrent %s as under repair: %v", t.GetKey(torrent), err)
|
||||
return
|
||||
}
|
||||
t.repair(torrent)
|
||||
}
|
||||
|
||||
// repairman
|
||||
func (t *TorrentManager) repair(torrent *Torrent) {
|
||||
t.repairLog.Infof("Started repair process for torrent %s (ids=%v)", t.GetKey(torrent), torrent.DownloadedIDs.ToSlice())
|
||||
|
||||
if torrent.UnassignedLinks.Cardinality() > 0 && !t.assignLinks(torrent) {
|
||||
@@ -311,7 +316,6 @@ func (t *TorrentManager) assignLinks(torrent *Torrent) bool {
|
||||
// try to assign to a selected file
|
||||
assigned := false
|
||||
torrent.SelectedFiles.IterCb(func(_ string, file *File) {
|
||||
// base it on size because why not?
|
||||
if !assigned && file.State.Is("broken_file") && file.Bytes == unrestrict.Filesize && strings.HasSuffix(strings.ToLower(file.Path), strings.ToLower(unrestrict.Filename)) {
|
||||
file.Link = link
|
||||
assignedLinks = append(assignedLinks, link)
|
||||
@@ -335,6 +339,7 @@ func (t *TorrentManager) assignLinks(torrent *Torrent) bool {
|
||||
// t.log.Debugf("contents: %v", contents)
|
||||
rarCount++
|
||||
} else {
|
||||
// it's possible that it is already repaired
|
||||
t.repairLog.Warnf("Cannot assign %s to any file in torrent %s", unrestrict.Filename, t.GetKey(torrent))
|
||||
}
|
||||
newUnassignedLinks.Set(link, unrestrict)
|
||||
@@ -508,10 +513,12 @@ func (t *TorrentManager) redownloadTorrent(torrent *Torrent, selection []string)
|
||||
if info.Progress == 100 && len(info.Links) != len(selection) {
|
||||
t.setToBinImmediately(newTorrentID)
|
||||
return nil, fmt.Errorf("only got %d links but we need %d", len(info.Links), len(selection))
|
||||
} else if info.Progress != 100 {
|
||||
t.repairLog.Infof("Downloading torrent %s (id=%s, progress=%d)", t.GetKey(torrent), info.ID, info.Progress)
|
||||
} else {
|
||||
t.repairLog.Infof("Downloaded %d file(s) of torrent %s (id=%s)", len(selection), t.GetKey(torrent), info.ID, info.Progress)
|
||||
}
|
||||
|
||||
t.repairLog.Infof("Redownloading %d file(s) of torrent %s successful (id=%s, progress=%d)", len(selection), t.GetKey(torrent), info.ID, info.Progress)
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user