mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-09 01:50:30 +00:00
Add DoneAt to the download task model: set when a task finishes and cleared when it is downloaded again. The value flows through the API model and task record, and is shown as "Completed at" in the task drawer info tab next to the creation time.
229 lines
5.9 KiB
Go
229 lines
5.9 KiB
Go
package download
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/GopeedLab/gopeed/internal/controller"
|
|
"github.com/GopeedLab/gopeed/internal/fetcher"
|
|
"github.com/GopeedLab/gopeed/internal/production"
|
|
"github.com/GopeedLab/gopeed/internal/protocol/bt"
|
|
"github.com/GopeedLab/gopeed/internal/protocol/ed2k"
|
|
"github.com/GopeedLab/gopeed/internal/protocol/hls"
|
|
"github.com/GopeedLab/gopeed/internal/protocol/http"
|
|
"github.com/GopeedLab/gopeed/pkg/base"
|
|
enginewebview "github.com/GopeedLab/gopeed/pkg/download/engine/webview"
|
|
"github.com/GopeedLab/gopeed/pkg/util"
|
|
gonanoid "github.com/matoous/go-nanoid/v2"
|
|
)
|
|
|
|
type ResolveResult struct {
|
|
ID string `json:"id"`
|
|
Res *base.Resource `json:"res"`
|
|
}
|
|
|
|
type Task struct {
|
|
ID string `json:"id"`
|
|
Protocol string `json:"protocol"`
|
|
Meta *fetcher.FetcherMeta `json:"meta"`
|
|
Status base.Status `json:"status"`
|
|
Uploading bool `json:"uploading"`
|
|
Progress *Progress `json:"progress"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
// DoneAt is the time when the task last completed; nil while the task has
|
|
// never finished or is being downloaded again.
|
|
DoneAt *time.Time `json:"doneAt"`
|
|
|
|
producer production.Source
|
|
producerReceived int64
|
|
|
|
fetcherManager fetcher.FetcherManager
|
|
fetcher fetcher.Fetcher
|
|
timer *util.Timer
|
|
statusLock *sync.Mutex
|
|
lock *sync.Mutex
|
|
statsLock *sync.Mutex
|
|
statsLoaded bool
|
|
stats *fetcher.Stats
|
|
statsSnapshot json.RawMessage
|
|
blobRefLock *sync.Mutex
|
|
blobURL string
|
|
runGeneration uint64
|
|
speedArr []int64
|
|
uploadSpeedArr []int64
|
|
}
|
|
|
|
// TaskRuntimeStatus is the lightweight, frequently refreshed view of a task.
|
|
// It intentionally excludes request, resource and option metadata returned by
|
|
// Task so detail pages can poll progress without repeatedly transferring the
|
|
// full task object.
|
|
type TaskRuntimeStatus struct {
|
|
Status base.Status `json:"status"`
|
|
Used int64 `json:"used"`
|
|
Speed int64 `json:"speed"`
|
|
Downloaded int64 `json:"downloaded"`
|
|
Total int64 `json:"total"`
|
|
UploadSpeed int64 `json:"uploadSpeed"`
|
|
Uploaded int64 `json:"uploaded"`
|
|
ExtractStatus ExtractStatus `json:"extractStatus"`
|
|
ExtractProgress int `json:"extractProgress"`
|
|
Files []FileRuntimeStatus `json:"files"`
|
|
}
|
|
|
|
// FileRuntimeStatus maps one value returned by fetcher.Progress to its
|
|
// original resource index. Fetchers return progress in SelectFiles order.
|
|
type FileRuntimeStatus struct {
|
|
Index int `json:"index"`
|
|
Size int64 `json:"size"`
|
|
Downloaded int64 `json:"downloaded"`
|
|
}
|
|
|
|
func NewTask() *Task {
|
|
id, err := gonanoid.New()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return &Task{
|
|
ID: id,
|
|
Status: base.DownloadStatusReady,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
}
|
|
|
|
// Name returns the display name of the task.
|
|
func (t *Task) Name() string {
|
|
// Custom name first
|
|
if t.Meta.Opts.Name != "" {
|
|
return t.Meta.Opts.Name
|
|
}
|
|
|
|
// Task is not resolved, parse the name from the URL
|
|
if t.Meta.Res == nil {
|
|
fallbackName := "unknown"
|
|
if t.fetcherManager == nil {
|
|
return fallbackName
|
|
}
|
|
parseName := t.fetcherManager.ParseName(t.Meta.Req.URL)
|
|
if parseName == "" {
|
|
return fallbackName
|
|
}
|
|
return parseName
|
|
}
|
|
|
|
// Task is a folder
|
|
if t.Meta.Res.Name != "" {
|
|
return t.Meta.Res.Name
|
|
}
|
|
|
|
// Get the name of the first file
|
|
return t.Meta.Res.Files[0].Name
|
|
}
|
|
|
|
func (t *Task) MarshalJSON() ([]byte, error) {
|
|
type rawTaskType Task
|
|
jsonTask := struct {
|
|
*rawTaskType
|
|
Name string `json:"name"`
|
|
}{
|
|
(*rawTaskType)(t),
|
|
t.Name(),
|
|
}
|
|
return json.Marshal(jsonTask)
|
|
}
|
|
|
|
func (t *Task) updateStatus(status base.Status) {
|
|
t.UpdatedAt = time.Now()
|
|
t.Status = status
|
|
if status == base.DownloadStatusDone {
|
|
doneAt := time.Now()
|
|
t.DoneAt = &doneAt
|
|
} else {
|
|
t.DoneAt = nil
|
|
}
|
|
}
|
|
|
|
func (t *Task) clone() *Task {
|
|
return util.DeepClone(t)
|
|
}
|
|
|
|
func (t *Task) updateSpeed(downloaded int64, usedTime float64) int64 {
|
|
return calcSpeed(&t.speedArr, downloaded, usedTime)
|
|
}
|
|
|
|
func (t *Task) updateUploadSpeed(downloaded int64, usedTime float64) int64 {
|
|
return calcSpeed(&t.uploadSpeedArr, downloaded, usedTime)
|
|
}
|
|
|
|
func calcSpeed(speedArr *[]int64, downloaded int64, usedTime float64) int64 {
|
|
if usedTime <= 0 {
|
|
return 0
|
|
}
|
|
if downloaded < 0 {
|
|
*speedArr = (*speedArr)[:0]
|
|
return 0
|
|
}
|
|
|
|
*speedArr = append(*speedArr, downloaded)
|
|
// Record last 5 seconds of download speed to calculate the average speed
|
|
if len(*speedArr) > int(5.0/usedTime) {
|
|
*speedArr = (*speedArr)[1:]
|
|
}
|
|
|
|
var total int64
|
|
for _, v := range *speedArr {
|
|
total += v
|
|
}
|
|
|
|
return int64(float64(total) / float64(len(*speedArr)) / usedTime)
|
|
}
|
|
|
|
type TaskFilter struct {
|
|
IDs []string
|
|
Statuses []base.Status
|
|
NotStatuses []base.Status
|
|
}
|
|
|
|
func (f *TaskFilter) IsEmpty() bool {
|
|
return len(f.IDs) == 0 && len(f.Statuses) == 0 && len(f.NotStatuses) == 0
|
|
}
|
|
|
|
type DownloaderConfig struct {
|
|
Controller *controller.Controller
|
|
FetchManagers []fetcher.FetcherManager
|
|
|
|
RefreshInterval int // RefreshInterval time duration to refresh task progress(ms)
|
|
Storage Storage
|
|
StorageDir string
|
|
TempDir string
|
|
WhiteDownloadDirs []string
|
|
WebViewProvider enginewebview.Provider
|
|
|
|
ProductionMode bool
|
|
|
|
*base.DownloaderStoreConfig
|
|
}
|
|
|
|
func (cfg *DownloaderConfig) Init() *DownloaderConfig {
|
|
if cfg.Controller == nil {
|
|
cfg.Controller = controller.NewController()
|
|
}
|
|
if len(cfg.FetchManagers) == 0 {
|
|
cfg.FetchManagers = []fetcher.FetcherManager{
|
|
new(hls.FetcherManager),
|
|
new(http.FetcherManager),
|
|
new(bt.FetcherManager),
|
|
new(ed2k.FetcherManager),
|
|
}
|
|
}
|
|
if cfg.RefreshInterval == 0 {
|
|
cfg.RefreshInterval = 350
|
|
}
|
|
if cfg.Storage == nil {
|
|
cfg.Storage = NewMemStorage()
|
|
}
|
|
return cfg
|
|
}
|