mirror of https://github.com/Xhofe/alist
368 lines
9.9 KiB
Go
368 lines
9.9 KiB
Go
package mopan
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/alist-org/alist/v3/drivers/base"
|
|
"github.com/alist-org/alist/v3/internal/driver"
|
|
"github.com/alist-org/alist/v3/internal/model"
|
|
"github.com/alist-org/alist/v3/internal/op"
|
|
"github.com/alist-org/alist/v3/pkg/errgroup"
|
|
"github.com/alist-org/alist/v3/pkg/utils"
|
|
"github.com/avast/retry-go"
|
|
"github.com/foxxorcat/mopan-sdk-go"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type MoPan struct {
|
|
model.Storage
|
|
Addition
|
|
client *mopan.MoClient
|
|
|
|
userID string
|
|
uploadThread int
|
|
}
|
|
|
|
func (d *MoPan) Config() driver.Config {
|
|
return config
|
|
}
|
|
|
|
func (d *MoPan) GetAddition() driver.Additional {
|
|
return &d.Addition
|
|
}
|
|
|
|
func (d *MoPan) Init(ctx context.Context) error {
|
|
d.uploadThread, _ = strconv.Atoi(d.UploadThread)
|
|
if d.uploadThread < 1 || d.uploadThread > 32 {
|
|
d.uploadThread, d.UploadThread = 3, "3"
|
|
}
|
|
|
|
defer func() { d.SMSCode = "" }()
|
|
|
|
login := func() (err error) {
|
|
var loginData *mopan.LoginResp
|
|
if d.SMSCode != "" {
|
|
loginData, err = d.client.LoginBySmsStep2(d.Phone, d.SMSCode)
|
|
} else {
|
|
loginData, err = d.client.Login(d.Phone, d.Password)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.client.SetAuthorization(loginData.Token)
|
|
|
|
info, err := d.client.GetUserInfo()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.userID = info.UserID
|
|
log.Debugf("[mopan] Phone: %s UserCloudStorageRelations: %+v", d.Phone, loginData.UserCloudStorageRelations)
|
|
cloudCircleApp, _ := d.client.QueryAllCloudCircleApp()
|
|
log.Debugf("[mopan] Phone: %s CloudCircleApp: %+v", d.Phone, cloudCircleApp)
|
|
if d.RootFolderID == "" {
|
|
for _, userCloudStorage := range loginData.UserCloudStorageRelations {
|
|
if userCloudStorage.Path == "/文件" {
|
|
d.RootFolderID = userCloudStorage.FolderID
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
d.client = mopan.NewMoClientWithRestyClient(base.NewRestyClient()).
|
|
SetRestyClient(base.RestyClient).
|
|
SetOnAuthorizationExpired(func(_ error) error {
|
|
err := login()
|
|
if err != nil {
|
|
d.Status = err.Error()
|
|
op.MustSaveDriverStorage(d)
|
|
}
|
|
return err
|
|
})
|
|
|
|
var deviceInfo mopan.DeviceInfo
|
|
if strings.TrimSpace(d.DeviceInfo) != "" && utils.Json.UnmarshalFromString(d.DeviceInfo, &deviceInfo) == nil {
|
|
d.client.SetDeviceInfo(&deviceInfo)
|
|
}
|
|
d.DeviceInfo, _ = utils.Json.MarshalToString(d.client.GetDeviceInfo())
|
|
|
|
if strings.Contains(d.SMSCode, "send") {
|
|
if _, err := d.client.LoginBySms(d.Phone); err != nil {
|
|
return err
|
|
}
|
|
return errors.New("please enter the SMS code")
|
|
}
|
|
return login()
|
|
}
|
|
|
|
func (d *MoPan) Drop(ctx context.Context) error {
|
|
d.client = nil
|
|
d.userID = ""
|
|
return nil
|
|
}
|
|
|
|
func (d *MoPan) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
|
var files []model.Obj
|
|
for page := 1; ; page++ {
|
|
data, err := d.client.QueryFiles(dir.GetID(), page, mopan.WarpParamOption(
|
|
func(j mopan.Json) {
|
|
j["orderBy"] = d.OrderBy
|
|
j["descending"] = d.OrderDirection == "desc"
|
|
},
|
|
mopan.ParamOptionShareFile(d.CloudID),
|
|
))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(data.FileListAO.FileList)+len(data.FileListAO.FolderList) == 0 {
|
|
break
|
|
}
|
|
|
|
log.Debugf("[mopan] Phone: %s folder: %+v", d.Phone, data.FileListAO.FolderList)
|
|
files = append(files, utils.MustSliceConvert(data.FileListAO.FolderList, folderToObj)...)
|
|
files = append(files, utils.MustSliceConvert(data.FileListAO.FileList, fileToObj)...)
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
func (d *MoPan) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
|
data, err := d.client.GetFileDownloadUrl(file.GetID(), mopan.WarpParamOption(mopan.ParamOptionShareFile(d.CloudID)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
data.DownloadUrl = strings.Replace(strings.ReplaceAll(data.DownloadUrl, "&", "&"), "http://", "https://", 1)
|
|
res, err := base.NoRedirectClient.R().SetDoNotParseResponse(true).SetContext(ctx).Get(data.DownloadUrl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
_ = res.RawBody().Close()
|
|
}()
|
|
if res.StatusCode() == 302 {
|
|
data.DownloadUrl = res.Header().Get("location")
|
|
}
|
|
|
|
return &model.Link{
|
|
URL: data.DownloadUrl,
|
|
}, nil
|
|
}
|
|
|
|
func (d *MoPan) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
|
|
f, err := d.client.CreateFolder(dirName, parentDir.GetID(), mopan.WarpParamOption(
|
|
mopan.ParamOptionShareFile(d.CloudID),
|
|
))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return folderToObj(*f), nil
|
|
}
|
|
|
|
func (d *MoPan) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
|
return d.newTask(srcObj, dstDir, mopan.TASK_MOVE)
|
|
}
|
|
|
|
func (d *MoPan) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
|
|
if srcObj.IsDir() {
|
|
_, err := d.client.RenameFolder(srcObj.GetID(), newName, mopan.WarpParamOption(
|
|
mopan.ParamOptionShareFile(d.CloudID),
|
|
))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
_, err := d.client.RenameFile(srcObj.GetID(), newName, mopan.WarpParamOption(
|
|
mopan.ParamOptionShareFile(d.CloudID),
|
|
))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return CloneObj(srcObj, srcObj.GetID(), newName), nil
|
|
}
|
|
|
|
func (d *MoPan) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
|
return d.newTask(srcObj, dstDir, mopan.TASK_COPY)
|
|
}
|
|
|
|
func (d *MoPan) newTask(srcObj, dstDir model.Obj, taskType mopan.TaskType) (model.Obj, error) {
|
|
param := mopan.TaskParam{
|
|
UserOrCloudID: d.userID,
|
|
Source: 1,
|
|
TaskType: taskType,
|
|
TargetSource: 1,
|
|
TargetUserOrCloudID: d.userID,
|
|
TargetType: 1,
|
|
TargetFolderID: dstDir.GetID(),
|
|
TaskStatusDetailDTOList: []mopan.TaskFileParam{
|
|
{
|
|
FileID: srcObj.GetID(),
|
|
IsFolder: srcObj.IsDir(),
|
|
FileName: srcObj.GetName(),
|
|
},
|
|
},
|
|
}
|
|
if d.CloudID != "" {
|
|
param.UserOrCloudID = d.CloudID
|
|
param.Source = 2
|
|
param.TargetSource = 2
|
|
param.TargetUserOrCloudID = d.CloudID
|
|
}
|
|
|
|
task, err := d.client.AddBatchTask(param)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for count := 0; count < 5; count++ {
|
|
stat, err := d.client.CheckBatchTask(mopan.TaskCheckParam{
|
|
TaskId: task.TaskIDList[0],
|
|
TaskType: task.TaskType,
|
|
TargetType: 1,
|
|
TargetFolderID: task.TargetFolderID,
|
|
TargetSource: param.TargetSource,
|
|
TargetUserOrCloudID: param.TargetUserOrCloudID,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch stat.TaskStatus {
|
|
case 2:
|
|
if err := d.client.CancelBatchTask(stat.TaskID, task.TaskType); err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, errors.New("file name conflict")
|
|
case 4:
|
|
if task.TaskType == mopan.TASK_MOVE {
|
|
return CloneObj(srcObj, srcObj.GetID(), srcObj.GetName()), nil
|
|
}
|
|
return CloneObj(srcObj, stat.SuccessedFileIDList[0], srcObj.GetName()), nil
|
|
}
|
|
time.Sleep(time.Second)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (d *MoPan) Remove(ctx context.Context, obj model.Obj) error {
|
|
_, err := d.client.DeleteToRecycle([]mopan.TaskFileParam{
|
|
{
|
|
FileID: obj.GetID(),
|
|
IsFolder: obj.IsDir(),
|
|
FileName: obj.GetName(),
|
|
},
|
|
}, mopan.WarpParamOption(mopan.ParamOptionShareFile(d.CloudID)))
|
|
return err
|
|
}
|
|
|
|
func (d *MoPan) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
|
|
file, err := stream.CacheFullInTempFile()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
_ = file.Close()
|
|
}()
|
|
|
|
// step.1
|
|
uploadPartData, err := mopan.InitUploadPartData(ctx, mopan.UpdloadFileParam{
|
|
ParentFolderId: dstDir.GetID(),
|
|
FileName: stream.GetName(),
|
|
FileSize: stream.GetSize(),
|
|
File: file,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 尝试恢复进度
|
|
initUpdload, ok := base.GetUploadProgress[*mopan.InitMultiUploadData](d, d.client.Authorization, uploadPartData.FileMd5)
|
|
if !ok {
|
|
// step.2
|
|
initUpdload, err = d.client.InitMultiUpload(ctx, *uploadPartData, mopan.WarpParamOption(
|
|
mopan.ParamOptionShareFile(d.CloudID),
|
|
))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if !initUpdload.FileDataExists {
|
|
// utils.Log.Error(d.client.CloudDiskStartBusiness())
|
|
|
|
threadG, upCtx := errgroup.NewGroupWithContext(ctx, d.uploadThread,
|
|
retry.Attempts(3),
|
|
retry.Delay(time.Second),
|
|
retry.DelayType(retry.BackOffDelay))
|
|
|
|
// step.3
|
|
parts, err := d.client.GetAllMultiUploadUrls(initUpdload.UploadFileID, initUpdload.PartInfos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i, part := range parts {
|
|
if utils.IsCanceled(upCtx) {
|
|
break
|
|
}
|
|
i, part, byteSize := i, part, initUpdload.PartSize
|
|
if part.PartNumber == uploadPartData.PartTotal {
|
|
byteSize = initUpdload.LastPartSize
|
|
}
|
|
|
|
// step.4
|
|
threadG.Go(func(ctx context.Context) error {
|
|
req, err := part.NewRequest(ctx, io.NewSectionReader(file, int64(part.PartNumber-1)*initUpdload.PartSize, byteSize))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.ContentLength = byteSize
|
|
resp, err := base.HttpClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("upload err,code=%d", resp.StatusCode)
|
|
}
|
|
up(100 * float64(threadG.Success()) / float64(len(parts)))
|
|
initUpdload.PartInfos[i] = ""
|
|
return nil
|
|
})
|
|
}
|
|
if err = threadG.Wait(); err != nil {
|
|
if errors.Is(err, context.Canceled) {
|
|
initUpdload.PartInfos = utils.SliceFilter(initUpdload.PartInfos, func(s string) bool { return s != "" })
|
|
base.SaveUploadProgress(d, initUpdload, d.client.Authorization, uploadPartData.FileMd5)
|
|
}
|
|
return nil, err
|
|
}
|
|
}
|
|
//step.5
|
|
uFile, err := d.client.CommitMultiUploadFile(initUpdload.UploadFileID, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &model.Object{
|
|
ID: uFile.UserFileID,
|
|
Name: uFile.FileName,
|
|
Size: int64(uFile.FileSize),
|
|
Modified: time.Time(uFile.CreateDate),
|
|
}, nil
|
|
}
|
|
|
|
var _ driver.Driver = (*MoPan)(nil)
|
|
var _ driver.MkdirResult = (*MoPan)(nil)
|
|
var _ driver.MoveResult = (*MoPan)(nil)
|
|
var _ driver.RenameResult = (*MoPan)(nil)
|
|
var _ driver.Remove = (*MoPan)(nil)
|
|
var _ driver.CopyResult = (*MoPan)(nil)
|
|
var _ driver.PutResult = (*MoPan)(nil)
|