From 34746e951cba8a48aa697c75ce24c2fee55c3808 Mon Sep 17 00:00:00 2001 From: Andy Hsu Date: Fri, 24 Nov 2023 16:26:05 +0800 Subject: [PATCH] feat(offline_download): add simple http tool (close #4002) --- internal/errs/errors.go | 4 + internal/offline_download/all.go | 1 + internal/offline_download/aria2/aria2.go | 5 ++ internal/offline_download/http/client.go | 85 ++++++++++++++++++++++ internal/offline_download/http/util.go | 21 ++++++ internal/offline_download/qbit/qbit.go | 5 ++ internal/offline_download/tool/base.go | 3 + internal/offline_download/tool/download.go | 7 ++ 8 files changed, 131 insertions(+) create mode 100644 internal/offline_download/http/client.go create mode 100644 internal/offline_download/http/util.go diff --git a/internal/errs/errors.go b/internal/errs/errors.go index b48718778a6..cd681e607b3 100644 --- a/internal/errs/errors.go +++ b/internal/errs/errors.go @@ -29,3 +29,7 @@ func NewErr(err error, format string, a ...any) error { func IsNotFoundError(err error) bool { return errors.Is(pkgerr.Cause(err), ObjectNotFound) || errors.Is(pkgerr.Cause(err), StorageNotFound) } + +func IsNotSupportError(err error) bool { + return errors.Is(pkgerr.Cause(err), NotSupport) +} diff --git a/internal/offline_download/all.go b/internal/offline_download/all.go index 0c7853cb13f..2229a855468 100644 --- a/internal/offline_download/all.go +++ b/internal/offline_download/all.go @@ -2,5 +2,6 @@ package offline_download import ( _ "github.com/alist-org/alist/v3/internal/offline_download/aria2" + _ "github.com/alist-org/alist/v3/internal/offline_download/http" _ "github.com/alist-org/alist/v3/internal/offline_download/qbit" ) diff --git a/internal/offline_download/aria2/aria2.go b/internal/offline_download/aria2/aria2.go index 4cdad64b924..ea6404a6229 100644 --- a/internal/offline_download/aria2/aria2.go +++ b/internal/offline_download/aria2/aria2.go @@ -3,6 +3,7 @@ package aria2 import ( "context" "fmt" + "github.com/alist-org/alist/v3/internal/errs" "strconv" "time" @@ -21,6 +22,10 @@ type Aria2 struct { client rpc.Client } +func (a *Aria2) Run(task *tool.DownloadTask) error { + return errs.NotSupport +} + func (a *Aria2) Name() string { return "aria2" } diff --git a/internal/offline_download/http/client.go b/internal/offline_download/http/client.go new file mode 100644 index 00000000000..0db05f35c15 --- /dev/null +++ b/internal/offline_download/http/client.go @@ -0,0 +1,85 @@ +package http + +import ( + "fmt" + "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/internal/offline_download/tool" + "github.com/alist-org/alist/v3/pkg/utils" + "net/http" + "net/url" + "os" + "path" + "path/filepath" +) + +type SimpleHttp struct { + client http.Client +} + +func (s SimpleHttp) Name() string { + return "SimpleHttp" +} + +func (s SimpleHttp) Items() []model.SettingItem { + return nil +} + +func (s SimpleHttp) Init() (string, error) { + return "ok", nil +} + +func (s SimpleHttp) IsReady() bool { + return true +} + +func (s SimpleHttp) AddURL(args *tool.AddUrlArgs) (string, error) { + panic("should not be called") +} + +func (s SimpleHttp) Remove(task *tool.DownloadTask) error { + panic("should not be called") +} + +func (s SimpleHttp) Status(task *tool.DownloadTask) (*tool.Status, error) { + panic("should not be called") +} + +func (s SimpleHttp) Run(task *tool.DownloadTask) error { + u := task.Url + // parse url + _u, err := url.Parse(u) + if err != nil { + return err + } + req, err := http.NewRequestWithContext(task.Ctx(), http.MethodGet, u, nil) + if err != nil { + return err + } + resp, err := s.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode >= 400 { + return fmt.Errorf("http status code %d", resp.StatusCode) + } + filename := path.Base(_u.Path) + if n, err := parseFilenameFromContentDisposition(resp.Header.Get("Content-Disposition")); err == nil { + filename = n + } + // save to temp dir + _ = os.MkdirAll(task.TempDir, os.ModePerm) + filePath := filepath.Join(task.TempDir, filename) + file, err := os.Create(filePath) + if err != nil { + return err + } + defer file.Close() + fileSize := resp.ContentLength + err = utils.CopyWithCtx(task.Ctx(), file, resp.Body, fileSize, task.SetProgress) + return err +} + +func init() { + tool.Tools.Add(&SimpleHttp{}) +} diff --git a/internal/offline_download/http/util.go b/internal/offline_download/http/util.go new file mode 100644 index 00000000000..eefefec24ac --- /dev/null +++ b/internal/offline_download/http/util.go @@ -0,0 +1,21 @@ +package http + +import ( + "fmt" + "mime" +) + +func parseFilenameFromContentDisposition(contentDisposition string) (string, error) { + if contentDisposition == "" { + return "", fmt.Errorf("Content-Disposition is empty") + } + _, params, err := mime.ParseMediaType(contentDisposition) + if err != nil { + return "", err + } + filename := params["filename"] + if filename == "" { + return "", fmt.Errorf("filename not found in Content-Disposition: [%s]", contentDisposition) + } + return filename, nil +} diff --git a/internal/offline_download/qbit/qbit.go b/internal/offline_download/qbit/qbit.go index 28a5170ec12..c2e92d2dce0 100644 --- a/internal/offline_download/qbit/qbit.go +++ b/internal/offline_download/qbit/qbit.go @@ -2,6 +2,7 @@ package qbit import ( "github.com/alist-org/alist/v3/internal/conf" + "github.com/alist-org/alist/v3/internal/errs" "github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/internal/offline_download/tool" "github.com/alist-org/alist/v3/internal/setting" @@ -13,6 +14,10 @@ type QBittorrent struct { client qbittorrent.Client } +func (a *QBittorrent) Run(task *tool.DownloadTask) error { + return errs.NotSupport +} + func (a *QBittorrent) Name() string { return "qBittorrent" } diff --git a/internal/offline_download/tool/base.go b/internal/offline_download/tool/base.go index 1dd8e82bb4e..3b9fb07a999 100644 --- a/internal/offline_download/tool/base.go +++ b/internal/offline_download/tool/base.go @@ -35,6 +35,9 @@ type Tool interface { Remove(task *DownloadTask) error // Status return the status of the download task, if an error occurred, return the error in Status.Err Status(task *DownloadTask) (*Status, error) + + // Run for simple http download + Run(task *DownloadTask) error } type GetFileser interface { diff --git a/internal/offline_download/tool/download.go b/internal/offline_download/tool/download.go index f4ff164c6fb..fd91df03be7 100644 --- a/internal/offline_download/tool/download.go +++ b/internal/offline_download/tool/download.go @@ -2,6 +2,7 @@ package tool import ( "fmt" + "github.com/alist-org/alist/v3/internal/errs" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/xhofe/tache" @@ -25,6 +26,12 @@ type DownloadTask struct { } func (t *DownloadTask) Run() error { + if err := t.tool.Run(t); !errs.IsNotSupportError(err) { + if err == nil { + return t.Complete() + } + return err + } t.Signal = make(chan int) t.finish = make(chan struct{}) defer func() {