Files
am-downloader/webdav_resync.go
T

138 lines
3.9 KiB
Go

package main
import (
"os"
"time"
"gorm.io/gorm"
)
// recoverMissingLocalUpload re-queues a task when its shared media file has
// disappeared. This can happen after an older version deleted a shared file
// after another user's upload. The asset is invalidated so the next worker
// downloads it again instead of retrying an impossible WebDAV upload forever.
func (a *App) recoverMissingLocalUpload(t *Task) bool {
if t.AssetID == 0 || t.LocalPath == "" {
return false
}
if _, err := os.Stat(t.LocalPath); err == nil || !os.IsNotExist(err) {
return false
}
var queued bool
err := a.transactionRetry(func(tx *gorm.DB) error {
var asset MediaAsset
if err := tx.First(&asset, t.AssetID).Error; err != nil {
return err
}
if asset.Status == "done" && asset.LocalPath != "" {
if _, statErr := os.Stat(asset.LocalPath); statErr == nil {
if err := tx.Model(&Task{}).Where("id = ?", t.ID).Updates(map[string]any{
"local_path": asset.LocalPath, "format": asset.Format,
"actual_quality": asset.ActualQuality,
"status": "upload_pending", "error": "共享资源已恢复,正在继续上传",
}).Error; err != nil {
return err
}
queued = true
return nil
}
}
if asset.LocalPath != "" && asset.LocalPath != t.LocalPath {
return nil
}
asset.Status = "error"
asset.OwnerTaskID = 0
asset.LeaseUntil = time.Time{}
asset.LocalPath = ""
asset.Size = 0
if err := tx.Save(&asset).Error; err != nil {
return err
}
if err := tx.Model(&Task{}).Where("id = ?", t.ID).Updates(map[string]any{
"status": "pending", "error": "本地共享文件不存在,正在重新下载",
"progress": 0, "downloaded_bytes": 0,
}).Error; err != nil {
return err
}
queued = true
return nil
})
if err != nil || !queued {
return false
}
a.enqueueTask(t.ID)
return true
}
// resyncPendingUploads repairs tasks that finished locally while WebDAV was
// unavailable or the user's WebDAV permission was not yet configured.
func (a *App) resyncPendingUploads() {
var tasks []Task
if err := a.db.Where("local_path <> ? AND ((status IN ? AND (remote_path = '' OR remote_path IS NULL)) OR status = ?)",
"", []string{"done", "upload_pending"}, "error").Order("id asc").Limit(100).Find(&tasks).Error; err != nil {
return
}
for i := range tasks {
t := tasks[i]
if t.Status == "error" {
status, err := a.taskReservationStatus(t.ID, t.CreditReservationID)
if err != nil {
continue
}
if status == "committed" {
if err := a.repairCommittedTask(&t); err != nil {
continue
}
} else {
var asset MediaAsset
if t.AssetID == 0 || a.db.First(&asset, t.AssetID).Error != nil ||
asset.Status != "done" || asset.LocalPath == "" {
continue
}
if _, statErr := os.Stat(asset.LocalPath); statErr != nil {
continue
}
t.LocalPath = asset.LocalPath
t.Format = asset.Format
t.ActualQuality = asset.ActualQuality
t.Status = "upload_pending"
t.Error = "共享资源已完成,历史失败任务恢复上传"
if err := a.db.Model(&t).Updates(map[string]any{
"local_path": t.LocalPath, "format": t.Format,
"actual_quality": t.ActualQuality,
"status": t.Status, "error": t.Error,
}).Error; err != nil {
continue
}
}
}
if _, _, ok := a.storageForTask(&t); !ok {
continue
}
if a.recoverMissingLocalUpload(&t) {
continue
}
var current Task
if a.db.First(&current, t.ID).Error != nil {
continue
}
if current.Status == "done" || current.Status == "upload_pending" {
a.enqueueTask(current.ID)
}
}
}
// startWebDAVResync runs a small periodic repair loop. It is intentionally
// conservative: only tasks with a completed local file and no confirmed remote path are retried.
func (a *App) startWebDAVResync() {
go func() {
a.resyncPendingUploads()
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for range ticker.C {
a.resyncPendingUploads()
}
}()
}