156 lines
3.9 KiB
Go
156 lines
3.9 KiB
Go
package store
|
||
|
||
import (
|
||
"encoding/json"
|
||
"godo/files"
|
||
"godo/libs"
|
||
"io"
|
||
"log"
|
||
"net/http"
|
||
"os"
|
||
"path/filepath"
|
||
"time"
|
||
)
|
||
|
||
type ProgressReader struct {
|
||
reader io.Reader
|
||
total int64
|
||
err error
|
||
}
|
||
|
||
type DownloadStatus struct {
|
||
Name string `json:"name"`
|
||
Path string `json:"path"`
|
||
Url string `json:"url"`
|
||
Current int64 `json:"current"`
|
||
Size int64 `json:"size"`
|
||
Speed float64 `json:"speed"`
|
||
Progress int `json:"progress"`
|
||
Downloading bool `json:"downloading"`
|
||
Done bool `json:"done"`
|
||
}
|
||
|
||
func (pr *ProgressReader) Read(p []byte) (n int, err error) {
|
||
n, err = pr.reader.Read(p)
|
||
pr.err = err
|
||
pr.total += int64(n)
|
||
return
|
||
}
|
||
|
||
func DownloadHandler(w http.ResponseWriter, r *http.Request) {
|
||
url := r.URL.Query().Get("url")
|
||
log.Printf("Download url: %s", url)
|
||
|
||
// 获取下载目录,这里假设从请求参数中获取,如果没有则使用默认值
|
||
downloadDir := libs.GetCacheDir()
|
||
if downloadDir == "" {
|
||
downloadDir = "./downloads"
|
||
}
|
||
|
||
// 拼接完整的文件路径
|
||
fileName := filepath.Base(url)
|
||
filePath := filepath.Join(downloadDir, fileName)
|
||
|
||
// 开始下载
|
||
resp, err := http.Get(url)
|
||
if err != nil {
|
||
log.Printf("Failed to get file: %v", err)
|
||
http.Error(w, "Failed to get file", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// 检查文件是否已存在且大小一致
|
||
if fileInfo, err := os.Stat(filePath); err == nil {
|
||
if fileInfo.Size() == resp.ContentLength {
|
||
// 文件已存在且大小一致,无需下载
|
||
runDir := libs.GetRunDir()
|
||
err := files.HandlerFile(filePath, runDir)
|
||
if err != nil {
|
||
log.Printf("Error moving file: %v", err)
|
||
}
|
||
libs.SuccessMsg(w, "success", "File already exists and is of correct size")
|
||
return
|
||
} else {
|
||
// 重新打开响应体以便后续读取
|
||
resp.Body = http.NoBody
|
||
resp, err = http.Get(url)
|
||
if err != nil {
|
||
log.Printf("Failed to get file: %v", err)
|
||
http.Error(w, "Failed to get file", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
// 创建文件
|
||
file, err := os.Create(filePath)
|
||
if err != nil {
|
||
log.Printf("Failed to create file: %v", err)
|
||
http.Error(w, "Failed to create file", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer file.Close()
|
||
|
||
// 使用ProgressReader来跟踪进度
|
||
pr := &ProgressReader{reader: resp.Body}
|
||
|
||
// 启动定时器来报告进度
|
||
ticker := time.NewTicker(200 * time.Millisecond)
|
||
defer ticker.Stop()
|
||
|
||
flusher, ok := w.(http.Flusher)
|
||
if !ok {
|
||
log.Printf("Streaming unsupported")
|
||
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
go func() {
|
||
for {
|
||
<-ticker.C
|
||
rp := &DownloadStatus{
|
||
Name: fileName,
|
||
Path: filePath,
|
||
Url: url,
|
||
Current: pr.total,
|
||
Size: resp.ContentLength,
|
||
Speed: 0, // 这里可以计算速度,但为了简化示例,我们暂时设为0
|
||
Progress: int(100 * (float64(pr.total) / float64(resp.ContentLength))),
|
||
Downloading: pr.err == nil && pr.total < resp.ContentLength,
|
||
Done: pr.total == resp.ContentLength,
|
||
}
|
||
if pr.err != nil || rp.Done {
|
||
rp.Downloading = false
|
||
//log.Printf("Download complete: %s", filePath)
|
||
runDir := libs.GetRunDir()
|
||
err := files.HandlerFile(filePath, runDir)
|
||
if err != nil {
|
||
log.Printf("Error moving file: %v", err)
|
||
}
|
||
break
|
||
}
|
||
if w != nil {
|
||
jsonBytes, err := json.Marshal(rp)
|
||
if err != nil {
|
||
log.Printf("Error marshaling DownloadStatus to JSON: %v", err)
|
||
continue
|
||
}
|
||
w.Write(jsonBytes)
|
||
w.Write([]byte("\n"))
|
||
flusher.Flush()
|
||
} else {
|
||
log.Println("ResponseWriter is nil, cannot send progress")
|
||
}
|
||
}
|
||
}()
|
||
|
||
// 将响应体的内容写入文件
|
||
_, err = io.Copy(file, pr)
|
||
if err != nil {
|
||
log.Printf("Failed to write file: %v", err)
|
||
http.Error(w, "Failed to write file", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
libs.SuccessMsg(w, "success", "Download complete")
|
||
}
|