package main import ( "os" "path/filepath" "strings" ) func (a *App) upload(t *Task) { if t.RemotePath != "" && t.Status == "done" { return } storage, root, enabled := a.storageForTask(t) if !enabled || storage == nil || root == "" { if t.Status == "upload_pending" { if err := a.db.Model(t).Updates(map[string]any{ "status": "done", "error": "", }).Error; err != nil { return } t.Status, t.Error = "done", "" } return } if !a.claimUpload(t) { return } remote := strings.TrimRight(root, "/") + "/" + safe(t.Artist) + "/" + safe(t.Album) + "/" + safe(t.Title) + "." + safe(t.Format) if err := storage.MkdirAll(filepath.ToSlash(filepath.Dir(remote))); err != nil { a.failUpload(t, err) return } fi, statErr := os.Stat(t.LocalPath) if statErr != nil { if a.recoverMissingLocalUpload(t) { return } a.failUpload(t, statErr) return } if same, _ := storage.ExistsSameSize(remote, fi.Size()); same { t.RemotePath = remote t.Status, t.Progress, t.Error = "done", 100, "" _ = a.db.Model(t).Updates(map[string]any{ "remote_path": remote, "status": "done", "progress": 100, "error": "", }).Error a.cleanupLocalFile(t) return } if err := storage.Upload(t.LocalPath, remote); err != nil { a.failUpload(t, err) return } t.RemotePath = remote t.Status, t.Progress, t.Error = "done", 100, "" if err := a.db.Model(t).Updates(map[string]any{ "remote_path": remote, "status": "done", "progress": 100, "error": "", }).Error; err != nil { return } if a.cfg.WebDAVVersionPolicy == "replace_lower" { a.cleanupLowerQualityVersions(t, storage) } a.cleanupLocalFile(t) } func (a *App) cleanupLocalFile(t *Task) { if a.cfg.LocalFilePolicy != "delete_after_upload" || t.LocalPath == "" { return } if t.AssetID != 0 { var pending int64 if err := a.db.Model(&Task{}). Where("asset_id = ? AND id <> ? AND local_path = ? AND "+ "(status IN ? OR (status = ? AND (remote_path = '' OR remote_path IS NULL)))", t.AssetID, t.ID, t.LocalPath, []string{"pending", "resolving", "downloading", "uploading", "upload_pending"}, "done").Count(&pending).Error; err != nil { return } if pending > 0 { return } } if err := os.Remove(t.LocalPath); err != nil && !os.IsNotExist(err) { t.Error = "WebDAV 上传成功,但删除本地文件失败: " + err.Error() _ = a.db.Save(t) return } if t.AssetID != 0 { _ = a.db.Model(&MediaAsset{}). Where("id = ? AND local_path = ?", t.AssetID, t.LocalPath). Updates(map[string]any{ "status": "evicted", "local_path": "", "size": 0, "owner_task_id": 0, "lease_until": nil, }).Error } } func (a *App) cleanupLowerQualityVersions(t *Task, storage RemoteStorage) { levels := map[string]int{"standard": 1, "exhigh": 2, "sq": 3, "lossless": 4, "hires": 5, "jyeffect": 6, "sky": 7, "jymaster": 8, "atmos": 9, "atmos_plus": 10, "master": 11} current := qualityRank(levels, t.ActualQuality, t.ResolvedQuality, t.Quality) if current == 0 { return } var old []Task a.db.Where("user_id = ? AND artist = ? AND title = ? AND album = ? "+ "AND status = ? AND id <> ?", t.UserID, t.Artist, t.Title, t.Album, "done", t.ID).Find(&old) for _, candidate := range old { q := qualityRank(levels, candidate.ActualQuality, candidate.ResolvedQuality, candidate.Quality) if q == 0 || q >= current || candidate.RemotePath == "" || candidate.RemotePath == t.RemotePath { continue } _ = storage.Remove(candidate.RemotePath) } } func qualityRank(levels map[string]int, values ...string) int { for _, q := range values { if n := levels[q]; n > 0 { return n } } return 0 }