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
@@ -0,0 +1,109 @@
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"ddc/internal/files"
|
||||
"ddc/internal/libraries"
|
||||
"ddc/internal/preferences"
|
||||
)
|
||||
|
||||
type moveTarget struct {
|
||||
destination string
|
||||
subfolder string
|
||||
}
|
||||
|
||||
func (target *moveTarget) displayPath() string {
|
||||
return filepath.Join(target.destination, target.subfolder)
|
||||
}
|
||||
|
||||
func (manager *Manager) resolveTarget(destination, subfolder string) (*moveTarget, error) {
|
||||
if strings.TrimSpace(destination) == "" {
|
||||
return nil, nil
|
||||
}
|
||||
libraryPath, extraFolder, err := manager.splitDownloadPath(destination)
|
||||
if err != nil {
|
||||
return nil, invalid(err)
|
||||
}
|
||||
cleanSubfolder, err := files.SanitizeSubfolder(subfolder)
|
||||
if err != nil {
|
||||
return nil, invalid(err)
|
||||
}
|
||||
combinedSubfolder, err := files.SanitizeSubfolder(extraFolder + "/" + cleanSubfolder)
|
||||
if err != nil {
|
||||
return nil, invalid(err)
|
||||
}
|
||||
return &moveTarget{destination: libraryPath, subfolder: combinedSubfolder}, nil
|
||||
}
|
||||
|
||||
func (manager *Manager) splitDownloadPath(fullPath string) (string, string, error) {
|
||||
cleaned := filepath.Clean(strings.TrimSpace(fullPath))
|
||||
for _, root := range manager.settings.LibraryRoots {
|
||||
if !files.IsWithin(root, cleaned) {
|
||||
continue
|
||||
}
|
||||
relative, err := filepath.Rel(root, cleaned)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
libraryName, extraFolder, _ := strings.Cut(filepath.ToSlash(relative), "/")
|
||||
library, err := libraries.Resolve(manager.settings.LibraryRoots, filepath.Join(root, libraryName))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return library.Path, extraFolder, nil
|
||||
}
|
||||
return "", "", libraries.ErrUnknownDestination
|
||||
}
|
||||
|
||||
func (manager *Manager) normalizeDownloadPaths(downloadPaths []string) ([]string, error) {
|
||||
normalized := make([]string, 0, len(downloadPaths))
|
||||
seenPaths := make(map[string]bool)
|
||||
for _, downloadPath := range downloadPaths {
|
||||
target, err := manager.resolveTarget(downloadPath, "")
|
||||
if err != nil || target == nil {
|
||||
return nil, fmt.Errorf("%w: %q is not inside a destination folder", preferences.ErrInvalid, downloadPath)
|
||||
}
|
||||
fullPath := target.displayPath()
|
||||
if !seenPaths[fullPath] {
|
||||
seenPaths[fullPath] = true
|
||||
normalized = append(normalized, fullPath)
|
||||
}
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
func (manager *Manager) moveToTarget(job *Job) {
|
||||
if job.target == nil {
|
||||
return
|
||||
}
|
||||
names, err := topLevelNames(job.directory)
|
||||
if err == nil {
|
||||
err = manager.Move(job.id, MoveRequest{
|
||||
Paths: names,
|
||||
Destination: job.target.destination,
|
||||
Subfolder: job.target.subfolder,
|
||||
Cleanup: true,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
job.setStatus(StatusReady, "Move failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func topLevelNames(directory string) ([]string, error) {
|
||||
entries, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names := make([]string, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if !files.IsInternal(entry.Name()) {
|
||||
names = append(names, entry.Name())
|
||||
}
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
Reference in new issue
Block a user