Skip to content

Commit 48e6f3b

Browse files
wangzexixhofe
andauthored
feat: add Seafile driver (AlistGo#2964)
* feat: add Seafile driver * docs: add Seafile support * refactor: optimization * fix: close redirect on `move` and `rename` Co-authored-by: Noah Hsu <i@nn.ci>
1 parent 0ad9e17 commit 48e6f3b

File tree

6 files changed

+250
-0
lines changed

6 files changed

+250
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ English | [中文](./README_cn.md) | [Contributing](./CONTRIBUTING.md) | [CODE_O
5353
- [x] FTP / SFTP
5454
- [x] [PikPak](https://www.mypikpak.com/)
5555
- [x] [S3](https://aws.amazon.com/s3/)
56+
- [x] [Seafile](https://seafile.com/)
5657
- [x] [UPYUN Storage Service](https://www.upyun.com/products/file-storage)
5758
- [x] WebDav(Support OneDrive/SharePoint without API)
5859
- [x] Teambition([China](https://www.teambition.com/ ),[International](https://us.teambition.com/ ))

drivers/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
_ "github.com/alist-org/alist/v3/drivers/pikpak_share"
2525
_ "github.com/alist-org/alist/v3/drivers/quark"
2626
_ "github.com/alist-org/alist/v3/drivers/s3"
27+
_ "github.com/alist-org/alist/v3/drivers/seafile"
2728
_ "github.com/alist-org/alist/v3/drivers/sftp"
2829
_ "github.com/alist-org/alist/v3/drivers/smb"
2930
_ "github.com/alist-org/alist/v3/drivers/teambition"

drivers/seafile/driver.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package seafile
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"path/filepath"
8+
"strings"
9+
"time"
10+
11+
"github.com/alist-org/alist/v3/internal/driver"
12+
"github.com/alist-org/alist/v3/internal/model"
13+
"github.com/alist-org/alist/v3/pkg/utils"
14+
"github.com/go-resty/resty/v2"
15+
)
16+
17+
type Seafile struct {
18+
model.Storage
19+
Addition
20+
21+
authorization string
22+
}
23+
24+
func (d *Seafile) Config() driver.Config {
25+
return config
26+
}
27+
28+
func (d *Seafile) GetAddition() driver.Additional {
29+
return &d.Addition
30+
}
31+
32+
func (d *Seafile) Init(ctx context.Context) error {
33+
d.Address = strings.TrimSuffix(d.Address, "/")
34+
return d.getToken()
35+
}
36+
37+
func (d *Seafile) Drop(ctx context.Context) error {
38+
return nil
39+
}
40+
41+
func (d *Seafile) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
42+
path := dir.GetPath()
43+
var resp []RepoDirItemResp
44+
_, err := d.request(http.MethodGet, fmt.Sprintf("/api2/repos/%s/dir/", d.Addition.RepoId), func(req *resty.Request) {
45+
req.SetResult(&resp).SetQueryParams(map[string]string{
46+
"p": path,
47+
})
48+
})
49+
if err != nil {
50+
return nil, err
51+
}
52+
return utils.SliceConvert(resp, func(f RepoDirItemResp) (model.Obj, error) {
53+
return &model.ObjThumb{
54+
Object: model.Object{
55+
Name: f.Name,
56+
Modified: time.Unix(f.Modified, 0),
57+
Size: f.Size,
58+
IsFolder: f.Type == "dir",
59+
},
60+
// Thumbnail: model.Thumbnail{Thumbnail: f.Thumb},
61+
}, nil
62+
})
63+
}
64+
65+
func (d *Seafile) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
66+
res, err := d.request(http.MethodGet, fmt.Sprintf("/api2/repos/%s/file/", d.Addition.RepoId), func(req *resty.Request) {
67+
req.SetQueryParams(map[string]string{
68+
"p": file.GetPath(),
69+
"reuse": "1",
70+
})
71+
})
72+
if err != nil {
73+
return nil, err
74+
}
75+
u := string(res)
76+
u = u[1 : len(u)-1] // remove quotes
77+
return &model.Link{URL: u}, nil
78+
}
79+
80+
func (d *Seafile) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
81+
_, err := d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/dir/", d.Addition.RepoId), func(req *resty.Request) {
82+
req.SetQueryParams(map[string]string{
83+
"p": filepath.Join(parentDir.GetPath(), dirName),
84+
}).SetFormData(map[string]string{
85+
"operation": "mkdir",
86+
})
87+
})
88+
return err
89+
}
90+
91+
func (d *Seafile) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
92+
_, err := d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/file/", d.Addition.RepoId), func(req *resty.Request) {
93+
req.SetQueryParams(map[string]string{
94+
"p": srcObj.GetPath(),
95+
}).SetFormData(map[string]string{
96+
"operation": "move",
97+
"dst_repo": d.Addition.RepoId,
98+
"dst_dir": dstDir.GetPath(),
99+
})
100+
}, true)
101+
return err
102+
}
103+
104+
func (d *Seafile) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
105+
_, err := d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/file/", d.Addition.RepoId), func(req *resty.Request) {
106+
req.SetQueryParams(map[string]string{
107+
"p": srcObj.GetPath(),
108+
}).SetFormData(map[string]string{
109+
"operation": "rename",
110+
"newname": newName,
111+
})
112+
}, true)
113+
return err
114+
}
115+
116+
func (d *Seafile) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
117+
_, err := d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/file/", d.Addition.RepoId), func(req *resty.Request) {
118+
req.SetQueryParams(map[string]string{
119+
"p": srcObj.GetPath(),
120+
}).SetFormData(map[string]string{
121+
"operation": "copy",
122+
"dst_repo": d.Addition.RepoId,
123+
"dst_dir": dstDir.GetPath(),
124+
})
125+
})
126+
return err
127+
}
128+
129+
func (d *Seafile) Remove(ctx context.Context, obj model.Obj) error {
130+
_, err := d.request(http.MethodDelete, fmt.Sprintf("/api2/repos/%s/file/", d.Addition.RepoId), func(req *resty.Request) {
131+
req.SetQueryParams(map[string]string{
132+
"p": obj.GetPath(),
133+
})
134+
})
135+
return err
136+
}
137+
138+
func (d *Seafile) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
139+
res, err := d.request(http.MethodGet, fmt.Sprintf("/api2/repos/%s/upload-link/", d.Addition.RepoId), func(req *resty.Request) {
140+
req.SetQueryParams(map[string]string{
141+
"p": dstDir.GetPath(),
142+
})
143+
})
144+
if err != nil {
145+
return err
146+
}
147+
148+
u := string(res)
149+
u = u[1 : len(u)-1] // remove quotes
150+
_, err = d.request(http.MethodPost, u, func(req *resty.Request) {
151+
req.SetFileReader("file", stream.GetName(), stream).
152+
SetFormData(map[string]string{
153+
"parent_dir": dstDir.GetPath(),
154+
"replace": "1",
155+
})
156+
})
157+
return err
158+
}
159+
160+
var _ driver.Driver = (*Seafile)(nil)

