2024-01-19 02:59:56 +00:00
|
|
|
|
package quqi
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
|
|
|
|
"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"
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"github.com/alist-org/alist/v3/pkg/utils/random"
|
2024-01-19 02:59:56 +00:00
|
|
|
|
"github.com/go-resty/resty/v2"
|
|
|
|
|
"github.com/tencentyun/cos-go-sdk-v5"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Quqi struct {
|
|
|
|
|
model.Storage
|
|
|
|
|
Addition
|
2024-01-19 05:57:31 +00:00
|
|
|
|
GroupID string // 私人云群组ID
|
|
|
|
|
ClientID string // 随机生成客户端ID 经过测试,部分接口调用若不携带client id会出现错误
|
2024-01-19 02:59:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-19 05:57:31 +00:00
|
|
|
|
// 生成随机client id (与网页端生成逻辑一致)
|
|
|
|
|
d.ClientID = "quqipc_" + random.String(10)
|
|
|
|
|
|
|
|
|
|
// 获取私人云ID (暂时仅获取私人云)
|
2024-01-19 02:59:56 +00:00
|
|
|
|
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{
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"quqi_id": d.GroupID,
|
|
|
|
|
"tree_id": "1",
|
|
|
|
|
"node_id": dir.GetID(),
|
|
|
|
|
"client_id": d.ClientID,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
})
|
|
|
|
|
}, listResp); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if listResp.Data == nil {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dirs
|
|
|
|
|
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,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// files
|
|
|
|
|
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) {
|
|
|
|
|
var getDocResp = &GetDocRes{}
|
|
|
|
|
|
|
|
|
|
if _, err := d.request("", "/api/doc/getDoc", resty.MethodPost, func(req *resty.Request) {
|
|
|
|
|
req.SetFormData(map[string]string{
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"quqi_id": d.GroupID,
|
|
|
|
|
"tree_id": "1",
|
|
|
|
|
"node_id": file.GetID(),
|
|
|
|
|
"client_id": d.ClientID,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
})
|
|
|
|
|
}, getDocResp); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &model.Link{
|
|
|
|
|
URL: getDocResp.Data.OriginPath,
|
|
|
|
|
Header: http.Header{
|
|
|
|
|
"Origin": []string{"https://quqi.com"},
|
|
|
|
|
"Cookie": []string{d.Cookie},
|
|
|
|
|
},
|
|
|
|
|
}, 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,
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"tree_id": "1",
|
2024-01-19 02:59:56 +00:00
|
|
|
|
"parent_id": parentDir.GetID(),
|
|
|
|
|
"name": dirName,
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"client_id": d.ClientID,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
})
|
|
|
|
|
}, 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,
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"tree_id": "1",
|
2024-01-19 02:59:56 +00:00
|
|
|
|
"node_id": dstDir.GetID(),
|
|
|
|
|
"source_quqi_id": d.GroupID,
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"source_tree_id": "1",
|
2024-01-19 02:59:56 +00:00
|
|
|
|
"source_node_id": srcObj.GetID(),
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"client_id": d.ClientID,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
})
|
|
|
|
|
}, 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) {
|
2024-01-19 05:57:31 +00:00
|
|
|
|
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], ".")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-19 02:59:56 +00:00
|
|
|
|
|
|
|
|
|
if _, err := d.request("", "/api/dir/renameDir", resty.MethodPost, func(req *resty.Request) {
|
|
|
|
|
req.SetFormData(map[string]string{
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"quqi_id": d.GroupID,
|
|
|
|
|
"tree_id": "1",
|
|
|
|
|
"node_id": srcObj.GetID(),
|
|
|
|
|
"rename": realName,
|
|
|
|
|
"client_id": d.ClientID,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
})
|
2024-01-19 05:57:31 +00:00
|
|
|
|
}, nil); err != nil {
|
2024-01-19 02:59:56 +00:00
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &model.Object{
|
2024-01-19 05:57:31 +00:00
|
|
|
|
ID: srcObj.GetID(),
|
|
|
|
|
Name: newName,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
Size: srcObj.GetSize(),
|
2024-01-19 05:57:31 +00:00
|
|
|
|
Modified: time.Now(),
|
2024-01-19 02:59:56 +00:00
|
|
|
|
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,
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"tree_id": "1",
|
2024-01-19 02:59:56 +00:00
|
|
|
|
"node_id": dstDir.GetID(),
|
|
|
|
|
"source_quqi_id": d.GroupID,
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"source_tree_id": "1",
|
2024-01-19 02:59:56 +00:00
|
|
|
|
"source_node_id": srcObj.GetID(),
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"client_id": d.ClientID,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
})
|
|
|
|
|
}, 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{
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"quqi_id": d.GroupID,
|
|
|
|
|
"tree_id": "1",
|
|
|
|
|
"node_id": obj.GetID(),
|
|
|
|
|
"client_id": d.ClientID,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
})
|
|
|
|
|
}, 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) {
|
|
|
|
|
// base info
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
// init upload
|
|
|
|
|
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",
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"client_id": d.ClientID,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
})
|
|
|
|
|
}, &uploadInitResp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-01-20 13:22:50 +00:00
|
|
|
|
// check exist
|
|
|
|
|
// if the file already exists in Quqi server, there is no need to actually upload it
|
|
|
|
|
if uploadInitResp.Data.Exist {
|
|
|
|
|
// the file name returned by Quqi does not include the extension name
|
|
|
|
|
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
|
|
|
|
|
}
|
2024-01-19 02:59:56 +00:00
|
|
|
|
// listParts
|
|
|
|
|
_, 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,
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"client_id": d.ClientID,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
})
|
|
|
|
|
}, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
// get temp key
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
// upload
|
|
|
|
|
u, err := url.Parse(fmt.Sprintf("https://%s.cos.ap-shanghai.myqcloud.com", uploadInitResp.Data.Bucket))
|
|
|
|
|
b := &cos.BaseURL{BucketURL: u}
|
|
|
|
|
client := cos.NewClient(b, &http.Client{
|
|
|
|
|
Transport: &cos.CredentialTransport{
|
|
|
|
|
Credential: cos.NewTokenCredential(tempKeyResp.Data.Credentials.TmpSecretID, tempKeyResp.Data.Credentials.TmpSecretKey, tempKeyResp.Data.Credentials.SessionToken),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
partSize := int64(1024 * 1024 * 2)
|
|
|
|
|
partCount := (stream.GetSize() + partSize - 1) / partSize
|
|
|
|
|
for i := 1; i <= int(partCount); i++ {
|
|
|
|
|
length := partSize
|
|
|
|
|
if i == int(partCount) {
|
|
|
|
|
length = stream.GetSize() - (int64(i)-1)*partSize
|
|
|
|
|
}
|
|
|
|
|
_, err := client.Object.UploadPart(
|
2024-01-19 04:05:10 +00:00
|
|
|
|
ctx, uploadInitResp.Data.Key, uploadInitResp.Data.UploadID, i, io.LimitReader(f, partSize), &cos.ObjectUploadPartOptions{
|
2024-01-19 02:59:56 +00:00
|
|
|
|
ContentLength: length,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
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("shanghai"),
|
|
|
|
|
// Endpoint: aws.String("cos.ap-shanghai.myqcloud.com"),
|
|
|
|
|
// // S3ForcePathStyle: aws.Bool(true),
|
|
|
|
|
//}
|
|
|
|
|
//s, err := session.NewSession(cfg)
|
|
|
|
|
//if err != nil {
|
|
|
|
|
// return nil, err
|
|
|
|
|
//}
|
|
|
|
|
//uploader := s3manager.NewUploader(s)
|
|
|
|
|
//input := &s3manager.UploadInput{
|
|
|
|
|
// Bucket: &uploadInitResp.Data.Bucket,
|
|
|
|
|
// Key: &uploadInitResp.Data.Key,
|
|
|
|
|
// Body: f,
|
|
|
|
|
//}
|
|
|
|
|
//_, err = uploader.UploadWithContext(ctx, input)
|
|
|
|
|
//if err != nil {
|
|
|
|
|
// return nil, err
|
|
|
|
|
//}
|
|
|
|
|
// finish upload
|
|
|
|
|
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,
|
2024-01-19 05:57:31 +00:00
|
|
|
|
"client_id": d.ClientID,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
})
|
|
|
|
|
}, &uploadFinishResp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-01-20 13:22:50 +00:00
|
|
|
|
// the file name returned by Quqi does not include the extension name
|
|
|
|
|
nodeName, nodeExt := uploadFinishResp.Data.NodeName, rawExt(stream.GetName())
|
|
|
|
|
if nodeExt != "" {
|
|
|
|
|
nodeName = nodeName + "." + nodeExt
|
|
|
|
|
}
|
2024-01-19 02:59:56 +00:00
|
|
|
|
return &model.Object{
|
|
|
|
|
ID: strconv.FormatInt(uploadFinishResp.Data.NodeID, 10),
|
2024-01-20 13:22:50 +00:00
|
|
|
|
Name: nodeName,
|
2024-01-19 02:59:56 +00:00
|
|
|
|
Size: stream.GetSize(),
|
|
|
|
|
Modified: stream.ModTime(),
|
|
|
|
|
Ctime: stream.CreateTime(),
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
|
|
|
|
// return nil, errs.NotSupport
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
var _ driver.Driver = (*Quqi)(nil)
|