Store unfinished tasks on disks

This commit is contained in:
2026-04-24 07:10:26 +03:00
parent 0afc1d761b
commit b8eabee393
4 changed files with 264 additions and 10 deletions

View File

@@ -2,6 +2,7 @@ package copier
import (
"context"
"encoding/json"
"errors"
"fmt"
"math/rand/v2"
@@ -73,6 +74,15 @@ func (c *Copier) LastCopiedAt(diskID string) (time.Time, bool, error) {
}
func (c *Copier) Start(ctx context.Context, opts Options) (string, error) {
return c.startTask(ctx, "", opts)
}
func (c *Copier) Resume(ctx context.Context, taskID string, opts Options) error {
_, err := c.startTask(ctx, taskID, opts)
return err
}
func (c *Copier) startTask(ctx context.Context, existingTaskID string, opts Options) (string, error) {
c.mu.Lock()
defer c.mu.Unlock()
@@ -86,8 +96,13 @@ func (c *Copier) Start(ctx context.Context, opts Options) (string, error) {
}
if opts.DestFolder == "" {
opts.DestFolder = "media"
opts.DestFolder = config.DefaultDestFolder
}
destFolder, err := config.NormalizeDestFolder(opts.DestFolder)
if err != nil {
destFolder = config.DefaultDestFolder
}
opts.DestFolder = destFolder
_, free, err := disk.DiskUsage(opts.MountPath)
if err != nil {
@@ -98,12 +113,39 @@ func (c *Copier) Start(ctx context.Context, opts Options) (string, error) {
return "", errors.New("free space is below reserve threshold")
}
t := c.tasks.Create("copy", opts.DiskID)
var taskID string
if existingTaskID == "" {
t := c.tasks.Create("copy", opts.DiskID)
payload, err := json.Marshal(opts)
if err != nil {
return "", err
}
if err := database.UpsertTask(*t, payload); err != nil {
return "", err
}
taskID = t.ID
} else {
taskID = existingTaskID
c.tasks.Update(taskID, func(t *task.Task) {
t.Status = task.StatusQueued
t.Phase = task.PhaseQueued
t.Message = "Resuming after restart..."
t.Error = ""
t.SpeedBPS = 0
t.ETASec = 0
})
if t, ok := c.tasks.Get(taskID); ok {
if err := database.UpdateTask(*t); err != nil {
return "", err
}
}
}
copyCtx, cancel := context.WithCancel(ctx)
c.cancels[opts.DiskID] = cancel
go c.run(copyCtx, t.ID, opts, database)
return t.ID, nil
go c.run(copyCtx, taskID, opts, database)
return taskID, nil
}
func (c *Copier) Cancel(diskID string) {
@@ -127,20 +169,43 @@ func (c *Copier) run(ctx context.Context, taskID string, opts Options, database
t.Message = msg
t.Progress = prog
})
if t, ok := c.tasks.Get(taskID); ok {
_ = database.UpdateTask(*t)
}
}
fail := func(err error) {
c.tasks.Update(taskID, func(t *task.Task) {
t.Status = task.StatusFailed
t.Error = err.Error()
})
if t, ok := c.tasks.Get(taskID); ok {
_ = database.UpdateTask(*t)
}
}
setStatus(task.StatusRunning, "Preparing...", 0)
c.tasks.Update(taskID, func(t *task.Task) {
t.Status = task.StatusRunning
t.Phase = task.PhasePreparing
t.Message = "Preparing..."
t.Progress = 0
t.Error = ""
})
if t, ok := c.tasks.Get(taskID); ok {
_ = database.UpdateTask(*t)
}
destRoot := filepath.Join(opts.MountPath, opts.DestFolder)
if opts.OverwriteMode == config.OverwriteDelete {
setStatus(task.StatusRunning, "Replacing destination media...", 0)
c.tasks.Update(taskID, func(t *task.Task) {
t.Status = task.StatusRunning
t.Phase = task.PhaseReplacing
t.Message = "Replacing destination media..."
t.Progress = 0
})
if t, ok := c.tasks.Get(taskID); ok {
_ = database.UpdateTask(*t)
}
if err := os.RemoveAll(destRoot); err != nil {
fail(err)
return
@@ -149,7 +214,15 @@ func (c *Copier) run(ctx context.Context, taskID string, opts Options, database
var copiedPaths map[string]struct{}
if opts.FileSelectMode == config.SelectNew {
setStatus(task.StatusRunning, "Loading copy history...", 0)
c.tasks.Update(taskID, func(t *task.Task) {
t.Status = task.StatusRunning
t.Phase = task.PhaseLoadingHistory
t.Message = "Loading copy history..."
t.Progress = 0
})
if t, ok := c.tasks.Get(taskID); ok {
_ = database.UpdateTask(*t)
}
var err error
copiedPaths, err = database.CopiedPaths(opts.DiskID)
if err != nil {
@@ -158,7 +231,15 @@ func (c *Copier) run(ctx context.Context, taskID string, opts Options, database
}
}
setStatus(task.StatusRunning, "Scanning sources...", 0)
c.tasks.Update(taskID, func(t *task.Task) {
t.Status = task.StatusRunning
t.Phase = task.PhaseScanning
t.Message = "Scanning sources..."
t.Progress = 0
})
if t, ok := c.tasks.Get(taskID); ok {
_ = database.UpdateTask(*t)
}
files, err := buildFileList(opts.MediaPath, opts.SourceRules, copiedPaths)
if err != nil {
fail(err)
@@ -204,6 +285,9 @@ func (c *Copier) run(ctx context.Context, taskID string, opts Options, database
t.SpeedBPS = 0
t.ETASec = 0
})
if t, ok := c.tasks.Get(taskID); ok {
_ = database.UpdateTask(*t)
}
return
default:
}
@@ -227,11 +311,15 @@ func (c *Copier) run(ctx context.Context, taskID string, opts Options, database
c.tasks.Update(taskID, func(t *task.Task) {
t.Status = task.StatusRunning
t.Phase = task.PhaseCopying
t.Message = msg
t.Progress = prog
t.SpeedBPS = speedBPS
t.ETASec = int(etaSec)
})
if t, ok := c.tasks.Get(taskID); ok {
_ = database.UpdateTask(*t)
}
dstAbs := filepath.Join(destRoot, f.relPath)
if err := rsyncFile(ctx, f.srcAbs, dstAbs); err != nil {
@@ -242,6 +330,9 @@ func (c *Copier) run(ctx context.Context, taskID string, opts Options, database
t.SpeedBPS = 0
t.ETASec = 0
})
if t, ok := c.tasks.Get(taskID); ok {
_ = database.UpdateTask(*t)
}
return
}
continue