From 611457c0e763b6f28f8d385197fe3e4e315af393 Mon Sep 17 00:00:00 2001 From: Noah Hsu Date: Fri, 2 Sep 2022 22:46:31 +0800 Subject: [PATCH] feat: add `baidu_netdisk` driver --- drivers/123/driver.go | 2 +- drivers/123/types.go | 4 + drivers/123/util.go | 2 +- drivers/aliyundrive/util.go | 2 +- drivers/all.go | 1 + drivers/baidu_netdisk/driver.go | 252 ++++++++++++++++++++++++++++++++ drivers/baidu_netdisk/meta.go | 35 +++++ drivers/baidu_netdisk/types.go | 163 +++++++++++++++++++++ drivers/baidu_netdisk/util.go | 199 +++++++++++++++++++++++++ drivers/base/types.go | 4 + drivers/local/driver.go | 24 +-- drivers/onedrive/driver.go | 29 ++-- drivers/onedrive/meta.go | 2 +- drivers/onedrive/types.go | 2 +- drivers/onedrive/util.go | 8 +- drivers/pikpak/util.go | 2 +- drivers/quark/util.go | 2 +- drivers/teambition/util.go | 2 +- internal/model/obj.go | 5 +- internal/model/object.go | 9 +- internal/op/fs.go | 10 +- 21 files changed, 711 insertions(+), 48 deletions(-) create mode 100644 drivers/baidu_netdisk/driver.go create mode 100644 drivers/baidu_netdisk/meta.go create mode 100644 drivers/baidu_netdisk/types.go create mode 100644 drivers/baidu_netdisk/util.go diff --git a/drivers/123/driver.go b/drivers/123/driver.go index 00e412db..f400b679 100644 --- a/drivers/123/driver.go +++ b/drivers/123/driver.go @@ -62,7 +62,7 @@ func (d *Pan123) List(ctx context.Context, dir model.Obj, args model.ListArgs) ( } //func (d *Pan123) Get(ctx context.Context, path string) (model.Obj, error) { -// // TODO this is optional +// // this is optional // return nil, errs.NotImplement //} diff --git a/drivers/123/types.go b/drivers/123/types.go index 6ef29c0f..6e0c96a9 100644 --- a/drivers/123/types.go +++ b/drivers/123/types.go @@ -30,6 +30,10 @@ type File struct { DownloadUrl string `json:"DownloadUrl"` } +func (f File) GetPath() string { + return "" +} + func (f File) GetSize() int64 { return f.Size } diff --git a/drivers/123/util.go b/drivers/123/util.go index cfe3ddd8..6d27f8f4 100644 --- a/drivers/123/util.go +++ b/drivers/123/util.go @@ -32,7 +32,7 @@ func (d *Pan123) login() error { return err } -func (d *Pan123) request(url string, method string, callback func(*resty.Request), resp interface{}) ([]byte, error) { +func (d *Pan123) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { req := base.RestyClient.R() req.SetHeader("Authorization", "Bearer "+d.AccessToken) if callback != nil { diff --git a/drivers/aliyundrive/util.go b/drivers/aliyundrive/util.go index 42baa87c..9f4c1cdc 100644 --- a/drivers/aliyundrive/util.go +++ b/drivers/aliyundrive/util.go @@ -34,7 +34,7 @@ func (d *AliDrive) refreshToken() error { return nil } -func (d *AliDrive) request(url, method string, callback func(*resty.Request), resp interface{}) ([]byte, error, RespErr) { +func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) { req := base.RestyClient.R() req.SetHeader("Authorization", "Bearer\t"+d.AccessToken) req.SetHeader("content-type", "application/json") diff --git a/drivers/all.go b/drivers/all.go index 60e22706..df629d1d 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -3,6 +3,7 @@ package drivers import ( _ "github.com/alist-org/alist/v3/drivers/123" _ "github.com/alist-org/alist/v3/drivers/aliyundrive" + _ "github.com/alist-org/alist/v3/drivers/baidu_netdisk" _ "github.com/alist-org/alist/v3/drivers/local" _ "github.com/alist-org/alist/v3/drivers/onedrive" _ "github.com/alist-org/alist/v3/drivers/pikpak" diff --git a/drivers/baidu_netdisk/driver.go b/drivers/baidu_netdisk/driver.go new file mode 100644 index 00000000..29e06992 --- /dev/null +++ b/drivers/baidu_netdisk/driver.go @@ -0,0 +1,252 @@ +package baidu_netdisk + +import ( + "bytes" + "context" + "crypto/md5" + "encoding/hex" + "fmt" + "io" + "math" + "os" + stdpath "path" + "strconv" + "strings" + + "github.com/alist-org/alist/v3/drivers/base" + "github.com/alist-org/alist/v3/internal/conf" + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/errs" + "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/pkg/utils" + log "github.com/sirupsen/logrus" +) + +type BaiduNetdisk struct { + model.Storage + Addition + AccessToken string +} + +func (d *BaiduNetdisk) Config() driver.Config { + return config +} + +func (d *BaiduNetdisk) GetAddition() driver.Additional { + return d.Addition +} + +func (d *BaiduNetdisk) Init(ctx context.Context, storage model.Storage) error { + d.Storage = storage + err := utils.Json.UnmarshalFromString(d.Storage.Addition, &d.Addition) + if err != nil { + return err + } + return d.refreshToken() +} + +func (d *BaiduNetdisk) Drop(ctx context.Context) error { + return nil +} + +func (d *BaiduNetdisk) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { + files, err := d.getFiles(dir.GetPath()) + if err != nil { + return nil, err + } + objs := make([]model.Obj, len(files)) + for i := 0; i < len(files); i++ { + objs[i] = fileToObj(files[i]) + } + return objs, nil +} + +//func (d *BaiduNetdisk) Get(ctx context.Context, path string) (model.Obj, error) { +// // this is optional +// return nil, errs.NotImplement +//} + +func (d *BaiduNetdisk) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { + if d.DownloadAPI == "crack" { + return d.linkCrack(file, args) + } + return d.linkOfficial(file, args) +} + +func (d *BaiduNetdisk) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { + _, err := d.create(stdpath.Join(parentDir.GetPath(), dirName), 0, 1, "", "") + return err +} + +func (d *BaiduNetdisk) Move(ctx context.Context, srcObj, dstDir model.Obj) error { + data := []base.Json{ + { + "path": srcObj.GetPath(), + "dest": dstDir.GetPath(), + "newname": srcObj.GetName(), + }, + } + _, err := d.manage("move", data) + return err +} + +func (d *BaiduNetdisk) Rename(ctx context.Context, srcObj model.Obj, newName string) error { + data := []base.Json{ + { + "path": srcObj.GetPath(), + "newname": newName, + }, + } + _, err := d.manage("rename", data) + return err +} + +func (d *BaiduNetdisk) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { + dest, newname := stdpath.Split(dstDir.GetPath()) + data := []base.Json{ + { + "path": srcObj.GetPath(), + "dest": dest, + "newname": newname, + }, + } + _, err := d.manage("copy", data) + return err +} + +func (d *BaiduNetdisk) Remove(ctx context.Context, obj model.Obj) error { + data := []string{obj.GetPath()} + _, err := d.manage("delete", data) + return err +} + +func (d *BaiduNetdisk) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { + var tempFile *os.File + var err error + if f, ok := stream.GetReadCloser().(*os.File); ok { + tempFile = f + } else { + tempFile, err = os.CreateTemp(conf.Conf.TempDir, "file-*") + if err != nil { + return err + } + defer func() { + _ = tempFile.Close() + _ = os.Remove(tempFile.Name()) + }() + _, err = io.Copy(tempFile, stream) + if err != nil { + return err + } + _, err = tempFile.Seek(0, io.SeekStart) + if err != nil { + return err + } + } + var Default int64 = 4 * 1024 * 1024 + defaultByteData := make([]byte, Default) + count := int(math.Ceil(float64(stream.GetSize()) / float64(Default))) + var SliceSize int64 = 256 * 1024 + // cal md5 + h1 := md5.New() + h2 := md5.New() + block_list := make([]string, 0) + content_md5 := "" + slice_md5 := "" + left := stream.GetSize() + for i := 0; i < count; i++ { + byteSize := Default + var byteData []byte + if left < Default { + byteSize = left + byteData = make([]byte, byteSize) + } else { + byteData = defaultByteData + } + left -= byteSize + _, err = io.ReadFull(tempFile, byteData) + if err != nil { + return err + } + h1.Write(byteData) + h2.Write(byteData) + block_list = append(block_list, fmt.Sprintf("\"%s\"", hex.EncodeToString(h2.Sum(nil)))) + h2.Reset() + } + content_md5 = hex.EncodeToString(h1.Sum(nil)) + _, err = tempFile.Seek(0, io.SeekStart) + if err != nil { + return err + } + if stream.GetSize() <= SliceSize { + slice_md5 = content_md5 + } else { + sliceData := make([]byte, SliceSize) + _, err = io.ReadFull(tempFile, sliceData) + if err != nil { + return err + } + h2.Write(sliceData) + slice_md5 = hex.EncodeToString(h2.Sum(nil)) + _, err = tempFile.Seek(0, io.SeekStart) + if err != nil { + return err + } + } + path := encodeURIComponent(stdpath.Join(dstDir.GetPath(), stream.GetName())) + block_list_str := fmt.Sprintf("[%s]", strings.Join(block_list, ",")) + data := fmt.Sprintf("path=%s&size=%d&isdir=0&autoinit=1&block_list=%s&content-md5=%s&slice-md5=%s", + path, stream.GetSize(), + block_list_str, + content_md5, slice_md5) + params := map[string]string{ + "method": "precreate", + } + var precreateResp PrecreateResp + _, err = d.post("/xpan/file", params, data, &precreateResp) + if err != nil { + return err + } + log.Debugf("%+v", precreateResp) + if precreateResp.ReturnType == 2 { + return nil + } + params = map[string]string{ + "method": "upload", + "access_token": d.AccessToken, + "type": "tmpfile", + "path": path, + "uploadid": precreateResp.Uploadid, + } + left = stream.GetSize() + for _, partseq := range precreateResp.BlockList { + byteSize := Default + var byteData []byte + if left < Default { + byteSize = left + byteData = make([]byte, byteSize) + } else { + byteData = defaultByteData + } + left -= byteSize + _, err = io.ReadFull(tempFile, byteData) + if err != nil { + return err + } + u := "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2" + params["partseq"] = strconv.Itoa(partseq) + res, err := base.RestyClient.R().SetQueryParams(params).SetFileReader("file", stream.GetName(), bytes.NewReader(byteData)).Post(u) + if err != nil { + return err + } + log.Debugln(res.String()) + } + _, err = d.create(path, stream.GetSize(), 0, precreateResp.Uploadid, block_list_str) + return err +} + +func (d *BaiduNetdisk) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) { + return nil, errs.NotSupport +} + +var _ driver.Driver = (*BaiduNetdisk)(nil) diff --git a/drivers/baidu_netdisk/meta.go b/drivers/baidu_netdisk/meta.go new file mode 100644 index 00000000..33e4b172 --- /dev/null +++ b/drivers/baidu_netdisk/meta.go @@ -0,0 +1,35 @@ +package baidu_netdisk + +import ( + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/op" +) + +type Addition struct { + RefreshToken string `json:"refresh_token" required:"true"` + driver.RootFolderPath + OrderBy string `json:"order_by" type:"select" options:"name,time,size" default:"name"` + OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` + DownloadAPI string `json:"download_api" type:"select" options:"official,crack" default:"official"` + ClientID string `json:"client_id" required:"true" default:"iYCeC9g08h5vuP9UqvPHKKSVrKFXGa1v"` + ClientSecret string `json:"client_secret" required:"true" default:"jXiFMOPVPCWlO2M5CwWQzffpNPaGTRBG"` +} + +var config = driver.Config{ + Name: "BaiduNetdisk", + LocalSort: false, + OnlyLocal: false, + OnlyProxy: false, + NoCache: false, + NoUpload: false, + NeedMs: false, + DefaultRoot: "root, / or other", +} + +func New() driver.Driver { + return &BaiduNetdisk{} +} + +func init() { + op.RegisterDriver(config, New) +} diff --git a/drivers/baidu_netdisk/types.go b/drivers/baidu_netdisk/types.go new file mode 100644 index 00000000..6833c780 --- /dev/null +++ b/drivers/baidu_netdisk/types.go @@ -0,0 +1,163 @@ +package baidu_netdisk + +import ( + "strconv" + "time" + + "github.com/alist-org/alist/v3/internal/model" +) + +type TokenErrResp struct { + ErrorDescription string `json:"error_description"` + Error string `json:"error"` +} + +type File struct { + //TkbindId int `json:"tkbind_id"` + //OwnerType int `json:"owner_type"` + //Category int `json:"category"` + //RealCategory string `json:"real_category"` + FsId int64 `json:"fs_id"` + ServerMtime int64 `json:"server_mtime"` + //OperId int `json:"oper_id"` + //ServerCtime int `json:"server_ctime"` + Thumbs struct { + //Icon string `json:"icon"` + Url3 string `json:"url3"` + //Url2 string `json:"url2"` + //Url1 string `json:"url1"` + } `json:"thumbs"` + //Wpfile int `json:"wpfile"` + //LocalMtime int `json:"local_mtime"` + Size int64 `json:"size"` + //ExtentTinyint7 int `json:"extent_tinyint7"` + Path string `json:"path"` + //Share int `json:"share"` + //ServerAtime int `json:"server_atime"` + //Pl int `json:"pl"` + //LocalCtime int `json:"local_ctime"` + ServerFilename string `json:"server_filename"` + //Md5 string `json:"md5"` + //OwnerId int `json:"owner_id"` + //Unlist int `json:"unlist"` + Isdir int `json:"isdir"` +} + +func fileToObj(f File) *model.ObjThumb { + return &model.ObjThumb{ + Object: model.Object{ + ID: strconv.FormatInt(f.FsId, 10), + Name: f.ServerFilename, + Size: f.Size, + Modified: time.Unix(f.ServerMtime, 0), + IsFolder: f.Isdir == 1, + }, + Thumbnail: model.Thumbnail{Thumbnail: f.Thumbs.Url3}, + } +} + +type ListResp struct { + Errno int `json:"errno"` + GuidInfo string `json:"guid_info"` + List []File `json:"list"` + RequestId int64 `json:"request_id"` + Guid int `json:"guid"` +} + +type DownloadResp struct { + Errmsg string `json:"errmsg"` + Errno int `json:"errno"` + List []struct { + //Category int `json:"category"` + //DateTaken int `json:"date_taken,omitempty"` + Dlink string `json:"dlink"` + //Filename string `json:"filename"` + //FsId int64 `json:"fs_id"` + //Height int `json:"height,omitempty"` + //Isdir int `json:"isdir"` + //Md5 string `json:"md5"` + //OperId int `json:"oper_id"` + //Path string `json:"path"` + //ServerCtime int `json:"server_ctime"` + //ServerMtime int `json:"server_mtime"` + //Size int `json:"size"` + //Thumbs struct { + // Icon string `json:"icon,omitempty"` + // Url1 string `json:"url1,omitempty"` + // Url2 string `json:"url2,omitempty"` + // Url3 string `json:"url3,omitempty"` + //} `json:"thumbs"` + //Width int `json:"width,omitempty"` + } `json:"list"` + //Names struct { + //} `json:"names"` + RequestId string `json:"request_id"` +} + +type DownloadResp2 struct { + Errno int `json:"errno"` + Info []struct { + //ExtentTinyint4 int `json:"extent_tinyint4"` + //ExtentTinyint1 int `json:"extent_tinyint1"` + //Bitmap string `json:"bitmap"` + //Category int `json:"category"` + //Isdir int `json:"isdir"` + //Videotag int `json:"videotag"` + Dlink string `json:"dlink"` + //OperID int64 `json:"oper_id"` + //PathMd5 int `json:"path_md5"` + //Wpfile int `json:"wpfile"` + //LocalMtime int `json:"local_mtime"` + /*Thumbs struct { + Icon string `json:"icon"` + URL3 string `json:"url3"` + URL2 string `json:"url2"` + URL1 string `json:"url1"` + } `json:"thumbs"`*/ + //PlaySource int `json:"play_source"` + //Share int `json:"share"` + //FileKey string `json:"file_key"` + //Errno int `json:"errno"` + //LocalCtime int `json:"local_ctime"` + //Rotate int `json:"rotate"` + //Metadata time.Time `json:"metadata"` + //Height int `json:"height"` + //SampleRate int `json:"sample_rate"` + //Width int `json:"width"` + //OwnerType int `json:"owner_type"` + //Privacy int `json:"privacy"` + //ExtentInt3 int64 `json:"extent_int3"` + //RealCategory string `json:"real_category"` + //SrcLocation string `json:"src_location"` + //MetaInfo string `json:"meta_info"` + //ID string `json:"id"` + //Duration int `json:"duration"` + //FileSize string `json:"file_size"` + //Channels int `json:"channels"` + //UseSegment int `json:"use_segment"` + //ServerCtime int `json:"server_ctime"` + //Resolution string `json:"resolution"` + //OwnerID int `json:"owner_id"` + //ExtraInfo string `json:"extra_info"` + //Size int `json:"size"` + //FsID int64 `json:"fs_id"` + //ExtentTinyint3 int `json:"extent_tinyint3"` + //Md5 string `json:"md5"` + //Path string `json:"path"` + //FrameRate int `json:"frame_rate"` + //ExtentTinyint2 int `json:"extent_tinyint2"` + //ServerFilename string `json:"server_filename"` + //ServerMtime int `json:"server_mtime"` + //TkbindID int `json:"tkbind_id"` + } `json:"info"` + RequestID int64 `json:"request_id"` +} + +type PrecreateResp struct { + Path string `json:"path"` + Uploadid string `json:"uploadid"` + ReturnType int `json:"return_type"` + BlockList []int `json:"block_list"` + Errno int `json:"errno"` + RequestId int64 `json:"request_id"` +} diff --git a/drivers/baidu_netdisk/util.go b/drivers/baidu_netdisk/util.go new file mode 100644 index 00000000..659266ab --- /dev/null +++ b/drivers/baidu_netdisk/util.go @@ -0,0 +1,199 @@ +package baidu_netdisk + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/alist-org/alist/v3/drivers/base" + "github.com/alist-org/alist/v3/internal/errs" + "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/internal/op" + "github.com/alist-org/alist/v3/pkg/utils" + "github.com/go-resty/resty/v2" +) + +// do others that not defined in Driver interface + +func (d *BaiduNetdisk) refreshToken() error { + err := d._refreshToken() + if err != nil && err == errs.EmptyToken { + err = d._refreshToken() + } + return err +} + +func (d *BaiduNetdisk) _refreshToken() error { + u := "https://openapi.baidu.com/oauth/2.0/token" + var resp base.TokenResp + var e TokenErrResp + _, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetQueryParams(map[string]string{ + "grant_type": "refresh_token", + "refresh_token": d.RefreshToken, + "client_id": d.ClientID, + "client_secret": d.ClientSecret, + }).Get(u) + if err != nil { + return err + } + if e.Error != "" { + return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription) + } + if resp.RefreshToken == "" { + return errs.EmptyToken + } + d.AccessToken, d.RefreshToken = resp.AccessToken, resp.RefreshToken + op.MustSaveDriverStorage(d) + return nil +} + +func (d *BaiduNetdisk) request(furl string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { + req := base.RestyClient.R() + req.SetQueryParam("access_token", d.AccessToken) + if callback != nil { + callback(req) + } + if resp != nil { + req.SetResult(resp) + } + res, err := req.Execute(method, furl) + if err != nil { + return nil, err + } + errno := utils.Json.Get(res.Body(), "errno").ToInt() + if errno != 0 { + if errno == -6 { + err = d.refreshToken() + if err != nil { + return nil, err + } + return d.request(furl, method, callback, resp) + } + return nil, fmt.Errorf("errno: %d, refer to https://pan.baidu.com/union/doc/", errno) + } + return res.Body(), nil +} + +func (d *BaiduNetdisk) get(pathname string, params map[string]string, resp interface{}) ([]byte, error) { + return d.request("https://pan.baidu.com/rest/2.0"+pathname, http.MethodGet, func(req *resty.Request) { + req.SetQueryParams(params) + }, resp) +} + +func (d *BaiduNetdisk) post(pathname string, params map[string]string, data interface{}, resp interface{}) ([]byte, error) { + return d.request("https://pan.baidu.com/rest/2.0"+pathname, http.MethodPost, func(req *resty.Request) { + req.SetQueryParams(params) + req.SetBody(data) + }, resp) +} + +func (d *BaiduNetdisk) getFiles(dir string) ([]File, error) { + start := 0 + limit := 200 + params := map[string]string{ + "method": "list", + "dir": dir, + "web": "web", + } + if d.OrderBy != "" { + params["order"] = d.OrderBy + if d.OrderDirection == "desc" { + params["desc"] = "1" + } + } + res := make([]File, 0) + for { + params["start"] = strconv.Itoa(start) + params["limit"] = strconv.Itoa(limit) + start += limit + var resp ListResp + _, err := d.get("/xpan/file", params, &resp) + if err != nil { + return nil, err + } + if len(resp.List) == 0 { + break + } + res = append(res, resp.List...) + } + return res, nil +} + +func (d *BaiduNetdisk) linkOfficial(file model.Obj, args model.LinkArgs) (*model.Link, error) { + var resp DownloadResp + params := map[string]string{ + "method": "filemetas", + "fsids": fmt.Sprintf("[%s]", file.GetID()), + "dlink": "1", + } + _, err := d.get("/xpan/multimedia", params, &resp) + if err != nil { + return nil, err + } + u := fmt.Sprintf("%s&access_token=%s", resp.List[0].Dlink, d.AccessToken) + res, err := base.NoRedirectClient.R().SetHeader("User-Agent", "pan.baidu.com").Head(u) + if err != nil { + return nil, err + } + //if res.StatusCode() == 302 { + u = res.Header().Get("location") + //} + return &model.Link{ + URL: u, + Header: http.Header{ + "User-Agent": []string{"pan.baidu.com"}, + }, + }, nil +} + +func (d *BaiduNetdisk) linkCrack(file model.Obj, args model.LinkArgs) (*model.Link, error) { + var resp DownloadResp2 + param := map[string]string{ + "target": fmt.Sprintf("[\"%s\"]", file.GetPath()), + "dlink": "1", + "web": "5", + "origin": "dlna", + } + _, err := d.get("https://pan.baidu.com/api/filemetas", param, &resp) + if err != nil { + return nil, err + } + return &model.Link{ + URL: resp.Info[0].Dlink, + Header: http.Header{ + "User-Agent": []string{"pan.baidu.com"}, + }, + }, nil +} + +func (d *BaiduNetdisk) manage(opera string, filelist interface{}) ([]byte, error) { + params := map[string]string{ + "method": "filemanager", + "opera": opera, + } + marshal, err := utils.Json.Marshal(filelist) + if err != nil { + return nil, err + } + data := fmt.Sprintf("async=0&filelist=%s&ondup=newcopy", string(marshal)) + return d.post("/xpan/file", params, data, nil) +} + +func (d *BaiduNetdisk) create(path string, size int64, isdir int, uploadid, block_list string) ([]byte, error) { + params := map[string]string{ + "method": "create", + } + data := fmt.Sprintf("path=%s&size=%d&isdir=%d", path, size, isdir) + if uploadid != "" { + data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list) + } + return d.post("/xpan/file", params, data, nil) +} + +func encodeURIComponent(str string) string { + r := url.QueryEscape(str) + r = strings.ReplaceAll(r, "+", "%20") + return r +} diff --git a/drivers/base/types.go b/drivers/base/types.go index d3ad3a60..e2757f21 100644 --- a/drivers/base/types.go +++ b/drivers/base/types.go @@ -1,8 +1,12 @@ package base +import "github.com/go-resty/resty/v2" + type Json map[string]interface{} type TokenResp struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` } + +type ReqCallback func(req *resty.Request) diff --git a/drivers/local/driver.go b/drivers/local/driver.go index 585a99e3..b089dc7c 100644 --- a/drivers/local/driver.go +++ b/drivers/local/driver.go @@ -62,7 +62,7 @@ func (d *Local) GetAddition() driver.Additional { } func (d *Local) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { - fullPath := dir.GetID() + fullPath := dir.GetPath() rawFiles, err := ioutil.ReadDir(fullPath) if err != nil { return nil, err @@ -103,7 +103,7 @@ func (d *Local) Get(ctx context.Context, path string) (model.Obj, error) { return nil, err } file := model.Object{ - ID: path, + Path: path, Name: f.Name(), Modified: f.ModTime(), Size: f.Size(), @@ -113,7 +113,7 @@ func (d *Local) Get(ctx context.Context, path string) (model.Obj, error) { } func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { - fullPath := file.GetID() + fullPath := file.GetPath() var link model.Link if args.Type == "thumb" && utils.Ext(file.GetName()) != "svg" { imgData, err := ioutil.ReadFile(fullPath) @@ -143,7 +143,7 @@ func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) ( } func (d *Local) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { - fullPath := filepath.Join(parentDir.GetID(), dirName) + fullPath := filepath.Join(parentDir.GetPath(), dirName) err := os.MkdirAll(fullPath, 0700) if err != nil { return err @@ -152,8 +152,8 @@ func (d *Local) MakeDir(ctx context.Context, parentDir model.Obj, dirName string } func (d *Local) Move(ctx context.Context, srcObj, dstDir model.Obj) error { - srcPath := srcObj.GetID() - dstPath := filepath.Join(dstDir.GetID(), srcObj.GetName()) + srcPath := srcObj.GetPath() + dstPath := filepath.Join(dstDir.GetPath(), srcObj.GetName()) err := os.Rename(srcPath, dstPath) if err != nil { return err @@ -162,7 +162,7 @@ func (d *Local) Move(ctx context.Context, srcObj, dstDir model.Obj) error { } func (d *Local) Rename(ctx context.Context, srcObj model.Obj, newName string) error { - srcPath := srcObj.GetID() + srcPath := srcObj.GetPath() dstPath := filepath.Join(filepath.Dir(srcPath), newName) err := os.Rename(srcPath, dstPath) if err != nil { @@ -172,8 +172,8 @@ func (d *Local) Rename(ctx context.Context, srcObj model.Obj, newName string) er } func (d *Local) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { - srcPath := srcObj.GetID() - dstPath := filepath.Join(dstDir.GetID(), srcObj.GetName()) + srcPath := srcObj.GetPath() + dstPath := filepath.Join(dstDir.GetPath(), srcObj.GetName()) var err error if srcObj.IsDir() { err = copyDir(srcPath, dstPath) @@ -189,9 +189,9 @@ func (d *Local) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { func (d *Local) Remove(ctx context.Context, obj model.Obj) error { var err error if obj.IsDir() { - err = os.RemoveAll(obj.GetID()) + err = os.RemoveAll(obj.GetPath()) } else { - err = os.Remove(obj.GetID()) + err = os.Remove(obj.GetPath()) } if err != nil { return err @@ -200,7 +200,7 @@ func (d *Local) Remove(ctx context.Context, obj model.Obj) error { } func (d *Local) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { - fullPath := filepath.Join(dstDir.GetID(), stream.GetName()) + fullPath := filepath.Join(dstDir.GetPath(), stream.GetName()) out, err := os.Create(fullPath) if err != nil { return err diff --git a/drivers/onedrive/driver.go b/drivers/onedrive/driver.go index 8cf70c04..91bec49f 100644 --- a/drivers/onedrive/driver.go +++ b/drivers/onedrive/driver.go @@ -9,6 +9,7 @@ import ( "github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/errs" "github.com/alist-org/alist/v3/internal/model" + "github.com/alist-org/alist/v3/internal/op" "github.com/alist-org/alist/v3/pkg/utils" "github.com/go-resty/resty/v2" ) @@ -41,7 +42,7 @@ func (d *Onedrive) Drop(ctx context.Context) error { } func (d *Onedrive) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { - files, err := d.GetFiles(dir.GetID()) + files, err := d.GetFiles(dir.GetPath()) if err != nil { return nil, err } @@ -53,7 +54,7 @@ func (d *Onedrive) List(ctx context.Context, dir model.Obj, args model.ListArgs) } func (d *Onedrive) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { - f, err := d.GetFile(file.GetID()) + f, err := d.GetFile(file.GetPath()) if err != nil { return nil, err } @@ -66,7 +67,7 @@ func (d *Onedrive) Link(ctx context.Context, file model.Obj, args model.LinkArgs } func (d *Onedrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { - url := d.GetMetaUrl(false, parentDir.GetID()) + "/children" + url := d.GetMetaUrl(false, parentDir.GetPath()) + "/children" data := base.Json{ "name": dirName, "folder": base.Json{}, @@ -79,35 +80,31 @@ func (d *Onedrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName str } func (d *Onedrive) Move(ctx context.Context, srcObj, dstDir model.Obj) error { - dst, err := d.GetFile(dstDir.GetID()) - if err != nil { - return err - } data := base.Json{ "parentReference": base.Json{ - "id": dst.Id, + "id": dstDir.GetID(), }, "name": srcObj.GetName(), } - url := d.GetMetaUrl(false, srcObj.GetID()) - _, err = d.Request(url, http.MethodPatch, func(req *resty.Request) { + url := d.GetMetaUrl(false, srcObj.GetPath()) + _, err := d.Request(url, http.MethodPatch, func(req *resty.Request) { req.SetBody(data) }, nil) return err } func (d *Onedrive) Rename(ctx context.Context, srcObj model.Obj, newName string) error { - dstDir, err := d.GetFile(stdpath.Dir(srcObj.GetID())) + dstDir, err := op.Get(ctx, d, stdpath.Dir(srcObj.GetPath())) if err != nil { return err } data := base.Json{ "parentReference": base.Json{ - "id": dstDir.Id, + "id": dstDir.GetID(), }, "name": newName, } - url := d.GetMetaUrl(false, srcObj.GetID()) + url := d.GetMetaUrl(false, srcObj.GetPath()) _, err = d.Request(url, http.MethodPatch, func(req *resty.Request) { req.SetBody(data) }, nil) @@ -115,7 +112,7 @@ func (d *Onedrive) Rename(ctx context.Context, srcObj model.Obj, newName string) } func (d *Onedrive) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { - dst, err := d.GetFile(dstDir.GetID()) + dst, err := d.GetFile(dstDir.GetPath()) if err != nil { return err } @@ -126,7 +123,7 @@ func (d *Onedrive) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { }, "name": srcObj.GetName(), } - url := d.GetMetaUrl(false, srcObj.GetID()) + "/copy" + url := d.GetMetaUrl(false, srcObj.GetPath()) + "/copy" _, err = d.Request(url, http.MethodPost, func(req *resty.Request) { req.SetBody(data) }, nil) @@ -134,7 +131,7 @@ func (d *Onedrive) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { } func (d *Onedrive) Remove(ctx context.Context, obj model.Obj) error { - url := d.GetMetaUrl(false, obj.GetID()) + url := d.GetMetaUrl(false, obj.GetPath()) _, err := d.Request(url, http.MethodDelete, nil, nil) return err } diff --git a/drivers/onedrive/meta.go b/drivers/onedrive/meta.go index f68cb174..c831c67d 100644 --- a/drivers/onedrive/meta.go +++ b/drivers/onedrive/meta.go @@ -9,7 +9,7 @@ type Addition struct { driver.RootFolderPath Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de"` IsSharepoint bool `json:"is_sharepoint"` - ClientId string `json:"client_id" required:"true"` + ClientID string `json:"client_id" required:"true"` ClientSecret string `json:"client_secret" required:"true"` RedirectUri string `json:"redirect_uri" required:"true" default:"https://tool.nn.ci/onedrive/callback"` RefreshToken string `json:"refresh_token" required:"true"` diff --git a/drivers/onedrive/types.go b/drivers/onedrive/types.go index 8f981d40..e1bd7620 100644 --- a/drivers/onedrive/types.go +++ b/drivers/onedrive/types.go @@ -49,7 +49,7 @@ func fileToObj(f File) *model.ObjThumbURL { } return &model.ObjThumbURL{ Object: model.Object{ - //ID: f.Id, + ID: f.Id, Name: f.Name, Size: f.Size, Modified: f.LastModifiedDateTime, diff --git a/drivers/onedrive/util.go b/drivers/onedrive/util.go index ea93b68e..058eeafd 100644 --- a/drivers/onedrive/util.go +++ b/drivers/onedrive/util.go @@ -77,7 +77,7 @@ func (d *Onedrive) _refreshToken() error { var e TokenErr _, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{ "grant_type": "refresh_token", - "client_id": d.ClientId, + "client_id": d.ClientID, "client_secret": d.ClientSecret, "redirect_uri": d.RedirectUri, "refresh_token": d.RefreshToken, @@ -96,7 +96,7 @@ func (d *Onedrive) _refreshToken() error { return nil } -func (d *Onedrive) Request(url string, method string, callback func(*resty.Request), resp interface{}) ([]byte, error) { +func (d *Onedrive) Request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { req := base.RestyClient.R() req.SetHeader("Authorization", "Bearer "+d.AccessToken) if callback != nil { @@ -147,7 +147,7 @@ func (d *Onedrive) GetFile(path string) (*File, error) { } func (d *Onedrive) upSmall(dstDir model.Obj, stream model.FileStreamer) error { - url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetID(), stream.GetName())) + "/content" + url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetPath(), stream.GetName())) + "/content" data, err := io.ReadAll(stream) if err != nil { return err @@ -159,7 +159,7 @@ func (d *Onedrive) upSmall(dstDir model.Obj, stream model.FileStreamer) error { } func (d *Onedrive) upBig(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { - url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetID(), stream.GetName())) + "/createUploadSession" + url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetPath(), stream.GetName())) + "/createUploadSession" res, err := d.Request(url, http.MethodPost, nil, nil) if err != nil { return err diff --git a/drivers/pikpak/util.go b/drivers/pikpak/util.go index 02b86374..0ac62cfd 100644 --- a/drivers/pikpak/util.go +++ b/drivers/pikpak/util.go @@ -66,7 +66,7 @@ func (d *PikPak) refreshToken() error { return nil } -func (d *PikPak) request(url string, method string, callback func(*resty.Request), resp interface{}) ([]byte, error) { +func (d *PikPak) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { req := base.RestyClient.R() req.SetHeader("Authorization", "Bearer "+d.AccessToken) if callback != nil { diff --git a/drivers/quark/util.go b/drivers/quark/util.go index 45396904..27f2b6c5 100644 --- a/drivers/quark/util.go +++ b/drivers/quark/util.go @@ -21,7 +21,7 @@ import ( // do others that not defined in Driver interface -func (d *Quark) request(pathname string, method string, callback func(req *resty.Request), resp interface{}) ([]byte, error) { +func (d *Quark) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { u := "https://drive.quark.cn/1/clouddrive" + pathname req := base.RestyClient.R() req.SetHeaders(map[string]string{ diff --git a/drivers/teambition/util.go b/drivers/teambition/util.go index bd293be2..4f5fd27f 100644 --- a/drivers/teambition/util.go +++ b/drivers/teambition/util.go @@ -22,7 +22,7 @@ func (d *Teambition) isInternational() bool { return d.Region == "international" } -func (d *Teambition) request(pathname string, method string, callback func(req *resty.Request), resp interface{}) ([]byte, error) { +func (d *Teambition) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { url := "https://www.teambition.com" + pathname if d.isInternational() { url = "https://us.teambition.com" + pathname diff --git a/internal/model/obj.go b/internal/model/obj.go index 60e3de5f..543b95a1 100644 --- a/internal/model/obj.go +++ b/internal/model/obj.go @@ -13,6 +13,7 @@ type Obj interface { ModTime() time.Time IsDir() bool GetID() string + GetPath() string } type FileStreamer interface { @@ -32,8 +33,8 @@ type Thumb interface { Thumb() string } -type SetID interface { - SetID(id string) +type SetPath interface { + SetPath(path string) } func SortFiles(objs []Obj, orderBy, orderDirection string) { diff --git a/internal/model/object.go b/internal/model/object.go index 1bc9ba75..542485d4 100644 --- a/internal/model/object.go +++ b/internal/model/object.go @@ -4,6 +4,7 @@ import "time" type Object struct { ID string + Path string Name string Size int64 Modified time.Time @@ -30,8 +31,12 @@ func (o *Object) GetID() string { return o.ID } -func (o *Object) SetID(id string) { - o.ID = id +func (o *Object) GetPath() string { + return o.Path +} + +func (o *Object) SetPath(id string) { + o.Path = id } type Thumbnail struct { diff --git a/internal/op/fs.go b/internal/op/fs.go index 306b1721..f6ef2406 100644 --- a/internal/op/fs.go +++ b/internal/op/fs.go @@ -35,6 +35,7 @@ func List(ctx context.Context, storage driver.Driver, path string, args model.Li if err != nil { return nil, errors.WithMessage(err, "failed get dir") } + log.Debugf("list dir: %+v", dir) if !dir.IsDir() { return nil, errors.WithStack(errs.NotFolder) } @@ -94,7 +95,7 @@ func Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, er } if r, ok := storage.GetAddition().(driver.IRootFolderPath); ok && isRoot(path, r.GetRootFolderPath()) { return &model.Object{ - ID: r.GetRootFolderPath(), + Path: r.GetRootFolderPath(), Name: "root", Size: 0, Modified: storage.GetStorage().Modified, @@ -108,12 +109,13 @@ func Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, er return nil, errors.WithMessage(err, "failed get parent list") } for _, f := range files { + // TODO maybe copy obj here if f.GetName() == name { // use path as id, why don't set id in List function? // because files maybe cache, set id here can reduce memory usage - if f.GetID() == "" { - if s, ok := f.(model.SetID); ok { - s.SetID(path) + if f.GetPath() == "" { + if s, ok := f.(model.SetPath); ok { + s.SetPath(path) } } return f, nil