Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
0f8a84f67e | |||
a475783b00 | |||
67413015e8 | |||
3a311a47af | |||
9ccd802126 | |||
0acba7cd22 | |||
3cdb8e7a81 | |||
d3efee2ea1 | |||
4ec274e748 | |||
3b07c72f88 | |||
0c5820a98f | |||
86beadc0ed | |||
be62d64dba | |||
112363031a | |||
48dc3552a6 | |||
663814c9ef | |||
bd892e6a63 | |||
4fd2c09845 | |||
0eab31bdf5 | |||
c6af22b97e | |||
b2a5110672 | |||
c628992ea6 | |||
c65d868e09 | |||
aeb48b2ecc | |||
cefec1a663 | |||
e7ad830aa8 | |||
b27eed265a | |||
3abe26473c | |||
023107226c | |||
8b109cfe40 | |||
b48e97d406 | |||
6c91cfeb90 | |||
bfd1f25972 | |||
8c0defce09 | |||
a1e88cfa05 | |||
443f5ffbcc | |||
b8bc94306d | |||
d9795ff22f | |||
c4108007cd | |||
f3db23a41e | |||
4741a75c92 | |||
301756ba03 | |||
3b2703a5e5 | |||
2a601f06cb | |||
adc3a56552 | |||
4d9a29bddd | |||
666e02f0c3 | |||
6aaec19c1c | |||
1091e1b740 | |||
d06c605421 | |||
43de823058 | |||
02d0aef611 |
2
.github/workflows/auto_lang.yml
vendored
2
.github/workflows/auto_lang.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- name: Setup go
|
- name: Setup go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
|
2
.github/workflows/issue_question.yml
vendored
2
.github/workflows/issue_question.yml
vendored
@ -17,4 +17,4 @@ jobs:
|
|||||||
issue-number: ${{ github.event.issue.number }}
|
issue-number: ${{ github.event.issue.number }}
|
||||||
body: |
|
body: |
|
||||||
Hello @${{ github.event.issue.user.login }}, please input issue by template and add detail. Issues labeled by `question` will be closed if no activities in 7 days.
|
Hello @${{ github.event.issue.user.login }}, please input issue by template and add detail. Issues labeled by `question` will be closed if no activities in 7 days.
|
||||||
你好 @${{ github.event.issue.user.login }},请按照issue模板填写, 并详细说明问题/复现步骤/复现链接/实现思路或提供更多信息等, 7天内未回复issue自动关闭。
|
你好 @${{ github.event.issue.user.login }},请按照issue模板填写, 并详细说明问题/日志记录/复现步骤/复现链接/实现思路或提供更多信息等, 7天内未回复issue自动关闭。
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
name: Release
|
name: Release
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- name: prerelease
|
- name: Prerelease
|
||||||
uses: irongut/EditRelease@v1.2.0
|
uses: irongut/EditRelease@v1.2.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MY_TOKEN }}
|
token: ${{ secrets.MY_TOKEN }}
|
||||||
@ -21,7 +21,7 @@ jobs:
|
|||||||
prerelease: true
|
prerelease: true
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
@ -41,14 +41,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
bash build.sh release
|
bash build.sh release
|
||||||
|
|
||||||
- name: prerelease
|
- name: Release latest
|
||||||
uses: irongut/EditRelease@v1.2.0
|
uses: irongut/EditRelease@v1.2.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MY_TOKEN }}
|
token: ${{ secrets.MY_TOKEN }}
|
||||||
id: ${{ github.event.release.id }}
|
id: ${{ github.event.release.id }}
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
- name: Release
|
- name: Upload assets
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
with:
|
with:
|
||||||
files: build/compress/*
|
files: build/compress/*
|
||||||
|
@ -15,4 +15,4 @@ RUN apk add --no-cache bash ca-certificates su-exec tzdata; \
|
|||||||
chmod +x /entrypoint.sh
|
chmod +x /entrypoint.sh
|
||||||
ENV PUID=0 PGID=0 UMASK=022
|
ENV PUID=0 PGID=0 UMASK=022
|
||||||
EXPOSE 5244
|
EXPOSE 5244
|
||||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
CMD [ "/entrypoint.sh" ]
|
||||||
|
6
build.sh
6
build.sh
@ -59,7 +59,8 @@ BuildDev() {
|
|||||||
mv alist-* dist
|
mv alist-* dist
|
||||||
cd dist
|
cd dist
|
||||||
upx -9 ./alist-linux*
|
upx -9 ./alist-linux*
|
||||||
upx -9 ./alist-windows-amd64.exe
|
cp ./alist-windows-amd64.exe ./alist-windows-amd64-upx.exe
|
||||||
|
upx -9 ./alist-windows-amd64-upx.exe
|
||||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
||||||
cat md5.txt
|
cat md5.txt
|
||||||
}
|
}
|
||||||
@ -95,7 +96,8 @@ BuildRelease() {
|
|||||||
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||||
# why? Because some target platforms seem to have issues with upx compression
|
# why? Because some target platforms seem to have issues with upx compression
|
||||||
upx -9 ./alist-linux-amd64
|
upx -9 ./alist-linux-amd64
|
||||||
upx -9 ./alist-windows-amd64.exe
|
cp ./alist-windows-amd64.exe ./alist-windows-amd64-upx.exe
|
||||||
|
upx -9 ./alist-windows-amd64-upx.exe
|
||||||
mv alist-* build
|
mv alist-* build
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ var Cancel2FACmd = &cobra.Command{
|
|||||||
err := op.Cancel2FAByUser(admin)
|
err := op.Cancel2FAByUser(admin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Log.Errorf("failed to cancel 2FA: %+v", err)
|
utils.Log.Errorf("failed to cancel 2FA: %+v", err)
|
||||||
|
} else {
|
||||||
|
utils.Log.Info("2FA canceled")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -44,7 +44,11 @@ func (d *Pan115) List(ctx context.Context, dir model.Obj, args model.ListArgs) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Pan115) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
func (d *Pan115) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
downloadInfo, err := d.client.Download(file.(driver115.File).PickCode)
|
downloadInfo, err := d.client.
|
||||||
|
SetUserAgent(driver115.UA115Browser).
|
||||||
|
Download(file.(driver115.File).PickCode)
|
||||||
|
// recover for upload
|
||||||
|
d.client.SetUserAgent(driver115.UA115Desktop)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Addition struct {
|
type Addition struct {
|
||||||
Cookie string `json:"cookie"`
|
Cookie string `json:"cookie" type:"text" help:"one of QR code token and cookie required"`
|
||||||
QRCodeToken string `json:"qrcode_token"`
|
QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
|
||||||
|
PageSize int64 `json:"page_size" type:"number" default:"56" help:"list api per page size of 115 driver"`
|
||||||
driver.RootID
|
driver.RootID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 115Browser/23.9.3.2 115disk/30.1.0"
|
var UserAgent = driver.UA115Desktop
|
||||||
|
|
||||||
func (d *Pan115) login() error {
|
func (d *Pan115) login() error {
|
||||||
var err error
|
var err error
|
||||||
@ -38,7 +38,10 @@ func (d *Pan115) login() error {
|
|||||||
|
|
||||||
func (d *Pan115) getFiles(fileId string) ([]driver.File, error) {
|
func (d *Pan115) getFiles(fileId string) ([]driver.File, error) {
|
||||||
res := make([]driver.File, 0)
|
res := make([]driver.File, 0)
|
||||||
files, err := d.client.List(fileId)
|
if d.PageSize <= 0 {
|
||||||
|
d.PageSize = driver.FileListLimit
|
||||||
|
}
|
||||||
|
files, err := d.client.ListWithLimit(fileId, d.PageSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ import (
|
|||||||
type Pan123 struct {
|
type Pan123 struct {
|
||||||
model.Storage
|
model.Storage
|
||||||
Addition
|
Addition
|
||||||
AccessToken string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Pan123) Config() driver.Config {
|
func (d *Pan123) Config() driver.Config {
|
||||||
@ -41,7 +40,8 @@ func (d *Pan123) GetAddition() driver.Additional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Pan123) Init(ctx context.Context) error {
|
func (d *Pan123) Init(ctx context.Context) error {
|
||||||
return d.login()
|
_, err := d.request(UserInfo, http.MethodGet, nil, nil)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Pan123) Drop(ctx context.Context) error {
|
func (d *Pan123) Drop(ctx context.Context) error {
|
||||||
@ -77,7 +77,7 @@ func (d *Pan123) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
|||||||
"size": f.Size,
|
"size": f.Size,
|
||||||
"type": f.Type,
|
"type": f.Type,
|
||||||
}
|
}
|
||||||
resp, err := d.request("https://www.123pan.com/api/file/download_info", http.MethodPost, func(req *resty.Request) {
|
resp, err := d.request(DownloadInfo, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data).SetHeaders(headers)
|
req.SetBody(data).SetHeaders(headers)
|
||||||
}, nil)
|
}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -124,7 +124,7 @@ func (d *Pan123) MakeDir(ctx context.Context, parentDir model.Obj, dirName strin
|
|||||||
"size": 0,
|
"size": 0,
|
||||||
"type": 1,
|
"type": 1,
|
||||||
}
|
}
|
||||||
_, err := d.request("https://www.123pan.com/api/file/upload_request", http.MethodPost, func(req *resty.Request) {
|
_, err := d.request(Mkdir, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data)
|
req.SetBody(data)
|
||||||
}, nil)
|
}, nil)
|
||||||
return err
|
return err
|
||||||
@ -135,7 +135,7 @@ func (d *Pan123) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|||||||
"fileIdList": []base.Json{{"FileId": srcObj.GetID()}},
|
"fileIdList": []base.Json{{"FileId": srcObj.GetID()}},
|
||||||
"parentFileId": dstDir.GetID(),
|
"parentFileId": dstDir.GetID(),
|
||||||
}
|
}
|
||||||
_, err := d.request("https://www.123pan.com/api/file/mod_pid", http.MethodPost, func(req *resty.Request) {
|
_, err := d.request(Move, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data)
|
req.SetBody(data)
|
||||||
}, nil)
|
}, nil)
|
||||||
return err
|
return err
|
||||||
@ -147,7 +147,7 @@ func (d *Pan123) Rename(ctx context.Context, srcObj model.Obj, newName string) e
|
|||||||
"fileId": srcObj.GetID(),
|
"fileId": srcObj.GetID(),
|
||||||
"fileName": newName,
|
"fileName": newName,
|
||||||
}
|
}
|
||||||
_, err := d.request("https://www.123pan.com/api/file/rename", http.MethodPost, func(req *resty.Request) {
|
_, err := d.request(Rename, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data)
|
req.SetBody(data)
|
||||||
}, nil)
|
}, nil)
|
||||||
return err
|
return err
|
||||||
@ -164,7 +164,7 @@ func (d *Pan123) Remove(ctx context.Context, obj model.Obj) error {
|
|||||||
"operation": true,
|
"operation": true,
|
||||||
"fileTrashInfoList": []File{f},
|
"fileTrashInfoList": []File{f},
|
||||||
}
|
}
|
||||||
_, err := d.request("https://www.123pan.com/b/api/file/trash", http.MethodPost, func(req *resty.Request) {
|
_, err := d.request(Trash, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data)
|
req.SetBody(data)
|
||||||
}, nil)
|
}, nil)
|
||||||
return err
|
return err
|
||||||
@ -220,7 +220,7 @@ func (d *Pan123) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
"type": 0,
|
"type": 0,
|
||||||
}
|
}
|
||||||
var resp UploadResp
|
var resp UploadResp
|
||||||
_, err := d.request("https://www.123pan.com/a/api/file/upload_request", http.MethodPost, func(req *resty.Request) {
|
_, err := d.request(UploadRequest, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(data).SetContext(ctx)
|
req.SetBody(data).SetContext(ctx)
|
||||||
}, &resp)
|
}, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -249,7 +249,7 @@ func (d *Pan123) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = d.request("https://www.123pan.com/api/file/upload_complete", http.MethodPost, func(req *resty.Request) {
|
_, err = d.request(UploadComplete, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(base.Json{
|
req.SetBody(base.Json{
|
||||||
"fileId": resp.Data.FileId,
|
"fileId": resp.Data.FileId,
|
||||||
}).SetContext(ctx)
|
}).SetContext(ctx)
|
||||||
|
@ -6,14 +6,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Addition struct {
|
type Addition struct {
|
||||||
Username string `json:"username" required:"true"`
|
Username string `json:"username" required:"true"`
|
||||||
Password string `json:"password" required:"true"`
|
Password string `json:"password" required:"true"`
|
||||||
|
driver.RootID
|
||||||
OrderBy string `json:"order_by" type:"select" options:"file_name,size,update_at" default:"file_name"`
|
OrderBy string `json:"order_by" type:"select" options:"file_name,size,update_at" default:"file_name"`
|
||||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
|
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
|
||||||
driver.RootID
|
StreamUpload bool `json:"stream_upload"`
|
||||||
// define other
|
AccessToken string
|
||||||
StreamUpload bool `json:"stream_upload"`
|
|
||||||
//Field string `json:"field" type:"select" required:"true" options:"a,b,c" default:"a"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
|
@ -14,9 +14,22 @@ import (
|
|||||||
|
|
||||||
// do others that not defined in Driver interface
|
// do others that not defined in Driver interface
|
||||||
|
|
||||||
|
const (
|
||||||
|
API = "https://www.123pan.com/b/api"
|
||||||
|
SignIn = API + "/user/sign_in"
|
||||||
|
UserInfo = API + "/user/info"
|
||||||
|
FileList = API + "/file/list/new"
|
||||||
|
DownloadInfo = API + "/file/download_info"
|
||||||
|
Mkdir = API + "/file/upload_request"
|
||||||
|
Move = API + "/file/mod_pid"
|
||||||
|
Rename = API + "/file/rename"
|
||||||
|
Trash = API + "/file/trash"
|
||||||
|
UploadRequest = API + "/file/upload_request"
|
||||||
|
UploadComplete = API + "/file/upload_complete"
|
||||||
|
)
|
||||||
|
|
||||||
func (d *Pan123) login() error {
|
func (d *Pan123) login() error {
|
||||||
var body base.Json
|
var body base.Json
|
||||||
url := "https://www.123pan.com/a/api/user/sign_in"
|
|
||||||
if utils.IsEmailFormat(d.Username) {
|
if utils.IsEmailFormat(d.Username) {
|
||||||
body = base.Json{
|
body = base.Json{
|
||||||
"mail": d.Username,
|
"mail": d.Username,
|
||||||
@ -30,7 +43,7 @@ func (d *Pan123) login() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
res, err := base.RestyClient.R().
|
res, err := base.RestyClient.R().
|
||||||
SetBody(body).Post(url)
|
SetBody(body).Post(SignIn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -90,7 +103,7 @@ func (d *Pan123) getFiles(parentId string) ([]File, error) {
|
|||||||
"trashed": "false",
|
"trashed": "false",
|
||||||
"Page": strconv.Itoa(page),
|
"Page": strconv.Itoa(page),
|
||||||
}
|
}
|
||||||
_, err := d.request("https://www.123pan.com/api/file/list/new", http.MethodGet, func(req *resty.Request) {
|
_, err := d.request(FileList, http.MethodGet, func(req *resty.Request) {
|
||||||
req.SetQueryParams(query)
|
req.SetQueryParams(query)
|
||||||
}, &resp)
|
}, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
114
drivers/alias/driver.go
Normal file
114
drivers/alias/driver.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package alias
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Alias struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
pathMap map[string][]string
|
||||||
|
autoFlatten bool
|
||||||
|
oneKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Alias) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Alias) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Alias) Init(ctx context.Context) error {
|
||||||
|
if d.Paths == "" {
|
||||||
|
return errors.New("paths is required")
|
||||||
|
}
|
||||||
|
d.pathMap = make(map[string][]string)
|
||||||
|
for _, path := range strings.Split(d.Paths, "\n") {
|
||||||
|
path = strings.TrimSpace(path)
|
||||||
|
if path == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k, v := getPair(path)
|
||||||
|
d.pathMap[k] = append(d.pathMap[k], v)
|
||||||
|
}
|
||||||
|
if len(d.pathMap) == 1 {
|
||||||
|
for k := range d.pathMap {
|
||||||
|
d.oneKey = k
|
||||||
|
}
|
||||||
|
d.autoFlatten = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Alias) Drop(ctx context.Context) error {
|
||||||
|
d.pathMap = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Alias) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||||
|
if utils.PathEqual(path, "/") {
|
||||||
|
return &model.Object{
|
||||||
|
Name: "Root",
|
||||||
|
IsFolder: true,
|
||||||
|
Path: "/",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
root, sub := d.getRootAndPath(path)
|
||||||
|
dsts, ok := d.pathMap[root]
|
||||||
|
if !ok {
|
||||||
|
return nil, errs.ObjectNotFound
|
||||||
|
}
|
||||||
|
for _, dst := range dsts {
|
||||||
|
obj, err := d.get(ctx, path, dst, sub)
|
||||||
|
if err == nil {
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errs.ObjectNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Alias) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
path := dir.GetPath()
|
||||||
|
if utils.PathEqual(path, "/") && !d.autoFlatten {
|
||||||
|
return d.listRoot(), nil
|
||||||
|
}
|
||||||
|
root, sub := d.getRootAndPath(path)
|
||||||
|
dsts, ok := d.pathMap[root]
|
||||||
|
if !ok {
|
||||||
|
return nil, errs.ObjectNotFound
|
||||||
|
}
|
||||||
|
var objs []model.Obj
|
||||||
|
for _, dst := range dsts {
|
||||||
|
tmp, err := d.list(ctx, dst, sub)
|
||||||
|
if err == nil {
|
||||||
|
objs = append(objs, tmp...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return objs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Alias) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
root, sub := d.getRootAndPath(file.GetPath())
|
||||||
|
dsts, ok := d.pathMap[root]
|
||||||
|
if !ok {
|
||||||
|
return nil, errs.ObjectNotFound
|
||||||
|
}
|
||||||
|
for _, dst := range dsts {
|
||||||
|
link, err := d.link(ctx, dst, sub, args)
|
||||||
|
if err == nil {
|
||||||
|
return link, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errs.ObjectNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*Alias)(nil)
|
27
drivers/alias/meta.go
Normal file
27
drivers/alias/meta.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package alias
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
// Usually one of two
|
||||||
|
// driver.RootPath
|
||||||
|
// define other
|
||||||
|
Paths string `json:"paths" required:"true" type:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "Alias",
|
||||||
|
LocalSort: true,
|
||||||
|
NoCache: true,
|
||||||
|
NoUpload: true,
|
||||||
|
DefaultRoot: "/",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &Alias{}
|
||||||
|
})
|
||||||
|
}
|
1
drivers/alias/types.go
Normal file
1
drivers/alias/types.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package alias
|
103
drivers/alias/util.go
Normal file
103
drivers/alias/util.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package alias
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
stdpath "path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/sign"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *Alias) listRoot() []model.Obj {
|
||||||
|
var objs []model.Obj
|
||||||
|
for k, _ := range d.pathMap {
|
||||||
|
obj := model.Object{
|
||||||
|
Name: k,
|
||||||
|
IsFolder: true,
|
||||||
|
Modified: d.Modified,
|
||||||
|
}
|
||||||
|
objs = append(objs, &obj)
|
||||||
|
}
|
||||||
|
return objs
|
||||||
|
}
|
||||||
|
|
||||||
|
// do others that not defined in Driver interface
|
||||||
|
func getPair(path string) (string, string) {
|
||||||
|
//path = strings.TrimSpace(path)
|
||||||
|
if strings.Contains(path, ":") {
|
||||||
|
pair := strings.SplitN(path, ":", 2)
|
||||||
|
if !strings.Contains(pair[0], "/") {
|
||||||
|
return pair[0], pair[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stdpath.Base(path), path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Alias) getRootAndPath(path string) (string, string) {
|
||||||
|
if d.autoFlatten {
|
||||||
|
return d.oneKey, path
|
||||||
|
}
|
||||||
|
path = strings.TrimPrefix(path, "/")
|
||||||
|
parts := strings.SplitN(path, "/", 2)
|
||||||
|
if len(parts) == 1 {
|
||||||
|
return parts[0], ""
|
||||||
|
}
|
||||||
|
return parts[0], parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Alias) get(ctx context.Context, path string, dst, sub string) (model.Obj, error) {
|
||||||
|
obj, err := fs.Get(ctx, stdpath.Join(dst, sub), &fs.GetArgs{NoLog: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.Object{
|
||||||
|
Path: path,
|
||||||
|
Name: obj.GetName(),
|
||||||
|
Size: obj.GetSize(),
|
||||||
|
Modified: obj.ModTime(),
|
||||||
|
IsFolder: obj.IsDir(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Alias) list(ctx context.Context, dst, sub string) ([]model.Obj, error) {
|
||||||
|
objs, err := fs.List(ctx, stdpath.Join(dst, sub), &fs.ListArgs{NoLog: true})
|
||||||
|
// the obj must implement the model.SetPath interface
|
||||||
|
// return objs, err
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return utils.SliceConvert(objs, func(obj model.Obj) (model.Obj, error) {
|
||||||
|
return &model.Object{
|
||||||
|
Name: obj.GetName(),
|
||||||
|
Size: obj.GetSize(),
|
||||||
|
Modified: obj.ModTime(),
|
||||||
|
IsFolder: obj.IsDir(),
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Alias) link(ctx context.Context, dst, sub string, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
reqPath := stdpath.Join(dst, sub)
|
||||||
|
storage, err := fs.GetStorage(reqPath, &fs.GetStoragesArgs{NoLog: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = fs.Get(ctx, reqPath, &fs.GetArgs{NoLog: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if common.ShouldProxy(storage, stdpath.Base(sub)) {
|
||||||
|
return &model.Link{
|
||||||
|
URL: fmt.Sprintf("/p%s?sign=%s",
|
||||||
|
utils.EncodePath(reqPath, true),
|
||||||
|
sign.Sign(reqPath)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
link, _, err := fs.Link(ctx, reqPath, args)
|
||||||
|
return link, err
|
||||||
|
}
|
@ -226,7 +226,7 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
delete(reqBody, "pre_hash")
|
delete(reqBody, "pre_hash")
|
||||||
h := sha1.New()
|
h := sha1.New()
|
||||||
if localFile != nil {
|
if localFile != nil {
|
||||||
if _, err = io.Copy(h, localFile); err != nil {
|
if err = utils.CopyWithCtx(ctx, h, localFile, 0, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err = localFile.Seek(0, io.SeekStart); err != nil {
|
if _, err = localFile.Seek(0, io.SeekStart); err != nil {
|
||||||
@ -241,7 +241,7 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
_ = tempFile.Close()
|
_ = tempFile.Close()
|
||||||
_ = os.Remove(tempFile.Name())
|
_ = os.Remove(tempFile.Name())
|
||||||
}()
|
}()
|
||||||
if _, err = io.Copy(io.MultiWriter(tempFile, h), file); err != nil {
|
if err = utils.CopyWithCtx(ctx, io.MultiWriter(tempFile, h), file, 0, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
localFile = tempFile
|
localFile = tempFile
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
@ -19,8 +20,7 @@ type AliyundriveOpen struct {
|
|||||||
Addition
|
Addition
|
||||||
base string
|
base string
|
||||||
|
|
||||||
AccessToken string
|
DriveId string
|
||||||
DriveId string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyundriveOpen) Config() driver.Config {
|
func (d *AliyundriveOpen) Config() driver.Config {
|
||||||
@ -32,10 +32,6 @@ func (d *AliyundriveOpen) GetAddition() driver.Additional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliyundriveOpen) Init(ctx context.Context) error {
|
func (d *AliyundriveOpen) Init(ctx context.Context) error {
|
||||||
err := d.refreshToken()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res, err := d.request("/adrive/v1.0/user/getDriveInfo", http.MethodPost, nil)
|
res, err := d.request("/adrive/v1.0/user/getDriveInfo", http.MethodPost, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -152,11 +148,7 @@ func (d *AliyundriveOpen) Put(ctx context.Context, dstDir model.Obj, stream mode
|
|||||||
count := 1
|
count := 1
|
||||||
if stream.GetSize() > DEFAULT {
|
if stream.GetSize() > DEFAULT {
|
||||||
count = int(math.Ceil(float64(stream.GetSize()) / float64(DEFAULT)))
|
count = int(math.Ceil(float64(stream.GetSize()) / float64(DEFAULT)))
|
||||||
partInfoList := make([]base.Json, 0, count)
|
createData["part_info_list"] = makePartInfos(count)
|
||||||
for i := 1; i <= count; i++ {
|
|
||||||
partInfoList = append(partInfoList, base.Json{"part_number": i})
|
|
||||||
}
|
|
||||||
createData["part_info_list"] = partInfoList
|
|
||||||
}
|
}
|
||||||
var createResp CreateResp
|
var createResp CreateResp
|
||||||
_, err := d.request("/adrive/v1.0/openFile/create", http.MethodPost, func(req *resty.Request) {
|
_, err := d.request("/adrive/v1.0/openFile/create", http.MethodPost, func(req *resty.Request) {
|
||||||
@ -166,23 +158,26 @@ func (d *AliyundriveOpen) Put(ctx context.Context, dstDir model.Obj, stream mode
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// 2. upload
|
// 2. upload
|
||||||
for i, partInfo := range createResp.PartInfoList {
|
preTime := time.Now()
|
||||||
|
for i := 1; i <= len(createResp.PartInfoList); i++ {
|
||||||
if utils.IsCanceled(ctx) {
|
if utils.IsCanceled(ctx) {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("PUT", partInfo.UploadUrl, io.LimitReader(stream, DEFAULT))
|
err = d.uploadPart(ctx, i, count, utils.NewMultiReadable(io.LimitReader(stream, DEFAULT)), &createResp, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req = req.WithContext(ctx)
|
|
||||||
res, err := base.HttpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res.Body.Close()
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
up(i * 100 / count)
|
up(i * 100 / count)
|
||||||
}
|
}
|
||||||
|
// refresh upload url if 50 minutes passed
|
||||||
|
if time.Since(preTime) > 50*time.Minute {
|
||||||
|
createResp.PartInfoList, err = d.getUploadUrl(count, createResp.FileId, createResp.UploadId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
preTime = time.Now()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 3. complete
|
// 3. complete
|
||||||
_, err = d.request("/adrive/v1.0/openFile/complete", http.MethodPost, func(req *resty.Request) {
|
_, err = d.request("/adrive/v1.0/openFile/complete", http.MethodPost, func(req *resty.Request) {
|
||||||
|
@ -14,6 +14,8 @@ type Addition struct {
|
|||||||
ClientID string `json:"client_id" required:"false" help:"Keep it empty if you don't have one"`
|
ClientID string `json:"client_id" required:"false" help:"Keep it empty if you don't have one"`
|
||||||
ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"`
|
ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"`
|
||||||
RemoveWay string `json:"remove_way" required:"true" type:"select" options:"trash,delete"`
|
RemoveWay string `json:"remove_way" required:"true" type:"select" options:"trash,delete"`
|
||||||
|
InternalUpload bool `json:"internal_upload" help:"If you are using Aliyun ECS is located in Beijing, you can turn it on to boost the upload speed"`
|
||||||
|
AccessToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
|
@ -45,6 +45,14 @@ func fileToObj(f File) *model.ObjThumb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PartInfo struct {
|
||||||
|
Etag interface{} `json:"etag"`
|
||||||
|
PartNumber int `json:"part_number"`
|
||||||
|
PartSize interface{} `json:"part_size"`
|
||||||
|
UploadUrl string `json:"upload_url"`
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateResp struct {
|
type CreateResp struct {
|
||||||
//Type string `json:"type"`
|
//Type string `json:"type"`
|
||||||
//ParentFileId string `json:"parent_file_id"`
|
//ParentFileId string `json:"parent_file_id"`
|
||||||
@ -56,12 +64,6 @@ type CreateResp struct {
|
|||||||
//FileName string `json:"file_name"`
|
//FileName string `json:"file_name"`
|
||||||
UploadId string `json:"upload_id"`
|
UploadId string `json:"upload_id"`
|
||||||
//Location string `json:"location"`
|
//Location string `json:"location"`
|
||||||
RapidUpload bool `json:"rapid_upload"`
|
RapidUpload bool `json:"rapid_upload"`
|
||||||
PartInfoList []struct {
|
PartInfoList []PartInfo `json:"part_info_list"`
|
||||||
Etag interface{} `json:"etag"`
|
|
||||||
PartNumber int `json:"part_number"`
|
|
||||||
PartSize interface{} `json:"part_size"`
|
|
||||||
UploadUrl string `json:"upload_url"`
|
|
||||||
ContentType string `json:"content_type"`
|
|
||||||
} `json:"part_info_list"`
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package aliyundrive_open
|
package aliyundrive_open
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -106,3 +109,59 @@ func (d *AliyundriveOpen) getFiles(fileId string) ([]File, error) {
|
|||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makePartInfos(size int) []base.Json {
|
||||||
|
partInfoList := make([]base.Json, size)
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
partInfoList[i] = base.Json{"part_number": 1 + i}
|
||||||
|
}
|
||||||
|
return partInfoList
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) getUploadUrl(count int, fileId, uploadId string) ([]PartInfo, error) {
|
||||||
|
partInfoList := makePartInfos(count)
|
||||||
|
var resp CreateResp
|
||||||
|
_, err := d.request("/adrive/v1.0/openFile/getUploadUrl", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(base.Json{
|
||||||
|
"drive_id": d.DriveId,
|
||||||
|
"file_id": fileId,
|
||||||
|
"part_info_list": partInfoList,
|
||||||
|
"upload_id": uploadId,
|
||||||
|
}).SetResult(&resp)
|
||||||
|
})
|
||||||
|
return resp.PartInfoList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliyundriveOpen) uploadPart(ctx context.Context, i, count int, reader *utils.MultiReadable, resp *CreateResp, retry bool) error {
|
||||||
|
partInfo := resp.PartInfoList[i-1]
|
||||||
|
uploadUrl := partInfo.UploadUrl
|
||||||
|
if d.InternalUpload {
|
||||||
|
uploadUrl = strings.ReplaceAll(uploadUrl, "https://cn-beijing-data.aliyundrive.net/", "http://ccp-bj29-bj-1592982087.oss-cn-beijing-internal.aliyuncs.com/")
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("PUT", uploadUrl, reader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
res, err := base.HttpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
if retry {
|
||||||
|
reader.Reset()
|
||||||
|
return d.uploadPart(ctx, i, count, reader, resp, false)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
if retry && res.StatusCode == http.StatusForbidden {
|
||||||
|
resp.PartInfoList, err = d.getUploadUrl(count, resp.FileId, resp.UploadId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reader.Reset()
|
||||||
|
return d.uploadPart(ctx, i, count, reader, resp, false)
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusConflict {
|
||||||
|
return fmt.Errorf("upload status: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
_ "github.com/alist-org/alist/v3/drivers/139"
|
_ "github.com/alist-org/alist/v3/drivers/139"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/189"
|
_ "github.com/alist-org/alist/v3/drivers/189"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/189pc"
|
_ "github.com/alist-org/alist/v3/drivers/189pc"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/alias"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/alist_v2"
|
_ "github.com/alist-org/alist/v3/drivers/alist_v2"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/alist_v3"
|
_ "github.com/alist-org/alist/v3/drivers/alist_v3"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/aliyundrive"
|
_ "github.com/alist-org/alist/v3/drivers/aliyundrive"
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
_ "github.com/alist-org/alist/v3/drivers/aliyundrive_share"
|
_ "github.com/alist-org/alist/v3/drivers/aliyundrive_share"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/baidu_netdisk"
|
_ "github.com/alist-org/alist/v3/drivers/baidu_netdisk"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/baidu_photo"
|
_ "github.com/alist-org/alist/v3/drivers/baidu_photo"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/baidu_share"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/cloudreve"
|
_ "github.com/alist-org/alist/v3/drivers/cloudreve"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/ftp"
|
_ "github.com/alist-org/alist/v3/drivers/ftp"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/google_drive"
|
_ "github.com/alist-org/alist/v3/drivers/google_drive"
|
||||||
@ -22,6 +24,7 @@ import (
|
|||||||
_ "github.com/alist-org/alist/v3/drivers/mediatrack"
|
_ "github.com/alist-org/alist/v3/drivers/mediatrack"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/mega"
|
_ "github.com/alist-org/alist/v3/drivers/mega"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
_ "github.com/alist-org/alist/v3/drivers/onedrive"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/onedrive_app"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/pikpak"
|
_ "github.com/alist-org/alist/v3/drivers/pikpak"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/pikpak_share"
|
_ "github.com/alist-org/alist/v3/drivers/pikpak_share"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/quark"
|
_ "github.com/alist-org/alist/v3/drivers/quark"
|
||||||
@ -32,6 +35,8 @@ import (
|
|||||||
_ "github.com/alist-org/alist/v3/drivers/teambition"
|
_ "github.com/alist-org/alist/v3/drivers/teambition"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/terabox"
|
_ "github.com/alist-org/alist/v3/drivers/terabox"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/thunder"
|
_ "github.com/alist-org/alist/v3/drivers/thunder"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/trainbit"
|
||||||
|
_ "github.com/alist-org/alist/v3/drivers/url_tree"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/uss"
|
_ "github.com/alist-org/alist/v3/drivers/uss"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/virtual"
|
_ "github.com/alist-org/alist/v3/drivers/virtual"
|
||||||
_ "github.com/alist-org/alist/v3/drivers/webdav"
|
_ "github.com/alist-org/alist/v3/drivers/webdav"
|
||||||
|
251
drivers/baidu_share/driver.go
Normal file
251
drivers/baidu_share/driver.go
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
package baidu_share
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaiduShare struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
client *resty.Client
|
||||||
|
info struct {
|
||||||
|
Root string
|
||||||
|
Seckey string
|
||||||
|
Shareid string
|
||||||
|
Uk string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) Init(ctx context.Context) error {
|
||||||
|
// TODO login / refresh token
|
||||||
|
//op.MustSaveDriverStorage(d)
|
||||||
|
d.client = resty.New().
|
||||||
|
SetBaseURL("https://pan.baidu.com").
|
||||||
|
SetHeader("User-Agent", "netdisk").
|
||||||
|
SetCookie(&http.Cookie{Name: "BDUSS", Value: d.BDUSS}).
|
||||||
|
SetCookie(&http.Cookie{Name: "ndut_fmt"})
|
||||||
|
respJson := struct {
|
||||||
|
Errno int64 `json:"errno"`
|
||||||
|
Data struct {
|
||||||
|
List [1]struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
} `json:"list"`
|
||||||
|
Uk json.Number `json:"uk"`
|
||||||
|
Shareid json.Number `json:"shareid"`
|
||||||
|
Seckey string `json:"seckey"`
|
||||||
|
} `json:"data"`
|
||||||
|
}{}
|
||||||
|
resp, err := d.client.R().
|
||||||
|
SetBody(url.Values{
|
||||||
|
"pwd": {d.Pwd},
|
||||||
|
"root": {"1"},
|
||||||
|
"shorturl": {d.Surl},
|
||||||
|
}.Encode()).
|
||||||
|
SetResult(&respJson).
|
||||||
|
Post("share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1")
|
||||||
|
if err == nil {
|
||||||
|
if resp.IsSuccess() && respJson.Errno == 0 {
|
||||||
|
d.info.Root = path.Dir(respJson.Data.List[0].Path)
|
||||||
|
d.info.Seckey = respJson.Data.Seckey
|
||||||
|
d.info.Shareid = respJson.Data.Shareid.String()
|
||||||
|
d.info.Uk = respJson.Data.Uk.String()
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
// TODO return the files list, required
|
||||||
|
reqDir := dir.GetPath()
|
||||||
|
isRoot := "0"
|
||||||
|
if reqDir == d.RootFolderPath {
|
||||||
|
reqDir = path.Join(d.info.Root, reqDir)
|
||||||
|
}
|
||||||
|
if reqDir == d.info.Root {
|
||||||
|
isRoot = "1"
|
||||||
|
}
|
||||||
|
objs := []model.Obj{}
|
||||||
|
var err error
|
||||||
|
var page uint64 = 1
|
||||||
|
more := true
|
||||||
|
for more && err == nil {
|
||||||
|
respJson := struct {
|
||||||
|
Errno int64 `json:"errno"`
|
||||||
|
Data struct {
|
||||||
|
More bool `json:"has_more"`
|
||||||
|
List []struct {
|
||||||
|
Fsid json.Number `json:"fs_id"`
|
||||||
|
Isdir json.Number `json:"isdir"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Name string `json:"server_filename"`
|
||||||
|
Mtime json.Number `json:"server_mtime"`
|
||||||
|
Size json.Number `json:"size"`
|
||||||
|
} `json:"list"`
|
||||||
|
} `json:"data"`
|
||||||
|
}{}
|
||||||
|
resp, e := d.client.R().
|
||||||
|
SetBody(url.Values{
|
||||||
|
"dir": {reqDir},
|
||||||
|
"num": {"1000"},
|
||||||
|
"order": {"time"},
|
||||||
|
"page": {fmt.Sprint(page)},
|
||||||
|
"pwd": {d.Pwd},
|
||||||
|
"root": {isRoot},
|
||||||
|
"shorturl": {d.Surl},
|
||||||
|
}.Encode()).
|
||||||
|
SetResult(&respJson).
|
||||||
|
Post("share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1")
|
||||||
|
err = e
|
||||||
|
if err == nil {
|
||||||
|
if resp.IsSuccess() && respJson.Errno == 0 {
|
||||||
|
page++
|
||||||
|
more = respJson.Data.More
|
||||||
|
for _, v := range respJson.Data.List {
|
||||||
|
size, _ := v.Size.Int64()
|
||||||
|
mtime, _ := v.Mtime.Int64()
|
||||||
|
objs = append(objs, &model.Object{
|
||||||
|
ID: v.Fsid.String(),
|
||||||
|
Path: v.Path,
|
||||||
|
Name: v.Name,
|
||||||
|
Size: size,
|
||||||
|
Modified: time.Unix(mtime, 0),
|
||||||
|
IsFolder: v.Isdir.String() == "1",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return objs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
// TODO return link of file, required
|
||||||
|
link := model.Link{Header: d.client.Header}
|
||||||
|
sign := ""
|
||||||
|
stamp := ""
|
||||||
|
signJson := struct {
|
||||||
|
Errno int64 `json:"errno"`
|
||||||
|
Data struct {
|
||||||
|
Stamp json.Number `json:"timestamp"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
} `json:"data"`
|
||||||
|
}{}
|
||||||
|
resp, err := d.client.R().
|
||||||
|
SetQueryParam("surl", d.Surl).
|
||||||
|
SetResult(&signJson).
|
||||||
|
Get("share/tplconfig?fields=sign,timestamp&channel=chunlei&web=1&app_id=250528&clienttype=0")
|
||||||
|
if err == nil {
|
||||||
|
if resp.IsSuccess() && signJson.Errno == 0 {
|
||||||
|
stamp = signJson.Data.Stamp.String()
|
||||||
|
sign = signJson.Data.Sign
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
respJson := struct {
|
||||||
|
Errno int64 `json:"errno"`
|
||||||
|
List [1]struct {
|
||||||
|
Dlink string `json:"dlink"`
|
||||||
|
} `json:"list"`
|
||||||
|
}{}
|
||||||
|
resp, err = d.client.R().
|
||||||
|
SetQueryParam("sign", sign).
|
||||||
|
SetQueryParam("timestamp", stamp).
|
||||||
|
SetBody(url.Values{
|
||||||
|
"encrypt": {"0"},
|
||||||
|
"extra": {fmt.Sprintf(`{"sekey":"%s"}`, d.info.Seckey)},
|
||||||
|
"fid_list": {fmt.Sprintf("[%s]", file.GetID())},
|
||||||
|
"primaryid": {d.info.Shareid},
|
||||||
|
"product": {"share"},
|
||||||
|
"type": {"nolimit"},
|
||||||
|
"uk": {d.info.Uk},
|
||||||
|
}.Encode()).
|
||||||
|
SetResult(&respJson).
|
||||||
|
Post("api/sharedownload?app_id=250528&channel=chunlei&clienttype=12&web=1")
|
||||||
|
if err == nil {
|
||||||
|
if resp.IsSuccess() && respJson.Errno == 0 && respJson.List[0].Dlink != "" {
|
||||||
|
link.URL = respJson.List[0].Dlink
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
resp, err = d.client.R().
|
||||||
|
SetDoNotParseResponse(true).
|
||||||
|
Get(link.URL)
|
||||||
|
if err == nil {
|
||||||
|
defer resp.RawBody().Close()
|
||||||
|
if resp.IsError() {
|
||||||
|
byt, _ := io.ReadAll(resp.RawBody())
|
||||||
|
err = fmt.Errorf(" %s; %s; ", resp.Status(), byt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &link, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
|
// TODO create folder, optional
|
||||||
|
return errs.NotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
// TODO move obj, optional
|
||||||
|
return errs.NotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||||
|
// TODO rename obj, optional
|
||||||
|
return errs.NotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
// TODO copy obj, optional
|
||||||
|
return errs.NotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
|
// TODO remove obj, optional
|
||||||
|
return errs.NotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *BaiduShare) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
|
// TODO upload file, optional
|
||||||
|
return errs.NotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
|
// return nil, errs.NotSupport
|
||||||
|
//}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*BaiduShare)(nil)
|
37
drivers/baidu_share/meta.go
Normal file
37
drivers/baidu_share/meta.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package baidu_share
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
// Usually one of two
|
||||||
|
driver.RootPath
|
||||||
|
// driver.RootID
|
||||||
|
// define other
|
||||||
|
// Field string `json:"field" type:"select" required:"true" options:"a,b,c" default:"a"`
|
||||||
|
Surl string `json:"surl"`
|
||||||
|
Pwd string `json:"pwd"`
|
||||||
|
BDUSS string `json:"BDUSS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "BaiduShare",
|
||||||
|
LocalSort: true,
|
||||||
|
OnlyLocal: false,
|
||||||
|
OnlyProxy: false,
|
||||||
|
NoCache: false,
|
||||||
|
NoUpload: true,
|
||||||
|
NeedMs: false,
|
||||||
|
DefaultRoot: "/",
|
||||||
|
CheckStatus: false,
|
||||||
|
Alert: "",
|
||||||
|
NoOverwriteUpload: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &BaiduShare{}
|
||||||
|
})
|
||||||
|
}
|
1
drivers/baidu_share/types.go
Normal file
1
drivers/baidu_share/types.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package baidu_share
|
3
drivers/baidu_share/util.go
Normal file
3
drivers/baidu_share/util.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package baidu_share
|
||||||
|
|
||||||
|
// do others that not defined in Driver interface
|
@ -2,15 +2,15 @@ package cloudreve
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Cloudreve struct {
|
type Cloudreve struct {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package cloudreve
|
package cloudreve
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Resp struct {
|
type Resp struct {
|
||||||
@ -52,3 +53,8 @@ func objectToObj(f Object) *model.Object {
|
|||||||
IsFolder: f.Type == "dir",
|
IsFolder: f.Type == "dir",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
LoginCaptcha bool `json:"loginCaptcha"`
|
||||||
|
CaptchaType string `json:"captcha_type"`
|
||||||
|
}
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
package cloudreve
|
package cloudreve
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"github.com/alist-org/alist/v3/pkg/cookie"
|
"github.com/alist-org/alist/v3/pkg/cookie"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
json "github.com/json-iterator/go"
|
json "github.com/json-iterator/go"
|
||||||
"net/http"
|
jsoniter "github.com/json-iterator/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// do others that not defined in Driver interface
|
// do others that not defined in Driver interface
|
||||||
@ -72,13 +78,57 @@ func (d *Cloudreve) request(method string, path string, callback base.ReqCallbac
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Cloudreve) login() error {
|
func (d *Cloudreve) login() error {
|
||||||
return d.request(http.MethodPost, loginPath, func(req *resty.Request) {
|
var siteConfig Config
|
||||||
|
err := d.request(http.MethodGet, "/site/config", nil, &siteConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
err = d.doLogin(siteConfig.LoginCaptcha)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil && err.Error() != "CAPTCHA not match." {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Cloudreve) doLogin(needCaptcha bool) error {
|
||||||
|
var captchaCode string
|
||||||
|
var err error
|
||||||
|
if needCaptcha {
|
||||||
|
var captcha string
|
||||||
|
err = d.request(http.MethodGet, "/site/captcha", nil, &captcha)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(captcha) == 0 {
|
||||||
|
return errors.New("can not get captcha")
|
||||||
|
}
|
||||||
|
i := strings.Index(captcha, ",")
|
||||||
|
dec := base64.NewDecoder(base64.StdEncoding, strings.NewReader(captcha[i+1:]))
|
||||||
|
vRes, err := base.RestyClient.R().SetMultipartField(
|
||||||
|
"image", "validateCode.png", "image/png", dec).
|
||||||
|
Post(setting.GetStr(conf.OcrApi))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
|
||||||
|
return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
|
||||||
|
}
|
||||||
|
captchaCode = jsoniter.Get(vRes.Body(), "result").ToString()
|
||||||
|
}
|
||||||
|
var resp Resp
|
||||||
|
err = d.request(http.MethodPost, loginPath, func(req *resty.Request) {
|
||||||
req.SetBody(base.Json{
|
req.SetBody(base.Json{
|
||||||
"username": d.Addition.Username,
|
"username": d.Addition.Username,
|
||||||
"Password": d.Addition.Password,
|
"Password": d.Addition.Password,
|
||||||
"captchaCode": "",
|
"captchaCode": captchaCode,
|
||||||
})
|
})
|
||||||
}, nil)
|
}, &resp)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertSrc(obj model.Obj) map[string]interface{} {
|
func convertSrc(obj model.Obj) map[string]interface{} {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
@ -44,8 +45,7 @@ func (d *FTP) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]m
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res := make([]model.Obj, 0)
|
res := make([]model.Obj, 0)
|
||||||
for i, _ := range entries {
|
for _, entry := range entries {
|
||||||
entry := entries[i]
|
|
||||||
if entry.Name == "." || entry.Name == ".." {
|
if entry.Name == "." || entry.Name == ".." {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -64,13 +64,13 @@ func (d *FTP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*m
|
|||||||
if err := d.login(); err != nil {
|
if err := d.login(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resp, err := d.conn.Retr(file.GetPath())
|
|
||||||
if err != nil {
|
r := NewFTPFileReader(d.conn, file.GetPath())
|
||||||
return nil, err
|
link := &model.Link{
|
||||||
|
Data: r,
|
||||||
}
|
}
|
||||||
return &model.Link{
|
base.HandleRange(link, r, args.Header, file.GetSize())
|
||||||
Data: resp,
|
return link, nil
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *FTP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
func (d *FTP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
package ftp
|
package ftp
|
||||||
|
|
||||||
import "github.com/jlaffaye/ftp"
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jlaffaye/ftp"
|
||||||
|
)
|
||||||
|
|
||||||
// do others that not defined in Driver interface
|
// do others that not defined in Driver interface
|
||||||
|
|
||||||
@ -11,7 +18,7 @@ func (d *FTP) login() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conn, err := ftp.Dial(d.Address)
|
conn, err := ftp.Dial(d.Address, ftp.DialWithShutTimeout(10*time.Second))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -22,3 +29,81 @@ func (d *FTP) login() error {
|
|||||||
d.conn = conn
|
d.conn = conn
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An FTP file reader that implements io.ReadSeekCloser for seeking.
|
||||||
|
type FTPFileReader struct {
|
||||||
|
conn *ftp.ServerConn
|
||||||
|
resp *ftp.Response
|
||||||
|
offset int64
|
||||||
|
mu sync.Mutex
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFTPFileReader(conn *ftp.ServerConn, path string) *FTPFileReader {
|
||||||
|
return &FTPFileReader{
|
||||||
|
conn: conn,
|
||||||
|
path: path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FTPFileReader) Read(buf []byte) (n int, err error) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
if r.resp == nil {
|
||||||
|
r.resp, err = r.conn.RetrFrom(r.path, uint64(r.offset))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = r.resp.Read(buf)
|
||||||
|
r.offset += int64(n)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FTPFileReader) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
|
oldOffset := r.offset
|
||||||
|
var newOffset int64
|
||||||
|
switch whence {
|
||||||
|
case io.SeekStart:
|
||||||
|
newOffset = offset
|
||||||
|
case io.SeekCurrent:
|
||||||
|
newOffset = oldOffset + offset
|
||||||
|
case io.SeekEnd:
|
||||||
|
size, err := r.conn.FileSize(r.path)
|
||||||
|
if err != nil {
|
||||||
|
return oldOffset, err
|
||||||
|
}
|
||||||
|
newOffset = offset + int64(size)
|
||||||
|
default:
|
||||||
|
return -1, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if newOffset < 0 {
|
||||||
|
// offset out of range
|
||||||
|
return oldOffset, os.ErrInvalid
|
||||||
|
}
|
||||||
|
if newOffset == oldOffset {
|
||||||
|
// offset not changed, so return directly
|
||||||
|
return oldOffset, nil
|
||||||
|
}
|
||||||
|
r.offset = newOffset
|
||||||
|
|
||||||
|
if r.resp != nil {
|
||||||
|
// close the existing ftp data connection, otherwise the next read will be blocked
|
||||||
|
_ = r.resp.Close() // we do not care about whether it returns an error
|
||||||
|
r.resp = nil
|
||||||
|
}
|
||||||
|
return newOffset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FTPFileReader) Close() error {
|
||||||
|
if r.resp != nil {
|
||||||
|
return r.resp.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
@ -68,7 +67,7 @@ func (d *Local) GetAddition() driver.Additional {
|
|||||||
|
|
||||||
func (d *Local) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
func (d *Local) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
fullPath := dir.GetPath()
|
fullPath := dir.GetPath()
|
||||||
rawFiles, err := ioutil.ReadDir(fullPath)
|
rawFiles, err := readDir(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -144,7 +143,7 @@ func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
|||||||
}
|
}
|
||||||
srcBuf = videoBuf
|
srcBuf = videoBuf
|
||||||
} else {
|
} else {
|
||||||
imgData, err := ioutil.ReadFile(fullPath)
|
imgData, err := os.ReadFile(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ package local
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
ffmpeg "github.com/u2takey/ffmpeg-go"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
ffmpeg "github.com/u2takey/ffmpeg-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isSymlinkDir(f fs.FileInfo, path string) bool {
|
func isSymlinkDir(f fs.FileInfo, path string) bool {
|
||||||
@ -39,3 +41,17 @@ func GetSnapshot(videoPath string, frameNum int) (imgData *bytes.Buffer, err err
|
|||||||
}
|
}
|
||||||
return srcBuf, nil
|
return srcBuf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readDir(dirname string) ([]fs.FileInfo, error) {
|
||||||
|
f, err := os.Open(dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
list, err := f.Readdir(-1)
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
160
drivers/onedrive_app/driver.go
Normal file
160
drivers/onedrive_app/driver.go
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package onedrive_app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OnedriveAPP struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
AccessToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Init(ctx context.Context) error {
|
||||||
|
if d.ChunkSize < 1 {
|
||||||
|
d.ChunkSize = 5
|
||||||
|
}
|
||||||
|
return d.accessToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
files, err := d.getFiles(dir.GetPath())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
||||||
|
return fileToObj(src, dir.GetID()), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
f, err := d.GetFile(file.GetPath())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if f.File == nil {
|
||||||
|
return nil, errs.NotFile
|
||||||
|
}
|
||||||
|
return &model.Link{
|
||||||
|
URL: f.Url,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
|
url := d.GetMetaUrl(false, parentDir.GetPath()) + "/children"
|
||||||
|
data := base.Json{
|
||||||
|
"name": dirName,
|
||||||
|
"folder": base.Json{},
|
||||||
|
"@microsoft.graph.conflictBehavior": "rename",
|
||||||
|
}
|
||||||
|
_, err := d.Request(url, http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
parentPath := ""
|
||||||
|
if dstDir.GetID() == "" {
|
||||||
|
parentPath = dstDir.GetPath()
|
||||||
|
if utils.PathEqual(parentPath, "/") {
|
||||||
|
parentPath = path.Join("/drive/root", parentPath)
|
||||||
|
} else {
|
||||||
|
parentPath = path.Join("/drive/root:/", parentPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data := base.Json{
|
||||||
|
"parentReference": base.Json{
|
||||||
|
"id": dstDir.GetID(),
|
||||||
|
"path": parentPath,
|
||||||
|
},
|
||||||
|
"name": srcObj.GetName(),
|
||||||
|
}
|
||||||
|
url := d.GetMetaUrl(false, srcObj.GetPath())
|
||||||
|
_, err := d.Request(url, http.MethodPatch, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||||
|
var parentID string
|
||||||
|
if o, ok := srcObj.(*Object); ok {
|
||||||
|
parentID = o.ParentID
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("srcObj is not Object")
|
||||||
|
}
|
||||||
|
if parentID == "" {
|
||||||
|
parentID = "root"
|
||||||
|
}
|
||||||
|
data := base.Json{
|
||||||
|
"parentReference": base.Json{
|
||||||
|
"id": parentID,
|
||||||
|
},
|
||||||
|
"name": newName,
|
||||||
|
}
|
||||||
|
url := d.GetMetaUrl(false, srcObj.GetPath())
|
||||||
|
_, err := d.Request(url, http.MethodPatch, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
dst, err := d.GetFile(dstDir.GetPath())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data := base.Json{
|
||||||
|
"parentReference": base.Json{
|
||||||
|
"driveId": dst.ParentReference.DriveId,
|
||||||
|
"id": dst.Id,
|
||||||
|
},
|
||||||
|
"name": srcObj.GetName(),
|
||||||
|
}
|
||||||
|
url := d.GetMetaUrl(false, srcObj.GetPath()) + "/copy"
|
||||||
|
_, err = d.Request(url, http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(data)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
|
url := d.GetMetaUrl(false, obj.GetPath())
|
||||||
|
_, err := d.Request(url, http.MethodDelete, nil, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
|
var err error
|
||||||
|
if stream.GetSize() <= 4*1024*1024 {
|
||||||
|
err = d.upSmall(ctx, dstDir, stream)
|
||||||
|
} else {
|
||||||
|
err = d.upBig(ctx, dstDir, stream, up)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*OnedriveAPP)(nil)
|
28
drivers/onedrive_app/meta.go
Normal file
28
drivers/onedrive_app/meta.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package onedrive_app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
driver.RootPath
|
||||||
|
Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de" default:"global"`
|
||||||
|
ClientID string `json:"client_id" required:"true"`
|
||||||
|
ClientSecret string `json:"client_secret" required:"true"`
|
||||||
|
TenantID string `json:"tenant_id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "OnedriveAPP",
|
||||||
|
LocalSort: true,
|
||||||
|
DefaultRoot: "/",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &OnedriveAPP{}
|
||||||
|
})
|
||||||
|
}
|
74
drivers/onedrive_app/types.go
Normal file
74
drivers/onedrive_app/types.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package onedrive_app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Host struct {
|
||||||
|
Oauth string
|
||||||
|
Api string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenErr struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrorDescription string `json:"error_description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RespErr struct {
|
||||||
|
Error struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
LastModifiedDateTime time.Time `json:"lastModifiedDateTime"`
|
||||||
|
Url string `json:"@microsoft.graph.downloadUrl"`
|
||||||
|
File *struct {
|
||||||
|
MimeType string `json:"mimeType"`
|
||||||
|
} `json:"file"`
|
||||||
|
Thumbnails []struct {
|
||||||
|
Medium struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
} `json:"medium"`
|
||||||
|
} `json:"thumbnails"`
|
||||||
|
ParentReference struct {
|
||||||
|
DriveId string `json:"driveId"`
|
||||||
|
} `json:"parentReference"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Object struct {
|
||||||
|
model.ObjThumb
|
||||||
|
ParentID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileToObj(f File, parentID string) *Object {
|
||||||
|
thumb := ""
|
||||||
|
if len(f.Thumbnails) > 0 {
|
||||||
|
thumb = f.Thumbnails[0].Medium.Url
|
||||||
|
}
|
||||||
|
return &Object{
|
||||||
|
ObjThumb: model.ObjThumb{
|
||||||
|
Object: model.Object{
|
||||||
|
ID: f.Id,
|
||||||
|
Name: f.Name,
|
||||||
|
Size: f.Size,
|
||||||
|
Modified: f.LastModifiedDateTime,
|
||||||
|
IsFolder: f.File == nil,
|
||||||
|
},
|
||||||
|
Thumbnail: model.Thumbnail{Thumbnail: thumb},
|
||||||
|
//Url: model.Url{Url: f.Url},
|
||||||
|
},
|
||||||
|
ParentID: parentID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Files struct {
|
||||||
|
Value []File `json:"value"`
|
||||||
|
NextLink string `json:"@odata.nextLink"`
|
||||||
|
}
|
196
drivers/onedrive_app/util.go
Normal file
196
drivers/onedrive_app/util.go
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
package onedrive_app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
stdpath "path"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var onedriveHostMap = map[string]Host{
|
||||||
|
"global": {
|
||||||
|
Oauth: "https://login.microsoftonline.com",
|
||||||
|
Api: "https://graph.microsoft.com",
|
||||||
|
},
|
||||||
|
"cn": {
|
||||||
|
Oauth: "https://login.chinacloudapi.cn",
|
||||||
|
Api: "https://microsoftgraph.chinacloudapi.cn",
|
||||||
|
},
|
||||||
|
"us": {
|
||||||
|
Oauth: "https://login.microsoftonline.us",
|
||||||
|
Api: "https://graph.microsoft.us",
|
||||||
|
},
|
||||||
|
"de": {
|
||||||
|
Oauth: "https://login.microsoftonline.de",
|
||||||
|
Api: "https://graph.microsoft.de",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) GetMetaUrl(auth bool, path string) string {
|
||||||
|
host, _ := onedriveHostMap[d.Region]
|
||||||
|
path = utils.EncodePath(path, true)
|
||||||
|
if auth {
|
||||||
|
return host.Oauth
|
||||||
|
}
|
||||||
|
if path == "/" || path == "\\" {
|
||||||
|
return fmt.Sprintf("%s/v1.0/users/%s/drive/root", host.Api, d.Email)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s/v1.0/users/%s/drive/root:%s:", host.Api, d.Email, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) accessToken() error {
|
||||||
|
var err error
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
err = d._accessToken()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) _accessToken() error {
|
||||||
|
url := d.GetMetaUrl(true, "") + "/" + d.TenantID + "/oauth2/token"
|
||||||
|
var resp base.TokenResp
|
||||||
|
var e TokenErr
|
||||||
|
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
|
||||||
|
"grant_type": "client_credentials",
|
||||||
|
"client_id": d.ClientID,
|
||||||
|
"client_secret": d.ClientSecret,
|
||||||
|
"resource": "https://graph.microsoft.com/",
|
||||||
|
"scope": "https://graph.microsoft.com/.default",
|
||||||
|
}).Post(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.Error != "" {
|
||||||
|
return fmt.Errorf("%s", e.ErrorDescription)
|
||||||
|
}
|
||||||
|
if resp.AccessToken == "" {
|
||||||
|
return errs.EmptyToken
|
||||||
|
}
|
||||||
|
d.AccessToken = resp.AccessToken
|
||||||
|
op.MustSaveDriverStorage(d)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) Request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||||
|
req := base.RestyClient.R()
|
||||||
|
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
||||||
|
if callback != nil {
|
||||||
|
callback(req)
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
req.SetResult(resp)
|
||||||
|
}
|
||||||
|
var e RespErr
|
||||||
|
req.SetError(&e)
|
||||||
|
res, err := req.Execute(method, url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if e.Error.Code != "" {
|
||||||
|
if e.Error.Code == "InvalidAuthenticationToken" {
|
||||||
|
err = d.accessToken()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d.Request(url, method, callback, resp)
|
||||||
|
}
|
||||||
|
return nil, errors.New(e.Error.Message)
|
||||||
|
}
|
||||||
|
return res.Body(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) getFiles(path string) ([]File, error) {
|
||||||
|
var res []File
|
||||||
|
nextLink := d.GetMetaUrl(false, path) + "/children?$top=5000&$expand=thumbnails($select=medium)&$select=id,name,size,lastModifiedDateTime,content.downloadUrl,file,parentReference"
|
||||||
|
for nextLink != "" {
|
||||||
|
var files Files
|
||||||
|
_, err := d.Request(nextLink, http.MethodGet, nil, &files)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = append(res, files.Value...)
|
||||||
|
nextLink = files.NextLink
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) GetFile(path string) (*File, error) {
|
||||||
|
var file File
|
||||||
|
u := d.GetMetaUrl(false, path)
|
||||||
|
_, err := d.Request(u, http.MethodGet, nil, &file)
|
||||||
|
return &file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) upSmall(ctx context.Context, dstDir model.Obj, stream model.FileStreamer) error {
|
||||||
|
url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetPath(), stream.GetName())) + "/content"
|
||||||
|
data, err := io.ReadAll(stream)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = d.Request(url, http.MethodPut, func(req *resty.Request) {
|
||||||
|
req.SetBody(data).SetContext(ctx)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *OnedriveAPP) upBig(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
|
url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetPath(), stream.GetName())) + "/createUploadSession"
|
||||||
|
res, err := d.Request(url, http.MethodPost, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
|
||||||
|
var finish int64 = 0
|
||||||
|
DEFAULT := d.ChunkSize * 1024 * 1024
|
||||||
|
for finish < stream.GetSize() {
|
||||||
|
if utils.IsCanceled(ctx) {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
log.Debugf("upload: %d", finish)
|
||||||
|
var byteSize int64 = DEFAULT
|
||||||
|
left := stream.GetSize() - finish
|
||||||
|
if left < DEFAULT {
|
||||||
|
byteSize = left
|
||||||
|
}
|
||||||
|
byteData := make([]byte, byteSize)
|
||||||
|
n, err := io.ReadFull(stream, byteData)
|
||||||
|
log.Debug(err, n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("PUT", uploadUrl, bytes.NewBuffer(byteData))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
||||||
|
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
|
||||||
|
finish += byteSize
|
||||||
|
res, err := base.HttpClient.Do(req)
|
||||||
|
if res.StatusCode != 201 && res.StatusCode != 202 {
|
||||||
|
data, _ := io.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
return errors.New(string(data))
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
up(int(finish * 100 / stream.GetSize()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -59,7 +59,8 @@ func (d *S3) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]mo
|
|||||||
|
|
||||||
func (d *S3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
func (d *S3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
path := getKey(file.GetPath(), false)
|
path := getKey(file.GetPath(), false)
|
||||||
disposition := fmt.Sprintf(`attachment;filename="%s"`, url.QueryEscape(stdpath.Base(path)))
|
filename := stdpath.Base(path)
|
||||||
|
disposition := fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename))
|
||||||
input := &s3.GetObjectInput{
|
input := &s3.GetObjectInput{
|
||||||
Bucket: &d.Bucket,
|
Bucket: &d.Bucket,
|
||||||
Key: &path,
|
Key: &path,
|
||||||
@ -127,6 +128,9 @@ func (d *S3) Remove(ctx context.Context, obj model.Obj) error {
|
|||||||
|
|
||||||
func (d *S3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
func (d *S3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
uploader := s3manager.NewUploader(d.Session)
|
uploader := s3manager.NewUploader(d.Session)
|
||||||
|
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||||
|
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||||
|
}
|
||||||
key := getKey(stdpath.Join(dstDir.GetPath(), stream.GetName()), false)
|
key := getKey(stdpath.Join(dstDir.GetPath(), stream.GetName()), false)
|
||||||
log.Debugln("key:", key)
|
log.Debugln("key:", key)
|
||||||
input := &s3manager.UploadInput{
|
input := &s3manager.UploadInput{
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
@ -16,10 +15,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SMB struct {
|
type SMB struct {
|
||||||
|
lastConnTime int64
|
||||||
model.Storage
|
model.Storage
|
||||||
Addition
|
Addition
|
||||||
fs *smb2.Share
|
fs *smb2.Share
|
||||||
lastConnTime time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SMB) Config() driver.Config {
|
func (d *SMB) Config() driver.Config {
|
||||||
|
@ -6,17 +6,22 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hirochachacha/go-smb2"
|
"github.com/hirochachacha/go-smb2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (d *SMB) updateLastConnTime() {
|
func (d *SMB) updateLastConnTime() {
|
||||||
d.lastConnTime = time.Now()
|
atomic.StoreInt64(&d.lastConnTime, time.Now().Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SMB) cleanLastConnTime() {
|
func (d *SMB) cleanLastConnTime() {
|
||||||
d.lastConnTime = time.Now().AddDate(0, 0, -1)
|
atomic.StoreInt64(&d.lastConnTime, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SMB) getLastConnTime() time.Time {
|
||||||
|
return time.Unix(atomic.LoadInt64(&d.lastConnTime), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SMB) initFS() error {
|
func (d *SMB) initFS() error {
|
||||||
@ -43,7 +48,7 @@ func (d *SMB) initFS() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *SMB) checkConn() error {
|
func (d *SMB) checkConn() error {
|
||||||
if time.Since(d.lastConnTime) < 5*time.Minute {
|
if time.Since(d.getLastConnTime()) < 5*time.Minute {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if d.fs != nil {
|
if d.fs != nil {
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/drivers/base"
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -124,11 +125,11 @@ func (d *Teambition) Remove(ctx context.Context, obj model.Obj) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Teambition) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
func (d *Teambition) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
res, err := d.request("/projects", http.MethodGet, nil, nil)
|
res, err := d.request("/api/v2/users/me", http.MethodGet, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
token := GetBetweenStr(string(res), "strikerAuth":"", "","phoneForLogin")
|
token := utils.Json.Get(res, "strikerAuth").ToString()
|
||||||
var newFile *FileUpload
|
var newFile *FileUpload
|
||||||
if stream.GetSize() <= 20971520 {
|
if stream.GetSize() <= 20971520 {
|
||||||
// post upload
|
// post upload
|
||||||
|
@ -210,7 +210,7 @@ func (d *Teambition) finishUpload(file *FileUpload, parentId string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBetweenStr(str, start, end string) string {
|
func getBetweenStr(str, start, end string) string {
|
||||||
n := strings.Index(str, start)
|
n := strings.Index(str, start)
|
||||||
if n == -1 {
|
if n == -1 {
|
||||||
return ""
|
return ""
|
||||||
|
@ -32,42 +32,42 @@ func (d *Template) Drop(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Template) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
func (d *Template) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
// TODO return the files list
|
// TODO return the files list, required
|
||||||
return nil, errs.NotImplement
|
return nil, errs.NotImplement
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Template) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
func (d *Template) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
// TODO return link of file
|
// TODO return link of file, required
|
||||||
return nil, errs.NotImplement
|
return nil, errs.NotImplement
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Template) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
func (d *Template) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
// TODO create folder
|
// TODO create folder, optional
|
||||||
return errs.NotImplement
|
return errs.NotImplement
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Template) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
func (d *Template) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
// TODO move obj
|
// TODO move obj, optional
|
||||||
return errs.NotImplement
|
return errs.NotImplement
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Template) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
func (d *Template) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||||
// TODO rename obj
|
// TODO rename obj, optional
|
||||||
return errs.NotImplement
|
return errs.NotImplement
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Template) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
func (d *Template) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
// TODO copy obj
|
// TODO copy obj, optional
|
||||||
return errs.NotImplement
|
return errs.NotImplement
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Template) Remove(ctx context.Context, obj model.Obj) error {
|
func (d *Template) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
// TODO remove obj
|
// TODO remove obj, optional
|
||||||
return errs.NotImplement
|
return errs.NotImplement
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Template) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
func (d *Template) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
// TODO upload file
|
// TODO upload file, optional
|
||||||
return errs.NotImplement
|
return errs.NotImplement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,14 +14,17 @@ type Addition struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
Name: "Template",
|
Name: "Template",
|
||||||
LocalSort: false,
|
LocalSort: false,
|
||||||
OnlyLocal: false,
|
OnlyLocal: false,
|
||||||
OnlyProxy: false,
|
OnlyProxy: false,
|
||||||
NoCache: false,
|
NoCache: false,
|
||||||
NoUpload: false,
|
NoUpload: false,
|
||||||
NeedMs: false,
|
NeedMs: false,
|
||||||
DefaultRoot: "root, / or other",
|
DefaultRoot: "root, / or other",
|
||||||
|
CheckStatus: false,
|
||||||
|
Alert: "",
|
||||||
|
NoOverwriteUpload: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
142
drivers/trainbit/driver.go
Normal file
142
drivers/trainbit/driver.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
package trainbit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Trainbit struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiExpiredate, guid string
|
||||||
|
|
||||||
|
func (d *Trainbit) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Trainbit) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Trainbit) Init(ctx context.Context) error {
|
||||||
|
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
apiExpiredate, guid, err = getToken(d.ApiKey, d.AUSHELLPORTAL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Trainbit) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Trainbit) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
form := make(url.Values)
|
||||||
|
form.Set("parentid", strings.Split(dir.GetID(), "_")[0])
|
||||||
|
res, err := postForm("https://trainbit.com/lib/api/v1/listoffiles", form, apiExpiredate, d.ApiKey, d.AUSHELLPORTAL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var jsonData any
|
||||||
|
json.Unmarshal(data, &jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
object, err := parseRawFileObject(jsonData.(map[string]any)["items"].([]any))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return object, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Trainbit) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
res, err := get(fmt.Sprintf("https://trainbit.com/files/%s/", strings.Split(file.GetID(), "_")[0]), d.ApiKey, d.AUSHELLPORTAL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &model.Link{
|
||||||
|
URL: res.Header.Get("Location"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Trainbit) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
|
form := make(url.Values)
|
||||||
|
form.Set("name", local2provider(dirName, true))
|
||||||
|
form.Set("parentid", strings.Split(parentDir.GetID(), "_")[0])
|
||||||
|
_, err := postForm("https://trainbit.com/lib/api/v1/createfolder", form, apiExpiredate, d.ApiKey, d.AUSHELLPORTAL)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Trainbit) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
form := make(url.Values)
|
||||||
|
form.Set("sourceid", strings.Split(srcObj.GetID(), "_")[0])
|
||||||
|
form.Set("destinationid", strings.Split(dstDir.GetID(), "_")[0])
|
||||||
|
_, err := postForm("https://trainbit.com/lib/api/v1/move", form, apiExpiredate, d.ApiKey, d.AUSHELLPORTAL)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Trainbit) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||||
|
form := make(url.Values)
|
||||||
|
form.Set("id", strings.Split(srcObj.GetID(), "_")[0])
|
||||||
|
form.Set("name", local2provider(newName, srcObj.IsDir()))
|
||||||
|
_, err := postForm("https://trainbit.com/lib/api/v1/edit", form, apiExpiredate, d.ApiKey, d.AUSHELLPORTAL)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Trainbit) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||||
|
return errs.NotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Trainbit) Remove(ctx context.Context, obj model.Obj) error {
|
||||||
|
form := make(url.Values)
|
||||||
|
form.Set("id", strings.Split(obj.GetID(), "_")[0])
|
||||||
|
_, err := postForm("https://trainbit.com/lib/api/v1/delete", form, apiExpiredate, d.ApiKey, d.AUSHELLPORTAL)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Trainbit) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
|
endpoint, _ := url.Parse("https://tb28.trainbit.com/api/upload/send_raw/")
|
||||||
|
query := &url.Values{}
|
||||||
|
query.Add("q", strings.Split(dstDir.GetID(), "_")[1])
|
||||||
|
query.Add("guid", guid)
|
||||||
|
query.Add("name", url.QueryEscape(local2provider(stream.GetName(), false) + "."))
|
||||||
|
endpoint.RawQuery = query.Encode()
|
||||||
|
var total int64
|
||||||
|
total = 0
|
||||||
|
progressReader := &ProgressReader{
|
||||||
|
stream,
|
||||||
|
func(byteNum int) {
|
||||||
|
total += int64(byteNum)
|
||||||
|
up(int(math.Round(float64(total) / float64(stream.GetSize()) * 100)))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(http.MethodPost, endpoint.String(), progressReader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "text/json; charset=UTF-8")
|
||||||
|
_, err = http.DefaultClient.Do(req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*Trainbit)(nil)
|
29
drivers/trainbit/meta.go
Normal file
29
drivers/trainbit/meta.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package trainbit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
driver.RootID
|
||||||
|
AUSHELLPORTAL string `json:"AUSHELLPORTAL" required:"true"`
|
||||||
|
ApiKey string `json:"apikey" required:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "Trainbit",
|
||||||
|
LocalSort: false,
|
||||||
|
OnlyLocal: false,
|
||||||
|
OnlyProxy: false,
|
||||||
|
NoCache: false,
|
||||||
|
NoUpload: false,
|
||||||
|
NeedMs: false,
|
||||||
|
DefaultRoot: "0_000",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &Trainbit{}
|
||||||
|
})
|
||||||
|
}
|
1
drivers/trainbit/types.go
Normal file
1
drivers/trainbit/types.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package trainbit
|
134
drivers/trainbit/util.go
Normal file
134
drivers/trainbit/util.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package trainbit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProgressReader struct {
|
||||||
|
io.Reader
|
||||||
|
reporter func(byteNum int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (progressReader *ProgressReader) Read(data []byte) (int, error) {
|
||||||
|
byteNum, err := progressReader.Reader.Read(data)
|
||||||
|
progressReader.reporter(byteNum)
|
||||||
|
return byteNum, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(url string, apiKey string, AUSHELLPORTAL string) (*http.Response, error) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: ".AUSHELLPORTAL",
|
||||||
|
Value: AUSHELLPORTAL,
|
||||||
|
MaxAge: 2 * 60,
|
||||||
|
})
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: "retkeyapi",
|
||||||
|
Value: apiKey,
|
||||||
|
MaxAge: 2 * 60,
|
||||||
|
})
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func postForm(endpoint string, data url.Values, apiExpiredate string, apiKey string, AUSHELLPORTAL string) (*http.Response, error) {
|
||||||
|
extData := make(url.Values)
|
||||||
|
for key, value := range data {
|
||||||
|
extData[key] = make([]string, len(value))
|
||||||
|
copy(extData[key], value)
|
||||||
|
}
|
||||||
|
extData.Set("apikey", apiKey)
|
||||||
|
extData.Set("expiredate", apiExpiredate)
|
||||||
|
req, err := http.NewRequest(http.MethodPost, endpoint, strings.NewReader(extData.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: ".AUSHELLPORTAL",
|
||||||
|
Value: AUSHELLPORTAL,
|
||||||
|
MaxAge: 2 * 60,
|
||||||
|
})
|
||||||
|
req.AddCookie(&http.Cookie{
|
||||||
|
Name: "retkeyapi",
|
||||||
|
Value: apiKey,
|
||||||
|
MaxAge: 2 * 60,
|
||||||
|
})
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getToken(apiKey string, AUSHELLPORTAL string) (string, string, error) {
|
||||||
|
res, err := get("https://trainbit.com/files/", apiKey, AUSHELLPORTAL)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
text := string(data)
|
||||||
|
apiExpiredateReg := regexp.MustCompile(`core.api.expiredate = '([^']*)';`)
|
||||||
|
result := apiExpiredateReg.FindAllStringSubmatch(text, -1)
|
||||||
|
apiExpiredate := result[0][1]
|
||||||
|
guidReg := regexp.MustCompile(`app.vars.upload.guid = '([^']*)';`)
|
||||||
|
result = guidReg.FindAllStringSubmatch(text, -1)
|
||||||
|
guid := result[0][1]
|
||||||
|
return apiExpiredate, guid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func local2provider(filename string, isFolder bool) string {
|
||||||
|
if isFolder {
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
return filename + ".delete_suffix"
|
||||||
|
}
|
||||||
|
|
||||||
|
func provider2local(filename string) string {
|
||||||
|
filename = html.UnescapeString(filename)
|
||||||
|
index := strings.LastIndex(filename, ".delete_suffix")
|
||||||
|
if index != -1 {
|
||||||
|
filename = filename[:index]
|
||||||
|
}
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRawFileObject(rawObject []any) ([]model.Obj, error) {
|
||||||
|
objectList := make([]model.Obj, 0)
|
||||||
|
for _, each := range rawObject {
|
||||||
|
object := each.(map[string]any)
|
||||||
|
if object["id"].(string) == "0" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
isFolder := int64(object["ty"].(float64)) == 1
|
||||||
|
var name string
|
||||||
|
if object["ext"].(string) != "" {
|
||||||
|
name = strings.Join([]string{object["name"].(string), object["ext"].(string)}, ".")
|
||||||
|
} else {
|
||||||
|
name = object["name"].(string)
|
||||||
|
}
|
||||||
|
modified, err := time.Parse("2006/01/02 15:04:05", object["modified"].(string))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
objectList = append(objectList, model.Obj(&model.Object{
|
||||||
|
ID: strings.Join([]string{object["id"].(string), strings.Split(object["uploadurl"].(string), "=")[1]}, "_"),
|
||||||
|
Name: provider2local(name),
|
||||||
|
Size: int64(object["byte"].(float64)),
|
||||||
|
Modified: modified.Add(-210 * time.Minute),
|
||||||
|
IsFolder: isFolder,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return objectList, nil
|
||||||
|
}
|
79
drivers/url_tree/driver.go
Normal file
79
drivers/url_tree/driver.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package url_tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
stdpath "path"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Urls struct {
|
||||||
|
model.Storage
|
||||||
|
Addition
|
||||||
|
root *Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) Config() driver.Config {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) GetAddition() driver.Additional {
|
||||||
|
return &d.Addition
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) Init(ctx context.Context) error {
|
||||||
|
node, err := BuildTree(d.UrlStructure, d.HeadSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
node.calSize()
|
||||||
|
d.root = node
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) Drop(ctx context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||||
|
node := GetNodeFromRootByPath(d.root, path)
|
||||||
|
return nodeToObj(node, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||||
|
node := GetNodeFromRootByPath(d.root, dir.GetPath())
|
||||||
|
log.Debugf("path: %s, node: %+v", dir.GetPath(), node)
|
||||||
|
if node == nil {
|
||||||
|
return nil, errs.ObjectNotFound
|
||||||
|
}
|
||||||
|
if node.isFile() {
|
||||||
|
return nil, errs.NotFolder
|
||||||
|
}
|
||||||
|
return utils.SliceConvert(node.Children, func(node *Node) (model.Obj, error) {
|
||||||
|
return nodeToObj(node, stdpath.Join(dir.GetPath(), node.Name))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Urls) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
|
node := GetNodeFromRootByPath(d.root, file.GetPath())
|
||||||
|
log.Debugf("path: %s, node: %+v", file.GetPath(), node)
|
||||||
|
if node == nil {
|
||||||
|
return nil, errs.ObjectNotFound
|
||||||
|
}
|
||||||
|
if node.isFile() {
|
||||||
|
return &model.Link{
|
||||||
|
URL: node.Url,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, errs.NotFile
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||||
|
// return nil, errs.NotSupport
|
||||||
|
//}
|
||||||
|
|
||||||
|
var _ driver.Driver = (*Urls)(nil)
|
35
drivers/url_tree/meta.go
Normal file
35
drivers/url_tree/meta.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package url_tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addition struct {
|
||||||
|
// Usually one of two
|
||||||
|
// driver.RootPath
|
||||||
|
// driver.RootID
|
||||||
|
// define other
|
||||||
|
UrlStructure string `json:"url_structure" type:"text" required:"true" default:"https://jsd.nn.ci/gh/alist-org/alist/README.md\nhttps://jsd.nn.ci/gh/alist-org/alist/README_cn.md\nfolder:\n CONTRIBUTING.md:1635:https://jsd.nn.ci/gh/alist-org/alist/CONTRIBUTING.md\n CODE_OF_CONDUCT.md:2093:https://jsd.nn.ci/gh/alist-org/alist/CODE_OF_CONDUCT.md" help:"structure:FolderName:\n [FileName:][FileSize:][Modified:]Url"`
|
||||||
|
HeadSize bool `json:"head_size" type:"bool" default:"false" help:"Use head method to get file size, but it may be failed."`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = driver.Config{
|
||||||
|
Name: "UrlTree",
|
||||||
|
LocalSort: true,
|
||||||
|
OnlyLocal: false,
|
||||||
|
OnlyProxy: false,
|
||||||
|
NoCache: true,
|
||||||
|
NoUpload: true,
|
||||||
|
NeedMs: false,
|
||||||
|
DefaultRoot: "",
|
||||||
|
CheckStatus: true,
|
||||||
|
Alert: "",
|
||||||
|
NoOverwriteUpload: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
op.RegisterDriver(func() driver.Driver {
|
||||||
|
return &Urls{}
|
||||||
|
})
|
||||||
|
}
|
46
drivers/url_tree/types.go
Normal file
46
drivers/url_tree/types.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package url_tree
|
||||||
|
|
||||||
|
// Node is a node in the folder tree
|
||||||
|
type Node struct {
|
||||||
|
Url string
|
||||||
|
Name string
|
||||||
|
Level int
|
||||||
|
Modified int64
|
||||||
|
Size int64
|
||||||
|
Children []*Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *Node) getByPath(paths []string) *Node {
|
||||||
|
if len(paths) == 0 || node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if node.Name != paths[0] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(paths) == 1 {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
for _, child := range node.Children {
|
||||||
|
tmp := child.getByPath(paths[1:])
|
||||||
|
if tmp != nil {
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *Node) isFile() bool {
|
||||||
|
return node.Url != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (node *Node) calSize() int64 {
|
||||||
|
if node.isFile() {
|
||||||
|
return node.Size
|
||||||
|
}
|
||||||
|
var size int64 = 0
|
||||||
|
for _, child := range node.Children {
|
||||||
|
size += child.calSize()
|
||||||
|
}
|
||||||
|
node.Size = size
|
||||||
|
return size
|
||||||
|
}
|
47
drivers/url_tree/urls_test.go
Normal file
47
drivers/url_tree/urls_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package url_tree_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/url_tree"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testTree() (*url_tree.Node, error) {
|
||||||
|
text := `folder1:
|
||||||
|
name1:https://url1
|
||||||
|
http://url2
|
||||||
|
folder2:
|
||||||
|
http://url3
|
||||||
|
http://url4
|
||||||
|
http://url5
|
||||||
|
folder3:
|
||||||
|
http://url6
|
||||||
|
http://url7
|
||||||
|
http://url8`
|
||||||
|
return url_tree.BuildTree(text, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildTree(t *testing.T) {
|
||||||
|
node, err := testTree()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to build tree: %+v", err)
|
||||||
|
} else {
|
||||||
|
t.Logf("tree: %+v", node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNode(t *testing.T) {
|
||||||
|
root, err := testTree()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to build tree: %+v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node := url_tree.GetNodeFromRootByPath(root, "/")
|
||||||
|
if node != root {
|
||||||
|
t.Errorf("got wrong node: %+v", node)
|
||||||
|
}
|
||||||
|
url3 := url_tree.GetNodeFromRootByPath(root, "/folder1/folder2/url3")
|
||||||
|
if url3 != root.Children[0].Children[2].Children[0] {
|
||||||
|
t.Errorf("got wrong node: %+v", url3)
|
||||||
|
}
|
||||||
|
}
|
192
drivers/url_tree/util.go
Normal file
192
drivers/url_tree/util.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package url_tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
stdpath "path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// build tree from text, text structure definition:
|
||||||
|
/**
|
||||||
|
* FolderName:
|
||||||
|
* [FileName:][FileSize:][Modified:]Url
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* For example:
|
||||||
|
* folder1:
|
||||||
|
* name1:url1
|
||||||
|
* url2
|
||||||
|
* folder2:
|
||||||
|
* url3
|
||||||
|
* url4
|
||||||
|
* url5
|
||||||
|
* folder3:
|
||||||
|
* url6
|
||||||
|
* url7
|
||||||
|
* url8
|
||||||
|
*/
|
||||||
|
// if there are no name, use the last segment of url as name
|
||||||
|
func BuildTree(text string, headSize bool) (*Node, error) {
|
||||||
|
lines := strings.Split(text, "\n")
|
||||||
|
var root = &Node{Level: -1, Name: "root"}
|
||||||
|
stack := []*Node{root}
|
||||||
|
for _, line := range lines {
|
||||||
|
// calculate indent
|
||||||
|
indent := 0
|
||||||
|
for i := 0; i < len(line); i++ {
|
||||||
|
if line[i] != ' ' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indent++
|
||||||
|
}
|
||||||
|
// if indent is not a multiple of 2, it is an error
|
||||||
|
if indent%2 != 0 {
|
||||||
|
return nil, fmt.Errorf("the line '%s' is not a multiple of 2", line)
|
||||||
|
}
|
||||||
|
// calculate level
|
||||||
|
level := indent / 2
|
||||||
|
line = strings.TrimSpace(line[indent:])
|
||||||
|
// if the line is empty, skip
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// if level isn't greater than the level of the top of the stack
|
||||||
|
// it is not the child of the top of the stack
|
||||||
|
for level <= stack[len(stack)-1].Level {
|
||||||
|
// pop the top of the stack
|
||||||
|
stack = stack[:len(stack)-1]
|
||||||
|
}
|
||||||
|
// if the line is a folder
|
||||||
|
if isFolder(line) {
|
||||||
|
// create a new node
|
||||||
|
node := &Node{
|
||||||
|
Level: level,
|
||||||
|
Name: strings.TrimSuffix(line, ":"),
|
||||||
|
}
|
||||||
|
// add the node to the top of the stack
|
||||||
|
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
|
||||||
|
// push the node to the stack
|
||||||
|
stack = append(stack, node)
|
||||||
|
} else {
|
||||||
|
// if the line is a file
|
||||||
|
// create a new node
|
||||||
|
node, err := parseFileLine(line, headSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
node.Level = level
|
||||||
|
// add the node to the top of the stack
|
||||||
|
stack[len(stack)-1].Children = append(stack[len(stack)-1].Children, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFolder(line string) bool {
|
||||||
|
return strings.HasSuffix(line, ":")
|
||||||
|
}
|
||||||
|
|
||||||
|
// line definition:
|
||||||
|
// [FileName:][FileSize:][Modified:]Url
|
||||||
|
func parseFileLine(line string, headSize bool) (*Node, error) {
|
||||||
|
// if there is no url, it is an error
|
||||||
|
if !strings.Contains(line, "http://") && !strings.Contains(line, "https://") {
|
||||||
|
return nil, fmt.Errorf("invalid line: %s, because url is required for file", line)
|
||||||
|
}
|
||||||
|
index := strings.Index(line, "http://")
|
||||||
|
if index == -1 {
|
||||||
|
index = strings.Index(line, "https://")
|
||||||
|
}
|
||||||
|
url := line[index:]
|
||||||
|
info := line[:index]
|
||||||
|
node := &Node{
|
||||||
|
Url: url,
|
||||||
|
}
|
||||||
|
haveSize := false
|
||||||
|
if index > 0 {
|
||||||
|
if !strings.HasSuffix(info, ":") {
|
||||||
|
return nil, fmt.Errorf("invalid line: %s, because file info must end with ':'", line)
|
||||||
|
}
|
||||||
|
info = info[:len(info)-1]
|
||||||
|
if info == "" {
|
||||||
|
return nil, fmt.Errorf("invalid line: %s, because file name can't be empty", line)
|
||||||
|
}
|
||||||
|
infoParts := strings.Split(info, ":")
|
||||||
|
node.Name = infoParts[0]
|
||||||
|
if len(infoParts) > 1 {
|
||||||
|
size, err := strconv.ParseInt(infoParts[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid line: %s, because file size must be an integer", line)
|
||||||
|
}
|
||||||
|
node.Size = size
|
||||||
|
haveSize = true
|
||||||
|
if len(infoParts) > 2 {
|
||||||
|
modified, err := strconv.ParseInt(infoParts[2], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid line: %s, because file modified must be an unix timestamp", line)
|
||||||
|
}
|
||||||
|
node.Modified = modified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node.Name = stdpath.Base(url)
|
||||||
|
}
|
||||||
|
if !haveSize && headSize {
|
||||||
|
size, err := getSizeFromUrl(url)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("get size from url error: %s", err)
|
||||||
|
} else {
|
||||||
|
node.Size = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitPath(path string) []string {
|
||||||
|
if path == "/" {
|
||||||
|
return []string{"root"}
|
||||||
|
}
|
||||||
|
parts := strings.Split(path, "/")
|
||||||
|
parts[0] = "root"
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNodeFromRootByPath(root *Node, path string) *Node {
|
||||||
|
return root.getByPath(splitPath(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeToObj(node *Node, path string) (model.Obj, error) {
|
||||||
|
if node == nil {
|
||||||
|
return nil, errs.ObjectNotFound
|
||||||
|
}
|
||||||
|
return &model.Object{
|
||||||
|
Name: node.Name,
|
||||||
|
Size: node.Size,
|
||||||
|
Modified: time.Unix(node.Modified, 0),
|
||||||
|
IsFolder: !node.isFile(),
|
||||||
|
Path: path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSizeFromUrl(url string) (int64, error) {
|
||||||
|
res, err := base.RestyClient.R().SetDoNotParseResponse(true).Head(url)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer res.RawResponse.Body.Close()
|
||||||
|
if res.StatusCode() >= 300 {
|
||||||
|
return 0, fmt.Errorf("get size from url %s failed, status code: %d", url, res.StatusCode())
|
||||||
|
}
|
||||||
|
size, err := strconv.ParseInt(res.Header().Get("Content-Length"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return size, nil
|
||||||
|
}
|
22
go.mod
22
go.mod
@ -3,12 +3,12 @@ module github.com/alist-org/alist/v3
|
|||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/SheltonZhu/115driver v1.0.13
|
github.com/SheltonZhu/115driver v1.0.14
|
||||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
||||||
github.com/aws/aws-sdk-go v1.44.194
|
github.com/aws/aws-sdk-go v1.44.194
|
||||||
github.com/blevesearch/bleve/v2 v2.3.6
|
github.com/blevesearch/bleve/v2 v2.3.7
|
||||||
github.com/caarlos0/env/v7 v7.1.0
|
github.com/caarlos0/env/v7 v7.1.0
|
||||||
github.com/deckarep/golang-set/v2 v2.2.0
|
github.com/deckarep/golang-set/v2 v2.3.0
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564
|
||||||
github.com/gin-contrib/cors v1.4.0
|
github.com/gin-contrib/cors v1.4.0
|
||||||
@ -26,10 +26,10 @@ require (
|
|||||||
github.com/pkg/sftp v1.13.5
|
github.com/pkg/sftp v1.13.5
|
||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.7.0
|
||||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca
|
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca
|
||||||
github.com/u2takey/ffmpeg-go v0.4.1
|
github.com/u2takey/ffmpeg-go v0.4.1
|
||||||
github.com/upyun/go-sdk/v3 v3.0.3
|
github.com/upyun/go-sdk/v3 v3.0.4
|
||||||
github.com/winfsp/cgofuse v1.5.0
|
github.com/winfsp/cgofuse v1.5.0
|
||||||
golang.org/x/crypto v0.7.0
|
golang.org/x/crypto v0.7.0
|
||||||
golang.org/x/image v0.6.0
|
golang.org/x/image v0.6.0
|
||||||
@ -48,20 +48,20 @@ require (
|
|||||||
github.com/andreburgaud/crypt2go v1.1.0 // indirect
|
github.com/andreburgaud/crypt2go v1.1.0 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
||||||
github.com/blevesearch/bleve_index_api v1.0.5 // indirect
|
github.com/blevesearch/bleve_index_api v1.0.5 // indirect
|
||||||
github.com/blevesearch/geo v0.1.16 // indirect
|
github.com/blevesearch/geo v0.1.17 // indirect
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.4 // indirect
|
github.com/blevesearch/scorch_segment_api/v2 v2.1.4 // indirect
|
||||||
github.com/blevesearch/segment v0.9.0 // indirect
|
github.com/blevesearch/segment v0.9.1 // indirect
|
||||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||||
github.com/blevesearch/upsidedown_store_api v1.0.1 // indirect
|
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||||
github.com/blevesearch/vellum v1.0.9 // indirect
|
github.com/blevesearch/vellum v1.0.9 // indirect
|
||||||
github.com/blevesearch/zapx/v11 v11.3.7 // indirect
|
github.com/blevesearch/zapx/v11 v11.3.7 // indirect
|
||||||
github.com/blevesearch/zapx/v12 v12.3.7 // indirect
|
github.com/blevesearch/zapx/v12 v12.3.7 // indirect
|
||||||
github.com/blevesearch/zapx/v13 v13.3.7 // indirect
|
github.com/blevesearch/zapx/v13 v13.3.7 // indirect
|
||||||
github.com/blevesearch/zapx/v14 v14.3.7 // indirect
|
github.com/blevesearch/zapx/v14 v14.3.7 // indirect
|
||||||
github.com/blevesearch/zapx/v15 v15.3.8 // indirect
|
github.com/blevesearch/zapx/v15 v15.3.9 // indirect
|
||||||
github.com/bluele/gcache v0.0.2 // indirect
|
github.com/bluele/gcache v0.0.2 // indirect
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||||
github.com/bytedance/sonic v1.8.0 // indirect
|
github.com/bytedance/sonic v1.8.0 // indirect
|
||||||
@ -76,10 +76,10 @@ require (
|
|||||||
github.com/goccy/go-json v0.10.0 // indirect
|
github.com/goccy/go-json v0.10.0 // indirect
|
||||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||||
github.com/golang/protobuf v1.5.0 // indirect
|
github.com/golang/protobuf v1.5.0 // indirect
|
||||||
github.com/golang/snappy v0.0.1 // indirect
|
github.com/golang/snappy v0.0.3 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgx/v5 v5.3.0 // indirect
|
github.com/jackc/pgx/v5 v5.3.0 // indirect
|
||||||
|
46
go.sum
46
go.sum
@ -2,8 +2,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
|
|||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXlp4riwo=
|
github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXlp4riwo=
|
||||||
github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
||||||
github.com/SheltonZhu/115driver v1.0.13 h1:YEhQ3iMvd5TD6Xp1wKg+73KgdCMjc0pDoT1eCNXnA3M=
|
github.com/SheltonZhu/115driver v1.0.14 h1:uW3dl8J9KDMw+3gPxQdhTysoGhw0/uI1484GT9xhfU4=
|
||||||
github.com/SheltonZhu/115driver v1.0.13/go.mod h1:00ixivHH5HqDj4S7kAWbkuUrjtsJTxc7cGv5RMw3RVs=
|
github.com/SheltonZhu/115driver v1.0.14/go.mod h1:00ixivHH5HqDj4S7kAWbkuUrjtsJTxc7cGv5RMw3RVs=
|
||||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a h1:RenIAa2q4H8UcS/cqmwdT1WCWIAH5aumP8m8RpbqVsE=
|
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a h1:RenIAa2q4H8UcS/cqmwdT1WCWIAH5aumP8m8RpbqVsE=
|
||||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a/go.mod h1:sSBbaOg90XwWKtpT56kVujF0bIeVITnPlssLclogS04=
|
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a/go.mod h1:sSBbaOg90XwWKtpT56kVujF0bIeVITnPlssLclogS04=
|
||||||
github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
|
github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
|
||||||
@ -17,12 +17,12 @@ github.com/aws/aws-sdk-go v1.44.194 h1:1ZDK+QDcc5oRbZGgRZSz561eR8XVizXCeGpoZKo33
|
|||||||
github.com/aws/aws-sdk-go v1.44.194/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
github.com/aws/aws-sdk-go v1.44.194/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||||
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||||
github.com/blevesearch/bleve/v2 v2.3.6 h1:NlntUHcV5CSWIhpugx4d/BRMGCiaoI8ZZXrXlahzNq4=
|
github.com/blevesearch/bleve/v2 v2.3.7 h1:nIfIrhv28tvgBpbVF8Dq7/U1zW/YiwSqg/PBgE3x8bo=
|
||||||
github.com/blevesearch/bleve/v2 v2.3.6/go.mod h1:JM2legf1cKVkdV8Ehu7msKIOKC0McSw0Q16Fmv9vsW4=
|
github.com/blevesearch/bleve/v2 v2.3.7/go.mod h1:2tToYD6mDeseIA13jcZiEEqYrVLg6xdk0v6+F7dWquU=
|
||||||
github.com/blevesearch/bleve_index_api v1.0.5 h1:Lc986kpC4Z0/n1g3gg8ul7H+lxgOQPcXb9SxvQGu+tw=
|
github.com/blevesearch/bleve_index_api v1.0.5 h1:Lc986kpC4Z0/n1g3gg8ul7H+lxgOQPcXb9SxvQGu+tw=
|
||||||
github.com/blevesearch/bleve_index_api v1.0.5/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
github.com/blevesearch/bleve_index_api v1.0.5/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||||
github.com/blevesearch/geo v0.1.16 h1:unVaqUmlwprk56596OQRkGjtq1VZ8XFWSARj+h2cIBY=
|
github.com/blevesearch/geo v0.1.17 h1:AguzI6/5mHXapzB0gE9IKWo+wWPHZmXZoscHcjFgAFA=
|
||||||
github.com/blevesearch/geo v0.1.16/go.mod h1:a1OlySNE+oDQ5qY0vJGYNoLIsMpbKbx8dnmuRP8D7H0=
|
github.com/blevesearch/geo v0.1.17/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM=
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||||
@ -31,12 +31,12 @@ github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCD
|
|||||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.4 h1:LmGmo5twU3gV+natJbKmOktS9eMhokPGKWuR+jX84vk=
|
github.com/blevesearch/scorch_segment_api/v2 v2.1.4 h1:LmGmo5twU3gV+natJbKmOktS9eMhokPGKWuR+jX84vk=
|
||||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.4/go.mod h1:PgVnbbg/t1UkgezPDu8EHLi1BHQ17xUwsFdU6NnOYS0=
|
github.com/blevesearch/scorch_segment_api/v2 v2.1.4/go.mod h1:PgVnbbg/t1UkgezPDu8EHLi1BHQ17xUwsFdU6NnOYS0=
|
||||||
github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac=
|
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||||
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
|
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
||||||
github.com/blevesearch/upsidedown_store_api v1.0.1 h1:1SYRwyoFLwG3sj0ed89RLtM15amfX2pXlYbFOnF8zNU=
|
github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMGZzVrdmaozG2MfoB+A=
|
||||||
github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q=
|
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||||
github.com/blevesearch/vellum v1.0.9 h1:PL+NWVk3dDGPCV0hoDu9XLLJgqU4E5s/dOeEJByQ2uQ=
|
github.com/blevesearch/vellum v1.0.9 h1:PL+NWVk3dDGPCV0hoDu9XLLJgqU4E5s/dOeEJByQ2uQ=
|
||||||
github.com/blevesearch/vellum v1.0.9/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
github.com/blevesearch/vellum v1.0.9/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||||
github.com/blevesearch/zapx/v11 v11.3.7 h1:Y6yIAF/DVPiqZUA/jNgSLXmqewfzwHzuwfKyfdG+Xaw=
|
github.com/blevesearch/zapx/v11 v11.3.7 h1:Y6yIAF/DVPiqZUA/jNgSLXmqewfzwHzuwfKyfdG+Xaw=
|
||||||
@ -47,8 +47,8 @@ github.com/blevesearch/zapx/v13 v13.3.7 h1:igIQg5eKmjw168I7av0Vtwedf7kHnQro/M+ub
|
|||||||
github.com/blevesearch/zapx/v13 v13.3.7/go.mod h1:yyrB4kJ0OT75UPZwT/zS+Ru0/jYKorCOOSY5dBzAy+s=
|
github.com/blevesearch/zapx/v13 v13.3.7/go.mod h1:yyrB4kJ0OT75UPZwT/zS+Ru0/jYKorCOOSY5dBzAy+s=
|
||||||
github.com/blevesearch/zapx/v14 v14.3.7 h1:gfe+fbWslDWP/evHLtp/GOvmNM3sw1BbqD7LhycBX20=
|
github.com/blevesearch/zapx/v14 v14.3.7 h1:gfe+fbWslDWP/evHLtp/GOvmNM3sw1BbqD7LhycBX20=
|
||||||
github.com/blevesearch/zapx/v14 v14.3.7/go.mod h1:9J/RbOkqZ1KSjmkOes03AkETX7hrXT0sFMpWH4ewC4w=
|
github.com/blevesearch/zapx/v14 v14.3.7/go.mod h1:9J/RbOkqZ1KSjmkOes03AkETX7hrXT0sFMpWH4ewC4w=
|
||||||
github.com/blevesearch/zapx/v15 v15.3.8 h1:q4uMngBHzL1IIhRc8AJUEkj6dGOE3u1l3phLu7hq8uk=
|
github.com/blevesearch/zapx/v15 v15.3.9 h1:/s9zqKxFaZKQTTcMO2b/Tup0ch5MSztlvw+frVDfIBk=
|
||||||
github.com/blevesearch/zapx/v15 v15.3.8/go.mod h1:m7Y6m8soYUvS7MjN9eKlz1xrLCcmqfFadmu7GhWIrLY=
|
github.com/blevesearch/zapx/v15 v15.3.9/go.mod h1:m7Y6m8soYUvS7MjN9eKlz1xrLCcmqfFadmu7GhWIrLY=
|
||||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
@ -66,8 +66,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/deckarep/golang-set/v2 v2.2.0 h1:2pMQd3Soi6qfw7E5MMKaEh5W5ES18bW3AbFFnGl6LgQ=
|
github.com/deckarep/golang-set/v2 v2.3.0 h1:qs18EKUfHm2X9fA50Mr/M5hccg2tNnVqsiBImnyDs0g=
|
||||||
github.com/deckarep/golang-set/v2 v2.2.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
github.com/deckarep/golang-set/v2 v2.3.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
|
||||||
@ -111,12 +111,11 @@ github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFig
|
|||||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
@ -130,8 +129,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9
|
|||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
|
github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
|
||||||
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
|
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
@ -150,7 +149,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
|||||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
@ -216,8 +214,8 @@ github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
|||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@ -246,8 +244,8 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
|
|||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||||
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
||||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/upyun/go-sdk/v3 v3.0.3 h1:2wUkNk2fyJReMYHMvJyav050D83rYwSjN7mEPR0Pp8Q=
|
github.com/upyun/go-sdk/v3 v3.0.4 h1:2DCJa/Yi7/3ZybT9UCPATSzvU3wpPPxhXinNlb1Hi8Q=
|
||||||
github.com/upyun/go-sdk/v3 v3.0.3/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
|
github.com/upyun/go-sdk/v3 v3.0.4/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
|
||||||
github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
|
github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
|
||||||
github.com/winfsp/cgofuse v1.5.0 h1:MsBP7Mi/LiJf/7/F3O/7HjjR009ds6KCdqXzKpZSWxI=
|
github.com/winfsp/cgofuse v1.5.0 h1:MsBP7Mi/LiJf/7/F3O/7HjjR009ds6KCdqXzKpZSWxI=
|
||||||
github.com/winfsp/cgofuse v1.5.0/go.mod h1:h3awhoUOcn2VYVKCwDaYxSLlZwnyK+A8KaDoLUp2lbU=
|
github.com/winfsp/cgofuse v1.5.0/go.mod h1:h3awhoUOcn2VYVKCwDaYxSLlZwnyK+A8KaDoLUp2lbU=
|
||||||
|
@ -158,6 +158,7 @@ func InitialSettings() []model.SettingItem {
|
|||||||
|
|
||||||
// qbittorrent settings
|
// qbittorrent settings
|
||||||
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||||
|
{Key: conf.QbittorrentSeedtime, Value: "0", Type: conf.TypeNumber, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||||
}
|
}
|
||||||
if flags.Dev {
|
if flags.Dev {
|
||||||
initialSettingItems = append(initialSettingItems, []model.SettingItem{
|
initialSettingItems = append(initialSettingItems, []model.SettingItem{
|
||||||
|
@ -61,7 +61,8 @@ const (
|
|||||||
SSOLoginplatform = "sso_login_platform"
|
SSOLoginplatform = "sso_login_platform"
|
||||||
|
|
||||||
// qbittorrent
|
// qbittorrent
|
||||||
QbittorrentUrl = "qbittorrent_url"
|
QbittorrentUrl = "qbittorrent_url"
|
||||||
|
QbittorrentSeedtime = "qbittorrent_seedtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -13,8 +13,13 @@ import (
|
|||||||
// So, the purpose of this package is to convert mount path to actual path
|
// So, the purpose of this package is to convert mount path to actual path
|
||||||
// then pass the actual path to the op package
|
// then pass the actual path to the op package
|
||||||
|
|
||||||
func List(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
|
type ListArgs struct {
|
||||||
res, err := list(ctx, path, refresh...)
|
Refresh bool
|
||||||
|
NoLog bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func List(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
|
||||||
|
res, err := list(ctx, path, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed list %s: %+v", path, err)
|
log.Errorf("failed list %s: %+v", path, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -22,9 +27,13 @@ func List(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(ctx context.Context, path string) (model.Obj, error) {
|
type GetArgs struct {
|
||||||
|
NoLog bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(ctx context.Context, path string, args *GetArgs) (model.Obj, error) {
|
||||||
res, err := get(ctx, path)
|
res, err := get(ctx, path)
|
||||||
if err != nil {
|
if err != nil && !args.NoLog {
|
||||||
log.Errorf("failed get %s: %+v", path, err)
|
log.Errorf("failed get %s: %+v", path, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -96,9 +105,13 @@ func PutAsTask(dstDirPath string, file *model.FileStream) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetStorage(path string) (driver.Driver, error) {
|
type GetStoragesArgs struct {
|
||||||
|
NoLog bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetStorage(path string, args *GetStoragesArgs) (driver.Driver, error) {
|
||||||
storageDriver, _, err := op.GetStorageAndActualPath(path)
|
storageDriver, _, err := op.GetStorageAndActualPath(path)
|
||||||
if err != nil {
|
if err != nil && !args.NoLog {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return storageDriver, nil
|
return storageDriver, nil
|
||||||
|
@ -2,9 +2,12 @@ package fs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,5 +16,14 @@ func link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, m
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.WithMessage(err, "failed get storage")
|
return nil, nil, errors.WithMessage(err, "failed get storage")
|
||||||
}
|
}
|
||||||
return op.Link(ctx, storage, actualPath, args)
|
l, obj, err := op.Link(ctx, storage, actualPath, args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.WithMessage(err, "failed link")
|
||||||
|
}
|
||||||
|
if l.URL != "" && !strings.HasPrefix(l.URL, "http://") && !strings.HasPrefix(l.URL, "https://") {
|
||||||
|
if c, ok := ctx.(*gin.Context); ok {
|
||||||
|
l.URL = common.GetApiUrl(c.Request) + l.URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return l, obj, nil
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// List files
|
// List files
|
||||||
func list(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
|
func list(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
|
||||||
meta := ctx.Value("meta").(*model.Meta)
|
meta := ctx.Value("meta").(*model.Meta)
|
||||||
user := ctx.Value("user").(*model.User)
|
user := ctx.Value("user").(*model.User)
|
||||||
virtualFiles := op.GetStorageVirtualFilesByPath(path)
|
virtualFiles := op.GetStorageVirtualFilesByPath(path)
|
||||||
@ -24,7 +24,7 @@ func list(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error
|
|||||||
if storage != nil {
|
if storage != nil {
|
||||||
_objs, err = op.List(ctx, storage, actualPath, model.ListArgs{
|
_objs, err = op.List(ctx, storage, actualPath, model.ListArgs{
|
||||||
ReqPath: path,
|
ReqPath: path,
|
||||||
}, refresh...)
|
}, args.Refresh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%+v", err)
|
log.Errorf("%+v", err)
|
||||||
if len(virtualFiles) == 0 {
|
if len(virtualFiles) == 0 {
|
||||||
|
@ -28,7 +28,7 @@ func WalkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn
|
|||||||
}
|
}
|
||||||
meta, _ := op.GetNearestMeta(name)
|
meta, _ := op.GetNearestMeta(name)
|
||||||
// Read directory names.
|
// Read directory names.
|
||||||
objs, err := List(context.WithValue(ctx, "meta", meta), name)
|
objs, err := List(context.WithValue(ctx, "meta", meta), name, &ListArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return walkFnErr
|
return walkFnErr
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ type Storage struct {
|
|||||||
Remark string `json:"remark"`
|
Remark string `json:"remark"`
|
||||||
Modified time.Time `json:"modified"`
|
Modified time.Time `json:"modified"`
|
||||||
Disabled bool `json:"disabled"` // if disabled
|
Disabled bool `json:"disabled"` // if disabled
|
||||||
|
EnableSign bool `json:"enable_sign"`
|
||||||
Sort
|
Sort
|
||||||
Proxy
|
Proxy
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,6 @@ package op
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
WORK = "work"
|
WORK = "work"
|
||||||
|
DISABLED = "disabled"
|
||||||
RootName = "root"
|
RootName = "root"
|
||||||
)
|
)
|
||||||
|
@ -122,9 +122,14 @@ func getMainItems(config driver.Config) []driver.Item {
|
|||||||
Type: conf.TypeSelect,
|
Type: conf.TypeSelect,
|
||||||
Options: "front,back",
|
Options: "front,back",
|
||||||
})
|
})
|
||||||
|
items = append(items, driver.Item{
|
||||||
|
Name: "enable_sign",
|
||||||
|
Type: conf.TypeBool,
|
||||||
|
Default: "false",
|
||||||
|
Required: true,
|
||||||
|
})
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAdditionalItems(t reflect.Type, defaultRoot string) []driver.Item {
|
func getAdditionalItems(t reflect.Type, defaultRoot string) []driver.Item {
|
||||||
var items []driver.Item
|
var items []driver.Item
|
||||||
for i := 0; i < t.NumField(); i++ {
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
@ -142,6 +142,7 @@ func DisableStorage(ctx context.Context, id uint) error {
|
|||||||
}
|
}
|
||||||
// delete the storage in the memory
|
// delete the storage in the memory
|
||||||
storage.Disabled = true
|
storage.Disabled = true
|
||||||
|
storage.SetStatus(DISABLED)
|
||||||
err = db.UpdateStorage(storage)
|
err = db.UpdateStorage(storage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithMessage(err, "failed update storage in db")
|
return errors.WithMessage(err, "failed update storage in db")
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"github.com/alist-org/alist/v3/pkg/task"
|
"github.com/alist-org/alist/v3/pkg/task"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -50,6 +51,7 @@ func AddURL(ctx context.Context, url string, dstDirPath string) error {
|
|||||||
tsk: tsk,
|
tsk: tsk,
|
||||||
tempDir: tempDir,
|
tempDir: tempDir,
|
||||||
dstDirPath: dstDirPath,
|
dstDirPath: dstDirPath,
|
||||||
|
seedtime: setting.GetInt(conf.QbittorrentSeedtime, 0),
|
||||||
}
|
}
|
||||||
return m.Loop()
|
return m.Loop()
|
||||||
},
|
},
|
||||||
|
@ -2,23 +2,26 @@ package qbittorrent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/pkg/task"
|
"github.com/alist-org/alist/v3/pkg/task"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
tsk *task.Task[string]
|
tsk *task.Task[string]
|
||||||
tempDir string
|
tempDir string
|
||||||
dstDirPath string
|
dstDirPath string
|
||||||
|
seedtime int
|
||||||
finish chan struct{}
|
finish chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,17 +117,27 @@ func (m *Monitor) complete() error {
|
|||||||
log.Debugf("files len: %d", len(files))
|
log.Debugf("files len: %d", len(files))
|
||||||
// delete qbittorrent task but do not delete the files before transferring to avoid qbittorrent
|
// delete qbittorrent task but do not delete the files before transferring to avoid qbittorrent
|
||||||
// accessing downloaded files and throw `cannot access the file because it is being used by another process` error
|
// accessing downloaded files and throw `cannot access the file because it is being used by another process` error
|
||||||
err = qbclient.Delete(m.tsk.ID, false)
|
// err = qbclient.Delete(m.tsk.ID, false)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
// upload files
|
// upload files
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(len(files))
|
wg.Add(len(files))
|
||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
err := os.RemoveAll(m.tempDir)
|
|
||||||
m.finish <- struct{}{}
|
m.finish <- struct{}{}
|
||||||
|
if m.seedtime < 0 {
|
||||||
|
log.Debugf("do not delete qb task %s", m.tsk.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("delete qb task %s after %d minutes", m.tsk.ID, m.seedtime)
|
||||||
|
<-time.After(time.Duration(m.seedtime) * time.Minute)
|
||||||
|
err := qbclient.Delete(m.tsk.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln(err.Error())
|
||||||
|
}
|
||||||
|
err = os.RemoveAll(m.tempDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to remove qbittorrent temp dir: %+v", err.Error())
|
log.Errorf("failed to remove qbittorrent temp dir: %+v", err.Error())
|
||||||
}
|
}
|
||||||
@ -151,7 +164,7 @@ func (m *Monitor) complete() error {
|
|||||||
Modified: time.Now(),
|
Modified: time.Now(),
|
||||||
IsFolder: false,
|
IsFolder: false,
|
||||||
},
|
},
|
||||||
ReadCloser: f,
|
ReadCloser: struct{ io.ReadSeekCloser }{f},
|
||||||
Mimetype: mimetype,
|
Mimetype: mimetype,
|
||||||
}
|
}
|
||||||
return op.Put(tsk.Ctx, storage, dstDir, stream, tsk.SetProgress)
|
return op.Put(tsk.Ctx, storage, dstDir, stream, tsk.SetProgress)
|
||||||
|
@ -141,7 +141,7 @@ func BuildIndex(ctx context.Context, indexPaths, ignorePaths []string, maxDepth
|
|||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
fi, err = fs.Get(ctx, indexPath)
|
fi, err = fs.Get(ctx, indexPath, &fs.GetArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ func (c *client) AddURI(uris []string, options ...interface{}) (gid string, err
|
|||||||
// If a file with the same name already exists, it is overwritten!
|
// If a file with the same name already exists, it is overwritten!
|
||||||
// If the file cannot be saved successfully or --rpc-save-upload-metadata is false, the downloads added by this method are not saved by --save-session.
|
// If the file cannot be saved successfully or --rpc-save-upload-metadata is false, the downloads added by this method are not saved by --save-session.
|
||||||
func (c *client) AddTorrent(filename string, options ...interface{}) (gid string, err error) {
|
func (c *client) AddTorrent(filename string, options ...interface{}) (gid string, err error) {
|
||||||
co, err := ioutil.ReadFile(filename)
|
co, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ func (c *client) AddTorrent(filename string, options ...interface{}) (gid string
|
|||||||
// If a file with the same name already exists, it is overwritten!
|
// If a file with the same name already exists, it is overwritten!
|
||||||
// If the file cannot be saved successfully or --rpc-save-upload-metadata is false, the downloads added by this method are not saved by --save-session.
|
// If the file cannot be saved successfully or --rpc-save-upload-metadata is false, the downloads added by this method are not saved by --save-session.
|
||||||
func (c *client) AddMetalink(filename string, options ...interface{}) (gid []string, err error) {
|
func (c *client) AddMetalink(filename string, options ...interface{}) (gid []string, err error) {
|
||||||
co, err := ioutil.ReadFile(filename)
|
co, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -343,9 +343,9 @@ func (c *Client) Read(path string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Link(path string) (string, http.Header, error) {
|
func (c *Client) Link(path string) (string, http.Header, error) {
|
||||||
method := "HEAD"
|
method := "GET"
|
||||||
url := PathEscape(Join(c.root, path))
|
u := PathEscape(Join(c.root, path))
|
||||||
r, err := http.NewRequest(method, url, nil)
|
r, err := http.NewRequest(method, u, nil)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, newPathErrorErr("Link", path, err)
|
return "", nil, newPathErrorErr("Link", path, err)
|
||||||
@ -366,31 +366,6 @@ func (c *Client) Link(path string) (string, http.Header, error) {
|
|||||||
if c.interceptor != nil {
|
if c.interceptor != nil {
|
||||||
c.interceptor(method, r)
|
c.interceptor(method, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
rs, err := c.c.Do(r)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil, newPathErrorErr("Link", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rs.StatusCode == 401 {
|
|
||||||
wwwAuthenticateHeader := strings.ToLower(rs.Header.Get("Www-Authenticate"))
|
|
||||||
if strings.Contains(wwwAuthenticateHeader, "digest") {
|
|
||||||
c.authMutex.Lock()
|
|
||||||
c.auth = &DigestAuth{auth.User(), auth.Pass(), digestParts(rs)}
|
|
||||||
c.auth.Authorize(r, method, path)
|
|
||||||
c.authMutex.Unlock()
|
|
||||||
} else if strings.Contains(wwwAuthenticateHeader, "basic") {
|
|
||||||
c.authMutex.Lock()
|
|
||||||
c.auth = &BasicAuth{auth.User(), auth.Pass()}
|
|
||||||
c.auth.Authorize(r, method, path)
|
|
||||||
c.authMutex.Unlock()
|
|
||||||
} else {
|
|
||||||
return "", nil, newPathError("Authorize", c.root, rs.StatusCode)
|
|
||||||
}
|
|
||||||
} else if rs.StatusCode > 400 {
|
|
||||||
return "", nil, newPathError("Authorize", path, rs.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.URL.String(), r.Header, nil
|
return r.URL.String(), r.Header, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +122,10 @@ func (tm *Manager[K]) ClearDone() {
|
|||||||
tm.RemoveByStates(SUCCEEDED, CANCELED, ERRORED)
|
tm.RemoveByStates(SUCCEEDED, CANCELED, ERRORED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tm *Manager[K]) ClearSucceeded() {
|
||||||
|
tm.RemoveByStates(SUCCEEDED)
|
||||||
|
}
|
||||||
|
|
||||||
func (tm *Manager[K]) RawTasks() *generic_sync.MapOf[K, *Task[K]] {
|
func (tm *Manager[K]) RawTasks() *generic_sync.MapOf[K, *Task[K]] {
|
||||||
return &tm.tasks
|
return &tm.tasks
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
@ -91,3 +92,46 @@ func NewReadCloser(reader io.Reader, close CloseFunc) io.ReadCloser {
|
|||||||
func NewLimitReadCloser(reader io.Reader, close CloseFunc, limit int64) io.ReadCloser {
|
func NewLimitReadCloser(reader io.Reader, close CloseFunc, limit int64) io.ReadCloser {
|
||||||
return NewReadCloser(io.LimitReader(reader, limit), close)
|
return NewReadCloser(io.LimitReader(reader, limit), close)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MultiReadable struct {
|
||||||
|
originReader io.Reader
|
||||||
|
reader io.Reader
|
||||||
|
cache *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMultiReadable(reader io.Reader) *MultiReadable {
|
||||||
|
return &MultiReadable{
|
||||||
|
originReader: reader,
|
||||||
|
reader: reader,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr *MultiReadable) Read(p []byte) (int, error) {
|
||||||
|
n, err := mr.reader.Read(p)
|
||||||
|
if _, ok := mr.reader.(io.Seeker); !ok && n > 0 {
|
||||||
|
if mr.cache == nil {
|
||||||
|
mr.cache = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
mr.cache.Write(p[:n])
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr *MultiReadable) Reset() error {
|
||||||
|
if seeker, ok := mr.reader.(io.Seeker); ok {
|
||||||
|
_, err := seeker.Seek(0, io.SeekStart)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if mr.cache != nil && mr.cache.Len() > 0 {
|
||||||
|
mr.reader = io.MultiReader(mr.cache, mr.reader)
|
||||||
|
mr.cache = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mr *MultiReadable) Close() error {
|
||||||
|
if closer, ok := mr.originReader.(io.Closer); ok {
|
||||||
|
return closer.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -5,10 +5,18 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func IsStorageSignEnabled(rawPath string) bool {
|
||||||
|
storage := op.GetBalancedStorage(rawPath)
|
||||||
|
return storage != nil && storage.GetStorage().EnableSign
|
||||||
|
}
|
||||||
|
|
||||||
func CanWrite(meta *model.Meta, path string) bool {
|
func CanWrite(meta *model.Meta, path string) bool {
|
||||||
if meta == nil || !meta.Write {
|
if meta == nil || !meta.Write {
|
||||||
return false
|
return false
|
||||||
@ -20,7 +28,7 @@ func IsApply(metaPath, reqPath string, applySub bool) bool {
|
|||||||
if utils.PathEqual(metaPath, reqPath) {
|
if utils.PathEqual(metaPath, reqPath) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return utils.IsSubPath(reqPath, metaPath) && applySub
|
return utils.IsSubPath(metaPath, reqPath) && applySub
|
||||||
}
|
}
|
||||||
|
|
||||||
func CanAccess(user *model.User, meta *model.Meta, reqPath string, password string) bool {
|
func CanAccess(user *model.User, meta *model.Meta, reqPath string, password string) bool {
|
||||||
@ -49,3 +57,18 @@ func CanAccess(user *model.User, meta *model.Meta, reqPath string, password stri
|
|||||||
// validate password
|
// validate password
|
||||||
return meta.Password == password
|
return meta.Password == password
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldProxy TODO need optimize
|
||||||
|
// when should be proxy?
|
||||||
|
// 1. config.MustProxy()
|
||||||
|
// 2. storage.WebProxy
|
||||||
|
// 3. proxy_types
|
||||||
|
func ShouldProxy(storage driver.Driver, filename string) bool {
|
||||||
|
if storage.Config().MustProxy() || storage.GetStorage().WebProxy {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if utils.SliceContains(conf.SlicesMap[conf.ProxyTypes], utils.Ext(filename)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
24
server/common/check_test.go
Normal file
24
server/common/check_test.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIsApply(t *testing.T) {
|
||||||
|
datas := []struct {
|
||||||
|
metaPath string
|
||||||
|
reqPath string
|
||||||
|
applySub bool
|
||||||
|
result bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
metaPath: "/",
|
||||||
|
reqPath: "/test",
|
||||||
|
applySub: true,
|
||||||
|
result: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, data := range datas {
|
||||||
|
if IsApply(data.metaPath, data.reqPath, data.applySub) != data.result {
|
||||||
|
t.Errorf("TestIsApply %d failed", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ package common
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@ -17,7 +16,15 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var HttpClient = &http.Client{}
|
var HttpClient = &http.Client{
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
if len(via) >= 10 {
|
||||||
|
return errors.New("stopped after 10 redirects")
|
||||||
|
}
|
||||||
|
req.Header.Del("Referer")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.Obj) error {
|
func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.Obj) error {
|
||||||
// read data with native
|
// read data with native
|
||||||
@ -26,8 +33,9 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
|||||||
defer func() {
|
defer func() {
|
||||||
_ = link.Data.Close()
|
_ = link.Data.Close()
|
||||||
}()
|
}()
|
||||||
|
filename := file.GetName()
|
||||||
w.Header().Set("Content-Type", "application/octet-stream")
|
w.Header().Set("Content-Type", "application/octet-stream")
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename)))
|
||||||
w.Header().Set("Content-Length", strconv.FormatInt(file.GetSize(), 10))
|
w.Header().Set("Content-Length", strconv.FormatInt(file.GetSize(), 10))
|
||||||
if link.Header != nil {
|
if link.Header != nil {
|
||||||
// TODO clean header with blacklist or whitelist
|
// TODO clean header with blacklist or whitelist
|
||||||
@ -60,7 +68,8 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
|
filename := file.GetName()
|
||||||
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename)))
|
||||||
http.ServeContent(w, r, file.GetName(), fileStat.ModTime(), f)
|
http.ServeContent(w, r, file.GetName(), fileStat.ModTime(), f)
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
@ -92,7 +101,7 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
|||||||
}
|
}
|
||||||
w.WriteHeader(res.StatusCode)
|
w.WriteHeader(res.StatusCode)
|
||||||
if res.StatusCode >= 400 {
|
if res.StatusCode >= 400 {
|
||||||
all, _ := ioutil.ReadAll(res.Body)
|
all, _ := io.ReadAll(res.Body)
|
||||||
msg := string(all)
|
msg := string(all)
|
||||||
log.Debugln(msg)
|
log.Debugln(msg)
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
|
@ -2,6 +2,7 @@ package handles
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -14,21 +15,21 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Down(c *gin.Context) {
|
func Down(c *gin.Context) {
|
||||||
rawPath := c.MustGet("path").(string)
|
rawPath := c.MustGet("path").(string)
|
||||||
filename := stdpath.Base(rawPath)
|
filename := stdpath.Base(rawPath)
|
||||||
storage, err := fs.GetStorage(rawPath)
|
storage, err := fs.GetStorage(rawPath, &fs.GetStoragesArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if shouldProxy(storage, filename) {
|
if common.ShouldProxy(storage, filename) {
|
||||||
Proxy(c)
|
Proxy(c)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
link, _, err := fs.Link(c, rawPath, model.LinkArgs{
|
link, _, err := fs.Link(c, rawPath, model.LinkArgs{
|
||||||
IP: c.ClientIP(),
|
IP: c.ClientIP(),
|
||||||
Header: c.Request.Header,
|
Header: c.Request.Header,
|
||||||
@ -38,6 +39,14 @@ func Down(c *gin.Context) {
|
|||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if link.Data != nil {
|
||||||
|
defer func(Data io.ReadCloser) {
|
||||||
|
err := Data.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("close data error: %s", err)
|
||||||
|
}
|
||||||
|
}(link.Data)
|
||||||
|
}
|
||||||
c.Header("Referrer-Policy", "no-referrer")
|
c.Header("Referrer-Policy", "no-referrer")
|
||||||
c.Header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
|
c.Header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
|
||||||
if setting.GetBool(conf.ForwardDirectLinkParams) {
|
if setting.GetBool(conf.ForwardDirectLinkParams) {
|
||||||
@ -56,7 +65,7 @@ func Down(c *gin.Context) {
|
|||||||
func Proxy(c *gin.Context) {
|
func Proxy(c *gin.Context) {
|
||||||
rawPath := c.MustGet("path").(string)
|
rawPath := c.MustGet("path").(string)
|
||||||
filename := stdpath.Base(rawPath)
|
filename := stdpath.Base(rawPath)
|
||||||
storage, err := fs.GetStorage(rawPath)
|
storage, err := fs.GetStorage(rawPath, &fs.GetStoragesArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
@ -102,21 +111,6 @@ func Proxy(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO need optimize
|
|
||||||
// when should be proxy?
|
|
||||||
// 1. config.MustProxy()
|
|
||||||
// 2. storage.WebProxy
|
|
||||||
// 3. proxy_types
|
|
||||||
func shouldProxy(storage driver.Driver, filename string) bool {
|
|
||||||
if storage.Config().MustProxy() || storage.GetStorage().WebProxy {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if utils.SliceContains(conf.SlicesMap[conf.ProxyTypes], utils.Ext(filename)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO need optimize
|
// TODO need optimize
|
||||||
// when can be proxy?
|
// when can be proxy?
|
||||||
// 1. text file
|
// 1. text file
|
||||||
|
@ -2,17 +2,21 @@ package handles
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/fs"
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/internal/sign"
|
"github.com/alist-org/alist/v3/internal/sign"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/generic"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MkdirOrLinkReq struct {
|
type MkdirOrLinkReq struct {
|
||||||
@ -92,6 +96,93 @@ func FsMove(c *gin.Context) {
|
|||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RecursiveMoveReq struct {
|
||||||
|
SrcDir string `json:"src_dir"`
|
||||||
|
DstDir string `json:"dst_dir"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func FsRecursiveMove(c *gin.Context) {
|
||||||
|
var req RecursiveMoveReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := c.MustGet("user").(*model.User)
|
||||||
|
if !user.CanMove() {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srcDir, err := user.JoinPath(req.SrcDir)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dstDir, err := user.JoinPath(req.DstDir)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
meta, err := op.GetNearestMeta(srcDir)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Set("meta", meta)
|
||||||
|
|
||||||
|
rootFiles, err := fs.List(c, srcDir, &fs.ListArgs{})
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// record the file path
|
||||||
|
filePathMap := make(map[model.Obj]string)
|
||||||
|
movingFiles := generic.NewQueue[model.Obj]()
|
||||||
|
for _, file := range rootFiles {
|
||||||
|
movingFiles.Push(file)
|
||||||
|
filePathMap[file] = srcDir
|
||||||
|
}
|
||||||
|
|
||||||
|
for !movingFiles.IsEmpty() {
|
||||||
|
|
||||||
|
movingFile := movingFiles.Pop()
|
||||||
|
movingFilePath := fmt.Sprintf("%s/%s", filePathMap[movingFile], movingFile.GetName())
|
||||||
|
if movingFile.IsDir() {
|
||||||
|
// directory, recursive move
|
||||||
|
subFilePath := movingFilePath
|
||||||
|
subFiles, err := fs.List(c, subFilePath, &fs.ListArgs{Refresh: true})
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, subFile := range subFiles {
|
||||||
|
movingFiles.Push(subFile)
|
||||||
|
filePathMap[subFile] = subFilePath
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if movingFilePath == dstDir {
|
||||||
|
// same directory, don't move
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// move
|
||||||
|
err := fs.Move(c, movingFilePath, dstDir, movingFiles.IsEmpty())
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
|
||||||
func FsCopy(c *gin.Context) {
|
func FsCopy(c *gin.Context) {
|
||||||
var req MoveCopyReq
|
var req MoveCopyReq
|
||||||
if err := c.ShouldBind(&req); err != nil {
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
@ -163,6 +254,67 @@ func FsRename(c *gin.Context) {
|
|||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RegexRenameReq struct {
|
||||||
|
SrcDir string `json:"src_dir"`
|
||||||
|
SrcNameRegex string `json:"src_name_regex"`
|
||||||
|
NewNameRegex string `json:"new_name_regex"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func FsRegexRename(c *gin.Context) {
|
||||||
|
var req RegexRenameReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := c.MustGet("user").(*model.User)
|
||||||
|
if !user.CanRename() {
|
||||||
|
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reqPath, err := user.JoinPath(req.SrcDir)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
meta, err := op.GetNearestMeta(reqPath)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||||
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Set("meta", meta)
|
||||||
|
|
||||||
|
srcRegexp, err := regexp.Compile(req.SrcNameRegex)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := fs.List(c, reqPath, &fs.ListArgs{})
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
|
||||||
|
if srcRegexp.MatchString(file.GetName()) {
|
||||||
|
filePath := fmt.Sprintf("%s/%s", reqPath, file.GetName())
|
||||||
|
newFileName := srcRegexp.ReplaceAllString(file.GetName(), req.NewNameRegex)
|
||||||
|
if err := fs.Rename(c, filePath, newFileName); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
|
||||||
type RemoveReq struct {
|
type RemoveReq struct {
|
||||||
Dir string `json:"dir"`
|
Dir string `json:"dir"`
|
||||||
Names []string `json:"names"`
|
Names []string `json:"names"`
|
||||||
@ -210,7 +362,7 @@ func Link(c *gin.Context) {
|
|||||||
//rawPath := stdpath.Join(user.BasePath, req.Path)
|
//rawPath := stdpath.Join(user.BasePath, req.Path)
|
||||||
// why need not join base_path? because it's always the full path
|
// why need not join base_path? because it's always the full path
|
||||||
rawPath := req.Path
|
rawPath := req.Path
|
||||||
storage, err := fs.GetStorage(rawPath)
|
storage, err := fs.GetStorage(rawPath, &fs.GetStoragesArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
@ -229,6 +381,14 @@ func Link(c *gin.Context) {
|
|||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if link.Data != nil {
|
||||||
|
defer func(Data io.ReadCloser) {
|
||||||
|
err := Data.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("close link data error: %v", err)
|
||||||
|
}
|
||||||
|
}(link.Data)
|
||||||
|
}
|
||||||
common.SuccessResp(c, link)
|
common.SuccessResp(c, link)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/fs"
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
"github.com/alist-org/alist/v3/internal/sign"
|
"github.com/alist-org/alist/v3/internal/sign"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
@ -77,14 +79,14 @@ func FsList(c *gin.Context) {
|
|||||||
common.ErrorStrResp(c, "Refresh without permission", 403)
|
common.ErrorStrResp(c, "Refresh without permission", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
objs, err := fs.List(c, reqPath, req.Refresh)
|
objs, err := fs.List(c, reqPath, &fs.ListArgs{Refresh: req.Refresh})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
total, objs := pagination(objs, &req.PageReq)
|
total, objs := pagination(objs, &req.PageReq)
|
||||||
provider := "unknown"
|
provider := "unknown"
|
||||||
storage, err := fs.GetStorage(reqPath)
|
storage, err := fs.GetStorage(reqPath, &fs.GetStoragesArgs{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
provider = storage.GetStorage().Driver
|
provider = storage.GetStorage().Driver
|
||||||
}
|
}
|
||||||
@ -130,7 +132,7 @@ func FsDirs(c *gin.Context) {
|
|||||||
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
objs, err := fs.List(c, reqPath)
|
objs, err := fs.List(c, reqPath, &fs.ListArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
@ -165,6 +167,9 @@ func getReadme(meta *model.Meta, path string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isEncrypt(meta *model.Meta, path string) bool {
|
func isEncrypt(meta *model.Meta, path string) bool {
|
||||||
|
if common.IsStorageSignEnabled(path) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if meta == nil || meta.Password == "" {
|
if meta == nil || meta.Password == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -242,14 +247,14 @@ func FsGet(c *gin.Context) {
|
|||||||
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
obj, err := fs.Get(c, reqPath)
|
obj, err := fs.Get(c, reqPath, &fs.GetArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var rawURL string
|
var rawURL string
|
||||||
|
|
||||||
storage, err := fs.GetStorage(reqPath)
|
storage, err := fs.GetStorage(reqPath, &fs.GetStoragesArgs{})
|
||||||
provider := "unknown"
|
provider := "unknown"
|
||||||
if err == nil {
|
if err == nil {
|
||||||
provider = storage.Config().Name
|
provider = storage.Config().Name
|
||||||
@ -260,16 +265,20 @@ func FsGet(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if storage.Config().MustProxy() || storage.GetStorage().WebProxy {
|
if storage.Config().MustProxy() || storage.GetStorage().WebProxy {
|
||||||
|
query := ""
|
||||||
|
if isEncrypt(meta, reqPath) || setting.GetBool(conf.SignAll) {
|
||||||
|
query = "?sign=" + sign.Sign(reqPath)
|
||||||
|
}
|
||||||
if storage.GetStorage().DownProxyUrl != "" {
|
if storage.GetStorage().DownProxyUrl != "" {
|
||||||
rawURL = fmt.Sprintf("%s%s?sign=%s",
|
rawURL = fmt.Sprintf("%s%s?sign=%s",
|
||||||
strings.Split(storage.GetStorage().DownProxyUrl, "\n")[0],
|
strings.Split(storage.GetStorage().DownProxyUrl, "\n")[0],
|
||||||
utils.EncodePath(reqPath, true),
|
utils.EncodePath(reqPath, true),
|
||||||
sign.Sign(reqPath))
|
sign.Sign(reqPath))
|
||||||
} else {
|
} else {
|
||||||
rawURL = fmt.Sprintf("%s/p%s?sign=%s",
|
rawURL = fmt.Sprintf("%s/p%s%s",
|
||||||
common.GetApiUrl(c.Request),
|
common.GetApiUrl(c.Request),
|
||||||
utils.EncodePath(reqPath, true),
|
utils.EncodePath(reqPath, true),
|
||||||
sign.Sign(reqPath))
|
query)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// file have raw url
|
// file have raw url
|
||||||
@ -288,11 +297,12 @@ func FsGet(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
var related []model.Obj
|
var related []model.Obj
|
||||||
parentPath := stdpath.Dir(reqPath)
|
parentPath := stdpath.Dir(reqPath)
|
||||||
sameLevelFiles, err := fs.List(c, parentPath)
|
sameLevelFiles, err := fs.List(c, parentPath, &fs.ListArgs{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
related = filterRelated(sameLevelFiles, obj)
|
related = filterRelated(sameLevelFiles, obj)
|
||||||
}
|
}
|
||||||
parentMeta, _ := op.GetNearestMeta(parentPath)
|
parentMeta, _ := op.GetNearestMeta(parentPath)
|
||||||
|
thumb, _ := model.GetThumb(obj)
|
||||||
common.SuccessResp(c, FsGetResp{
|
common.SuccessResp(c, FsGetResp{
|
||||||
ObjResp: ObjResp{
|
ObjResp: ObjResp{
|
||||||
Name: obj.GetName(),
|
Name: obj.GetName(),
|
||||||
@ -301,6 +311,7 @@ func FsGet(c *gin.Context) {
|
|||||||
Modified: obj.ModTime(),
|
Modified: obj.ModTime(),
|
||||||
Sign: common.Sign(obj, parentPath, isEncrypt(meta, reqPath)),
|
Sign: common.Sign(obj, parentPath, isEncrypt(meta, reqPath)),
|
||||||
Type: utils.GetFileType(obj.GetName()),
|
Type: utils.GetFileType(obj.GetName()),
|
||||||
|
Thumb: thumb,
|
||||||
},
|
},
|
||||||
RawURL: rawURL,
|
RawURL: rawURL,
|
||||||
Readme: getReadme(meta, reqPath),
|
Readme: getReadme(meta, reqPath),
|
||||||
|
@ -69,7 +69,7 @@ func FsForm(c *gin.Context) {
|
|||||||
common.ErrorResp(c, err, 403)
|
common.ErrorResp(c, err, 403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
storage, err := fs.GetStorage(path)
|
storage, err := fs.GetStorage(path, &fs.GetStoragesArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 400)
|
common.ErrorResp(c, err, 400)
|
||||||
return
|
return
|
||||||
|
@ -10,7 +10,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SetQbittorrentReq struct {
|
type SetQbittorrentReq struct {
|
||||||
Url string `json:"url" form:"url"`
|
Url string `json:"url" form:"url"`
|
||||||
|
Seedtime string `json:"seedtime" form:"seedtime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetQbittorrent(c *gin.Context) {
|
func SetQbittorrent(c *gin.Context) {
|
||||||
@ -19,14 +20,11 @@ func SetQbittorrent(c *gin.Context) {
|
|||||||
common.ErrorResp(c, err, 400)
|
common.ErrorResp(c, err, 400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item := &model.SettingItem{
|
items := []model.SettingItem{
|
||||||
Key: conf.QbittorrentUrl,
|
{Key: conf.QbittorrentUrl, Value: req.Url, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||||
Value: req.Url,
|
{Key: conf.QbittorrentSeedtime, Value: req.Seedtime, Type: conf.TypeNumber, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||||
Type: conf.TypeString,
|
|
||||||
Group: model.SINGLE,
|
|
||||||
Flag: model.PRIVATE,
|
|
||||||
}
|
}
|
||||||
if err := op.SaveSettingItem(item); err != nil {
|
if err := op.SaveSettingItems(items); err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -92,10 +92,27 @@ func taskRoute[K comparable](g *gin.RouterGroup, manager *task.Manager[K], k2Str
|
|||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
g.POST("/retry", func(c *gin.Context) {
|
||||||
|
tid := c.Query("tid")
|
||||||
|
id, err := str2K(tid)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := manager.Retry(id); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
})
|
||||||
g.POST("/clear_done", func(c *gin.Context) {
|
g.POST("/clear_done", func(c *gin.Context) {
|
||||||
manager.ClearDone()
|
manager.ClearDone()
|
||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
})
|
})
|
||||||
|
g.POST("/clear_succeeded", func(c *gin.Context) {
|
||||||
|
manager.ClearSucceeded()
|
||||||
|
common.SuccessResp(c)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupTaskRoute(g *gin.RouterGroup) {
|
func SetupTaskRoute(g *gin.RouterGroup) {
|
||||||
|
@ -4,10 +4,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/errs"
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
"github.com/alist-org/alist/v3/internal/model"
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
"github.com/alist-org/alist/v3/internal/op"
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
"github.com/alist-org/alist/v3/internal/setting"
|
|
||||||
"github.com/alist-org/alist/v3/internal/sign"
|
"github.com/alist-org/alist/v3/internal/sign"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
@ -49,6 +50,9 @@ func needSign(meta *model.Meta, path string) bool {
|
|||||||
if setting.GetBool(conf.SignAll) {
|
if setting.GetBool(conf.SignAll) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if common.IsStorageSignEnabled(path) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if meta == nil || meta.Password == "" {
|
if meta == nil || meta.Password == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,9 @@ func _fs(g *gin.RouterGroup) {
|
|||||||
g.Any("/dirs", handles.FsDirs)
|
g.Any("/dirs", handles.FsDirs)
|
||||||
g.POST("/mkdir", handles.FsMkdir)
|
g.POST("/mkdir", handles.FsMkdir)
|
||||||
g.POST("/rename", handles.FsRename)
|
g.POST("/rename", handles.FsRename)
|
||||||
|
g.POST("/regex_rename", handles.FsRegexRename)
|
||||||
g.POST("/move", handles.FsMove)
|
g.POST("/move", handles.FsMove)
|
||||||
|
g.POST("/recursive_move", handles.FsRecursiveMove)
|
||||||
g.POST("/copy", handles.FsCopy)
|
g.POST("/copy", handles.FsCopy)
|
||||||
g.POST("/remove", handles.FsRemove)
|
g.POST("/remove", handles.FsRemove)
|
||||||
g.PUT("/put", middlewares.FsUp, handles.FsStream)
|
g.PUT("/put", middlewares.FsUp, handles.FsStream)
|
||||||
|
@ -84,7 +84,7 @@ func walkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn
|
|||||||
}
|
}
|
||||||
meta, _ := op.GetNearestMeta(name)
|
meta, _ := op.GetNearestMeta(name)
|
||||||
// Read directory names.
|
// Read directory names.
|
||||||
objs, err := fs.List(context.WithValue(ctx, "meta", meta), name)
|
objs, err := fs.List(context.WithValue(ctx, "meta", meta), name, &fs.ListArgs{})
|
||||||
//f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
//f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
// return walkFn(name, info, err)
|
// return walkFn(name, info, err)
|
||||||
|
@ -188,7 +188,7 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status
|
|||||||
return 403, err
|
return 403, err
|
||||||
}
|
}
|
||||||
allow := "OPTIONS, LOCK, PUT, MKCOL"
|
allow := "OPTIONS, LOCK, PUT, MKCOL"
|
||||||
if fi, err := fs.Get(ctx, reqPath); err == nil {
|
if fi, err := fs.Get(ctx, reqPath, &fs.GetArgs{}); err == nil {
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
|
allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND"
|
||||||
} else {
|
} else {
|
||||||
@ -215,7 +215,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 403, err
|
return 403, err
|
||||||
}
|
}
|
||||||
fi, err := fs.Get(ctx, reqPath)
|
fi, err := fs.Get(ctx, reqPath, &fs.GetArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
||||||
@ -228,7 +228,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
|
|||||||
}
|
}
|
||||||
w.Header().Set("ETag", etag)
|
w.Header().Set("ETag", etag)
|
||||||
// Let ServeContent determine the Content-Type header.
|
// Let ServeContent determine the Content-Type header.
|
||||||
storage, _ := fs.GetStorage(reqPath)
|
storage, _ := fs.GetStorage(reqPath, &fs.GetStoragesArgs{})
|
||||||
downProxyUrl := storage.GetStorage().DownProxyUrl
|
downProxyUrl := storage.GetStorage().DownProxyUrl
|
||||||
if storage.GetStorage().WebdavNative() || (storage.GetStorage().WebdavProxy() && downProxyUrl == "") {
|
if storage.GetStorage().WebdavNative() || (storage.GetStorage().WebdavProxy() && downProxyUrl == "") {
|
||||||
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{Header: r.Header})
|
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{Header: r.Header})
|
||||||
@ -278,7 +278,7 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status i
|
|||||||
// "godoc os RemoveAll" says that "If the path does not exist, RemoveAll
|
// "godoc os RemoveAll" says that "If the path does not exist, RemoveAll
|
||||||
// returns nil (no error)." WebDAV semantics are that it should return a
|
// returns nil (no error)." WebDAV semantics are that it should return a
|
||||||
// "404 Not Found". We therefore have to Stat before we RemoveAll.
|
// "404 Not Found". We therefore have to Stat before we RemoveAll.
|
||||||
if _, err := fs.Get(ctx, reqPath); err != nil {
|
if _, err := fs.Get(ctx, reqPath, &fs.GetArgs{}); err != nil {
|
||||||
if errs.IsObjectNotFound(err) {
|
if errs.IsObjectNotFound(err) {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
||||||
@ -331,7 +331,7 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusMethodNotAllowed, err
|
return http.StatusMethodNotAllowed, err
|
||||||
}
|
}
|
||||||
fi, err := fs.Get(ctx, reqPath)
|
fi, err := fs.Get(ctx, reqPath, &fs.GetArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fi = &obj
|
fi = &obj
|
||||||
}
|
}
|
||||||
@ -591,7 +591,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 403, err
|
return 403, err
|
||||||
}
|
}
|
||||||
fi, err := fs.Get(ctx, reqPath)
|
fi, err := fs.Get(ctx, reqPath, &fs.GetArgs{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errs.IsObjectNotFound(err) {
|
if errs.IsObjectNotFound(err) {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
@ -670,7 +670,7 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 403, err
|
return 403, err
|
||||||
}
|
}
|
||||||
if _, err := fs.Get(ctx, reqPath); err != nil {
|
if _, err := fs.Get(ctx, reqPath, &fs.GetArgs{}); err != nil {
|
||||||
if errs.IsObjectNotFound(err) {
|
if errs.IsObjectNotFound(err) {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user