Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/baidu_photo/driver.go
1987 views
1
package baiduphoto
2
3
import (
4
"context"
5
"crypto/md5"
6
"encoding/hex"
7
"errors"
8
"fmt"
9
"io"
10
"os"
11
"regexp"
12
"strconv"
13
"strings"
14
"time"
15
16
"golang.org/x/sync/semaphore"
17
18
"github.com/alist-org/alist/v3/drivers/base"
19
"github.com/alist-org/alist/v3/internal/conf"
20
"github.com/alist-org/alist/v3/internal/driver"
21
"github.com/alist-org/alist/v3/internal/errs"
22
"github.com/alist-org/alist/v3/internal/model"
23
"github.com/alist-org/alist/v3/pkg/errgroup"
24
"github.com/alist-org/alist/v3/pkg/utils"
25
"github.com/avast/retry-go"
26
"github.com/go-resty/resty/v2"
27
)
28
29
type BaiduPhoto struct {
30
model.Storage
31
Addition
32
33
// AccessToken string
34
Uk int64
35
bdstoken string
36
root model.Obj
37
38
uploadThread int
39
}
40
41
func (d *BaiduPhoto) Config() driver.Config {
42
return config
43
}
44
45
func (d *BaiduPhoto) GetAddition() driver.Additional {
46
return &d.Addition
47
}
48
49
func (d *BaiduPhoto) Init(ctx context.Context) error {
50
d.uploadThread, _ = strconv.Atoi(d.UploadThread)
51
if d.uploadThread < 1 || d.uploadThread > 32 {
52
d.uploadThread, d.UploadThread = 3, "3"
53
}
54
55
// if err := d.refreshToken(); err != nil {
56
// return err
57
// }
58
59
// root
60
if d.AlbumID != "" {
61
albumID := strings.Split(d.AlbumID, "|")[0]
62
album, err := d.GetAlbumDetail(ctx, albumID)
63
if err != nil {
64
return err
65
}
66
d.root = album
67
} else {
68
d.root = &Root{
69
Name: "root",
70
Modified: d.Modified,
71
IsFolder: true,
72
}
73
}
74
75
// uk
76
info, err := d.uInfo()
77
if err != nil {
78
return err
79
}
80
d.bdstoken, err = d.getBDStoken()
81
if err != nil {
82
return err
83
}
84
d.Uk, err = strconv.ParseInt(info.YouaID, 10, 64)
85
return err
86
}
87
88
func (d *BaiduPhoto) GetRoot(ctx context.Context) (model.Obj, error) {
89
return d.root, nil
90
}
91
92
func (d *BaiduPhoto) Drop(ctx context.Context) error {
93
// d.AccessToken = ""
94
d.Uk = 0
95
d.root = nil
96
return nil
97
}
98
99
func (d *BaiduPhoto) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
100
var err error
101
102
/* album */
103
if album, ok := dir.(*Album); ok {
104
var files []AlbumFile
105
files, err = d.GetAllAlbumFile(ctx, album, "")
106
if err != nil {
107
return nil, err
108
}
109
110
return utils.MustSliceConvert(files, func(file AlbumFile) model.Obj {
111
return &file
112
}), nil
113
}
114
115
/* root */
116
var albums []Album
117
if d.ShowType != "root_only_file" {
118
albums, err = d.GetAllAlbum(ctx)
119
if err != nil {
120
return nil, err
121
}
122
}
123
124
var files []File
125
if d.ShowType != "root_only_album" {
126
files, err = d.GetAllFile(ctx)
127
if err != nil {
128
return nil, err
129
}
130
}
131
132
return append(
133
utils.MustSliceConvert(albums, func(album Album) model.Obj {
134
return &album
135
}),
136
utils.MustSliceConvert(files, func(album File) model.Obj {
137
return &album
138
})...,
139
), nil
140
141
}
142
143
func (d *BaiduPhoto) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
144
switch file := file.(type) {
145
case *File:
146
return d.linkFile(ctx, file, args)
147
case *AlbumFile:
148
// 处理共享相册
149
if d.Uk != file.Uk {
150
// 有概率无法获取到链接
151
// return d.linkAlbum(ctx, file, args)
152
153
f, err := d.CopyAlbumFile(ctx, file)
154
if err != nil {
155
return nil, err
156
}
157
return d.linkFile(ctx, f, args)
158
}
159
return d.linkFile(ctx, &file.File, args)
160
}
161
return nil, errs.NotFile
162
}
163
164
var joinReg = regexp.MustCompile(`(?i)join:([\S]*)`)
165
166
func (d *BaiduPhoto) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
167
if _, ok := parentDir.(*Root); ok {
168
code := joinReg.FindStringSubmatch(dirName)
169
if len(code) > 1 {
170
return d.JoinAlbum(ctx, code[1])
171
}
172
return d.CreateAlbum(ctx, dirName)
173
}
174
return nil, errs.NotSupport
175
}
176
177
func (d *BaiduPhoto) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
178
switch file := srcObj.(type) {
179
case *File:
180
if album, ok := dstDir.(*Album); ok {
181
//rootfile -> album
182
return d.AddAlbumFile(ctx, album, file)
183
}
184
case *AlbumFile:
185
switch album := dstDir.(type) {
186
case *Root:
187
//albumfile -> root
188
return d.CopyAlbumFile(ctx, file)
189
case *Album:
190
// albumfile -> root -> album
191
rootfile, err := d.CopyAlbumFile(ctx, file)
192
if err != nil {
193
return nil, err
194
}
195
return d.AddAlbumFile(ctx, album, rootfile)
196
}
197
}
198
return nil, errs.NotSupport
199
}
200
201
func (d *BaiduPhoto) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
202
if file, ok := srcObj.(*AlbumFile); ok {
203
switch dstDir.(type) {
204
case *Album, *Root: // albumfile -> root -> album or albumfile -> root
205
newObj, err := d.Copy(ctx, srcObj, dstDir)
206
if err != nil {
207
return nil, err
208
}
209
// 删除原相册文件
210
_ = d.DeleteAlbumFile(ctx, file)
211
return newObj, nil
212
}
213
}
214
return nil, errs.NotSupport
215
}
216
217
func (d *BaiduPhoto) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
218
// 仅支持相册改名
219
if album, ok := srcObj.(*Album); ok {
220
return d.SetAlbumName(ctx, album, newName)
221
}
222
return nil, errs.NotSupport
223
}
224
225
func (d *BaiduPhoto) Remove(ctx context.Context, obj model.Obj) error {
226
switch obj := obj.(type) {
227
case *File:
228
return d.DeleteFile(ctx, obj)
229
case *AlbumFile:
230
return d.DeleteAlbumFile(ctx, obj)
231
case *Album:
232
return d.DeleteAlbum(ctx, obj)
233
}
234
return errs.NotSupport
235
}
236
237
func (d *BaiduPhoto) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
238
// 不支持大小为0的文件
239
if stream.GetSize() == 0 {
240
return nil, fmt.Errorf("file size cannot be zero")
241
}
242
243
// TODO:
244
// 暂时没有找到妙传方式
245
var (
246
cache = stream.GetFile()
247
tmpF *os.File
248
err error
249
)
250
if _, ok := cache.(io.ReaderAt); !ok {
251
tmpF, err = os.CreateTemp(conf.Conf.TempDir, "file-*")
252
if err != nil {
253
return nil, err
254
}
255
defer func() {
256
_ = tmpF.Close()
257
_ = os.Remove(tmpF.Name())
258
}()
259
cache = tmpF
260
}
261
262
const DEFAULT int64 = 1 << 22
263
const SliceSize int64 = 1 << 18
264
265
// 计算需要的数据
266
streamSize := stream.GetSize()
267
count := int(streamSize / DEFAULT)
268
lastBlockSize := streamSize % DEFAULT
269
if lastBlockSize > 0 {
270
count++
271
} else {
272
lastBlockSize = DEFAULT
273
}
274
275
// step.1 计算MD5
276
sliceMD5List := make([]string, 0, count)
277
byteSize := int64(DEFAULT)
278
fileMd5H := md5.New()
279
sliceMd5H := md5.New()
280
sliceMd5H2 := md5.New()
281
slicemd5H2Write := utils.LimitWriter(sliceMd5H2, SliceSize)
282
writers := []io.Writer{fileMd5H, sliceMd5H, slicemd5H2Write}
283
if tmpF != nil {
284
writers = append(writers, tmpF)
285
}
286
written := int64(0)
287
for i := 1; i <= count; i++ {
288
if utils.IsCanceled(ctx) {
289
return nil, ctx.Err()
290
}
291
if i == count {
292
byteSize = lastBlockSize
293
}
294
n, err := utils.CopyWithBufferN(io.MultiWriter(writers...), stream, byteSize)
295
written += n
296
if err != nil && err != io.EOF {
297
return nil, err
298
}
299
sliceMD5List = append(sliceMD5List, hex.EncodeToString(sliceMd5H.Sum(nil)))
300
sliceMd5H.Reset()
301
}
302
if tmpF != nil {
303
if written != streamSize {
304
return nil, errs.NewErr(err, "CreateTempFile failed, incoming stream actual size= %d, expect = %d ", written, streamSize)
305
}
306
_, err = tmpF.Seek(0, io.SeekStart)
307
if err != nil {
308
return nil, errs.NewErr(err, "CreateTempFile failed, can't seek to 0 ")
309
}
310
}
311
contentMd5 := hex.EncodeToString(fileMd5H.Sum(nil))
312
sliceMd5 := hex.EncodeToString(sliceMd5H2.Sum(nil))
313
blockListStr, _ := utils.Json.MarshalToString(sliceMD5List)
314
315
// step.2 预上传
316
params := map[string]string{
317
"autoinit": "1",
318
"isdir": "0",
319
"rtype": "1",
320
"ctype": "11",
321
"path": fmt.Sprintf("/%s", stream.GetName()),
322
"size": fmt.Sprint(streamSize),
323
"slice-md5": sliceMd5,
324
"content-md5": contentMd5,
325
"block_list": blockListStr,
326
}
327
328
// 尝试获取之前的进度
329
precreateResp, ok := base.GetUploadProgress[*PrecreateResp](d, strconv.FormatInt(d.Uk, 10), contentMd5)
330
if !ok {
331
_, err = d.Post(FILE_API_URL_V1+"/precreate", func(r *resty.Request) {
332
r.SetContext(ctx)
333
r.SetFormData(params)
334
r.SetQueryParam("bdstoken", d.bdstoken)
335
}, &precreateResp)
336
if err != nil {
337
return nil, err
338
}
339
}
340
341
switch precreateResp.ReturnType {
342
case 1: //step.3 上传文件切片
343
threadG, upCtx := errgroup.NewGroupWithContext(ctx, d.uploadThread,
344
retry.Attempts(3),
345
retry.Delay(time.Second),
346
retry.DelayType(retry.BackOffDelay))
347
sem := semaphore.NewWeighted(3)
348
for i, partseq := range precreateResp.BlockList {
349
if utils.IsCanceled(upCtx) {
350
break
351
}
352
353
i, partseq, offset, byteSize := i, partseq, int64(partseq)*DEFAULT, DEFAULT
354
if partseq+1 == count {
355
byteSize = lastBlockSize
356
}
357
358
threadG.Go(func(ctx context.Context) error {
359
if err = sem.Acquire(ctx, 1); err != nil {
360
return err
361
}
362
defer sem.Release(1)
363
uploadParams := map[string]string{
364
"method": "upload",
365
"path": params["path"],
366
"partseq": fmt.Sprint(partseq),
367
"uploadid": precreateResp.UploadID,
368
"app_id": "16051585",
369
}
370
_, err = d.Post("https://c3.pcs.baidu.com/rest/2.0/pcs/superfile2", func(r *resty.Request) {
371
r.SetContext(ctx)
372
r.SetQueryParams(uploadParams)
373
r.SetFileReader("file", stream.GetName(),
374
driver.NewLimitedUploadStream(ctx, io.NewSectionReader(cache, offset, byteSize)))
375
}, nil)
376
if err != nil {
377
return err
378
}
379
up(float64(threadG.Success()) * 100 / float64(len(precreateResp.BlockList)))
380
precreateResp.BlockList[i] = -1
381
return nil
382
})
383
}
384
if err = threadG.Wait(); err != nil {
385
if errors.Is(err, context.Canceled) {
386
precreateResp.BlockList = utils.SliceFilter(precreateResp.BlockList, func(s int) bool { return s >= 0 })
387
base.SaveUploadProgress(d, strconv.FormatInt(d.Uk, 10), contentMd5)
388
}
389
return nil, err
390
}
391
fallthrough
392
case 2: //step.4 创建文件
393
params["uploadid"] = precreateResp.UploadID
394
_, err = d.Post(FILE_API_URL_V1+"/create", func(r *resty.Request) {
395
r.SetContext(ctx)
396
r.SetFormData(params)
397
r.SetQueryParam("bdstoken", d.bdstoken)
398
}, &precreateResp)
399
if err != nil {
400
return nil, err
401
}
402
fallthrough
403
case 3: //step.5 增加到相册
404
rootfile := precreateResp.Data.toFile()
405
if album, ok := dstDir.(*Album); ok {
406
return d.AddAlbumFile(ctx, album, rootfile)
407
}
408
return rootfile, nil
409
}
410
return nil, errs.NotSupport
411
}
412
413
var _ driver.Driver = (*BaiduPhoto)(nil)
414
var _ driver.GetRooter = (*BaiduPhoto)(nil)
415
var _ driver.MkdirResult = (*BaiduPhoto)(nil)
416
var _ driver.CopyResult = (*BaiduPhoto)(nil)
417
var _ driver.MoveResult = (*BaiduPhoto)(nil)
418
var _ driver.Remove = (*BaiduPhoto)(nil)
419
var _ driver.PutResult = (*BaiduPhoto)(nil)
420
var _ driver.RenameResult = (*BaiduPhoto)(nil)
421
422