|
package quqi |
|
|
|
import ( |
|
"bytes" |
|
"context" |
|
"io" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"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" |
|
"github.com/alist-org/alist/v3/pkg/utils/random" |
|
"github.com/aws/aws-sdk-go/aws" |
|
"github.com/aws/aws-sdk-go/aws/credentials" |
|
"github.com/aws/aws-sdk-go/aws/session" |
|
"github.com/aws/aws-sdk-go/service/s3" |
|
"github.com/aws/aws-sdk-go/service/s3/s3manager" |
|
"github.com/go-resty/resty/v2" |
|
log "github.com/sirupsen/logrus" |
|
) |
|
|
|
type Quqi struct { |
|
model.Storage |
|
Addition |
|
Cookie string |
|
GroupID string |
|
ClientID string |
|
} |
|
|
|
func (d *Quqi) Config() driver.Config { |
|
return config |
|
} |
|
|
|
func (d *Quqi) GetAddition() driver.Additional { |
|
return &d.Addition |
|
} |
|
|
|
func (d *Quqi) Init(ctx context.Context) error { |
|
|
|
if err := d.login(); err != nil { |
|
return err |
|
} |
|
|
|
|
|
d.ClientID = "quqipc_" + random.String(10) |
|
|
|
|
|
groupResp := &GroupRes{} |
|
if _, err := d.request("group.quqi.com", "/v1/group/list", resty.MethodGet, nil, groupResp); err != nil { |
|
return err |
|
} |
|
for _, groupInfo := range groupResp.Data { |
|
if groupInfo == nil { |
|
continue |
|
} |
|
if groupInfo.Type == 2 { |
|
d.GroupID = strconv.Itoa(groupInfo.ID) |
|
break |
|
} |
|
} |
|
if d.GroupID == "" { |
|
return errs.StorageNotFound |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func (d *Quqi) Drop(ctx context.Context) error { |
|
return nil |
|
} |
|
|
|
func (d *Quqi) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { |
|
var ( |
|
listResp = &ListRes{} |
|
files []model.Obj |
|
) |
|
|
|
if _, err := d.request("", "/api/dir/ls", resty.MethodPost, func(req *resty.Request) { |
|
req.SetFormData(map[string]string{ |
|
"quqi_id": d.GroupID, |
|
"tree_id": "1", |
|
"node_id": dir.GetID(), |
|
"client_id": d.ClientID, |
|
}) |
|
}, listResp); err != nil { |
|
return nil, err |
|
} |
|
|
|
if listResp.Data == nil { |
|
return nil, nil |
|
} |
|
|
|
|
|
for _, dirInfo := range listResp.Data.Dir { |
|
if dirInfo == nil { |
|
continue |
|
} |
|
files = append(files, &model.Object{ |
|
ID: strconv.FormatInt(dirInfo.NodeID, 10), |
|
Name: dirInfo.Name, |
|
Modified: time.Unix(dirInfo.UpdateTime, 0), |
|
Ctime: time.Unix(dirInfo.AddTime, 0), |
|
IsFolder: true, |
|
}) |
|
} |
|
|
|
|
|
for _, fileInfo := range listResp.Data.File { |
|
if fileInfo == nil { |
|
continue |
|
} |
|
if fileInfo.EXT != "" { |
|
fileInfo.Name = strings.Join([]string{fileInfo.Name, fileInfo.EXT}, ".") |
|
} |
|
|
|
files = append(files, &model.Object{ |
|
ID: strconv.FormatInt(fileInfo.NodeID, 10), |
|
Name: fileInfo.Name, |
|
Size: fileInfo.Size, |
|
Modified: time.Unix(fileInfo.UpdateTime, 0), |
|
Ctime: time.Unix(fileInfo.AddTime, 0), |
|
}) |
|
} |
|
|
|
return files, nil |
|
} |
|
|
|
func (d *Quqi) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { |
|
if d.CDN { |
|
link, err := d.linkFromCDN(file.GetID()) |
|
if err != nil { |
|
log.Warn(err) |
|
} else { |
|
return link, nil |
|
} |
|
} |
|
|
|
link, err := d.linkFromPreview(file.GetID()) |
|
if err != nil { |
|
log.Warn(err) |
|
} else { |
|
return link, nil |
|
} |
|
|
|
link, err = d.linkFromDownload(file.GetID()) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return link, nil |
|
} |
|
|
|
func (d *Quqi) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) { |
|
var ( |
|
makeDirRes = &MakeDirRes{} |
|
timeNow = time.Now() |
|
) |
|
|
|
if _, err := d.request("", "/api/dir/mkDir", resty.MethodPost, func(req *resty.Request) { |
|
req.SetFormData(map[string]string{ |
|
"quqi_id": d.GroupID, |
|
"tree_id": "1", |
|
"parent_id": parentDir.GetID(), |
|
"name": dirName, |
|
"client_id": d.ClientID, |
|
}) |
|
}, makeDirRes); err != nil { |
|
return nil, err |
|
} |
|
|
|
return &model.Object{ |
|
ID: strconv.FormatInt(makeDirRes.Data.NodeID, 10), |
|
Name: dirName, |
|
Modified: timeNow, |
|
Ctime: timeNow, |
|
IsFolder: true, |
|
}, nil |
|
} |
|
|
|
func (d *Quqi) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) { |
|
var moveRes = &MoveRes{} |
|
|
|
if _, err := d.request("", "/api/dir/mvDir", resty.MethodPost, func(req *resty.Request) { |
|
req.SetFormData(map[string]string{ |
|
"quqi_id": d.GroupID, |
|
"tree_id": "1", |
|
"node_id": dstDir.GetID(), |
|
"source_quqi_id": d.GroupID, |
|
"source_tree_id": "1", |
|
"source_node_id": srcObj.GetID(), |
|
"client_id": d.ClientID, |
|
}) |
|
}, moveRes); err != nil { |
|
return nil, err |
|
} |
|
|
|
return &model.Object{ |
|
ID: strconv.FormatInt(moveRes.Data.NodeID, 10), |
|
Name: moveRes.Data.NodeName, |
|
Size: srcObj.GetSize(), |
|
Modified: time.Now(), |
|
Ctime: srcObj.CreateTime(), |
|
IsFolder: srcObj.IsDir(), |
|
}, nil |
|
} |
|
|
|
func (d *Quqi) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) { |
|
var realName = newName |
|
|
|
if !srcObj.IsDir() { |
|
srcExt, newExt := utils.Ext(srcObj.GetName()), utils.Ext(newName) |
|
|
|
|
|
if srcExt != "" && srcExt == newExt { |
|
parts := strings.Split(newName, ".") |
|
if len(parts) > 1 { |
|
realName = strings.Join(parts[:len(parts)-1], ".") |
|
} |
|
} |
|
} |
|
|
|
if _, err := d.request("", "/api/dir/renameDir", resty.MethodPost, func(req *resty.Request) { |
|
req.SetFormData(map[string]string{ |
|
"quqi_id": d.GroupID, |
|
"tree_id": "1", |
|
"node_id": srcObj.GetID(), |
|
"rename": realName, |
|
"client_id": d.ClientID, |
|
}) |
|
}, nil); err != nil { |
|
return nil, err |
|
} |
|
|
|
return &model.Object{ |
|
ID: srcObj.GetID(), |
|
Name: newName, |
|
Size: srcObj.GetSize(), |
|
Modified: time.Now(), |
|
Ctime: srcObj.CreateTime(), |
|
IsFolder: srcObj.IsDir(), |
|
}, nil |
|
} |
|
|
|
func (d *Quqi) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) { |
|
|
|
if _, err := d.request("", "/api/node/copy", resty.MethodPost, func(req *resty.Request) { |
|
req.SetFormData(map[string]string{ |
|
"quqi_id": d.GroupID, |
|
"tree_id": "1", |
|
"node_id": dstDir.GetID(), |
|
"source_quqi_id": d.GroupID, |
|
"source_tree_id": "1", |
|
"source_node_id": srcObj.GetID(), |
|
"client_id": d.ClientID, |
|
}) |
|
}, nil); err != nil { |
|
return nil, err |
|
} |
|
|
|
return nil, nil |
|
} |
|
|
|
func (d *Quqi) Remove(ctx context.Context, obj model.Obj) error { |
|
|
|
if _, err := d.request("", "/api/node/del", resty.MethodPost, func(req *resty.Request) { |
|
req.SetFormData(map[string]string{ |
|
"quqi_id": d.GroupID, |
|
"tree_id": "1", |
|
"node_id": obj.GetID(), |
|
"client_id": d.ClientID, |
|
}) |
|
}, nil); err != nil { |
|
return err |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func (d *Quqi) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) { |
|
|
|
sizeStr := strconv.FormatInt(stream.GetSize(), 10) |
|
f, err := stream.CacheFullInTempFile() |
|
if err != nil { |
|
return nil, err |
|
} |
|
md5, err := utils.HashFile(utils.MD5, f) |
|
if err != nil { |
|
return nil, err |
|
} |
|
sha, err := utils.HashFile(utils.SHA256, f) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var uploadInitResp UploadInitResp |
|
_, err = d.request("", "/api/upload/v1/file/init", resty.MethodPost, func(req *resty.Request) { |
|
req.SetFormData(map[string]string{ |
|
"quqi_id": d.GroupID, |
|
"tree_id": "1", |
|
"parent_id": dstDir.GetID(), |
|
"size": sizeStr, |
|
"file_name": stream.GetName(), |
|
"md5": md5, |
|
"sha": sha, |
|
"is_slice": "true", |
|
"client_id": d.ClientID, |
|
}) |
|
}, &uploadInitResp) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
|
|
if uploadInitResp.Data.Exist { |
|
|
|
nodeName, nodeExt := uploadInitResp.Data.NodeName, rawExt(stream.GetName()) |
|
if nodeExt != "" { |
|
nodeName = nodeName + "." + nodeExt |
|
} |
|
return &model.Object{ |
|
ID: strconv.FormatInt(uploadInitResp.Data.NodeID, 10), |
|
Name: nodeName, |
|
Size: stream.GetSize(), |
|
Modified: stream.ModTime(), |
|
Ctime: stream.CreateTime(), |
|
}, nil |
|
} |
|
|
|
_, err = d.request("upload.quqi.com:20807", "/upload/v1/listParts", resty.MethodPost, func(req *resty.Request) { |
|
req.SetFormData(map[string]string{ |
|
"token": uploadInitResp.Data.Token, |
|
"task_id": uploadInitResp.Data.TaskID, |
|
"client_id": d.ClientID, |
|
}) |
|
}, nil) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var tempKeyResp TempKeyResp |
|
_, err = d.request("upload.quqi.com:20807", "/upload/v1/tempKey", resty.MethodGet, func(req *resty.Request) { |
|
req.SetQueryParams(map[string]string{ |
|
"token": uploadInitResp.Data.Token, |
|
"task_id": uploadInitResp.Data.TaskID, |
|
}) |
|
}, &tempKeyResp) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cfg := &aws.Config{ |
|
Credentials: credentials.NewStaticCredentials(tempKeyResp.Data.Credentials.TmpSecretID, tempKeyResp.Data.Credentials.TmpSecretKey, tempKeyResp.Data.Credentials.SessionToken), |
|
Region: aws.String("ap-shanghai"), |
|
Endpoint: aws.String("cos.ap-shanghai.myqcloud.com"), |
|
} |
|
s, err := session.NewSession(cfg) |
|
if err != nil { |
|
return nil, err |
|
} |
|
uploader := s3manager.NewUploader(s) |
|
buf := make([]byte, 1024*1024*2) |
|
for partNumber := int64(1); ; partNumber++ { |
|
n, err := io.ReadFull(f, buf) |
|
if err != nil && err != io.ErrUnexpectedEOF { |
|
if err == io.EOF { |
|
break |
|
} |
|
return nil, err |
|
} |
|
_, err = uploader.S3.UploadPartWithContext(ctx, &s3.UploadPartInput{ |
|
UploadId: &uploadInitResp.Data.UploadID, |
|
Key: &uploadInitResp.Data.Key, |
|
Bucket: &uploadInitResp.Data.Bucket, |
|
PartNumber: aws.Int64(partNumber), |
|
Body: bytes.NewReader(buf[:n]), |
|
}) |
|
if err != nil { |
|
return nil, err |
|
} |
|
} |
|
|
|
var uploadFinishResp UploadFinishResp |
|
_, err = d.request("", "/api/upload/v1/file/finish", resty.MethodPost, func(req *resty.Request) { |
|
req.SetFormData(map[string]string{ |
|
"token": uploadInitResp.Data.Token, |
|
"task_id": uploadInitResp.Data.TaskID, |
|
"client_id": d.ClientID, |
|
}) |
|
}, &uploadFinishResp) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
nodeName, nodeExt := uploadFinishResp.Data.NodeName, rawExt(stream.GetName()) |
|
if nodeExt != "" { |
|
nodeName = nodeName + "." + nodeExt |
|
} |
|
return &model.Object{ |
|
ID: strconv.FormatInt(uploadFinishResp.Data.NodeID, 10), |
|
Name: nodeName, |
|
Size: stream.GetSize(), |
|
Modified: stream.ModTime(), |
|
Ctime: stream.CreateTime(), |
|
}, nil |
|
} |
|
|
|
|
|
|
|
|
|
|
|
var _ driver.Driver = (*Quqi)(nil) |
|
|