Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/gitee/driver.go
1987 views
1
package gitee
2
3
import (
4
"context"
5
"errors"
6
"fmt"
7
"net/http"
8
"net/url"
9
stdpath "path"
10
"strings"
11
12
"github.com/alist-org/alist/v3/drivers/base"
13
"github.com/alist-org/alist/v3/internal/driver"
14
"github.com/alist-org/alist/v3/internal/errs"
15
"github.com/alist-org/alist/v3/internal/model"
16
"github.com/alist-org/alist/v3/pkg/utils"
17
"github.com/go-resty/resty/v2"
18
)
19
20
type Gitee struct {
21
model.Storage
22
Addition
23
client *resty.Client
24
}
25
26
func (d *Gitee) Config() driver.Config {
27
return config
28
}
29
30
func (d *Gitee) GetAddition() driver.Additional {
31
return &d.Addition
32
}
33
34
func (d *Gitee) Init(ctx context.Context) error {
35
d.RootFolderPath = utils.FixAndCleanPath(d.RootFolderPath)
36
d.Endpoint = strings.TrimSpace(d.Endpoint)
37
if d.Endpoint == "" {
38
d.Endpoint = "https://gitee.com/api/v5"
39
}
40
d.Endpoint = strings.TrimSuffix(d.Endpoint, "/")
41
d.Owner = strings.TrimSpace(d.Owner)
42
d.Repo = strings.TrimSpace(d.Repo)
43
d.Token = strings.TrimSpace(d.Token)
44
d.DownloadProxy = strings.TrimSpace(d.DownloadProxy)
45
if d.Owner == "" || d.Repo == "" {
46
return errors.New("owner and repo are required")
47
}
48
d.client = base.NewRestyClient().
49
SetBaseURL(d.Endpoint).
50
SetHeader("Accept", "application/json")
51
repo, err := d.getRepo()
52
if err != nil {
53
return err
54
}
55
d.Ref = strings.TrimSpace(d.Ref)
56
if d.Ref == "" {
57
d.Ref = repo.DefaultBranch
58
}
59
return nil
60
}
61
62
func (d *Gitee) Drop(ctx context.Context) error {
63
return nil
64
}
65
66
func (d *Gitee) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
67
relPath := d.relativePath(dir.GetPath())
68
contents, err := d.listContents(relPath)
69
if err != nil {
70
return nil, err
71
}
72
objs := make([]model.Obj, 0, len(contents))
73
for i := range contents {
74
objs = append(objs, contents[i].toModelObj())
75
}
76
return objs, nil
77
}
78
79
func (d *Gitee) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
80
var downloadURL string
81
if obj, ok := file.(*Object); ok {
82
downloadURL = obj.DownloadURL
83
if downloadURL == "" {
84
relPath := d.relativePath(file.GetPath())
85
content, err := d.getContent(relPath)
86
if err != nil {
87
return nil, err
88
}
89
if content.DownloadURL == "" {
90
return nil, errors.New("empty download url")
91
}
92
obj.DownloadURL = content.DownloadURL
93
downloadURL = content.DownloadURL
94
}
95
} else {
96
relPath := d.relativePath(file.GetPath())
97
content, err := d.getContent(relPath)
98
if err != nil {
99
return nil, err
100
}
101
if content.DownloadURL == "" {
102
return nil, errors.New("empty download url")
103
}
104
downloadURL = content.DownloadURL
105
}
106
url := d.applyProxy(downloadURL)
107
return &model.Link{
108
URL: url,
109
Header: http.Header{
110
"Cookie": {d.Cookie},
111
},
112
}, nil
113
}
114
115
func (d *Gitee) newRequest() *resty.Request {
116
req := d.client.R()
117
if d.Token != "" {
118
req.SetQueryParam("access_token", d.Token)
119
}
120
if d.Ref != "" {
121
req.SetQueryParam("ref", d.Ref)
122
}
123
return req
124
}
125
126
func (d *Gitee) apiPath(path string) string {
127
escapedOwner := url.PathEscape(d.Owner)
128
escapedRepo := url.PathEscape(d.Repo)
129
if path == "" {
130
return fmt.Sprintf("/repos/%s/%s/contents", escapedOwner, escapedRepo)
131
}
132
return fmt.Sprintf("/repos/%s/%s/contents/%s", escapedOwner, escapedRepo, encodePath(path))
133
}
134
135
func (d *Gitee) listContents(path string) ([]Content, error) {
136
res, err := d.newRequest().Get(d.apiPath(path))
137
if err != nil {
138
return nil, err
139
}
140
if res.IsError() {
141
return nil, toErr(res)
142
}
143
var contents []Content
144
if err := utils.Json.Unmarshal(res.Body(), &contents); err != nil {
145
var single Content
146
if err2 := utils.Json.Unmarshal(res.Body(), &single); err2 == nil && single.Type != "" {
147
if single.Type != "dir" {
148
return nil, errs.NotFolder
149
}
150
return []Content{}, nil
151
}
152
return nil, err
153
}
154
for i := range contents {
155
contents[i].Path = joinPath(path, contents[i].Name)
156
}
157
return contents, nil
158
}
159
160
func (d *Gitee) getContent(path string) (*Content, error) {
161
res, err := d.newRequest().Get(d.apiPath(path))
162
if err != nil {
163
return nil, err
164
}
165
if res.IsError() {
166
return nil, toErr(res)
167
}
168
var content Content
169
if err := utils.Json.Unmarshal(res.Body(), &content); err != nil {
170
return nil, err
171
}
172
if content.Type == "" {
173
return nil, errors.New("invalid response")
174
}
175
if content.Path == "" {
176
content.Path = path
177
}
178
return &content, nil
179
}
180
181
func (d *Gitee) relativePath(full string) string {
182
full = utils.FixAndCleanPath(full)
183
root := utils.FixAndCleanPath(d.RootFolderPath)
184
if root == "/" {
185
return strings.TrimPrefix(full, "/")
186
}
187
if utils.PathEqual(full, root) {
188
return ""
189
}
190
prefix := utils.PathAddSeparatorSuffix(root)
191
if strings.HasPrefix(full, prefix) {
192
return strings.TrimPrefix(full, prefix)
193
}
194
return strings.TrimPrefix(full, "/")
195
}
196
197
func (d *Gitee) applyProxy(raw string) string {
198
if raw == "" || d.DownloadProxy == "" {
199
return raw
200
}
201
proxy := d.DownloadProxy
202
if !strings.HasSuffix(proxy, "/") {
203
proxy += "/"
204
}
205
return proxy + strings.TrimLeft(raw, "/")
206
}
207
208
func encodePath(p string) string {
209
if p == "" {
210
return ""
211
}
212
parts := strings.Split(p, "/")
213
for i, part := range parts {
214
parts[i] = url.PathEscape(part)
215
}
216
return strings.Join(parts, "/")
217
}
218
219
func joinPath(base, name string) string {
220
if base == "" {
221
return name
222
}
223
return strings.TrimPrefix(stdpath.Join(base, name), "./")
224
}
225
226