feat: add download progress and deduplication

This commit is contained in:
OpenClaw Agent
2026-09-16 02:20:43 +08:00
parent 1de4f9d85d
commit 0da24cb183
2 changed files with 82 additions and 21 deletions
+80 -19
View File
@@ -22,21 +22,24 @@ import (
)
type Task struct {
ID uint `gorm:"primaryKey" json:"id"`
Source string `json:"source"`
RID string `json:"rid"`
Title string `json:"title"`
Artist string `json:"artist"`
Album string `json:"album"`
Pic string `json:"pic"`
Quality string `json:"quality"`
Format string `json:"format"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
LocalPath string `json:"localPath,omitempty"`
RemotePath string `json:"remotePath,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID uint `gorm:"primaryKey" json:"id"`
Source string `json:"source"`
RID string `json:"rid"`
Title string `json:"title"`
Artist string `json:"artist"`
Album string `json:"album"`
Pic string `json:"pic"`
Quality string `json:"quality"`
Format string `json:"format"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
LocalPath string `json:"localPath,omitempty"`
RemotePath string `json:"remotePath,omitempty"`
TotalBytes int64 `json:"totalBytes"`
DownloadedBytes int64 `json:"downloadedBytes"`
Progress float64 `json:"progress"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type Setting struct {
@@ -146,6 +149,11 @@ func (a *App) addTask(c *gin.Context) {
if in.Quality == "" {
in.Quality = a.cfg.DefaultQuality
}
var existing Task
if a.db.Where("source = ? AND rid = ? AND quality = ? AND status IN ?", in.Source, in.RID, in.Quality, []string{"pending", "resolving", "downloading", "uploading", "done"}).First(&existing).Error == nil {
c.JSON(200, existing)
return
}
t := Task{Source: in.Source, RID: in.RID, Title: in.Title, Artist: in.Artist, Album: in.Album, Pic: in.Pic, Quality: in.Quality, Status: "pending"}
if err := a.db.Create(&t).Error; err != nil {
c.JSON(500, gin.H{"error": err.Error()})
@@ -205,7 +213,7 @@ func (a *App) download(id uint) {
if a.db.First(&t, id).Error != nil {
return
}
a.db.Model(&t).Updates(map[string]any{"status": "downloading", "error": ""})
a.db.Model(&t).Updates(map[string]any{"status": "resolving", "error": "", "progress": 0})
info, err := a.resolve(t)
if err != nil {
a.fail(&t, err)
@@ -241,12 +249,46 @@ func (a *App) download(id uint) {
a.fail(&t, fmt.Errorf("media http %d", resp.StatusCode))
return
}
f, err := os.Create(local)
t.Status = "downloading"
t.TotalBytes = resp.ContentLength
t.DownloadedBytes = 0
t.Progress = 0
a.db.Save(&t)
tmp := local + ".part"
f, err := os.Create(tmp)
if err != nil {
a.fail(&t, err)
return
}
_, err = io.Copy(f, resp.Body)
var n int64
buf := make([]byte, 256*1024)
lastReport := int64(0)
for {
nr, er := resp.Body.Read(buf)
if nr > 0 {
wn, ew := f.Write(buf[:nr])
n += int64(wn)
if ew != nil {
err = ew
break
}
if n-lastReport >= 1024*1024 {
progress := 0.0
if resp.ContentLength > 0 {
progress = float64(n) * 100 / float64(resp.ContentLength)
}
a.db.Model(&t).Updates(map[string]any{"downloaded_bytes": n, "total_bytes": resp.ContentLength, "progress": progress})
lastReport = n
}
}
if er == io.EOF {
break
}
if er != nil {
err = er
break
}
}
cerr := f.Close()
if err != nil {
a.fail(&t, err)
@@ -256,6 +298,15 @@ func (a *App) download(id uint) {
a.fail(&t, cerr)
return
}
if err = os.Rename(tmp, local); err != nil {
a.fail(&t, err)
return
}
t.DownloadedBytes = n
if t.TotalBytes > 0 {
t.Progress = 100
}
a.db.Save(&t)
a.upload(&t)
}
@@ -294,12 +345,21 @@ func (a *App) upload(t *Task) {
a.db.Model(t).Updates(map[string]any{"status": "uploading"})
remote := strings.TrimRight(a.cfg.WebDAVPath, "/") + "/" + safe(t.Artist) + "/" + safe(t.Album) + "/" + safe(t.Title) + "." + safe(t.Format)
t.RemotePath = remote
remoteDir := filepath.ToSlash(filepath.Dir(remote))
client := gowebdav.NewClient(a.cfg.WebDAVURL, a.cfg.WebDAVUser, a.cfg.WebDAVPass)
client.SetTimeout(10 * time.Minute)
if err := client.MkdirAll(strings.TrimSuffix(remote, "/"), 0755); err != nil {
if err := client.MkdirAll(remoteDir, 0755); err != nil {
a.fail(t, err)
return
}
if fi, err := os.Stat(t.LocalPath); err == nil {
if ri, err := client.Stat(remote); err == nil && ri.Size() == fi.Size() {
t.Status = "done"
t.Progress = 100
a.db.Save(t)
return
}
}
f, err := os.Open(t.LocalPath)
if err != nil {
a.fail(t, err)
@@ -311,6 +371,7 @@ func (a *App) upload(t *Task) {
return
}
t.Status = "done"
t.Progress = 100
a.db.Save(t)
}
func (a *App) fail(t *Task, err error) { t.Status = "error"; t.Error = err.Error(); a.db.Save(t) }