Files
ddc/internal/jobs/restore.go
T

97 lines
2.1 KiB
Go
Executable File

package jobs
import (
"io/fs"
"log/slog"
"os"
"path/filepath"
"regexp"
"sort"
"time"
"ddc/internal/files"
)
var jobIDPattern = regexp.MustCompile(`^[0-9a-f]{32}$`)
func (manager *Manager) RestoreExisting() error {
entries, err := os.ReadDir(manager.settings.DownloadDir)
if err != nil {
return err
}
var queuedJobs []*Job
restoredCount := 0
for _, entry := range entries {
if !entry.IsDir() || !jobIDPattern.MatchString(entry.Name()) {
continue
}
job := manager.restoreJob(entry)
manager.store(job)
restoredCount++
if job.status == StatusQueued {
queuedJobs = append(queuedJobs, job)
}
}
sort.Slice(queuedJobs, func(first, second int) bool {
return queuedJobs[first].createdAt.Before(queuedJobs[second].createdAt)
})
for _, job := range queuedJobs {
job.workers.Add(1)
manager.launchDownload(job)
}
slog.Info("restored downloads", "total", restoredCount, "resuming", len(queuedJobs))
return nil
}
func (manager *Manager) restoreJob(entry fs.DirEntry) *Job {
directory := filepath.Join(manager.settings.DownloadDir, entry.Name())
createdAt := time.Now()
if info, err := entry.Info(); err == nil {
createdAt = info.ModTime()
}
job := manager.newJob(entry.Name(), directory, createdAt)
record, found := readRecord(directory)
if found {
job.applyRecord(record)
}
if !found || record.Phase == phaseDone {
os.Remove(filepath.Join(directory, files.PartFileName))
job.status = StatusReady
job.downloaded = true
job.fileName = firstEntryName(directory, entry.Name())
return job
}
job.downloadState = record.Download
if record.Download != nil {
job.bytesDone.Store(record.Download.DoneBytes())
job.bytesTotal.Store(record.Download.TotalBytes)
}
switch record.Phase {
case phasePaused:
job.status = StatusPaused
case phaseFailed:
job.status = StatusFailed
job.message = record.Message
default:
job.status = StatusQueued
}
return job
}
func firstEntryName(directory, fallback string) string {
entries, err := os.ReadDir(directory)
if err != nil {
return fallback
}
for _, entry := range entries {
if !files.IsInternal(entry.Name()) {
return entry.Name()
}
}
return fallback
}