package download import ( "encoding/json" "sync" "time" "github.com/GopeedLab/gopeed/internal/controller" "github.com/GopeedLab/gopeed/internal/fetcher" "github.com/GopeedLab/gopeed/internal/protocol/bt" "github.com/GopeedLab/gopeed/internal/protocol/ed2k" "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"` 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 } 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 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(http.FetcherManager), new(bt.FetcherManager), new(ed2k.FetcherManager), } } if cfg.RefreshInterval == 0 { cfg.RefreshInterval = 350 } if cfg.Storage == nil { cfg.Storage = NewMemStorage() } return cfg }