Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/189pc/utils.go
1987 views
1
package _189pc
2
3
import (
4
"bytes"
5
"context"
6
"encoding/base64"
7
"encoding/hex"
8
"encoding/xml"
9
"fmt"
10
"io"
11
"net/http"
12
"net/http/cookiejar"
13
"net/url"
14
"os"
15
"path"
16
"regexp"
17
"sort"
18
"strconv"
19
"strings"
20
"time"
21
"unicode/utf8"
22
23
"golang.org/x/sync/semaphore"
24
25
"github.com/alist-org/alist/v3/drivers/base"
26
"github.com/alist-org/alist/v3/internal/conf"
27
"github.com/alist-org/alist/v3/internal/driver"
28
"github.com/alist-org/alist/v3/internal/errs"
29
"github.com/alist-org/alist/v3/internal/model"
30
"github.com/alist-org/alist/v3/internal/op"
31
"github.com/alist-org/alist/v3/internal/setting"
32
"github.com/alist-org/alist/v3/internal/stream"
33
"github.com/alist-org/alist/v3/pkg/errgroup"
34
"github.com/alist-org/alist/v3/pkg/utils"
35
36
"github.com/avast/retry-go"
37
"github.com/go-resty/resty/v2"
38
"github.com/google/uuid"
39
jsoniter "github.com/json-iterator/go"
40
"github.com/pkg/errors"
41
)
42
43
const (
44
ACCOUNT_TYPE = "02"
45
APP_ID = "8025431004"
46
CLIENT_TYPE = "10020"
47
VERSION = "6.2"
48
49
WEB_URL = "https://cloud.189.cn"
50
AUTH_URL = "https://open.e.189.cn"
51
API_URL = "https://api.cloud.189.cn"
52
UPLOAD_URL = "https://upload.cloud.189.cn"
53
54
RETURN_URL = "https://m.cloud.189.cn/zhuanti/2020/loginErrorPc/index.html"
55
56
PC = "TELEPC"
57
MAC = "TELEMAC"
58
59
CHANNEL_ID = "web_cloud.189.cn"
60
)
61
62
func (y *Cloud189PC) sanitizeName(name string) string {
63
if !y.StripEmoji {
64
return name
65
}
66
b := strings.Builder{}
67
for _, r := range name {
68
if utf8.RuneLen(r) == 4 {
69
continue
70
}
71
b.WriteRune(r)
72
}
73
sanitized := b.String()
74
if sanitized == "" {
75
ext := path.Ext(name)
76
if ext != "" {
77
sanitized = "file" + ext
78
} else {
79
sanitized = "file"
80
}
81
}
82
return sanitized
83
}
84
85
func (y *Cloud189PC) SignatureHeader(url, method, params string, isFamily bool) map[string]string {
86
dateOfGmt := getHttpDateStr()
87
sessionKey := y.getTokenInfo().SessionKey
88
sessionSecret := y.getTokenInfo().SessionSecret
89
if isFamily {
90
sessionKey = y.getTokenInfo().FamilySessionKey
91
sessionSecret = y.getTokenInfo().FamilySessionSecret
92
}
93
94
header := map[string]string{
95
"Date": dateOfGmt,
96
"SessionKey": sessionKey,
97
"X-Request-ID": uuid.NewString(),
98
"Signature": signatureOfHmac(sessionSecret, sessionKey, method, url, dateOfGmt, params),
99
}
100
return header
101
}
102
103
func (y *Cloud189PC) EncryptParams(params Params, isFamily bool) string {
104
sessionSecret := y.getTokenInfo().SessionSecret
105
if isFamily {
106
sessionSecret = y.getTokenInfo().FamilySessionSecret
107
}
108
if params != nil {
109
return AesECBEncrypt(params.Encode(), sessionSecret[:16])
110
}
111
return ""
112
}
113
114
func (y *Cloud189PC) request(url, method string, callback base.ReqCallback, params Params, resp interface{}, isFamily ...bool) ([]byte, error) {
115
req := y.getClient().R().SetQueryParams(clientSuffix())
116
117
// 设置params
118
paramsData := y.EncryptParams(params, isBool(isFamily...))
119
if paramsData != "" {
120
req.SetQueryParam("params", paramsData)
121
}
122
123
// Signature
124
req.SetHeaders(y.SignatureHeader(url, method, paramsData, isBool(isFamily...)))
125
126
var erron RespErr
127
req.SetError(&erron)
128
129
if callback != nil {
130
callback(req)
131
}
132
if resp != nil {
133
req.SetResult(resp)
134
}
135
res, err := req.Execute(method, url)
136
if err != nil {
137
return nil, err
138
}
139
140
if strings.Contains(res.String(), "userSessionBO is null") {
141
if err = y.refreshSession(); err != nil {
142
return nil, err
143
}
144
return y.request(url, method, callback, params, resp, isFamily...)
145
}
146
147
// if erron.ErrorCode == "InvalidSessionKey" || erron.Code == "InvalidSessionKey" {
148
if strings.Contains(res.String(), "InvalidSessionKey") {
149
if err = y.refreshSession(); err != nil {
150
return nil, err
151
}
152
return y.request(url, method, callback, params, resp, isFamily...)
153
}
154
155
// 处理错误
156
if erron.HasError() {
157
return nil, &erron
158
}
159
return res.Body(), nil
160
}
161
162
func (y *Cloud189PC) get(url string, callback base.ReqCallback, resp interface{}, isFamily ...bool) ([]byte, error) {
163
return y.request(url, http.MethodGet, callback, nil, resp, isFamily...)
164
}
165
166
func (y *Cloud189PC) post(url string, callback base.ReqCallback, resp interface{}, isFamily ...bool) ([]byte, error) {
167
return y.request(url, http.MethodPost, callback, nil, resp, isFamily...)
168
}
169
170
func (y *Cloud189PC) put(ctx context.Context, url string, headers map[string]string, sign bool, file io.Reader, isFamily bool) ([]byte, error) {
171
req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, file)
172
if err != nil {
173
return nil, err
174
}
175
176
query := req.URL.Query()
177
for key, value := range clientSuffix() {
178
query.Add(key, value)
179
}
180
req.URL.RawQuery = query.Encode()
181
182
for key, value := range headers {
183
req.Header.Add(key, value)
184
}
185
186
if sign {
187
for key, value := range y.SignatureHeader(url, http.MethodPut, "", isFamily) {
188
req.Header.Add(key, value)
189
}
190
}
191
192
resp, err := base.HttpClient.Do(req)
193
if err != nil {
194
return nil, err
195
}
196
defer resp.Body.Close()
197
198
body, err := io.ReadAll(resp.Body)
199
if err != nil {
200
return nil, err
201
}
202
203
var erron RespErr
204
_ = jsoniter.Unmarshal(body, &erron)
205
_ = xml.Unmarshal(body, &erron)
206
if erron.HasError() {
207
return nil, &erron
208
}
209
if resp.StatusCode != http.StatusOK {
210
return nil, errors.Errorf("put fail,err:%s", string(body))
211
}
212
return body, nil
213
}
214
func (y *Cloud189PC) getFiles(ctx context.Context, fileId string, isFamily bool) ([]model.Obj, error) {
215
res := make([]model.Obj, 0, 100)
216
for pageNum := 1; ; pageNum++ {
217
resp, err := y.getFilesWithPage(ctx, fileId, isFamily, pageNum, 1000, y.OrderBy, y.OrderDirection)
218
if err != nil {
219
return nil, err
220
}
221
// 获取完毕跳出
222
if resp.FileListAO.Count == 0 {
223
break
224
}
225
226
for i := 0; i < len(resp.FileListAO.FolderList); i++ {
227
res = append(res, &resp.FileListAO.FolderList[i])
228
}
229
for i := 0; i < len(resp.FileListAO.FileList); i++ {
230
res = append(res, &resp.FileListAO.FileList[i])
231
}
232
}
233
return res, nil
234
}
235
236
func (y *Cloud189PC) getFilesWithPage(ctx context.Context, fileId string, isFamily bool, pageNum int, pageSize int, orderBy string, orderDirection string) (*Cloud189FilesResp, error) {
237
fullUrl := API_URL
238
if isFamily {
239
fullUrl += "/family/file"
240
}
241
fullUrl += "/listFiles.action"
242
243
var resp Cloud189FilesResp
244
_, err := y.get(fullUrl, func(r *resty.Request) {
245
r.SetContext(ctx)
246
r.SetQueryParams(map[string]string{
247
"folderId": fileId,
248
"fileType": "0",
249
"mediaAttr": "0",
250
"iconOption": "5",
251
"pageNum": fmt.Sprint(pageNum),
252
"pageSize": fmt.Sprint(pageSize),
253
})
254
if isFamily {
255
r.SetQueryParams(map[string]string{
256
"familyId": y.FamilyID,
257
"orderBy": toFamilyOrderBy(orderBy),
258
"descending": toDesc(orderDirection),
259
})
260
} else {
261
r.SetQueryParams(map[string]string{
262
"recursive": "0",
263
"orderBy": orderBy,
264
"descending": toDesc(orderDirection),
265
})
266
}
267
}, &resp, isFamily)
268
if err != nil {
269
return nil, err
270
}
271
return &resp, nil
272
}
273
274
func (y *Cloud189PC) findFileByName(ctx context.Context, searchName string, folderId string, isFamily bool) (*Cloud189File, error) {
275
for pageNum := 1; ; pageNum++ {
276
resp, err := y.getFilesWithPage(ctx, folderId, isFamily, pageNum, 10, "filename", "asc")
277
if err != nil {
278
return nil, err
279
}
280
// 获取完毕跳出
281
if resp.FileListAO.Count == 0 {
282
return nil, errs.ObjectNotFound
283
}
284
for i := 0; i < len(resp.FileListAO.FileList); i++ {
285
file := resp.FileListAO.FileList[i]
286
if file.Name == searchName {
287
return &file, nil
288
}
289
}
290
}
291
}
292
293
func (y *Cloud189PC) login() (err error) {
294
// 初始化登陆所需参数
295
if y.loginParam == nil {
296
if err = y.initLoginParam(); err != nil {
297
// 验证码也通过错误返回
298
return err
299
}
300
}
301
defer func() {
302
// 销毁验证码
303
y.VCode = ""
304
// 销毁登陆参数
305
y.loginParam = nil
306
// 遇到错误,重新加载登陆参数(刷新验证码)
307
if err != nil && y.NoUseOcr {
308
if err1 := y.initLoginParam(); err1 != nil {
309
err = fmt.Errorf("err1: %s \nerr2: %s", err, err1)
310
}
311
}
312
}()
313
314
param := y.loginParam
315
var loginresp LoginResp
316
_, err = y.client.R().
317
ForceContentType("application/json;charset=UTF-8").SetResult(&loginresp).
318
SetHeaders(map[string]string{
319
"REQID": param.ReqId,
320
"lt": param.Lt,
321
}).
322
SetFormData(map[string]string{
323
"appKey": APP_ID,
324
"accountType": ACCOUNT_TYPE,
325
"userName": param.RsaUsername,
326
"password": param.RsaPassword,
327
"validateCode": y.VCode,
328
"captchaToken": param.CaptchaToken,
329
"returnUrl": RETURN_URL,
330
// "mailSuffix": "@189.cn",
331
"dynamicCheck": "FALSE",
332
"clientType": CLIENT_TYPE,
333
"cb_SaveName": "1",
334
"isOauth2": "false",
335
"state": "",
336
"paramId": param.ParamId,
337
}).
338
Post(AUTH_URL + "/api/logbox/oauth2/loginSubmit.do")
339
if err != nil {
340
return err
341
}
342
if loginresp.ToUrl == "" {
343
return fmt.Errorf("login failed,No toUrl obtained, msg: %s", loginresp.Msg)
344
}
345
346
// 获取Session
347
var erron RespErr
348
var tokenInfo AppSessionResp
349
_, err = y.client.R().
350
SetResult(&tokenInfo).SetError(&erron).
351
SetQueryParams(clientSuffix()).
352
SetQueryParam("redirectURL", loginresp.ToUrl).
353
Post(API_URL + "/getSessionForPC.action")
354
if err != nil {
355
return
356
}
357
358
if erron.HasError() {
359
return &erron
360
}
361
if tokenInfo.ResCode != 0 {
362
err = fmt.Errorf(tokenInfo.ResMessage)
363
return
364
}
365
y.tokenInfo = &tokenInfo
366
return
367
}
368
369
/* 初始化登陆需要的参数
370
* 如果遇到验证码返回错误
371
*/
372
func (y *Cloud189PC) initLoginParam() error {
373
// 清除cookie
374
jar, _ := cookiejar.New(nil)
375
y.client.SetCookieJar(jar)
376
377
res, err := y.client.R().
378
SetQueryParams(map[string]string{
379
"appId": APP_ID,
380
"clientType": CLIENT_TYPE,
381
"returnURL": RETURN_URL,
382
"timeStamp": fmt.Sprint(timestamp()),
383
}).
384
Get(WEB_URL + "/api/portal/unifyLoginForPC.action")
385
if err != nil {
386
return err
387
}
388
389
param := LoginParam{
390
CaptchaToken: regexp.MustCompile(`'captchaToken' value='(.+?)'`).FindStringSubmatch(res.String())[1],
391
Lt: regexp.MustCompile(`lt = "(.+?)"`).FindStringSubmatch(res.String())[1],
392
ParamId: regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(res.String())[1],
393
ReqId: regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(res.String())[1],
394
// jRsaKey: regexp.MustCompile(`"j_rsaKey" value="(.+?)"`).FindStringSubmatch(res.String())[1],
395
}
396
397
// 获取rsa公钥
398
var encryptConf EncryptConfResp
399
_, err = y.client.R().
400
ForceContentType("application/json;charset=UTF-8").SetResult(&encryptConf).
401
SetFormData(map[string]string{"appId": APP_ID}).
402
Post(AUTH_URL + "/api/logbox/config/encryptConf.do")
403
if err != nil {
404
return err
405
}
406
407
param.jRsaKey = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", encryptConf.Data.PubKey)
408
param.RsaUsername = encryptConf.Data.Pre + RsaEncrypt(param.jRsaKey, y.Username)
409
param.RsaPassword = encryptConf.Data.Pre + RsaEncrypt(param.jRsaKey, y.Password)
410
y.loginParam = &param
411
412
// 判断是否需要验证码
413
resp, err := y.client.R().
414
SetHeader("REQID", param.ReqId).
415
SetFormData(map[string]string{
416
"appKey": APP_ID,
417
"accountType": ACCOUNT_TYPE,
418
"userName": param.RsaUsername,
419
}).Post(AUTH_URL + "/api/logbox/oauth2/needcaptcha.do")
420
if err != nil {
421
return err
422
}
423
if resp.String() == "0" {
424
return nil
425
}
426
427
// 拉取验证码
428
imgRes, err := y.client.R().
429
SetQueryParams(map[string]string{
430
"token": param.CaptchaToken,
431
"REQID": param.ReqId,
432
"rnd": fmt.Sprint(timestamp()),
433
}).
434
Get(AUTH_URL + "/api/logbox/oauth2/picCaptcha.do")
435
if err != nil {
436
return fmt.Errorf("failed to obtain verification code")
437
}
438
if imgRes.Size() > 20 {
439
if setting.GetStr(conf.OcrApi) != "" && !y.NoUseOcr {
440
vRes, err := base.RestyClient.R().
441
SetMultipartField("image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
442
Post(setting.GetStr(conf.OcrApi))
443
if err != nil {
444
return err
445
}
446
if jsoniter.Get(vRes.Body(), "status").ToInt() == 200 {
447
y.VCode = jsoniter.Get(vRes.Body(), "result").ToString()
448
return nil
449
}
450
}
451
452
// 返回验证码图片给前端
453
return fmt.Errorf(`need img validate code: <img src="data:image/png;base64,%s"/>`, base64.StdEncoding.EncodeToString(imgRes.Body()))
454
}
455
return nil
456
}
457
458
// 刷新会话
459
func (y *Cloud189PC) refreshSession() (err error) {
460
if y.ref != nil {
461
return y.ref.refreshSession()
462
}
463
var erron RespErr
464
var userSessionResp UserSessionResp
465
_, err = y.client.R().
466
SetResult(&userSessionResp).SetError(&erron).
467
SetQueryParams(clientSuffix()).
468
SetQueryParams(map[string]string{
469
"appId": APP_ID,
470
"accessToken": y.tokenInfo.AccessToken,
471
}).
472
SetHeader("X-Request-ID", uuid.NewString()).
473
Get(API_URL + "/getSessionForPC.action")
474
if err != nil {
475
return err
476
}
477
478
// 错误影响正常访问,下线该储存
479
defer func() {
480
if err != nil {
481
y.GetStorage().SetStatus(fmt.Sprintf("%+v", err.Error()))
482
op.MustSaveDriverStorage(y)
483
}
484
}()
485
486
if erron.HasError() {
487
if erron.ResCode == "UserInvalidOpenToken" {
488
if err = y.login(); err != nil {
489
return err
490
}
491
}
492
return &erron
493
}
494
y.tokenInfo.UserSessionResp = userSessionResp
495
return
496
}
497
498
// 普通上传
499
// 无法上传大小为0的文件
500
func (y *Cloud189PC) StreamUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress, isFamily bool, overwrite bool) (model.Obj, error) {
501
size := file.GetSize()
502
sliceSize := partSize(size)
503
safeName := y.sanitizeName(file.GetName())
504
505
params := Params{
506
"parentFolderId": dstDir.GetID(),
507
"fileName": url.QueryEscape(safeName),
508
"fileSize": fmt.Sprint(file.GetSize()),
509
"sliceSize": fmt.Sprint(sliceSize),
510
"lazyCheck": "1",
511
}
512
513
fullUrl := UPLOAD_URL
514
if isFamily {
515
params.Set("familyId", y.FamilyID)
516
fullUrl += "/family"
517
} else {
518
//params.Set("extend", `{"opScene":"1","relativepath":"","rootfolderid":""}`)
519
fullUrl += "/person"
520
}
521
522
// 初始化上传
523
var initMultiUpload InitMultiUploadResp
524
_, err := y.request(fullUrl+"/initMultiUpload", http.MethodGet, func(req *resty.Request) {
525
req.SetContext(ctx)
526
}, params, &initMultiUpload, isFamily)
527
if err != nil {
528
return nil, err
529
}
530
531
threadG, upCtx := errgroup.NewGroupWithContext(ctx, y.uploadThread,
532
retry.Attempts(3),
533
retry.Delay(time.Second),
534
retry.DelayType(retry.BackOffDelay))
535
sem := semaphore.NewWeighted(3)
536
537
count := int(size / sliceSize)
538
lastPartSize := size % sliceSize
539
if lastPartSize > 0 {
540
count++
541
} else {
542
lastPartSize = sliceSize
543
}
544
fileMd5 := utils.MD5.NewFunc()
545
silceMd5 := utils.MD5.NewFunc()
546
silceMd5Hexs := make([]string, 0, count)
547
teeReader := io.TeeReader(file, io.MultiWriter(fileMd5, silceMd5))
548
byteSize := sliceSize
549
for i := 1; i <= count; i++ {
550
if utils.IsCanceled(upCtx) {
551
break
552
}
553
if i == count {
554
byteSize = lastPartSize
555
}
556
byteData := make([]byte, byteSize)
557
// 读取块
558
silceMd5.Reset()
559
if _, err := io.ReadFull(teeReader, byteData); err != io.EOF && err != nil {
560
sem.Release(1)
561
return nil, err
562
}
563
564
// 计算块md5并进行hex和base64编码
565
md5Bytes := silceMd5.Sum(nil)
566
silceMd5Hexs = append(silceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Bytes)))
567
partInfo := fmt.Sprintf("%d-%s", i, base64.StdEncoding.EncodeToString(md5Bytes))
568
569
threadG.Go(func(ctx context.Context) error {
570
if err = sem.Acquire(ctx, 1); err != nil {
571
return err
572
}
573
defer sem.Release(1)
574
uploadUrls, err := y.GetMultiUploadUrls(ctx, isFamily, initMultiUpload.Data.UploadFileID, partInfo)
575
if err != nil {
576
return err
577
}
578
579
// step.4 上传切片
580
uploadUrl := uploadUrls[0]
581
_, err = y.put(ctx, uploadUrl.RequestURL, uploadUrl.Headers, false,
582
driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)), isFamily)
583
if err != nil {
584
return err
585
}
586
up(float64(threadG.Success()) * 100 / float64(count))
587
return nil
588
})
589
}
590
if err = threadG.Wait(); err != nil {
591
return nil, err
592
}
593
594
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
595
sliceMd5Hex := fileMd5Hex
596
if file.GetSize() > sliceSize {
597
sliceMd5Hex = strings.ToUpper(utils.GetMD5EncodeStr(strings.Join(silceMd5Hexs, "\n")))
598
}
599
600
// 提交上传
601
var resp CommitMultiUploadFileResp
602
_, err = y.request(fullUrl+"/commitMultiUploadFile", http.MethodGet,
603
func(req *resty.Request) {
604
req.SetContext(ctx)
605
}, Params{
606
"uploadFileId": initMultiUpload.Data.UploadFileID,
607
"fileMd5": fileMd5Hex,
608
"sliceMd5": sliceMd5Hex,
609
"lazyCheck": "1",
610
"isLog": "0",
611
"opertype": IF(overwrite, "3", "1"),
612
}, &resp, isFamily)
613
if err != nil {
614
return nil, err
615
}
616
return resp.toFile(), nil
617
}
618
619
func (y *Cloud189PC) RapidUpload(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, isFamily bool, overwrite bool) (model.Obj, error) {
620
fileMd5 := stream.GetHash().GetHash(utils.MD5)
621
if len(fileMd5) < utils.MD5.Width {
622
return nil, errors.New("invalid hash")
623
}
624
625
safeName := y.sanitizeName(stream.GetName())
626
uploadInfo, err := y.OldUploadCreate(ctx, dstDir.GetID(), fileMd5, safeName, fmt.Sprint(stream.GetSize()), isFamily)
627
if err != nil {
628
return nil, err
629
}
630
631
if uploadInfo.FileDataExists != 1 {
632
return nil, errors.New("rapid upload fail")
633
}
634
635
return y.OldUploadCommit(ctx, uploadInfo.FileCommitUrl, uploadInfo.UploadFileId, isFamily, overwrite)
636
}
637
638
// 快传
639
func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress, isFamily bool, overwrite bool) (model.Obj, error) {
640
var (
641
cache = file.GetFile()
642
tmpF *os.File
643
err error
644
)
645
safeName := y.sanitizeName(file.GetName())
646
size := file.GetSize()
647
if _, ok := cache.(io.ReaderAt); !ok && size > 0 {
648
tmpF, err = os.CreateTemp(conf.Conf.TempDir, "file-*")
649
if err != nil {
650
return nil, err
651
}
652
defer func() {
653
_ = tmpF.Close()
654
_ = os.Remove(tmpF.Name())
655
}()
656
cache = tmpF
657
}
658
sliceSize := partSize(size)
659
count := int(size / sliceSize)
660
lastSliceSize := size % sliceSize
661
if lastSliceSize > 0 {
662
count++
663
} else {
664
lastSliceSize = sliceSize
665
}
666
667
//step.1 优先计算所需信息
668
byteSize := sliceSize
669
fileMd5 := utils.MD5.NewFunc()
670
sliceMd5 := utils.MD5.NewFunc()
671
sliceMd5Hexs := make([]string, 0, count)
672
partInfos := make([]string, 0, count)
673
writers := []io.Writer{fileMd5, sliceMd5}
674
if tmpF != nil {
675
writers = append(writers, tmpF)
676
}
677
written := int64(0)
678
for i := 1; i <= count; i++ {
679
if utils.IsCanceled(ctx) {
680
return nil, ctx.Err()
681
}
682
683
if i == count {
684
byteSize = lastSliceSize
685
}
686
687
n, err := utils.CopyWithBufferN(io.MultiWriter(writers...), file, byteSize)
688
written += n
689
if err != nil && err != io.EOF {
690
return nil, err
691
}
692
md5Byte := sliceMd5.Sum(nil)
693
sliceMd5Hexs = append(sliceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Byte)))
694
partInfos = append(partInfos, fmt.Sprint(i, "-", base64.StdEncoding.EncodeToString(md5Byte)))
695
sliceMd5.Reset()
696
}
697
698
if tmpF != nil {
699
if size > 0 && written != size {
700
return nil, errs.NewErr(err, "CreateTempFile failed, incoming stream actual size= %d, expect = %d ", written, size)
701
}
702
_, err = tmpF.Seek(0, io.SeekStart)
703
if err != nil {
704
return nil, errs.NewErr(err, "CreateTempFile failed, can't seek to 0 ")
705
}
706
}
707
708
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
709
sliceMd5Hex := fileMd5Hex
710
if size > sliceSize {
711
sliceMd5Hex = strings.ToUpper(utils.GetMD5EncodeStr(strings.Join(sliceMd5Hexs, "\n")))
712
}
713
714
fullUrl := UPLOAD_URL
715
if isFamily {
716
fullUrl += "/family"
717
} else {
718
//params.Set("extend", `{"opScene":"1","relativepath":"","rootfolderid":""}`)
719
fullUrl += "/person"
720
}
721
722
// 尝试恢复进度
723
uploadProgress, ok := base.GetUploadProgress[*UploadProgress](y, y.getTokenInfo().SessionKey, fileMd5Hex)
724
if !ok {
725
//step.2 预上传
726
params := Params{
727
"parentFolderId": dstDir.GetID(),
728
"fileName": url.QueryEscape(safeName),
729
"fileSize": fmt.Sprint(file.GetSize()),
730
"fileMd5": fileMd5Hex,
731
"sliceSize": fmt.Sprint(sliceSize),
732
"sliceMd5": sliceMd5Hex,
733
}
734
if isFamily {
735
params.Set("familyId", y.FamilyID)
736
}
737
var uploadInfo InitMultiUploadResp
738
_, err = y.request(fullUrl+"/initMultiUpload", http.MethodGet, func(req *resty.Request) {
739
req.SetContext(ctx)
740
}, params, &uploadInfo, isFamily)
741
if err != nil {
742
return nil, err
743
}
744
uploadProgress = &UploadProgress{
745
UploadInfo: uploadInfo,
746
UploadParts: partInfos,
747
}
748
}
749
750
uploadInfo := uploadProgress.UploadInfo.Data
751
// 网盘中不存在该文件,开始上传
752
if uploadInfo.FileDataExists != 1 {
753
threadG, upCtx := errgroup.NewGroupWithContext(ctx, y.uploadThread,
754
retry.Attempts(3),
755
retry.Delay(time.Second),
756
retry.DelayType(retry.BackOffDelay))
757
for i, uploadPart := range uploadProgress.UploadParts {
758
if utils.IsCanceled(upCtx) {
759
break
760
}
761
762
i, uploadPart := i, uploadPart
763
threadG.Go(func(ctx context.Context) error {
764
// step.3 获取上传链接
765
uploadUrls, err := y.GetMultiUploadUrls(ctx, isFamily, uploadInfo.UploadFileID, uploadPart)
766
if err != nil {
767
return err
768
}
769
uploadUrl := uploadUrls[0]
770
771
byteSize, offset := sliceSize, int64(uploadUrl.PartNumber-1)*sliceSize
772
if uploadUrl.PartNumber == count {
773
byteSize = lastSliceSize
774
}
775
776
// step.4 上传切片
777
_, err = y.put(ctx, uploadUrl.RequestURL, uploadUrl.Headers, false, io.NewSectionReader(cache, offset, byteSize), isFamily)
778
if err != nil {
779
return err
780
}
781
782
up(float64(threadG.Success()) * 100 / float64(len(uploadUrls)))
783
uploadProgress.UploadParts[i] = ""
784
return nil
785
})
786
}
787
if err = threadG.Wait(); err != nil {
788
if errors.Is(err, context.Canceled) {
789
uploadProgress.UploadParts = utils.SliceFilter(uploadProgress.UploadParts, func(s string) bool { return s != "" })
790
base.SaveUploadProgress(y, uploadProgress, y.getTokenInfo().SessionKey, fileMd5Hex)
791
}
792
return nil, err
793
}
794
}
795
796
// step.5 提交
797
var resp CommitMultiUploadFileResp
798
_, err = y.request(fullUrl+"/commitMultiUploadFile", http.MethodGet,
799
func(req *resty.Request) {
800
req.SetContext(ctx)
801
}, Params{
802
"uploadFileId": uploadInfo.UploadFileID,
803
"isLog": "0",
804
"opertype": IF(overwrite, "3", "1"),
805
}, &resp, isFamily)
806
if err != nil {
807
return nil, err
808
}
809
return resp.toFile(), nil
810
}
811
812
// 获取上传切片信息
813
// 对http body有大小限制,分片信息太多会出错
814
func (y *Cloud189PC) GetMultiUploadUrls(ctx context.Context, isFamily bool, uploadFileId string, partInfo ...string) ([]UploadUrlInfo, error) {
815
fullUrl := UPLOAD_URL
816
if isFamily {
817
fullUrl += "/family"
818
} else {
819
fullUrl += "/person"
820
}
821
822
var uploadUrlsResp UploadUrlsResp
823
_, err := y.request(fullUrl+"/getMultiUploadUrls", http.MethodGet,
824
func(req *resty.Request) {
825
req.SetContext(ctx)
826
}, Params{
827
"uploadFileId": uploadFileId,
828
"partInfo": strings.Join(partInfo, ","),
829
}, &uploadUrlsResp, isFamily)
830
if err != nil {
831
return nil, err
832
}
833
uploadUrls := uploadUrlsResp.Data
834
835
if len(uploadUrls) != len(partInfo) {
836
return nil, fmt.Errorf("uploadUrls get error, due to get length %d, real length %d", len(partInfo), len(uploadUrls))
837
}
838
839
uploadUrlInfos := make([]UploadUrlInfo, 0, len(uploadUrls))
840
for k, uploadUrl := range uploadUrls {
841
partNumber, err := strconv.Atoi(strings.TrimPrefix(k, "partNumber_"))
842
if err != nil {
843
return nil, err
844
}
845
uploadUrlInfos = append(uploadUrlInfos, UploadUrlInfo{
846
PartNumber: partNumber,
847
Headers: ParseHttpHeader(uploadUrl.RequestHeader),
848
UploadUrlsData: uploadUrl,
849
})
850
}
851
sort.Slice(uploadUrlInfos, func(i, j int) bool {
852
return uploadUrlInfos[i].PartNumber < uploadUrlInfos[j].PartNumber
853
})
854
return uploadUrlInfos, nil
855
}
856
857
// 旧版本上传,家庭云不支持覆盖
858
func (y *Cloud189PC) OldUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress, isFamily bool, overwrite bool) (model.Obj, error) {
859
tempFile, fileMd5, err := stream.CacheFullInTempFileAndHash(file, utils.MD5)
860
if err != nil {
861
return nil, err
862
}
863
rateLimited := driver.NewLimitedUploadStream(ctx, io.NopCloser(tempFile))
864
safeName := y.sanitizeName(file.GetName())
865
866
// 创建上传会话
867
uploadInfo, err := y.OldUploadCreate(ctx, dstDir.GetID(), fileMd5, safeName, fmt.Sprint(file.GetSize()), isFamily)
868
if err != nil {
869
return nil, err
870
}
871
872
// 网盘中不存在该文件,开始上传
873
status := GetUploadFileStatusResp{CreateUploadFileResp: *uploadInfo}
874
for status.GetSize() < file.GetSize() && status.FileDataExists != 1 {
875
if utils.IsCanceled(ctx) {
876
return nil, ctx.Err()
877
}
878
879
header := map[string]string{
880
"ResumePolicy": "1",
881
"Expect": "100-continue",
882
}
883
884
if isFamily {
885
header["FamilyId"] = fmt.Sprint(y.FamilyID)
886
header["UploadFileId"] = fmt.Sprint(status.UploadFileId)
887
} else {
888
header["Edrive-UploadFileId"] = fmt.Sprint(status.UploadFileId)
889
}
890
891
_, err := y.put(ctx, status.FileUploadUrl, header, true, rateLimited, isFamily)
892
if err, ok := err.(*RespErr); ok && err.Code != "InputStreamReadError" {
893
return nil, err
894
}
895
896
// 获取断点状态
897
fullUrl := API_URL + "/getUploadFileStatus.action"
898
if y.isFamily() {
899
fullUrl = API_URL + "/family/file/getFamilyFileStatus.action"
900
}
901
_, err = y.get(fullUrl, func(req *resty.Request) {
902
req.SetContext(ctx).SetQueryParams(map[string]string{
903
"uploadFileId": fmt.Sprint(status.UploadFileId),
904
"resumePolicy": "1",
905
})
906
if isFamily {
907
req.SetQueryParam("familyId", fmt.Sprint(y.FamilyID))
908
}
909
}, &status, isFamily)
910
if err != nil {
911
return nil, err
912
}
913
if _, err := tempFile.Seek(status.GetSize(), io.SeekStart); err != nil {
914
return nil, err
915
}
916
up(float64(status.GetSize()) / float64(file.GetSize()) * 100)
917
}
918
919
return y.OldUploadCommit(ctx, status.FileCommitUrl, status.UploadFileId, isFamily, overwrite)
920
}
921
922
// 创建上传会话
923
func (y *Cloud189PC) OldUploadCreate(ctx context.Context, parentID string, fileMd5, fileName, fileSize string, isFamily bool) (*CreateUploadFileResp, error) {
924
var uploadInfo CreateUploadFileResp
925
926
fullUrl := API_URL + "/createUploadFile.action"
927
if isFamily {
928
fullUrl = API_URL + "/family/file/createFamilyFile.action"
929
}
930
_, err := y.post(fullUrl, func(req *resty.Request) {
931
req.SetContext(ctx)
932
if isFamily {
933
req.SetQueryParams(map[string]string{
934
"familyId": y.FamilyID,
935
"parentId": parentID,
936
"fileMd5": fileMd5,
937
"fileName": fileName,
938
"fileSize": fileSize,
939
"resumePolicy": "1",
940
})
941
} else {
942
req.SetFormData(map[string]string{
943
"parentFolderId": parentID,
944
"fileName": fileName,
945
"size": fileSize,
946
"md5": fileMd5,
947
"opertype": "3",
948
"flag": "1",
949
"resumePolicy": "1",
950
"isLog": "0",
951
})
952
}
953
}, &uploadInfo, isFamily)
954
955
if err != nil {
956
return nil, err
957
}
958
return &uploadInfo, nil
959
}
960
961
// 提交上传文件
962
func (y *Cloud189PC) OldUploadCommit(ctx context.Context, fileCommitUrl string, uploadFileID int64, isFamily bool, overwrite bool) (model.Obj, error) {
963
var resp OldCommitUploadFileResp
964
_, err := y.post(fileCommitUrl, func(req *resty.Request) {
965
req.SetContext(ctx)
966
if isFamily {
967
req.SetHeaders(map[string]string{
968
"ResumePolicy": "1",
969
"UploadFileId": fmt.Sprint(uploadFileID),
970
"FamilyId": fmt.Sprint(y.FamilyID),
971
})
972
} else {
973
req.SetFormData(map[string]string{
974
"opertype": IF(overwrite, "3", "1"),
975
"resumePolicy": "1",
976
"uploadFileId": fmt.Sprint(uploadFileID),
977
"isLog": "0",
978
})
979
}
980
}, &resp, isFamily)
981
if err != nil {
982
return nil, err
983
}
984
return resp.toFile(), nil
985
}
986
987
func (y *Cloud189PC) isFamily() bool {
988
return y.Type == "family"
989
}
990
991
func (y *Cloud189PC) isLogin() bool {
992
if y.tokenInfo == nil {
993
return false
994
}
995
_, err := y.get(API_URL+"/getUserInfo.action", nil, nil)
996
return err == nil
997
}
998
999
// 创建家庭云中转文件夹
1000
func (y *Cloud189PC) createFamilyTransferFolder() error {
1001
var rootFolder Cloud189Folder
1002
_, err := y.post(API_URL+"/family/file/createFolder.action", func(req *resty.Request) {
1003
req.SetQueryParams(map[string]string{
1004
"folderName": "FamilyTransferFolder",
1005
"familyId": y.FamilyID,
1006
})
1007
}, &rootFolder, true)
1008
if err != nil {
1009
return err
1010
}
1011
y.familyTransferFolder = &rootFolder
1012
return nil
1013
}
1014
1015
// 清理中转文件夹
1016
func (y *Cloud189PC) cleanFamilyTransfer(ctx context.Context) error {
1017
transferFolderId := y.familyTransferFolder.GetID()
1018
for pageNum := 1; ; pageNum++ {
1019
resp, err := y.getFilesWithPage(ctx, transferFolderId, true, pageNum, 100, "lastOpTime", "asc")
1020
if err != nil {
1021
return err
1022
}
1023
// 获取完毕跳出
1024
if resp.FileListAO.Count == 0 {
1025
break
1026
}
1027
1028
var tasks []BatchTaskInfo
1029
for i := 0; i < len(resp.FileListAO.FolderList); i++ {
1030
folder := resp.FileListAO.FolderList[i]
1031
tasks = append(tasks, BatchTaskInfo{
1032
FileId: folder.GetID(),
1033
FileName: folder.GetName(),
1034
IsFolder: BoolToNumber(folder.IsDir()),
1035
})
1036
}
1037
for i := 0; i < len(resp.FileListAO.FileList); i++ {
1038
file := resp.FileListAO.FileList[i]
1039
tasks = append(tasks, BatchTaskInfo{
1040
FileId: file.GetID(),
1041
FileName: file.GetName(),
1042
IsFolder: BoolToNumber(file.IsDir()),
1043
})
1044
}
1045
1046
if len(tasks) > 0 {
1047
// 删除
1048
resp, err := y.CreateBatchTask("DELETE", y.FamilyID, "", nil, tasks...)
1049
if err != nil {
1050
return err
1051
}
1052
err = y.WaitBatchTask("DELETE", resp.TaskID, time.Second)
1053
if err != nil {
1054
return err
1055
}
1056
// 永久删除
1057
resp, err = y.CreateBatchTask("CLEAR_RECYCLE", y.FamilyID, "", nil, tasks...)
1058
if err != nil {
1059
return err
1060
}
1061
err = y.WaitBatchTask("CLEAR_RECYCLE", resp.TaskID, time.Second)
1062
return err
1063
}
1064
}
1065
return nil
1066
}
1067
1068
// 获取家庭云所有用户信息
1069
func (y *Cloud189PC) getFamilyInfoList() ([]FamilyInfoResp, error) {
1070
var resp FamilyInfoListResp
1071
_, err := y.get(API_URL+"/family/manage/getFamilyList.action", nil, &resp, true)
1072
if err != nil {
1073
return nil, err
1074
}
1075
return resp.FamilyInfoResp, nil
1076
}
1077
1078
// 抽取家庭云ID
1079
func (y *Cloud189PC) getFamilyID() (string, error) {
1080
infos, err := y.getFamilyInfoList()
1081
if err != nil {
1082
return "", err
1083
}
1084
if len(infos) == 0 {
1085
return "", fmt.Errorf("cannot get automatically,please input family_id")
1086
}
1087
for _, info := range infos {
1088
if strings.Contains(y.getTokenInfo().LoginName, info.RemarkName) {
1089
return fmt.Sprint(info.FamilyID), nil
1090
}
1091
}
1092
return fmt.Sprint(infos[0].FamilyID), nil
1093
}
1094
1095
// 保存家庭云中的文件到个人云
1096
func (y *Cloud189PC) SaveFamilyFileToPersonCloud(ctx context.Context, familyId string, srcObj, dstDir model.Obj, overwrite bool) error {
1097
// _, err := y.post(API_URL+"/family/file/saveFileToMember.action", func(req *resty.Request) {
1098
// req.SetQueryParams(map[string]string{
1099
// "channelId": "home",
1100
// "familyId": familyId,
1101
// "destParentId": destParentId,
1102
// "fileIdList": familyFileId,
1103
// })
1104
// }, nil)
1105
// return err
1106
1107
task := BatchTaskInfo{
1108
FileId: srcObj.GetID(),
1109
FileName: srcObj.GetName(),
1110
IsFolder: BoolToNumber(srcObj.IsDir()),
1111
}
1112
resp, err := y.CreateBatchTask("COPY", familyId, dstDir.GetID(), map[string]string{
1113
"groupId": "null",
1114
"copyType": "2",
1115
"shareId": "null",
1116
}, task)
1117
if err != nil {
1118
return err
1119
}
1120
1121
for {
1122
state, err := y.CheckBatchTask("COPY", resp.TaskID)
1123
if err != nil {
1124
return err
1125
}
1126
switch state.TaskStatus {
1127
case 2:
1128
task.DealWay = IF(overwrite, 3, 2)
1129
// 冲突时覆盖文件
1130
if err := y.ManageBatchTask("COPY", resp.TaskID, dstDir.GetID(), task); err != nil {
1131
return err
1132
}
1133
case 4:
1134
return nil
1135
}
1136
time.Sleep(time.Millisecond * 400)
1137
}
1138
}
1139
1140
// 永久删除文件
1141
func (y *Cloud189PC) Delete(ctx context.Context, familyId string, srcObj model.Obj) error {
1142
task := BatchTaskInfo{
1143
FileId: srcObj.GetID(),
1144
FileName: srcObj.GetName(),
1145
IsFolder: BoolToNumber(srcObj.IsDir()),
1146
}
1147
// 删除源文件
1148
resp, err := y.CreateBatchTask("DELETE", familyId, "", nil, task)
1149
if err != nil {
1150
return err
1151
}
1152
err = y.WaitBatchTask("DELETE", resp.TaskID, time.Second)
1153
if err != nil {
1154
return err
1155
}
1156
// 清除回收站
1157
resp, err = y.CreateBatchTask("CLEAR_RECYCLE", familyId, "", nil, task)
1158
if err != nil {
1159
return err
1160
}
1161
err = y.WaitBatchTask("CLEAR_RECYCLE", resp.TaskID, time.Second)
1162
if err != nil {
1163
return err
1164
}
1165
return nil
1166
}
1167
1168
func (y *Cloud189PC) CreateBatchTask(aType string, familyID string, targetFolderId string, other map[string]string, taskInfos ...BatchTaskInfo) (*CreateBatchTaskResp, error) {
1169
var resp CreateBatchTaskResp
1170
_, err := y.post(API_URL+"/batch/createBatchTask.action", func(req *resty.Request) {
1171
req.SetFormData(map[string]string{
1172
"type": aType,
1173
"taskInfos": MustString(utils.Json.MarshalToString(taskInfos)),
1174
})
1175
if targetFolderId != "" {
1176
req.SetFormData(map[string]string{"targetFolderId": targetFolderId})
1177
}
1178
if familyID != "" {
1179
req.SetFormData(map[string]string{"familyId": familyID})
1180
}
1181
req.SetFormData(other)
1182
}, &resp, familyID != "")
1183
if err != nil {
1184
return nil, err
1185
}
1186
return &resp, nil
1187
}
1188
1189
// 检测任务状态
1190
func (y *Cloud189PC) CheckBatchTask(aType string, taskID string) (*BatchTaskStateResp, error) {
1191
var resp BatchTaskStateResp
1192
_, err := y.post(API_URL+"/batch/checkBatchTask.action", func(req *resty.Request) {
1193
req.SetFormData(map[string]string{
1194
"type": aType,
1195
"taskId": taskID,
1196
})
1197
}, &resp)
1198
if err != nil {
1199
return nil, err
1200
}
1201
return &resp, nil
1202
}
1203
1204
// 获取冲突的任务信息
1205
func (y *Cloud189PC) GetConflictTaskInfo(aType string, taskID string) (*BatchTaskConflictTaskInfoResp, error) {
1206
var resp BatchTaskConflictTaskInfoResp
1207
_, err := y.post(API_URL+"/batch/getConflictTaskInfo.action", func(req *resty.Request) {
1208
req.SetFormData(map[string]string{
1209
"type": aType,
1210
"taskId": taskID,
1211
})
1212
}, &resp)
1213
if err != nil {
1214
return nil, err
1215
}
1216
return &resp, nil
1217
}
1218
1219
// 处理冲突
1220
func (y *Cloud189PC) ManageBatchTask(aType string, taskID string, targetFolderId string, taskInfos ...BatchTaskInfo) error {
1221
_, err := y.post(API_URL+"/batch/manageBatchTask.action", func(req *resty.Request) {
1222
req.SetFormData(map[string]string{
1223
"targetFolderId": targetFolderId,
1224
"type": aType,
1225
"taskId": taskID,
1226
"taskInfos": MustString(utils.Json.MarshalToString(taskInfos)),
1227
})
1228
}, nil)
1229
return err
1230
}
1231
1232
var ErrIsConflict = errors.New("there is a conflict with the target object")
1233
1234
// 等待任务完成
1235
func (y *Cloud189PC) WaitBatchTask(aType string, taskID string, t time.Duration) error {
1236
for {
1237
state, err := y.CheckBatchTask(aType, taskID)
1238
if err != nil {
1239
return err
1240
}
1241
switch state.TaskStatus {
1242
case 2:
1243
return ErrIsConflict
1244
case 4:
1245
return nil
1246
}
1247
time.Sleep(t)
1248
}
1249
}
1250
1251
func (y *Cloud189PC) getTokenInfo() *AppSessionResp {
1252
if y.ref != nil {
1253
return y.ref.getTokenInfo()
1254
}
1255
return y.tokenInfo
1256
}
1257
1258
func (y *Cloud189PC) getClient() *resty.Client {
1259
if y.ref != nil {
1260
return y.ref.getClient()
1261
}
1262
return y.client
1263
}
1264
1265