drivers/seafile/meta.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package seafile
2+
3+
import (
4+
"github.com/alist-org/alist/v3/internal/driver"
5+
"github.com/alist-org/alist/v3/internal/op"
6+
)
7+
8+
type Addition struct {
9+
driver.RootPath
10+
11+
Address string `json:"address" required:"true"`
12+
UserName string `json:"username" required:"true"`
13+
Password string `json:"password" required:"true"`
14+
RepoId string `json:"repoId" required:"true"`
15+
}
16+
17+
var config = driver.Config{
18+
Name: "Seafile",
19+
DefaultRoot: "/",
20+
}
21+
22+
func init() {
23+
op.RegisterDriver(func() driver.Driver {
24+
return &Seafile{}
25+
})
26+
}

drivers/seafile/types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package seafile
2+
3+
type AuthTokenResp struct {
4+
Token string `json:"token"`
5+
}
6+
7+
type RepoDirItemResp struct {
8+
Id string `json:"id"`
9+
Type string `json:"type"` // dir, file
10+
Name string `json:"name"`
11+
Size int64 `json:"size"`
12+
Modified int64 `json:"mtime"`
13+
Permission string `json:"permission"`
14+
}

drivers/seafile/util.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package seafile
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/alist-org/alist/v3/drivers/base"
8+
)
9+
10+
func (d *Seafile) getToken() error {
11+
var authResp AuthTokenResp
12+
res, err := base.RestyClient.R().
13+
SetResult(&authResp).
14+
SetFormData(map[string]string{
15+
"username": d.UserName,
16+
"password": d.Password,
17+
}).
18+
Post(d.Address + "/api2/auth-token/")
19+
if err != nil {
20+
return err
21+
}
22+
if res.StatusCode() >= 400 {
23+
return fmt.Errorf("get token failed: %s", res.String())
24+
}
25+
d.authorization = fmt.Sprintf("Token %s", authResp.Token)
26+
return nil
27+
}
28+
29+
func (d *Seafile) request(method string, pathname string, callback base.ReqCallback, noRedirect ...bool) ([]byte, error) {
30+
full := pathname
31+
if !strings.HasPrefix(pathname, "http") {
32+
full = d.Address + pathname
33+
}
34+
req := base.RestyClient.R()
35+
if len(noRedirect) > 0 && noRedirect[0] {
36+
req = base.NoRedirectClient.R()
37+
}
38+
req.SetHeader("Authorization", d.authorization)
39+
callback(req)
40+
res, err := req.Execute(method, full)
41+
if err != nil {
42+
return nil, err
43+
}
44+
if res.StatusCode() >= 400 {
45+
return nil, fmt.Errorf("request failed: %s", res.String())
46+
}
47+
return res.Body(), nil
48+
}

0 commit comments

Comments
 (0)