diff --git a/drivers/all.go b/drivers/all.go index 1f7c808b..6dfd8b18 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -44,6 +44,7 @@ import ( _ "github.com/alist-org/alist/v3/drivers/uss" _ "github.com/alist-org/alist/v3/drivers/virtual" _ "github.com/alist-org/alist/v3/drivers/webdav" + _ "github.com/alist-org/alist/v3/drivers/weiyun" _ "github.com/alist-org/alist/v3/drivers/wopan" _ "github.com/alist-org/alist/v3/drivers/yandex_disk" ) diff --git a/drivers/weiyun/driver.go b/drivers/weiyun/driver.go new file mode 100644 index 00000000..84088566 --- /dev/null +++ b/drivers/weiyun/driver.go @@ -0,0 +1,381 @@ +package weiyun + +import ( + "context" + "io" + "math" + "net/http" + "os" + "sync" + "time" + + "github.com/alist-org/alist/v3/drivers/base" + "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/cron" + "github.com/alist-org/alist/v3/pkg/utils" + weiyunsdkgo "github.com/foxxorcat/weiyun-sdk-go" +) + +type WeiYun struct { + model.Storage + Addition + + client *weiyunsdkgo.WeiYunClient + cron *cron.Cron + rootFolder *Folder +} + +func (d *WeiYun) Config() driver.Config { + return config +} + +func (d *WeiYun) GetAddition() driver.Additional { + return &d.Addition +} + +func (d *WeiYun) Init(ctx context.Context) error { + d.client = weiyunsdkgo.NewWeiYunClientWithRestyClient(base.RestyClient) + err := d.client.SetCookiesStr(d.Cookies).RefreshCtoken() + if err != nil { + return err + } + + // Cookie过期回调 + d.client.SetOnCookieExpired(func(err error) { + d.Status = err.Error() + op.MustSaveDriverStorage(d) + }) + + // cookie更新回调 + d.client.SetOnCookieUpload(func(c []*http.Cookie) { + d.Cookies = weiyunsdkgo.CookieToString(weiyunsdkgo.ClearCookie(c)) + op.MustSaveDriverStorage(d) + }) + + // qqCookie保活 + if d.client.LoginType() == 1 { + d.cron = cron.NewCron(time.Minute * 5) + d.cron.Do(func() { + d.client.KeepAlive() + }) + } + + // 获取默认根目录dirKey + if d.RootFolderID == "" { + userInfo, err := d.client.DiskUserInfoGet() + if err != nil { + return err + } + d.RootFolderID = userInfo.MainDirKey + } + + // 处理目录ID,找到PdirKey + folders, err := d.client.LibDirPathGet(d.RootFolderID) + if err != nil { + return err + } + folder := folders[len(folders)-1] + d.rootFolder = &Folder{ + PFolder: &Folder{ + Folder: weiyunsdkgo.Folder{ + DirKey: folder.PdirKey, + }, + }, + Folder: folder.Folder, + } + return nil +} + +func (d *WeiYun) Drop(ctx context.Context) error { + d.client = nil + if d.cron != nil { + d.cron.Stop() + d.cron = nil + } + return nil +} + +func (d *WeiYun) GetRoot(ctx context.Context) (model.Obj, error) { + return d.rootFolder, nil +} + +func (d *WeiYun) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { + if folder, ok := dir.(*Folder); ok { + var files []model.Obj + for { + data, err := d.client.DiskDirFileList(folder.GetID(), weiyunsdkgo.WarpParamOption( + weiyunsdkgo.QueryFileOptionOffest(int64(len(files))), + weiyunsdkgo.QueryFileOptionGetType(weiyunsdkgo.FileAndDir), + weiyunsdkgo.QueryFileOptionSort(func() weiyunsdkgo.OrderBy { + switch d.OrderBy { + case "name": + return weiyunsdkgo.FileName + case "size": + return weiyunsdkgo.FileSize + case "updated_at": + return weiyunsdkgo.FileMtime + default: + return weiyunsdkgo.FileName + } + }(), d.OrderDirection == "desc"), + )) + if err != nil { + return nil, err + } + + if files == nil { + files = make([]model.Obj, 0, data.TotalDirCount+data.TotalFileCount) + } + + for _, dir := range data.DirList { + files = append(files, &Folder{ + PFolder: folder, + Folder: dir, + }) + } + + for _, file := range data.FileList { + files = append(files, &File{ + PFolder: folder, + File: file, + }) + } + + if data.FinishFlag || len(data.DirList)+len(data.FileList) == 0 { + return files, nil + } + } + } + return nil, errs.NotSupport +} + +func (d *WeiYun) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { + if file, ok := file.(*File); ok { + data, err := d.client.DiskFileDownload(weiyunsdkgo.FileParam{PdirKey: file.GetPKey(), FileID: file.GetID()}) + if err != nil { + return nil, err + } + return &model.Link{ + URL: data.DownloadUrl, + Header: http.Header{ + "Cookie": []string{data.CookieName + "=" + data.CookieValue}, + }, + }, nil + } + return nil, errs.NotSupport +} + +func (d *WeiYun) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) { + if folder, ok := parentDir.(*Folder); ok { + newFolder, err := d.client.DiskDirCreate(weiyunsdkgo.FolderParam{ + PPdirKey: folder.GetPKey(), + PdirKey: folder.DirKey, + DirName: dirName, + }) + if err != nil { + return nil, err + } + return &Folder{ + PFolder: folder, + Folder: *newFolder, + }, nil + } + return nil, errs.NotSupport +} + +func (d *WeiYun) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) { + if dstDir, ok := dstDir.(*Folder); ok { + dstParam := weiyunsdkgo.FolderParam{ + PdirKey: dstDir.GetPKey(), + DirKey: dstDir.GetID(), + DirName: dstDir.GetName(), + } + switch srcObj := srcObj.(type) { + case *File: + err := d.client.DiskFileMove(weiyunsdkgo.FileParam{ + PPdirKey: srcObj.PFolder.GetPKey(), + PdirKey: srcObj.GetPKey(), + FileID: srcObj.GetID(), + FileName: srcObj.GetName(), + }, dstParam) + if err != nil { + return nil, err + } + + return &File{ + PFolder: dstDir, + File: srcObj.File, + }, nil + case *Folder: + err := d.client.DiskDirMove(weiyunsdkgo.FolderParam{ + PPdirKey: srcObj.PFolder.GetPKey(), + PdirKey: srcObj.GetPKey(), + DirKey: srcObj.GetID(), + DirName: srcObj.GetName(), + }, dstParam) + if err != nil { + return nil, err + } + + return &Folder{ + PFolder: dstDir, + Folder: srcObj.Folder, + }, nil + } + } + return nil, errs.NotSupport +} + +func (d *WeiYun) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) { + switch srcObj := srcObj.(type) { + case *File: + err := d.client.DiskFileRename(weiyunsdkgo.FileParam{ + PPdirKey: srcObj.PFolder.GetPKey(), + PdirKey: srcObj.GetPKey(), + FileID: srcObj.GetID(), + FileName: srcObj.GetName(), + }, newName) + if err != nil { + return nil, err + } + newFile := srcObj.File + newFile.FileName = newName + newFile.FileCtime = weiyunsdkgo.TimeStamp(time.Now()) + return &File{ + PFolder: srcObj.PFolder, + File: newFile, + }, nil + case *Folder: + err := d.client.DiskDirAttrModify(weiyunsdkgo.FolderParam{ + PPdirKey: srcObj.PFolder.GetPKey(), + PdirKey: srcObj.GetPKey(), + DirKey: srcObj.GetID(), + DirName: srcObj.GetName(), + }, newName) + if err != nil { + return nil, err + } + + newFolder := srcObj.Folder + newFolder.DirName = newName + newFolder.DirCtime = weiyunsdkgo.TimeStamp(time.Now()) + return &Folder{ + PFolder: srcObj.PFolder, + Folder: newFolder, + }, nil + } + return nil, errs.NotSupport +} + +func (d *WeiYun) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { + // TODO copy obj, optional + return errs.NotImplement +} + +func (d *WeiYun) Remove(ctx context.Context, obj model.Obj) error { + switch obj := obj.(type) { + case *File: + return d.client.DiskFileDelete(weiyunsdkgo.FileParam{ + PPdirKey: obj.PFolder.GetPKey(), + PdirKey: obj.GetPKey(), + FileID: obj.GetID(), + FileName: obj.GetName(), + }) + case *Folder: + return d.client.DiskDirDelete(weiyunsdkgo.FolderParam{ + PPdirKey: obj.PFolder.GetPKey(), + PdirKey: obj.GetPKey(), + DirKey: obj.GetID(), + DirName: obj.GetName(), + }) + } + // TODO remove obj, optional + return errs.NotSupport +} + +func (d *WeiYun) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) { + if folder, ok := dstDir.(*Folder); ok { + file, err := utils.CreateTempFile(stream) + if err != nil { + return nil, err + } + defer func() { + _ = file.Close() + _ = os.Remove(file.Name()) + }() + + // step 1. + preData, err := d.client.PreUpload(ctx, weiyunsdkgo.UpdloadFileParam{ + PdirKey: folder.GetPKey(), + DirKey: folder.DirKey, + + FileName: stream.GetName(), + FileSize: stream.GetSize(), + File: file, + + ChannelCount: 4, + FileExistOption: 1, + }) + if err != nil { + return nil, err + } + + // fast upload + if !preData.FileExist { + // step 2. + upCtx, cancel := context.WithCancelCause(ctx) + var wg sync.WaitGroup + for _, channel := range preData.ChannelList { + wg.Add(1) + go func(channel weiyunsdkgo.UploadChannelData) { + defer wg.Done() + if utils.IsCanceled(upCtx) { + return + } + for { + channel.Len = int(math.Min(float64(stream.GetSize()-channel.Offset), float64(channel.Len))) + upData, err := d.client.UploadFile(upCtx, channel, preData.UploadAuthData, + io.NewSectionReader(file, channel.Offset, int64(channel.Len))) + if err != nil { + cancel(err) + return + } + // 上传完成 + if upData.UploadState != 1 { + return + } + channel = upData.Channel + } + }(channel) + } + wg.Wait() + if utils.IsCanceled(upCtx) { + return nil, context.Cause(upCtx) + } + } + + return &File{ + PFolder: folder, + File: preData.File, + }, nil + } + return nil, errs.NotSupport +} + +// func (d *WeiYun) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) { +// return nil, errs.NotSupport +// } + +var _ driver.Driver = (*WeiYun)(nil) +var _ driver.GetRooter = (*WeiYun)(nil) +var _ driver.MkdirResult = (*WeiYun)(nil) + +// var _ driver.CopyResult = (*WeiYun)(nil) +var _ driver.MoveResult = (*WeiYun)(nil) +var _ driver.Remove = (*WeiYun)(nil) + +var _ driver.PutResult = (*WeiYun)(nil) +var _ driver.RenameResult = (*WeiYun)(nil) diff --git a/drivers/weiyun/meta.go b/drivers/weiyun/meta.go new file mode 100644 index 00000000..c9bbbf7b --- /dev/null +++ b/drivers/weiyun/meta.go @@ -0,0 +1,28 @@ +package weiyun + +import ( + "github.com/alist-org/alist/v3/internal/driver" + "github.com/alist-org/alist/v3/internal/op" +) + +type Addition struct { + RootFolderID string `json:"root_folder_id"` + Cookies string `json:"cookies" required:"true"` + OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at" default:"name"` + OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` +} + +var config = driver.Config{ + Name: "WeiYun", + LocalSort: false, + OnlyProxy: true, + CheckStatus: true, + Alert: "", + NoOverwriteUpload: false, +} + +func init() { + op.RegisterDriver(func() driver.Driver { + return &WeiYun{} + }) +} diff --git a/drivers/weiyun/types.go b/drivers/weiyun/types.go new file mode 100644 index 00000000..35558327 --- /dev/null +++ b/drivers/weiyun/types.go @@ -0,0 +1,39 @@ +package weiyun + +import ( + "time" + + weiyunsdkgo "github.com/foxxorcat/weiyun-sdk-go" +) + +type File struct { + PFolder *Folder + weiyunsdkgo.File +} + +func (f *File) GetID() string { return f.FileID } +func (f *File) GetSize() int64 { return f.FileSize } +func (f *File) GetName() string { return f.FileName } +func (f *File) ModTime() time.Time { return time.Time(f.FileMtime) } +func (f *File) IsDir() bool { return false } +func (f *File) GetPath() string { return "" } + +func (f *File) GetPKey() string { + return f.PFolder.DirKey +} + +type Folder struct { + PFolder *Folder + weiyunsdkgo.Folder +} + +func (f *Folder) GetID() string { return f.DirKey } +func (f *Folder) GetSize() int64 { return 0 } +func (f *Folder) GetName() string { return f.DirName } +func (f *Folder) ModTime() time.Time { return time.Time(f.DirMtime) } +func (f *Folder) IsDir() bool { return true } +func (f *Folder) GetPath() string { return "" } + +func (f *Folder) GetPKey() string { + return f.PFolder.DirKey +} diff --git a/go.mod b/go.mod index 14a87403..318b1275 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/disintegration/imaging v1.6.2 github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 github.com/foxxorcat/mopan-sdk-go v0.1.1 + github.com/foxxorcat/weiyun-sdk-go v0.1.1 github.com/gin-contrib/cors v1.4.0 github.com/gin-gonic/gin v1.9.1 github.com/go-resty/resty/v2 v2.7.0 diff --git a/go.sum b/go.sum index a8f987bd..28bf38e1 100644 --- a/go.sum +++ b/go.sum @@ -195,6 +195,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/foxxorcat/mopan-sdk-go v0.1.1 h1:JYMeCu4PFpqgHapvOz4jPMT7CxR6Yebu3aWkgGMDeIU= github.com/foxxorcat/mopan-sdk-go v0.1.1/go.mod h1:LpBPmwezjQNyhaNo3HGzgFtQbhvxmF5ZybSVuKi7OVA= +github.com/foxxorcat/weiyun-sdk-go v0.1.1 h1:m4qcJk0adr+bpM4es2zCqP3jhMEwEPyTMGICsamygEQ= +github.com/foxxorcat/weiyun-sdk-go v0.1.1/go.mod h1:AKsLFuWhWlClpGrg1zxTdMejugZEZtmhIuElAk3W83s= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=