Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/gofile/util.go
1987 views
1
package gofile
2
3
import (
4
"bytes"
5
"context"
6
"encoding/json"
7
"fmt"
8
"io"
9
"mime/multipart"
10
"net/http"
11
"path/filepath"
12
"strings"
13
"time"
14
15
"github.com/alist-org/alist/v3/drivers/base"
16
"github.com/alist-org/alist/v3/internal/driver"
17
"github.com/alist-org/alist/v3/internal/model"
18
log "github.com/sirupsen/logrus"
19
)
20
21
const (
22
baseAPI = "https://api.gofile.io"
23
uploadAPI = "https://upload.gofile.io"
24
)
25
26
func (d *Gofile) request(ctx context.Context, method, endpoint string, body io.Reader, headers map[string]string) (*http.Response, error) {
27
var url string
28
if strings.HasPrefix(endpoint, "http") {
29
url = endpoint
30
} else {
31
url = baseAPI + endpoint
32
}
33
34
req, err := http.NewRequestWithContext(ctx, method, url, body)
35
if err != nil {
36
return nil, err
37
}
38
39
req.Header.Set("Authorization", "Bearer "+d.APIToken)
40
req.Header.Set("User-Agent", "AList/3.0")
41
42
for k, v := range headers {
43
req.Header.Set(k, v)
44
}
45
46
return base.HttpClient.Do(req)
47
}
48
49
func (d *Gofile) getJSON(ctx context.Context, endpoint string, result interface{}) error {
50
resp, err := d.request(ctx, "GET", endpoint, nil, nil)
51
if err != nil {
52
return err
53
}
54
defer resp.Body.Close()
55
56
if resp.StatusCode != http.StatusOK {
57
return d.handleError(resp)
58
}
59
60
return json.NewDecoder(resp.Body).Decode(result)
61
}
62
63
func (d *Gofile) postJSON(ctx context.Context, endpoint string, data interface{}, result interface{}) error {
64
jsonData, err := json.Marshal(data)
65
if err != nil {
66
return err
67
}
68
69
headers := map[string]string{
70
"Content-Type": "application/json",
71
}
72
73
resp, err := d.request(ctx, "POST", endpoint, bytes.NewBuffer(jsonData), headers)
74
if err != nil {
75
return err
76
}
77
defer resp.Body.Close()
78
79
if resp.StatusCode != http.StatusOK {
80
return d.handleError(resp)
81
}
82
83
if result != nil {
84
return json.NewDecoder(resp.Body).Decode(result)
85
}
86
87
return nil
88
}
89
90
func (d *Gofile) putJSON(ctx context.Context, endpoint string, data interface{}, result interface{}) error {
91
jsonData, err := json.Marshal(data)
92
if err != nil {
93
return err
94
}
95
96
headers := map[string]string{
97
"Content-Type": "application/json",
98
}
99
100
resp, err := d.request(ctx, "PUT", endpoint, bytes.NewBuffer(jsonData), headers)
101
if err != nil {
102
return err
103
}
104
defer resp.Body.Close()
105
106
if resp.StatusCode != http.StatusOK {
107
return d.handleError(resp)
108
}
109
110
if result != nil {
111
return json.NewDecoder(resp.Body).Decode(result)
112
}
113
114
return nil
115
}
116
117
func (d *Gofile) deleteJSON(ctx context.Context, endpoint string, data interface{}) error {
118
jsonData, err := json.Marshal(data)
119
if err != nil {
120
return err
121
}
122
123
headers := map[string]string{
124
"Content-Type": "application/json",
125
}
126
127
resp, err := d.request(ctx, "DELETE", endpoint, bytes.NewBuffer(jsonData), headers)
128
if err != nil {
129
return err
130
}
131
defer resp.Body.Close()
132
133
if resp.StatusCode != http.StatusOK {
134
return d.handleError(resp)
135
}
136
137
return nil
138
}
139
140
func (d *Gofile) handleError(resp *http.Response) error {
141
body, _ := io.ReadAll(resp.Body)
142
log.Debugf("Gofile API error (HTTP %d): %s", resp.StatusCode, string(body))
143
144
var errorResp ErrorResponse
145
if err := json.Unmarshal(body, &errorResp); err == nil && errorResp.Status == "error" {
146
return fmt.Errorf("gofile API error: %s (code: %s)", errorResp.Error.Message, errorResp.Error.Code)
147
}
148
149
return fmt.Errorf("gofile API error: HTTP %d - %s", resp.StatusCode, string(body))
150
}
151
152
func (d *Gofile) uploadFile(ctx context.Context, folderId string, file model.FileStreamer, up driver.UpdateProgress) (*UploadResponse, error) {
153
var body bytes.Buffer
154
writer := multipart.NewWriter(&body)
155
156
if folderId != "" {
157
writer.WriteField("folderId", folderId)
158
}
159
160
part, err := writer.CreateFormFile("file", filepath.Base(file.GetName()))
161
if err != nil {
162
return nil, err
163
}
164
165
// Copy with progress tracking if available
166
if up != nil {
167
reader := &progressReader{
168
reader: file,
169
total: file.GetSize(),
170
up: up,
171
}
172
_, err = io.Copy(part, reader)
173
} else {
174
_, err = io.Copy(part, file)
175
}
176
177
if err != nil {
178
return nil, err
179
}
180
181
writer.Close()
182
183
headers := map[string]string{
184
"Content-Type": writer.FormDataContentType(),
185
}
186
187
resp, err := d.request(ctx, "POST", uploadAPI+"/uploadfile", &body, headers)
188
if err != nil {
189
return nil, err
190
}
191
defer resp.Body.Close()
192
193
if resp.StatusCode != http.StatusOK {
194
return nil, d.handleError(resp)
195
}
196
197
var result UploadResponse
198
err = json.NewDecoder(resp.Body).Decode(&result)
199
return &result, err
200
}
201
202
func (d *Gofile) createDirectLink(ctx context.Context, contentId string) (string, error) {
203
data := map[string]interface{}{}
204
205
if d.DirectLinkExpiry > 0 {
206
expireTime := time.Now().Add(time.Duration(d.DirectLinkExpiry) * time.Hour).Unix()
207
data["expireTime"] = expireTime
208
}
209
210
var result DirectLinkResponse
211
err := d.postJSON(ctx, fmt.Sprintf("/contents/%s/directlinks", contentId), data, &result)
212
if err != nil {
213
return "", err
214
}
215
216
return result.Data.DirectLink, nil
217
}
218
219
func (d *Gofile) convertContentToObj(content Content) model.Obj {
220
return &model.ObjThumb{
221
Object: model.Object{
222
ID: content.ID,
223
Name: content.Name,
224
Size: content.Size,
225
Modified: content.ModifiedTime(),
226
IsFolder: content.IsDir(),
227
},
228
}
229
}
230
231
func (d *Gofile) getAccountId(ctx context.Context) (string, error) {
232
var result AccountResponse
233
err := d.getJSON(ctx, "/accounts/getid", &result)
234
if err != nil {
235
return "", err
236
}
237
return result.Data.ID, nil
238
}
239
240
func (d *Gofile) getAccountInfo(ctx context.Context, accountId string) (*AccountInfoResponse, error) {
241
var result AccountInfoResponse
242
err := d.getJSON(ctx, fmt.Sprintf("/accounts/%s", accountId), &result)
243
if err != nil {
244
return nil, err
245
}
246
return &result, nil
247
}
248
249
// progressReader wraps an io.Reader to track upload progress
250
type progressReader struct {
251
reader io.Reader
252
total int64
253
read int64
254
up driver.UpdateProgress
255
}
256
257
func (pr *progressReader) Read(p []byte) (n int, err error) {
258
n, err = pr.reader.Read(p)
259
pr.read += int64(n)
260
if pr.up != nil && pr.total > 0 {
261
progress := float64(pr.read) * 100 / float64(pr.total)
262
pr.up(progress)
263
}
264
return n, err
265
}
266
267