Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/alist_v3/driver.go
1986 views
1
package alist_v3
2
3
import (
4
"context"
5
"fmt"
6
"io"
7
"net/http"
8
"net/url"
9
"path"
10
"strings"
11
12
"github.com/alist-org/alist/v3/drivers/base"
13
"github.com/alist-org/alist/v3/internal/conf"
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
"github.com/alist-org/alist/v3/pkg/utils"
18
"github.com/alist-org/alist/v3/server/common"
19
"github.com/go-resty/resty/v2"
20
log "github.com/sirupsen/logrus"
21
)
22
23
type AListV3 struct {
24
model.Storage
25
Addition
26
}
27
28
func (d *AListV3) Config() driver.Config {
29
return config
30
}
31
32
func (d *AListV3) GetAddition() driver.Additional {
33
return &d.Addition
34
}
35
36
func (d *AListV3) Init(ctx context.Context) error {
37
d.Addition.Address = strings.TrimSuffix(d.Addition.Address, "/")
38
var resp common.Resp[MeResp]
39
_, _, err := d.request("/me", http.MethodGet, func(req *resty.Request) {
40
req.SetResult(&resp)
41
})
42
if err != nil {
43
return err
44
}
45
// if the username is not empty and the username is not the same as the current username, then login again
46
if d.Username != resp.Data.Username {
47
err = d.login()
48
if err != nil {
49
return err
50
}
51
}
52
// re-get the user info
53
_, _, err = d.request("/me", http.MethodGet, func(req *resty.Request) {
54
req.SetResult(&resp)
55
})
56
if err != nil {
57
return err
58
}
59
if utils.SliceContains(resp.Data.Role, model.GUEST) {
60
u := d.Address + "/api/public/settings"
61
res, err := base.RestyClient.R().Get(u)
62
if err != nil {
63
return err
64
}
65
allowMounted := utils.Json.Get(res.Body(), "data", conf.AllowMounted).ToString() == "true"
66
if !allowMounted {
67
return fmt.Errorf("the site does not allow mounted")
68
}
69
}
70
return err
71
}
72
73
func (d *AListV3) Drop(ctx context.Context) error {
74
return nil
75
}
76
77
func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
78
var resp common.Resp[FsListResp]
79
_, _, err := d.request("/fs/list", http.MethodPost, func(req *resty.Request) {
80
req.SetResult(&resp).SetBody(ListReq{
81
PageReq: model.PageReq{
82
Page: 1,
83
PerPage: 0,
84
},
85
Path: dir.GetPath(),
86
Password: d.MetaPassword,
87
Refresh: false,
88
})
89
})
90
if err != nil {
91
return nil, err
92
}
93
var files []model.Obj
94
for _, f := range resp.Data.Content {
95
file := model.ObjThumb{
96
Object: model.Object{
97
Name: f.Name,
98
Modified: f.Modified,
99
Ctime: f.Created,
100
Size: f.Size,
101
IsFolder: f.IsDir,
102
HashInfo: utils.FromString(f.HashInfo),
103
},
104
Thumbnail: model.Thumbnail{Thumbnail: f.Thumb},
105
}
106
files = append(files, &file)
107
}
108
return files, nil
109
}
110
111
func (d *AListV3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
112
var resp common.Resp[FsGetResp]
113
// if PassUAToUpsteam is true, then pass the user-agent to the upstream
114
userAgent := base.UserAgent
115
if d.PassUAToUpsteam {
116
userAgent = args.Header.Get("user-agent")
117
if userAgent == "" {
118
userAgent = base.UserAgent
119
}
120
}
121
_, _, err := d.request("/fs/get", http.MethodPost, func(req *resty.Request) {
122
req.SetResult(&resp).SetBody(FsGetReq{
123
Path: file.GetPath(),
124
Password: d.MetaPassword,
125
}).SetHeader("user-agent", userAgent)
126
})
127
if err != nil {
128
return nil, err
129
}
130
return &model.Link{
131
URL: resp.Data.RawURL,
132
}, nil
133
}
134
135
func (d *AListV3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
136
_, _, err := d.request("/fs/mkdir", http.MethodPost, func(req *resty.Request) {
137
req.SetBody(MkdirOrLinkReq{
138
Path: path.Join(parentDir.GetPath(), dirName),
139
})
140
})
141
return err
142
}
143
144
func (d *AListV3) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
145
_, _, err := d.request("/fs/move", http.MethodPost, func(req *resty.Request) {
146
req.SetBody(MoveCopyReq{
147
SrcDir: path.Dir(srcObj.GetPath()),
148
DstDir: dstDir.GetPath(),
149
Names: []string{srcObj.GetName()},
150
})
151
})
152
return err
153
}
154
155
func (d *AListV3) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
156
_, _, err := d.request("/fs/rename", http.MethodPost, func(req *resty.Request) {
157
req.SetBody(RenameReq{
158
Path: srcObj.GetPath(),
159
Name: newName,
160
})
161
})
162
return err
163
}
164
165
func (d *AListV3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
166
_, _, err := d.request("/fs/copy", http.MethodPost, func(req *resty.Request) {
167
req.SetBody(MoveCopyReq{
168
SrcDir: path.Dir(srcObj.GetPath()),
169
DstDir: dstDir.GetPath(),
170
Names: []string{srcObj.GetName()},
171
})
172
})
173
return err
174
}
175
176
func (d *AListV3) Remove(ctx context.Context, obj model.Obj) error {
177
_, _, err := d.request("/fs/remove", http.MethodPost, func(req *resty.Request) {
178
req.SetBody(RemoveReq{
179
Dir: path.Dir(obj.GetPath()),
180
Names: []string{obj.GetName()},
181
})
182
})
183
return err
184
}
185
186
func (d *AListV3) Put(ctx context.Context, dstDir model.Obj, s model.FileStreamer, up driver.UpdateProgress) error {
187
reader := driver.NewLimitedUploadStream(ctx, &driver.ReaderUpdatingProgress{
188
Reader: s,
189
UpdateProgress: up,
190
})
191
req, err := http.NewRequestWithContext(ctx, http.MethodPut, d.Address+"/api/fs/put", reader)
192
if err != nil {
193
return err
194
}
195
req.Header.Set("Authorization", d.Token)
196
req.Header.Set("File-Path", path.Join(dstDir.GetPath(), s.GetName()))
197
req.Header.Set("Password", d.MetaPassword)
198
if md5 := s.GetHash().GetHash(utils.MD5); len(md5) > 0 {
199
req.Header.Set("X-File-Md5", md5)
200
}
201
if sha1 := s.GetHash().GetHash(utils.SHA1); len(sha1) > 0 {
202
req.Header.Set("X-File-Sha1", sha1)
203
}
204
if sha256 := s.GetHash().GetHash(utils.SHA256); len(sha256) > 0 {
205
req.Header.Set("X-File-Sha256", sha256)
206
}
207
208
req.ContentLength = s.GetSize()
209
// client := base.NewHttpClient()
210
// client.Timeout = time.Hour * 6
211
res, err := base.HttpClient.Do(req)
212
if err != nil {
213
return err
214
}
215
216
bytes, err := io.ReadAll(res.Body)
217
if err != nil {
218
return err
219
}
220
log.Debugf("[alist_v3] response body: %s", string(bytes))
221
if res.StatusCode >= 400 {
222
return fmt.Errorf("request failed, status: %s", res.Status)
223
}
224
code := utils.Json.Get(bytes, "code").ToInt()
225
if code != 200 {
226
if code == 401 || code == 403 {
227
err = d.login()
228
if err != nil {
229
return err
230
}
231
}
232
return fmt.Errorf("request failed,code: %d, message: %s", code, utils.Json.Get(bytes, "message").ToString())
233
}
234
return nil
235
}
236
237
func (d *AListV3) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
238
if !d.ForwardArchiveReq {
239
return nil, errs.NotImplement
240
}
241
var resp common.Resp[ArchiveMetaResp]
242
_, code, err := d.request("/fs/archive/meta", http.MethodPost, func(req *resty.Request) {
243
req.SetResult(&resp).SetBody(ArchiveMetaReq{
244
ArchivePass: args.Password,
245
Password: d.MetaPassword,
246
Path: obj.GetPath(),
247
Refresh: false,
248
})
249
})
250
if code == 202 {
251
return nil, errs.WrongArchivePassword
252
}
253
if err != nil {
254
return nil, err
255
}
256
var tree []model.ObjTree
257
if resp.Data.Content != nil {
258
tree = make([]model.ObjTree, 0, len(resp.Data.Content))
259
for _, content := range resp.Data.Content {
260
tree = append(tree, &content)
261
}
262
}
263
return &model.ArchiveMetaInfo{
264
Comment: resp.Data.Comment,
265
Encrypted: resp.Data.Encrypted,
266
Tree: tree,
267
}, nil
268
}
269
270
func (d *AListV3) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) {
271
if !d.ForwardArchiveReq {
272
return nil, errs.NotImplement
273
}
274
var resp common.Resp[ArchiveListResp]
275
_, code, err := d.request("/fs/archive/list", http.MethodPost, func(req *resty.Request) {
276
req.SetResult(&resp).SetBody(ArchiveListReq{
277
ArchiveMetaReq: ArchiveMetaReq{
278
ArchivePass: args.Password,
279
Password: d.MetaPassword,
280
Path: obj.GetPath(),
281
Refresh: false,
282
},
283
PageReq: model.PageReq{
284
Page: 1,
285
PerPage: 0,
286
},
287
InnerPath: args.InnerPath,
288
})
289
})
290
if code == 202 {
291
return nil, errs.WrongArchivePassword
292
}
293
if err != nil {
294
return nil, err
295
}
296
var files []model.Obj
297
for _, f := range resp.Data.Content {
298
file := model.ObjThumb{
299
Object: model.Object{
300
Name: f.Name,
301
Modified: f.Modified,
302
Ctime: f.Created,
303
Size: f.Size,
304
IsFolder: f.IsDir,
305
HashInfo: utils.FromString(f.HashInfo),
306
},
307
Thumbnail: model.Thumbnail{Thumbnail: f.Thumb},
308
}
309
files = append(files, &file)
310
}
311
return files, nil
312
}
313
314
func (d *AListV3) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) {
315
if !d.ForwardArchiveReq {
316
return nil, errs.NotSupport
317
}
318
var resp common.Resp[ArchiveMetaResp]
319
_, _, err := d.request("/fs/archive/meta", http.MethodPost, func(req *resty.Request) {
320
req.SetResult(&resp).SetBody(ArchiveMetaReq{
321
ArchivePass: args.Password,
322
Password: d.MetaPassword,
323
Path: obj.GetPath(),
324
Refresh: false,
325
})
326
})
327
if err != nil {
328
return nil, err
329
}
330
return &model.Link{
331
URL: fmt.Sprintf("%s?inner=%s&pass=%s&sign=%s",
332
resp.Data.RawURL,
333
utils.EncodePath(args.InnerPath, true),
334
url.QueryEscape(args.Password),
335
resp.Data.Sign),
336
}, nil
337
}
338
339
func (d *AListV3) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) error {
340
if !d.ForwardArchiveReq {
341
return errs.NotImplement
342
}
343
dir, name := path.Split(srcObj.GetPath())
344
_, _, err := d.request("/fs/archive/decompress", http.MethodPost, func(req *resty.Request) {
345
req.SetBody(DecompressReq{
346
ArchivePass: args.Password,
347
CacheFull: args.CacheFull,
348
DstDir: dstDir.GetPath(),
349
InnerPath: args.InnerPath,
350
Name: []string{name},
351
PutIntoNewDir: args.PutIntoNewDir,
352
SrcDir: dir,
353
})
354
})
355
return err
356
}
357
358
//func (d *AList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
359
// return nil, errs.NotSupport
360
//}
361
362
var _ driver.Driver = (*AListV3)(nil)
363
364