Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/dropbox/driver.go
1987 views
1
package dropbox
2
3
import (
4
"context"
5
"fmt"
6
"io"
7
"math"
8
"net/http"
9
"time"
10
11
"github.com/alist-org/alist/v3/drivers/base"
12
"github.com/alist-org/alist/v3/internal/driver"
13
"github.com/alist-org/alist/v3/internal/model"
14
"github.com/alist-org/alist/v3/pkg/utils"
15
"github.com/go-resty/resty/v2"
16
log "github.com/sirupsen/logrus"
17
)
18
19
type Dropbox struct {
20
model.Storage
21
Addition
22
base string
23
contentBase string
24
}
25
26
func (d *Dropbox) Config() driver.Config {
27
return config
28
}
29
30
func (d *Dropbox) GetAddition() driver.Additional {
31
return &d.Addition
32
}
33
34
func (d *Dropbox) Init(ctx context.Context) error {
35
query := "foo"
36
res, err := d.request("/2/check/user", http.MethodPost, func(req *resty.Request) {
37
req.SetBody(base.Json{
38
"query": query,
39
})
40
})
41
if err != nil {
42
return err
43
}
44
result := utils.Json.Get(res, "result").ToString()
45
if result != query {
46
return fmt.Errorf("failed to check user: %s", string(res))
47
}
48
d.RootNamespaceId, err = d.GetRootNamespaceId(ctx)
49
50
return err
51
}
52
53
func (d *Dropbox) GetRootNamespaceId(ctx context.Context) (string, error) {
54
res, err := d.request("/2/users/get_current_account", http.MethodPost, func(req *resty.Request) {
55
req.SetBody(nil)
56
})
57
if err != nil {
58
return "", err
59
}
60
var currentAccountResp CurrentAccountResp
61
err = utils.Json.Unmarshal(res, &currentAccountResp)
62
if err != nil {
63
return "", err
64
}
65
rootNamespaceId := currentAccountResp.RootInfo.RootNamespaceId
66
return rootNamespaceId, nil
67
}
68
69
func (d *Dropbox) Drop(ctx context.Context) error {
70
return nil
71
}
72
73
func (d *Dropbox) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
74
files, err := d.getFiles(ctx, dir.GetPath())
75
if err != nil {
76
return nil, err
77
}
78
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
79
return fileToObj(src), nil
80
})
81
}
82
83
func (d *Dropbox) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
84
res, err := d.request("/2/files/get_temporary_link", http.MethodPost, func(req *resty.Request) {
85
req.SetContext(ctx).SetBody(base.Json{
86
"path": file.GetPath(),
87
})
88
})
89
if err != nil {
90
return nil, err
91
}
92
url := utils.Json.Get(res, "link").ToString()
93
exp := time.Hour
94
return &model.Link{
95
URL: url,
96
Expiration: &exp,
97
}, nil
98
}
99
100
func (d *Dropbox) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
101
_, err := d.request("/2/files/create_folder_v2", http.MethodPost, func(req *resty.Request) {
102
req.SetContext(ctx).SetBody(base.Json{
103
"autorename": false,
104
"path": parentDir.GetPath() + "/" + dirName,
105
})
106
})
107
return err
108
}
109
110
func (d *Dropbox) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
111
toPath := dstDir.GetPath() + "/" + srcObj.GetName()
112
113
_, err := d.request("/2/files/move_v2", http.MethodPost, func(req *resty.Request) {
114
req.SetContext(ctx).SetBody(base.Json{
115
"allow_ownership_transfer": false,
116
"allow_shared_folder": false,
117
"autorename": false,
118
"from_path": srcObj.GetID(),
119
"to_path": toPath,
120
})
121
})
122
return err
123
}
124
125
func (d *Dropbox) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
126
path := srcObj.GetPath()
127
fileName := srcObj.GetName()
128
toPath := path[:len(path)-len(fileName)] + newName
129
130
_, err := d.request("/2/files/move_v2", http.MethodPost, func(req *resty.Request) {
131
req.SetContext(ctx).SetBody(base.Json{
132
"allow_ownership_transfer": false,
133
"allow_shared_folder": false,
134
"autorename": false,
135
"from_path": srcObj.GetID(),
136
"to_path": toPath,
137
})
138
})
139
return err
140
}
141
142
func (d *Dropbox) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
143
toPath := dstDir.GetPath() + "/" + srcObj.GetName()
144
_, err := d.request("/2/files/copy_v2", http.MethodPost, func(req *resty.Request) {
145
req.SetContext(ctx).SetBody(base.Json{
146
"allow_ownership_transfer": false,
147
"allow_shared_folder": false,
148
"autorename": false,
149
"from_path": srcObj.GetID(),
150
"to_path": toPath,
151
})
152
})
153
return err
154
}
155
156
func (d *Dropbox) Remove(ctx context.Context, obj model.Obj) error {
157
uri := "/2/files/delete_v2"
158
_, err := d.request(uri, http.MethodPost, func(req *resty.Request) {
159
req.SetContext(ctx).SetBody(base.Json{
160
"path": obj.GetID(),
161
})
162
})
163
return err
164
}
165
166
func (d *Dropbox) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
167
// 1. start
168
sessionId, err := d.startUploadSession(ctx)
169
if err != nil {
170
return err
171
}
172
173
// 2.append
174
// A single request should not upload more than 150 MB, and each call must be multiple of 4MB (except for last call)
175
const PartSize = 20971520
176
count := 1
177
if stream.GetSize() > PartSize {
178
count = int(math.Ceil(float64(stream.GetSize()) / float64(PartSize)))
179
}
180
offset := int64(0)
181
182
for i := 0; i < count; i++ {
183
if utils.IsCanceled(ctx) {
184
return ctx.Err()
185
}
186
187
start := i * PartSize
188
byteSize := stream.GetSize() - int64(start)
189
if byteSize > PartSize {
190
byteSize = PartSize
191
}
192
193
url := d.contentBase + "/2/files/upload_session/append_v2"
194
reader := driver.NewLimitedUploadStream(ctx, io.LimitReader(stream, PartSize))
195
req, err := http.NewRequest(http.MethodPost, url, reader)
196
if err != nil {
197
log.Errorf("failed to update file when append to upload session, err: %+v", err)
198
return err
199
}
200
req = req.WithContext(ctx)
201
req.Header.Set("Content-Type", "application/octet-stream")
202
req.Header.Set("Authorization", "Bearer "+d.AccessToken)
203
204
args := UploadAppendArgs{
205
Close: false,
206
Cursor: UploadCursor{
207
Offset: offset,
208
SessionID: sessionId,
209
},
210
}
211
argsJson, err := utils.Json.MarshalToString(args)
212
if err != nil {
213
return err
214
}
215
req.Header.Set("Dropbox-API-Arg", argsJson)
216
217
res, err := base.HttpClient.Do(req)
218
if err != nil {
219
return err
220
}
221
_ = res.Body.Close()
222
up(float64(i+1) * 100 / float64(count))
223
offset += byteSize
224
}
225
// 3.finish
226
toPath := dstDir.GetPath() + "/" + stream.GetName()
227
err2 := d.finishUploadSession(ctx, toPath, offset, sessionId)
228
if err2 != nil {
229
return err2
230
}
231
232
return err
233
}
234
235
var _ driver.Driver = (*Dropbox)(nil)
236
237