Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/halalcloud/driver.go
1986 views
1
package halalcloud
2
3
import (
4
"context"
5
"crypto/sha1"
6
"fmt"
7
"io"
8
"net/url"
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/model"
16
"github.com/alist-org/alist/v3/internal/op"
17
"github.com/alist-org/alist/v3/pkg/http_range"
18
"github.com/aws/aws-sdk-go/aws"
19
"github.com/aws/aws-sdk-go/aws/credentials"
20
"github.com/aws/aws-sdk-go/aws/session"
21
"github.com/aws/aws-sdk-go/service/s3/s3manager"
22
"github.com/city404/v6-public-rpc-proto/go/v6/common"
23
pbPublicUser "github.com/city404/v6-public-rpc-proto/go/v6/user"
24
pubUserFile "github.com/city404/v6-public-rpc-proto/go/v6/userfile"
25
"github.com/rclone/rclone/lib/readers"
26
"github.com/zzzhr1990/go-common-entity/userfile"
27
)
28
29
type HalalCloud struct {
30
*HalalCommon
31
model.Storage
32
Addition
33
34
uploadThread int
35
}
36
37
func (d *HalalCloud) Config() driver.Config {
38
return config
39
}
40
41
func (d *HalalCloud) GetAddition() driver.Additional {
42
return &d.Addition
43
}
44
45
func (d *HalalCloud) Init(ctx context.Context) error {
46
d.uploadThread, _ = strconv.Atoi(d.UploadThread)
47
if d.uploadThread < 1 || d.uploadThread > 32 {
48
d.uploadThread, d.UploadThread = 3, "3"
49
}
50
51
if d.HalalCommon == nil {
52
d.HalalCommon = &HalalCommon{
53
Common: &Common{},
54
AuthService: &AuthService{
55
appID: func() string {
56
if d.Addition.AppID != "" {
57
return d.Addition.AppID
58
}
59
return AppID
60
}(),
61
appVersion: func() string {
62
if d.Addition.AppVersion != "" {
63
return d.Addition.AppVersion
64
}
65
return AppVersion
66
}(),
67
appSecret: func() string {
68
if d.Addition.AppSecret != "" {
69
return d.Addition.AppSecret
70
}
71
return AppSecret
72
}(),
73
tr: &TokenResp{
74
RefreshToken: d.Addition.RefreshToken,
75
},
76
},
77
UserInfo: &UserInfo{},
78
refreshTokenFunc: func(token string) error {
79
d.Addition.RefreshToken = token
80
op.MustSaveDriverStorage(d)
81
return nil
82
},
83
}
84
}
85
86
// 防止重复登录
87
if d.Addition.RefreshToken == "" || !d.IsLogin() {
88
as, err := d.NewAuthServiceWithOauth()
89
if err != nil {
90
d.GetStorage().SetStatus(fmt.Sprintf("%+v", err.Error()))
91
return err
92
}
93
d.HalalCommon.AuthService = as
94
d.SetTokenResp(as.tr)
95
op.MustSaveDriverStorage(d)
96
}
97
var err error
98
d.HalalCommon.serv, err = d.NewAuthService(d.Addition.RefreshToken)
99
if err != nil {
100
return err
101
}
102
103
return nil
104
}
105
106
func (d *HalalCloud) Drop(ctx context.Context) error {
107
return nil
108
}
109
110
func (d *HalalCloud) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
111
return d.getFiles(ctx, dir)
112
}
113
114
func (d *HalalCloud) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
115
return d.getLink(ctx, file, args)
116
}
117
118
func (d *HalalCloud) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
119
return d.makeDir(ctx, parentDir, dirName)
120
}
121
122
func (d *HalalCloud) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
123
return d.move(ctx, srcObj, dstDir)
124
}
125
126
func (d *HalalCloud) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
127
return d.rename(ctx, srcObj, newName)
128
}
129
130
func (d *HalalCloud) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
131
return d.copy(ctx, srcObj, dstDir)
132
}
133
134
func (d *HalalCloud) Remove(ctx context.Context, obj model.Obj) error {
135
return d.remove(ctx, obj)
136
}
137
138
func (d *HalalCloud) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
139
return d.put(ctx, dstDir, stream, up)
140
}
141
142
func (d *HalalCloud) IsLogin() bool {
143
if d.AuthService.tr == nil {
144
return false
145
}
146
serv, err := d.NewAuthService(d.Addition.RefreshToken)
147
if err != nil {
148
return false
149
}
150
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
151
defer cancel()
152
result, err := pbPublicUser.NewPubUserClient(serv.GetGrpcConnection()).Get(ctx, &pbPublicUser.User{
153
Identity: "",
154
})
155
if result == nil || err != nil {
156
return false
157
}
158
d.UserInfo.Identity = result.Identity
159
d.UserInfo.CreateTs = result.CreateTs
160
d.UserInfo.Name = result.Name
161
d.UserInfo.UpdateTs = result.UpdateTs
162
return true
163
}
164
165
type HalalCommon struct {
166
*Common
167
*AuthService // 登录信息
168
*UserInfo // 用户信息
169
refreshTokenFunc func(token string) error
170
serv *AuthService
171
}
172
173
func (d *HalalCloud) SetTokenResp(tr *TokenResp) {
174
d.Addition.RefreshToken = tr.RefreshToken
175
}
176
177
func (d *HalalCloud) getFiles(ctx context.Context, dir model.Obj) ([]model.Obj, error) {
178
179
files := make([]model.Obj, 0)
180
limit := int64(100)
181
token := ""
182
client := pubUserFile.NewPubUserFileClient(d.HalalCommon.serv.GetGrpcConnection())
183
184
opDir := d.GetCurrentDir(dir)
185
186
for {
187
result, err := client.List(ctx, &pubUserFile.FileListRequest{
188
Parent: &pubUserFile.File{Path: opDir},
189
ListInfo: &common.ScanListRequest{
190
Limit: limit,
191
Token: token,
192
},
193
})
194
if err != nil {
195
return nil, err
196
}
197
198
for i := 0; len(result.Files) > i; i++ {
199
files = append(files, (*Files)(result.Files[i]))
200
}
201
202
if result.ListInfo == nil || result.ListInfo.Token == "" {
203
break
204
}
205
token = result.ListInfo.Token
206
207
}
208
return files, nil
209
}
210
211
func (d *HalalCloud) getLink(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
212
213
client := pubUserFile.NewPubUserFileClient(d.HalalCommon.serv.GetGrpcConnection())
214
ctx1, cancelFunc := context.WithCancel(context.Background())
215
defer cancelFunc()
216
217
result, err := client.ParseFileSlice(ctx1, (*pubUserFile.File)(file.(*Files)))
218
if err != nil {
219
return nil, err
220
}
221
fileAddrs := []*pubUserFile.SliceDownloadInfo{}
222
var addressDuration int64
223
224
nodesNumber := len(result.RawNodes)
225
nodesIndex := nodesNumber - 1
226
startIndex, endIndex := 0, nodesIndex
227
for nodesIndex >= 0 {
228
if nodesIndex >= 200 {
229
endIndex = 200
230
} else {
231
endIndex = nodesNumber
232
}
233
for ; endIndex <= nodesNumber; endIndex += 200 {
234
if endIndex == 0 {
235
endIndex = 1
236
}
237
sliceAddress, err := client.GetSliceDownloadAddress(ctx, &pubUserFile.SliceDownloadAddressRequest{
238
Identity: result.RawNodes[startIndex:endIndex],
239
Version: 1,
240
})
241
if err != nil {
242
return nil, err
243
}
244
addressDuration = sliceAddress.ExpireAt
245
fileAddrs = append(fileAddrs, sliceAddress.Addresses...)
246
startIndex = endIndex
247
nodesIndex -= 200
248
}
249
250
}
251
252
size := result.FileSize
253
chunks := getChunkSizes(result.Sizes)
254
resultRangeReader := func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
255
length := httpRange.Length
256
if httpRange.Length >= 0 && httpRange.Start+httpRange.Length >= size {
257
length = -1
258
}
259
if err != nil {
260
return nil, fmt.Errorf("open download file failed: %w", err)
261
}
262
oo := &openObject{
263
ctx: ctx,
264
d: fileAddrs,
265
chunk: &[]byte{},
266
chunks: &chunks,
267
skip: httpRange.Start,
268
sha: result.Sha1,
269
shaTemp: sha1.New(),
270
}
271
272
return readers.NewLimitedReadCloser(oo, length), nil
273
}
274
275
var duration time.Duration
276
if addressDuration != 0 {
277
duration = time.Until(time.UnixMilli(addressDuration))
278
} else {
279
duration = time.Until(time.Now().Add(time.Hour))
280
}
281
282
resultRangeReadCloser := &model.RangeReadCloser{RangeReader: resultRangeReader}
283
return &model.Link{
284
RangeReadCloser: resultRangeReadCloser,
285
Expiration: &duration,
286
}, nil
287
}
288
289
func (d *HalalCloud) makeDir(ctx context.Context, dir model.Obj, name string) (model.Obj, error) {
290
newDir := userfile.NewFormattedPath(d.GetCurrentOpDir(dir, []string{name}, 0)).GetPath()
291
_, err := pubUserFile.NewPubUserFileClient(d.HalalCommon.serv.GetGrpcConnection()).Create(ctx, &pubUserFile.File{
292
Path: newDir,
293
})
294
return nil, err
295
}
296
297
func (d *HalalCloud) move(ctx context.Context, obj model.Obj, dir model.Obj) (model.Obj, error) {
298
oldDir := userfile.NewFormattedPath(d.GetCurrentDir(obj)).GetPath()
299
newDir := userfile.NewFormattedPath(d.GetCurrentDir(dir)).GetPath()
300
_, err := pubUserFile.NewPubUserFileClient(d.HalalCommon.serv.GetGrpcConnection()).Move(ctx, &pubUserFile.BatchOperationRequest{
301
Source: []*pubUserFile.File{
302
{
303
Identity: obj.GetID(),
304
Path: oldDir,
305
},
306
},
307
Dest: &pubUserFile.File{
308
Identity: dir.GetID(),
309
Path: newDir,
310
},
311
})
312
return nil, err
313
}
314
315
func (d *HalalCloud) rename(ctx context.Context, obj model.Obj, name string) (model.Obj, error) {
316
id := obj.GetID()
317
newPath := userfile.NewFormattedPath(d.GetCurrentOpDir(obj, []string{name}, 0)).GetPath()
318
319
_, err := pubUserFile.NewPubUserFileClient(d.HalalCommon.serv.GetGrpcConnection()).Rename(ctx, &pubUserFile.File{
320
Path: newPath,
321
Identity: id,
322
Name: name,
323
})
324
return nil, err
325
}
326
327
func (d *HalalCloud) copy(ctx context.Context, obj model.Obj, dir model.Obj) (model.Obj, error) {
328
id := obj.GetID()
329
sourcePath := userfile.NewFormattedPath(d.GetCurrentDir(obj)).GetPath()
330
if len(id) > 0 {
331
sourcePath = ""
332
}
333
dest := &pubUserFile.File{
334
Identity: dir.GetID(),
335
Path: userfile.NewFormattedPath(d.GetCurrentDir(dir)).GetPath(),
336
}
337
_, err := pubUserFile.NewPubUserFileClient(d.HalalCommon.serv.GetGrpcConnection()).Copy(ctx, &pubUserFile.BatchOperationRequest{
338
Source: []*pubUserFile.File{
339
{
340
Path: sourcePath,
341
Identity: id,
342
},
343
},
344
Dest: dest,
345
})
346
return nil, err
347
}
348
349
func (d *HalalCloud) remove(ctx context.Context, obj model.Obj) error {
350
id := obj.GetID()
351
newPath := userfile.NewFormattedPath(d.GetCurrentDir(obj)).GetPath()
352
//if len(id) > 0 {
353
// newPath = ""
354
//}
355
_, err := pubUserFile.NewPubUserFileClient(d.HalalCommon.serv.GetGrpcConnection()).Delete(ctx, &pubUserFile.BatchOperationRequest{
356
Source: []*pubUserFile.File{
357
{
358
Path: newPath,
359
Identity: id,
360
},
361
},
362
})
363
return err
364
}
365
366
func (d *HalalCloud) put(ctx context.Context, dstDir model.Obj, fileStream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
367
368
newDir := path.Join(dstDir.GetPath(), fileStream.GetName())
369
370
result, err := pubUserFile.NewPubUserFileClient(d.HalalCommon.serv.GetGrpcConnection()).CreateUploadToken(ctx, &pubUserFile.File{
371
Path: newDir,
372
})
373
if err != nil {
374
return nil, err
375
}
376
u, _ := url.Parse(result.Endpoint)
377
u.Host = "s3." + u.Host
378
result.Endpoint = u.String()
379
s, err := session.NewSession(&aws.Config{
380
HTTPClient: base.HttpClient,
381
Credentials: credentials.NewStaticCredentials(result.AccessKey, result.SecretKey, result.Token),
382
Region: aws.String(result.Region),
383
Endpoint: aws.String(result.Endpoint),
384
S3ForcePathStyle: aws.Bool(true),
385
})
386
if err != nil {
387
return nil, err
388
}
389
uploader := s3manager.NewUploader(s, func(u *s3manager.Uploader) {
390
u.Concurrency = d.uploadThread
391
})
392
if fileStream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
393
uploader.PartSize = fileStream.GetSize() / (s3manager.MaxUploadParts - 1)
394
}
395
reader := driver.NewLimitedUploadStream(ctx, fileStream)
396
_, err = uploader.UploadWithContext(ctx, &s3manager.UploadInput{
397
Bucket: aws.String(result.Bucket),
398
Key: aws.String(result.Key),
399
Body: io.TeeReader(reader, driver.NewProgress(fileStream.GetSize(), up)),
400
})
401
return nil, err
402
403
}
404
405
var _ driver.Driver = (*HalalCloud)(nil)
406
407