138 lines
3.9 KiB
Go
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(¤t, 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()
|
|
}
|
|
}()
|
|
}
|