Add resumable downloads, download paths, queue controls and history
This commit is contained in:
1 parent
434f6441da
commit
6027cfabb9
43 files changed
+2138
-291
No files matched your search
@@ -10,6 +10,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"ddc/internal/fetch"
|
||||
"ddc/internal/files"
|
||||
"ddc/internal/guard"
|
||||
"ddc/internal/history"
|
||||
"ddc/internal/libraries"
|
||||
"ddc/internal/preferences"
|
||||
)
|
||||
@@ -31,6 +33,7 @@ var (
|
||||
type Manager struct {
|
||||
settings config.Settings
|
||||
preferences *preferences.Store
|
||||
history *history.Store
|
||||
policy *guard.Policy
|
||||
downloader *fetch.Downloader
|
||||
rootContext context.Context
|
||||
@@ -40,12 +43,13 @@ type Manager struct {
|
||||
jobs map[string]*Job
|
||||
}
|
||||
|
||||
func NewManager(settings config.Settings, store *preferences.Store) *Manager {
|
||||
func NewManager(settings config.Settings, store *preferences.Store, historyStore *history.Store) *Manager {
|
||||
policy := guard.NewPolicy(store.Get().AllowedPorts)
|
||||
rootContext, cancelAll := context.WithCancel(context.Background())
|
||||
return &Manager{
|
||||
settings: settings,
|
||||
preferences: store,
|
||||
history: historyStore,
|
||||
policy: policy,
|
||||
downloader: fetch.NewDownloader(policy),
|
||||
rootContext: rootContext,
|
||||
@@ -63,6 +67,11 @@ func (manager *Manager) Preferences() preferences.Preferences {
|
||||
}
|
||||
|
||||
func (manager *Manager) UpdatePreferences(next preferences.Preferences) error {
|
||||
downloadPaths, err := manager.normalizeDownloadPaths(next.DownloadPaths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
next.DownloadPaths = downloadPaths
|
||||
if err := manager.preferences.Update(next); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -82,11 +91,27 @@ func (manager *Manager) AvailableBytes() (int64, error) {
|
||||
return diskspace.Available(manager.settings.DownloadDir)
|
||||
}
|
||||
|
||||
func (manager *Manager) Create(rawURL string, autoExtract bool) (Snapshot, error) {
|
||||
target, err := manager.policy.ValidateURL(rawURL)
|
||||
type CreateRequest struct {
|
||||
URL string `json:"url"`
|
||||
FileName string `json:"fileName"`
|
||||
AutoExtract bool `json:"autoExtract"`
|
||||
Destination string `json:"destination"`
|
||||
Subfolder string `json:"subfolder"`
|
||||
}
|
||||
|
||||
func (manager *Manager) Create(request CreateRequest) (Snapshot, error) {
|
||||
target, err := manager.policy.ValidateURL(request.URL)
|
||||
if err != nil {
|
||||
return Snapshot{}, invalid(err)
|
||||
}
|
||||
requestedName := fetch.SanitizeFileName(request.FileName)
|
||||
if strings.TrimSpace(request.FileName) != "" && requestedName == "" {
|
||||
return Snapshot{}, invalid(errors.New("file name is not allowed"))
|
||||
}
|
||||
moveTarget, err := manager.resolveTarget(request.Destination, request.Subfolder)
|
||||
if err != nil {
|
||||
return Snapshot{}, err
|
||||
}
|
||||
if err := diskspace.EnsureFree(manager.settings.DownloadDir, 0, manager.reservedBytes()); err != nil {
|
||||
return Snapshot{}, err
|
||||
}
|
||||
@@ -103,13 +128,16 @@ func (manager *Manager) Create(rawURL string, autoExtract bool) (Snapshot, error
|
||||
job := manager.newJob(id, directory, time.Now())
|
||||
job.sourceURL = target.String()
|
||||
job.displayURL = target.Redacted()
|
||||
job.autoExtract = autoExtract
|
||||
job.requestedName = requestedName
|
||||
job.autoExtract = request.AutoExtract
|
||||
job.target = moveTarget
|
||||
job.status = StatusQueued
|
||||
job.fileName = initialFileName(target.Path, target.Host)
|
||||
job.fileName = initialFileName(requestedName, target.Path, target.Host)
|
||||
job.workers.Add(1)
|
||||
|
||||
manager.store(job)
|
||||
go manager.download(job)
|
||||
job.saveRecord()
|
||||
manager.launchDownload(job)
|
||||
return job.snapshot(), nil
|
||||
}
|
||||
|
||||
@@ -117,7 +145,11 @@ func (manager *Manager) List() []Snapshot {
|
||||
manager.mutex.RLock()
|
||||
snapshots := make([]Snapshot, 0, len(manager.jobs))
|
||||
for _, job := range manager.jobs {
|
||||
snapshots = append(snapshots, job.snapshot())
|
||||
snapshot := job.snapshot()
|
||||
if snapshot.Status == StatusQueued {
|
||||
snapshot.QueuePosition = manager.slots.position(job.id)
|
||||
}
|
||||
snapshots = append(snapshots, snapshot)
|
||||
}
|
||||
manager.mutex.RUnlock()
|
||||
|
||||
@@ -157,8 +189,26 @@ func (manager *Manager) Delete(id string) error {
|
||||
return os.RemoveAll(job.directory)
|
||||
}
|
||||
|
||||
func (manager *Manager) CancelAll() {
|
||||
func (manager *Manager) Shutdown(ctx context.Context) {
|
||||
manager.cancelAll()
|
||||
manager.mutex.RLock()
|
||||
allJobs := make([]*Job, 0, len(manager.jobs))
|
||||
for _, job := range manager.jobs {
|
||||
allJobs = append(allJobs, job)
|
||||
}
|
||||
manager.mutex.RUnlock()
|
||||
|
||||
finished := make(chan struct{})
|
||||
go func() {
|
||||
for _, job := range allJobs {
|
||||
job.workers.Wait()
|
||||
}
|
||||
close(finished)
|
||||
}()
|
||||
select {
|
||||
case <-finished:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *Manager) newJob(id, directory string, createdAt time.Time) *Job {
|
||||
@@ -196,7 +246,10 @@ func newJobID() (string, error) {
|
||||
return hex.EncodeToString(buffer), nil
|
||||
}
|
||||
|
||||
func initialFileName(urlPath, host string) string {
|
||||
func initialFileName(requestedName, urlPath, host string) string {
|
||||
if requestedName != "" {
|
||||
return requestedName
|
||||
}
|
||||
if name := fetch.SanitizeFileName(path.Base(urlPath)); name != "" {
|
||||
return name
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user