mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-06 00:25:43 +00:00
Done tasks never leave the done state (doStart, pause and markTaskError all guard against Done), so the else branch clearing DoneAt was unreachable dead code. DoneAt is now written once on completion and kept for the task lifetime.
230 lines
6.0 KiB
Go
230 lines
6.0 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 completed; nil if it has never
|
|
// finished. A done task never leaves the done state, so the value is
|
|
// written once and kept.
|
|
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
|
|
// A done task never leaves the done state, so there is no branch to clear
|
|
// DoneAt on other transitions.
|
|
if status == base.DownloadStatusDone {
|
|
doneAt := time.Now()
|
|
t.DoneAt = &doneAt
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|