Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/139/driver.go
1986 views
1
package _139
2
3
import (
4
"context"
5
"encoding/xml"
6
"fmt"
7
"io"
8
"net/http"
9
"path"
10
"strconv"
11
"time"
12
13
"github.com/alist-org/alist/v3/drivers/base"
14
"github.com/alist-org/alist/v3/internal/driver"
15
"github.com/alist-org/alist/v3/internal/errs"
16
"github.com/alist-org/alist/v3/internal/model"
17
streamPkg "github.com/alist-org/alist/v3/internal/stream"
18
"github.com/alist-org/alist/v3/pkg/cron"
19
"github.com/alist-org/alist/v3/pkg/utils"
20
"github.com/alist-org/alist/v3/pkg/utils/random"
21
log "github.com/sirupsen/logrus"
22
)
23
24
type Yun139 struct {
25
model.Storage
26
Addition
27
cron *cron.Cron
28
Account string
29
ref *Yun139
30
PersonalCloudHost string
31
}
32
33
func (d *Yun139) Config() driver.Config {
34
return config
35
}
36
37
func (d *Yun139) GetAddition() driver.Additional {
38
return &d.Addition
39
}
40
41
func (d *Yun139) Init(ctx context.Context) error {
42
if d.ref == nil {
43
if len(d.Authorization) == 0 {
44
return fmt.Errorf("authorization is empty")
45
}
46
err := d.refreshToken()
47
if err != nil {
48
return err
49
}
50
51
// Query Route Policy
52
var resp QueryRoutePolicyResp
53
_, err = d.requestRoute(base.Json{
54
"userInfo": base.Json{
55
"userType": 1,
56
"accountType": 1,
57
"accountName": d.Account},
58
"modAddrType": 1,
59
}, &resp)
60
if err != nil {
61
return err
62
}
63
for _, policyItem := range resp.Data.RoutePolicyList {
64
if policyItem.ModName == "personal" {
65
d.PersonalCloudHost = policyItem.HttpsUrl
66
break
67
}
68
}
69
if len(d.PersonalCloudHost) == 0 {
70
return fmt.Errorf("PersonalCloudHost is empty")
71
}
72
73
d.cron = cron.NewCron(time.Hour * 12)
74
d.cron.Do(func() {
75
err := d.refreshToken()
76
if err != nil {
77
log.Errorf("%+v", err)
78
}
79
})
80
}
81
switch d.Addition.Type {
82
case MetaPersonalNew:
83
if len(d.Addition.RootFolderID) == 0 {
84
d.RootFolderID = "/"
85
}
86
case MetaPersonal:
87
if len(d.Addition.RootFolderID) == 0 {
88
d.RootFolderID = "root"
89
}
90
case MetaGroup:
91
if len(d.Addition.RootFolderID) == 0 {
92
d.RootFolderID = d.CloudID
93
}
94
case MetaFamily:
95
default:
96
return errs.NotImplement
97
}
98
return nil
99
}
100
101
func (d *Yun139) InitReference(storage driver.Driver) error {
102
refStorage, ok := storage.(*Yun139)
103
if ok {
104
d.ref = refStorage
105
return nil
106
}
107
return errs.NotSupport
108
}
109
110
func (d *Yun139) Drop(ctx context.Context) error {
111
if d.cron != nil {
112
d.cron.Stop()
113
}
114
d.ref = nil
115
return nil
116
}
117
118
func (d *Yun139) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
119
switch d.Addition.Type {
120
case MetaPersonalNew:
121
return d.personalGetFiles(dir.GetID())
122
case MetaPersonal:
123
return d.getFiles(dir.GetID())
124
case MetaFamily:
125
return d.familyGetFiles(dir.GetID())
126
case MetaGroup:
127
return d.groupGetFiles(dir.GetID())
128
default:
129
return nil, errs.NotImplement
130
}
131
}
132
133
func (d *Yun139) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
134
var url string
135
var err error
136
switch d.Addition.Type {
137
case MetaPersonalNew:
138
url, err = d.personalGetLink(file.GetID())
139
case MetaPersonal:
140
url, err = d.getLink(file.GetID())
141
case MetaFamily:
142
url, err = d.familyGetLink(file.GetID(), file.GetPath())
143
case MetaGroup:
144
url, err = d.groupGetLink(file.GetID(), file.GetPath())
145
default:
146
return nil, errs.NotImplement
147
}
148
if err != nil {
149
return nil, err
150
}
151
return &model.Link{URL: url}, nil
152
}
153
154
func (d *Yun139) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
155
var err error
156
switch d.Addition.Type {
157
case MetaPersonalNew:
158
data := base.Json{
159
"parentFileId": parentDir.GetID(),
160
"name": dirName,
161
"description": "",
162
"type": "folder",
163
"fileRenameMode": "force_rename",
164
}
165
pathname := "/file/create"
166
_, err = d.personalPost(pathname, data, nil)
167
case MetaPersonal:
168
data := base.Json{
169
"createCatalogExtReq": base.Json{
170
"parentCatalogID": parentDir.GetID(),
171
"newCatalogName": dirName,
172
"commonAccountInfo": base.Json{
173
"account": d.getAccount(),
174
"accountType": 1,
175
},
176
},
177
}
178
pathname := "/orchestration/personalCloud/catalog/v1.0/createCatalogExt"
179
_, err = d.post(pathname, data, nil)
180
case MetaFamily:
181
data := base.Json{
182
"cloudID": d.CloudID,
183
"commonAccountInfo": base.Json{
184
"account": d.getAccount(),
185
"accountType": 1,
186
},
187
"docLibName": dirName,
188
"path": path.Join(parentDir.GetPath(), parentDir.GetID()),
189
}
190
pathname := "/orchestration/familyCloud-rebuild/cloudCatalog/v1.0/createCloudDoc"
191
_, err = d.post(pathname, data, nil)
192
case MetaGroup:
193
data := base.Json{
194
"catalogName": dirName,
195
"commonAccountInfo": base.Json{
196
"account": d.getAccount(),
197
"accountType": 1,
198
},
199
"groupID": d.CloudID,
200
"parentFileId": parentDir.GetID(),
201
"path": path.Join(parentDir.GetPath(), parentDir.GetID()),
202
}
203
pathname := "/orchestration/group-rebuild/catalog/v1.0/createGroupCatalog"
204
_, err = d.post(pathname, data, nil)
205
default:
206
err = errs.NotImplement
207
}
208
return err
209
}
210
211
func (d *Yun139) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
212
switch d.Addition.Type {
213
case MetaPersonalNew:
214
data := base.Json{
215
"fileIds": []string{srcObj.GetID()},
216
"toParentFileId": dstDir.GetID(),
217
}
218
pathname := "/file/batchMove"
219
_, err := d.personalPost(pathname, data, nil)
220
if err != nil {
221
return nil, err
222
}
223
return srcObj, nil
224
case MetaGroup:
225
var contentList []string
226
var catalogList []string
227
if srcObj.IsDir() {
228
catalogList = append(catalogList, srcObj.GetID())
229
} else {
230
contentList = append(contentList, srcObj.GetID())
231
}
232
data := base.Json{
233
"taskType": 3,
234
"srcType": 2,
235
"srcGroupID": d.CloudID,
236
"destType": 2,
237
"destGroupID": d.CloudID,
238
"destPath": dstDir.GetPath(),
239
"contentList": contentList,
240
"catalogList": catalogList,
241
"commonAccountInfo": base.Json{
242
"account": d.getAccount(),
243
"accountType": 1,
244
},
245
}
246
pathname := "/orchestration/group-rebuild/task/v1.0/createBatchOprTask"
247
_, err := d.post(pathname, data, nil)
248
if err != nil {
249
return nil, err
250
}
251
return srcObj, nil
252
case MetaPersonal:
253
var contentInfoList []string
254
var catalogInfoList []string
255
if srcObj.IsDir() {
256
catalogInfoList = append(catalogInfoList, srcObj.GetID())
257
} else {
258
contentInfoList = append(contentInfoList, srcObj.GetID())
259
}
260
data := base.Json{
261
"createBatchOprTaskReq": base.Json{
262
"taskType": 3,
263
"actionType": "304",
264
"taskInfo": base.Json{
265
"contentInfoList": contentInfoList,
266
"catalogInfoList": catalogInfoList,
267
"newCatalogID": dstDir.GetID(),
268
},
269
"commonAccountInfo": base.Json{
270
"account": d.getAccount(),
271
"accountType": 1,
272
},
273
},
274
}
275
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
276
_, err := d.post(pathname, data, nil)
277
if err != nil {
278
return nil, err
279
}
280
return srcObj, nil
281
default:
282
return nil, errs.NotImplement
283
}
284
}
285
286
func (d *Yun139) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
287
var err error
288
switch d.Addition.Type {
289
case MetaPersonalNew:
290
data := base.Json{
291
"fileId": srcObj.GetID(),
292
"name": newName,
293
"description": "",
294
}
295
pathname := "/file/update"
296
_, err = d.personalPost(pathname, data, nil)
297
case MetaPersonal:
298
var data base.Json
299
var pathname string
300
if srcObj.IsDir() {
301
data = base.Json{
302
"catalogID": srcObj.GetID(),
303
"catalogName": newName,
304
"commonAccountInfo": base.Json{
305
"account": d.getAccount(),
306
"accountType": 1,
307
},
308
}
309
pathname = "/orchestration/personalCloud/catalog/v1.0/updateCatalogInfo"
310
} else {
311
data = base.Json{
312
"contentID": srcObj.GetID(),
313
"contentName": newName,
314
"commonAccountInfo": base.Json{
315
"account": d.getAccount(),
316
"accountType": 1,
317
},
318
}
319
pathname = "/orchestration/personalCloud/content/v1.0/updateContentInfo"
320
}
321
_, err = d.post(pathname, data, nil)
322
case MetaGroup:
323
var data base.Json
324
var pathname string
325
if srcObj.IsDir() {
326
data = base.Json{
327
"groupID": d.CloudID,
328
"modifyCatalogID": srcObj.GetID(),
329
"modifyCatalogName": newName,
330
"path": srcObj.GetPath(),
331
"commonAccountInfo": base.Json{
332
"account": d.getAccount(),
333
"accountType": 1,
334
},
335
}
336
pathname = "/orchestration/group-rebuild/catalog/v1.0/modifyGroupCatalog"
337
} else {
338
data = base.Json{
339
"groupID": d.CloudID,
340
"contentID": srcObj.GetID(),
341
"contentName": newName,
342
"path": srcObj.GetPath(),
343
"commonAccountInfo": base.Json{
344
"account": d.getAccount(),
345
"accountType": 1,
346
},
347
}
348
pathname = "/orchestration/group-rebuild/content/v1.0/modifyGroupContent"
349
}
350
_, err = d.post(pathname, data, nil)
351
case MetaFamily:
352
var data base.Json
353
var pathname string
354
if srcObj.IsDir() {
355
// 网页接口不支持重命名家庭云文件夹
356
// data = base.Json{
357
// "catalogType": 3,
358
// "catalogID": srcObj.GetID(),
359
// "catalogName": newName,
360
// "commonAccountInfo": base.Json{
361
// "account": d.getAccount(),
362
// "accountType": 1,
363
// },
364
// "path": srcObj.GetPath(),
365
// }
366
// pathname = "/orchestration/familyCloud-rebuild/photoContent/v1.0/modifyCatalogInfo"
367
return errs.NotImplement
368
} else {
369
data = base.Json{
370
"contentID": srcObj.GetID(),
371
"contentName": newName,
372
"commonAccountInfo": base.Json{
373
"account": d.getAccount(),
374
"accountType": 1,
375
},
376
"path": srcObj.GetPath(),
377
}
378
pathname = "/orchestration/familyCloud-rebuild/photoContent/v1.0/modifyContentInfo"
379
}
380
_, err = d.post(pathname, data, nil)
381
default:
382
err = errs.NotImplement
383
}
384
return err
385
}
386
387
func (d *Yun139) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
388
var err error
389
switch d.Addition.Type {
390
case MetaPersonalNew:
391
data := base.Json{
392
"fileIds": []string{srcObj.GetID()},
393
"toParentFileId": dstDir.GetID(),
394
}
395
pathname := "/file/batchCopy"
396
_, err := d.personalPost(pathname, data, nil)
397
return err
398
case MetaPersonal:
399
var contentInfoList []string
400
var catalogInfoList []string
401
if srcObj.IsDir() {
402
catalogInfoList = append(catalogInfoList, srcObj.GetID())
403
} else {
404
contentInfoList = append(contentInfoList, srcObj.GetID())
405
}
406
data := base.Json{
407
"createBatchOprTaskReq": base.Json{
408
"taskType": 3,
409
"actionType": 309,
410
"taskInfo": base.Json{
411
"contentInfoList": contentInfoList,
412
"catalogInfoList": catalogInfoList,
413
"newCatalogID": dstDir.GetID(),
414
},
415
"commonAccountInfo": base.Json{
416
"account": d.getAccount(),
417
"accountType": 1,
418
},
419
},
420
}
421
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
422
_, err = d.post(pathname, data, nil)
423
default:
424
err = errs.NotImplement
425
}
426
return err
427
}
428
429
func (d *Yun139) Remove(ctx context.Context, obj model.Obj) error {
430
switch d.Addition.Type {
431
case MetaPersonalNew:
432
data := base.Json{
433
"fileIds": []string{obj.GetID()},
434
}
435
pathname := "/recyclebin/batchTrash"
436
_, err := d.personalPost(pathname, data, nil)
437
return err
438
case MetaGroup:
439
var contentList []string
440
var catalogList []string
441
// 必须使用完整路径删除
442
if obj.IsDir() {
443
catalogList = append(catalogList, obj.GetPath())
444
} else {
445
contentList = append(contentList, path.Join(obj.GetPath(), obj.GetID()))
446
}
447
data := base.Json{
448
"taskType": 2,
449
"srcGroupID": d.CloudID,
450
"contentList": contentList,
451
"catalogList": catalogList,
452
"commonAccountInfo": base.Json{
453
"account": d.getAccount(),
454
"accountType": 1,
455
},
456
}
457
pathname := "/orchestration/group-rebuild/task/v1.0/createBatchOprTask"
458
_, err := d.post(pathname, data, nil)
459
return err
460
case MetaPersonal:
461
fallthrough
462
case MetaFamily:
463
var contentInfoList []string
464
var catalogInfoList []string
465
if obj.IsDir() {
466
catalogInfoList = append(catalogInfoList, obj.GetID())
467
} else {
468
contentInfoList = append(contentInfoList, obj.GetID())
469
}
470
data := base.Json{
471
"createBatchOprTaskReq": base.Json{
472
"taskType": 2,
473
"actionType": 201,
474
"taskInfo": base.Json{
475
"newCatalogID": "",
476
"contentInfoList": contentInfoList,
477
"catalogInfoList": catalogInfoList,
478
},
479
"commonAccountInfo": base.Json{
480
"account": d.getAccount(),
481
"accountType": 1,
482
},
483
},
484
}
485
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
486
if d.isFamily() {
487
data = base.Json{
488
"catalogList": catalogInfoList,
489
"contentList": contentInfoList,
490
"commonAccountInfo": base.Json{
491
"account": d.getAccount(),
492
"accountType": 1,
493
},
494
"sourceCloudID": d.CloudID,
495
"sourceCatalogType": 1002,
496
"taskType": 2,
497
"path": obj.GetPath(),
498
}
499
pathname = "/orchestration/familyCloud-rebuild/batchOprTask/v1.0/createBatchOprTask"
500
}
501
_, err := d.post(pathname, data, nil)
502
return err
503
default:
504
return errs.NotImplement
505
}
506
}
507
508
func (d *Yun139) getPartSize(size int64) int64 {
509
if d.CustomUploadPartSize != 0 {
510
return d.CustomUploadPartSize
511
}
512
// 网盘对于分片数量存在上限
513
if size/utils.GB > 30 {
514
return 512 * utils.MB
515
}
516
return 100 * utils.MB
517
}
518
519
func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
520
switch d.Addition.Type {
521
case MetaPersonalNew:
522
var err error
523
fullHash := stream.GetHash().GetHash(utils.SHA256)
524
if len(fullHash) != utils.SHA256.Width {
525
_, fullHash, err = streamPkg.CacheFullInTempFileAndHash(stream, utils.SHA256)
526
if err != nil {
527
return err
528
}
529
}
530
531
size := stream.GetSize()
532
var partSize = d.getPartSize(size)
533
part := size / partSize
534
if size%partSize > 0 {
535
part++
536
} else if part == 0 {
537
part = 1
538
}
539
partInfos := make([]PartInfo, 0, part)
540
for i := int64(0); i < part; i++ {
541
if utils.IsCanceled(ctx) {
542
return ctx.Err()
543
}
544
start := i * partSize
545
byteSize := size - start
546
if byteSize > partSize {
547
byteSize = partSize
548
}
549
partNumber := i + 1
550
partInfo := PartInfo{
551
PartNumber: partNumber,
552
PartSize: byteSize,
553
ParallelHashCtx: ParallelHashCtx{
554
PartOffset: start,
555
},
556
}
557
partInfos = append(partInfos, partInfo)
558
}
559
560
// 筛选出前 100 个 partInfos
561
firstPartInfos := partInfos
562
if len(firstPartInfos) > 100 {
563
firstPartInfos = firstPartInfos[:100]
564
}
565
566
// 创建任务,获取上传信息和前100个分片的上传地址
567
data := base.Json{
568
"contentHash": fullHash,
569
"contentHashAlgorithm": "SHA256",
570
"contentType": "application/octet-stream",
571
"parallelUpload": false,
572
"partInfos": firstPartInfos,
573
"size": size,
574
"parentFileId": dstDir.GetID(),
575
"name": stream.GetName(),
576
"type": "file",
577
"fileRenameMode": "auto_rename",
578
}
579
pathname := "/file/create"
580
var resp PersonalUploadResp
581
_, err = d.personalPost(pathname, data, &resp)
582
if err != nil {
583
return err
584
}
585
586
// 判断文件是否已存在
587
// resp.Data.Exist: true 已存在同名文件且校验相同,云端不会重复增加文件,无需手动处理冲突
588
if resp.Data.Exist {
589
return nil
590
}
591
592
// 判断文件是否支持快传
593
// resp.Data.RapidUpload: true 支持快传,但此处直接检测是否返回分片的上传地址
594
// 快传的情况下同样需要手动处理冲突
595
if resp.Data.PartInfos != nil {
596
// 读取前100个分片的上传地址
597
uploadPartInfos := resp.Data.PartInfos
598
599
// 获取后续分片的上传地址
600
for i := 101; i < len(partInfos); i += 100 {
601
end := i + 100
602
if end > len(partInfos) {
603
end = len(partInfos)
604
}
605
batchPartInfos := partInfos[i:end]
606
607
moredata := base.Json{
608
"fileId": resp.Data.FileId,
609
"uploadId": resp.Data.UploadId,
610
"partInfos": batchPartInfos,
611
"commonAccountInfo": base.Json{
612
"account": d.getAccount(),
613
"accountType": 1,
614
},
615
}
616
pathname := "/file/getUploadUrl"
617
var moreresp PersonalUploadUrlResp
618
_, err = d.personalPost(pathname, moredata, &moreresp)
619
if err != nil {
620
return err
621
}
622
uploadPartInfos = append(uploadPartInfos, moreresp.Data.PartInfos...)
623
}
624
625
// Progress
626
p := driver.NewProgress(size, up)
627
628
rateLimited := driver.NewLimitedUploadStream(ctx, stream)
629
// 上传所有分片
630
for _, uploadPartInfo := range uploadPartInfos {
631
index := uploadPartInfo.PartNumber - 1
632
partSize := partInfos[index].PartSize
633
log.Debugf("[139] uploading part %+v/%+v", index, len(uploadPartInfos))
634
limitReader := io.LimitReader(rateLimited, partSize)
635
636
// Update Progress
637
r := io.TeeReader(limitReader, p)
638
639
req, err := http.NewRequest("PUT", uploadPartInfo.UploadUrl, r)
640
if err != nil {
641
return err
642
}
643
req = req.WithContext(ctx)
644
req.Header.Set("Content-Type", "application/octet-stream")
645
req.Header.Set("Content-Length", fmt.Sprint(partSize))
646
req.Header.Set("Origin", "https://yun.139.com")
647
req.Header.Set("Referer", "https://yun.139.com/")
648
req.ContentLength = partSize
649
650
res, err := base.HttpClient.Do(req)
651
if err != nil {
652
return err
653
}
654
_ = res.Body.Close()
655
log.Debugf("[139] uploaded: %+v", res)
656
if res.StatusCode != http.StatusOK {
657
return fmt.Errorf("unexpected status code: %d", res.StatusCode)
658
}
659
}
660
661
data = base.Json{
662
"contentHash": fullHash,
663
"contentHashAlgorithm": "SHA256",
664
"fileId": resp.Data.FileId,
665
"uploadId": resp.Data.UploadId,
666
}
667
_, err = d.personalPost("/file/complete", data, nil)
668
if err != nil {
669
return err
670
}
671
}
672
673
// 处理冲突
674
if resp.Data.FileName != stream.GetName() {
675
log.Debugf("[139] conflict detected: %s != %s", resp.Data.FileName, stream.GetName())
676
// 给服务器一定时间处理数据,避免无法刷新文件列表
677
time.Sleep(time.Millisecond * 500)
678
// 刷新并获取文件列表
679
files, err := d.List(ctx, dstDir, model.ListArgs{Refresh: true})
680
if err != nil {
681
return err
682
}
683
// 删除旧文件
684
for _, file := range files {
685
if file.GetName() == stream.GetName() {
686
log.Debugf("[139] conflict: removing old: %s", file.GetName())
687
// 删除前重命名旧文件,避免仍旧冲突
688
err = d.Rename(ctx, file, stream.GetName()+random.String(4))
689
if err != nil {
690
return err
691
}
692
err = d.Remove(ctx, file)
693
if err != nil {
694
return err
695
}
696
break
697
}
698
}
699
// 重命名新文件
700
for _, file := range files {
701
if file.GetName() == resp.Data.FileName {
702
log.Debugf("[139] conflict: renaming new: %s => %s", file.GetName(), stream.GetName())
703
err = d.Rename(ctx, file, stream.GetName())
704
if err != nil {
705
return err
706
}
707
break
708
}
709
}
710
}
711
return nil
712
case MetaPersonal:
713
fallthrough
714
case MetaFamily:
715
// 处理冲突
716
// 获取文件列表
717
files, err := d.List(ctx, dstDir, model.ListArgs{})
718
if err != nil {
719
return err
720
}
721
// 删除旧文件
722
for _, file := range files {
723
if file.GetName() == stream.GetName() {
724
log.Debugf("[139] conflict: removing old: %s", file.GetName())
725
// 删除前重命名旧文件,避免仍旧冲突
726
err = d.Rename(ctx, file, stream.GetName()+random.String(4))
727
if err != nil {
728
return err
729
}
730
err = d.Remove(ctx, file)
731
if err != nil {
732
return err
733
}
734
break
735
}
736
}
737
var reportSize int64
738
if d.ReportRealSize {
739
reportSize = stream.GetSize()
740
} else {
741
reportSize = 0
742
}
743
data := base.Json{
744
"manualRename": 2,
745
"operation": 0,
746
"fileCount": 1,
747
"totalSize": reportSize,
748
"uploadContentList": []base.Json{{
749
"contentName": stream.GetName(),
750
"contentSize": reportSize,
751
// "digest": "5a3231986ce7a6b46e408612d385bafa"
752
}},
753
"parentCatalogID": dstDir.GetID(),
754
"newCatalogName": "",
755
"commonAccountInfo": base.Json{
756
"account": d.getAccount(),
757
"accountType": 1,
758
},
759
}
760
pathname := "/orchestration/personalCloud/uploadAndDownload/v1.0/pcUploadFileRequest"
761
if d.isFamily() {
762
data = d.newJson(base.Json{
763
"fileCount": 1,
764
"manualRename": 2,
765
"operation": 0,
766
"path": path.Join(dstDir.GetPath(), dstDir.GetID()),
767
"seqNo": random.String(32), //序列号不能为空
768
"totalSize": reportSize,
769
"uploadContentList": []base.Json{{
770
"contentName": stream.GetName(),
771
"contentSize": reportSize,
772
// "digest": "5a3231986ce7a6b46e408612d385bafa"
773
}},
774
})
775
pathname = "/orchestration/familyCloud-rebuild/content/v1.0/getFileUploadURL"
776
}
777
var resp UploadResp
778
_, err = d.post(pathname, data, &resp)
779
if err != nil {
780
return err
781
}
782
if resp.Data.Result.ResultCode != "0" {
783
return fmt.Errorf("get file upload url failed with result code: %s, message: %s", resp.Data.Result.ResultCode, resp.Data.Result.ResultDesc)
784
}
785
786
size := stream.GetSize()
787
// Progress
788
p := driver.NewProgress(size, up)
789
var partSize = d.getPartSize(size)
790
part := size / partSize
791
if size%partSize > 0 {
792
part++
793
} else if part == 0 {
794
part = 1
795
}
796
rateLimited := driver.NewLimitedUploadStream(ctx, stream)
797
for i := int64(0); i < part; i++ {
798
if utils.IsCanceled(ctx) {
799
return ctx.Err()
800
}
801
802
start := i * partSize
803
byteSize := min(size-start, partSize)
804
805
limitReader := io.LimitReader(rateLimited, byteSize)
806
// Update Progress
807
r := io.TeeReader(limitReader, p)
808
req, err := http.NewRequest("POST", resp.Data.UploadResult.RedirectionURL, r)
809
if err != nil {
810
return err
811
}
812
813
req = req.WithContext(ctx)
814
req.Header.Set("Content-Type", "text/plain;name="+unicode(stream.GetName()))
815
req.Header.Set("contentSize", strconv.FormatInt(size, 10))
816
req.Header.Set("range", fmt.Sprintf("bytes=%d-%d", start, start+byteSize-1))
817
req.Header.Set("uploadtaskID", resp.Data.UploadResult.UploadTaskID)
818
req.Header.Set("rangeType", "0")
819
req.ContentLength = byteSize
820
821
res, err := base.HttpClient.Do(req)
822
if err != nil {
823
return err
824
}
825
if res.StatusCode != http.StatusOK {
826
res.Body.Close()
827
return fmt.Errorf("unexpected status code: %d", res.StatusCode)
828
}
829
bodyBytes, err := io.ReadAll(res.Body)
830
if err != nil {
831
return fmt.Errorf("error reading response body: %v", err)
832
}
833
var result InterLayerUploadResult
834
err = xml.Unmarshal(bodyBytes, &result)
835
if err != nil {
836
return fmt.Errorf("error parsing XML: %v", err)
837
}
838
if result.ResultCode != 0 {
839
return fmt.Errorf("upload failed with result code: %d, message: %s", result.ResultCode, result.Msg)
840
}
841
}
842
return nil
843
default:
844
return errs.NotImplement
845
}
846
}
847
848
func (d *Yun139) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
849
switch d.Addition.Type {
850
case MetaPersonalNew:
851
var resp base.Json
852
var uri string
853
data := base.Json{
854
"category": "video",
855
"fileId": args.Obj.GetID(),
856
}
857
switch args.Method {
858
case "video_preview":
859
uri = "/videoPreview/getPreviewInfo"
860
default:
861
return nil, errs.NotSupport
862
}
863
_, err := d.personalPost(uri, data, &resp)
864
if err != nil {
865
return nil, err
866
}
867
return resp["data"], nil
868
default:
869
return nil, errs.NotImplement
870
}
871
}
872
873
var _ driver.Driver = (*Yun139)(nil)
874
875