Compare commits

...

46 Commits

Author SHA1 Message Date
5cba10446e fix(123): adapt new upload method (close #4141) 2023-04-14 15:48:39 +08:00
a9bdb15205 ci: fix golang version in auto_lang [skip ci] 2023-04-14 13:49:13 +08:00
c5f6a90f54 fix(quark): download file size limit (close #4140) 2023-04-14 13:47:05 +08:00
46f9aefb04 feat: empty folder clear API [ckip ci] (#4132)
* 增加清理空文件夹API

* 修复嵌套文件夹删除Bug

 Author:    varg247 <varg247@gmail.com>

---------

Co-authored-by: varg247 <varg247@qq.com>
2023-04-13 15:39:21 +08:00
fdcad9c154 fix(123): incorrect endpoint (close #4046) 2023-04-12 23:04:12 +08:00
027025361a ci: fixed version of alpine 2023-04-12 16:01:49 +08:00
f1245153b9 chore(deps): upgrade to go@1.20 2023-04-12 15:42:27 +08:00
570b8be022 fix(onedrive): error check in upBig 2023-04-11 22:52:42 +08:00
86a773674a feat(task): print stack trace if panic 2023-04-11 15:16:57 +08:00
75fd0ee185 feat(s3): optional remove bucket name from path (close #4069) 2023-04-09 19:25:52 +08:00
cc43238bd1 fix(alias): disable log completely (#4054) 2023-04-09 15:46:26 +08:00
c0a6beecea fix(alias): panic on nil pointer (close #4093) 2023-04-09 14:06:04 +08:00
c77eebb035 fix(deps): update module golang.org/x/image to v0.7.0 (#4065)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-08 21:51:51 +08:00
b1efb86b28 fix(deps): update module golang.org/x/net to v0.9.0 [skip ci] (#4066)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-08 21:20:19 +08:00
0707449c8f fix(deps): update module golang.org/x/crypto to v0.8.0 [skip ci] (#4076)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-08 21:18:39 +08:00
0f8a84f67e perf(alias): disabled log on fs call (close #4054) 2023-04-07 00:02:07 +08:00
a475783b00 fix(deps): update module github.com/spf13/cobra to v1.7.0 [skip ci] (#4041)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-06 21:41:41 +08:00
67413015e8 ci: use non-upx prebuilt for windows by default 2023-04-06 21:38:57 +08:00
3a311a47af fix(deps): update module github.com/upyun/go-sdk/v3 to v3.0.4 (#4039)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-04 17:10:31 +08:00
9ccd802126 fix(123): api prefix changed (close #4038) 2023-04-04 16:39:56 +08:00
0acba7cd22 perf(123): reduce login count 2023-04-03 11:24:29 +08:00
3cdb8e7a81 fix(trainbit): incorrect filename display (#4027) 2023-04-02 21:13:20 +08:00
d3efee2ea1 fix(s3): increase PartSize if filesize > 50000MB (close #4017) 2023-04-02 16:09:27 +08:00
4ec274e748 fix(aliyundrive_open): refresh upload url if expired (#3999 close #3823)
* fix(aliyundrive_open): refresh upload url for large files

* fix(aliyundrive_open): retry upload on url expiry

* fix(aliyundrive_open): ignore 409 error

* feat(aliyundrive): cleanup upload retry logic

* feat(util): add multireadable io utility

* feat(aliyundrive_open): make upload fully stream

* feat(aliyundrive_open): refresh upload url every 20 puts

* fix(aliyundrive_open): part info panic

* chore: change refresh upload url strategy

---------

Co-authored-by: Andy Hsu <i@nn.ci>
2023-04-01 14:54:29 +08:00
3b07c72f88 fix(proxy): ignore Referer if got redirect (close #3996) 2023-03-31 20:29:55 +08:00
0c5820a98f docs(aliyundrive_open): revised the sentence that may cause ambiguity (#3989) 2023-03-29 20:26:21 +08:00
86beadc0ed fix: missed sign with enable sign_all (close #3957) 2023-03-26 16:19:01 +08:00
be62d64dba chore: cancel 2fa succeed tips 2023-03-25 18:36:13 +08:00
112363031a feat: add fine-grained control for link signing (#3924)
* Determine whether the URL requires Sign

* Add File and Mem based KV

NOT TESTED: TokenKV Function

* Change Token KV func to common func.

Add File based KV func

* Remove KV, Remove Token

I found that the original Sign function is enough to complete the link signature, and only need to add simple configuration items to meet the requirements.

* Add IsStorageSigned func to judge if Signing is enabled in the storage settings.

It should be working now.

* Add a SIGN button to the management panel.

* Add enable_sign to the basic storage struct.

Can enable sign for every driver now.

Bug: When sign enabled, in download page, Copy link doesn't contain a sign.

(Not done yet)

* Fix a bug from commit 8f6c25f.

Response of fsread function does not contain sign.

* Optimize code and follow advices.

- Add back public/dist/README.md

- Enable sign when DownProxyUrl is enabled

- Merge needSign() to isEncrypt() in fsread.go

* simplify code

---------

Co-authored-by: Andy Hsu <i@nn.ci>
2023-03-24 22:44:33 +08:00
48dc3552a6 fix(url_tree): incorrect tree structure 2023-03-24 20:34:03 +08:00
663814c9ef fix(url_tree): fix test url [skip ci] (#3940) 2023-03-24 20:26:00 +08:00
bd892e6a63 feat(drivers): new driver UrlTree (close #3268 in #3933)
* feat(drivers): new driver `urls` (close #3268)

* chore: rename

* support customize basic info or get from url

* dfs tree to calculate folder size

* go mod tidy

* add help message
2023-03-24 15:13:54 +08:00
4fd2c09845 fix(115): download issue due to ua (close #3931 in #3932) 2023-03-23 22:57:44 +08:00
0eab31bdf5 fix(local): filename with whitespace issue (#3928)
* fix(local): filename whitespace problem

* fix(deps): remove deprecated package io/ioutil

---------

Co-authored-by: XZB <i@1248.ink>
2023-03-23 15:18:37 +08:00
c6af22b97e feat: add thumbnail to fs/get api (#3927) 2023-03-23 13:59:39 +08:00
b2a5110672 feat(onedrive): support application authorization method (#3906) 2023-03-23 13:26:03 +08:00
c628992ea6 ci: add log required on question label [skip ci] 2023-03-22 14:03:04 +08:00
c65d868e09 fix(baidu_share): large file download (#3887 close #3876)
* fix(baidushare): large file download

* refactor: optimize client
2023-03-20 17:46:15 +08:00
aeb48b2ecc perf(aliyundrive_open): don't refresh token on init if token valid 2023-03-20 15:00:02 +08:00
cefec1a663 style: sort imports 2023-03-20 14:59:01 +08:00
e7ad830aa8 fix(cloudreve): captcha code ocr (#3889 close #3662) 2023-03-19 20:30:39 +08:00
b27eed265a fix(deps): update module github.com/blevesearch/bleve/v2 to v2.3.7 [skip ci] (#3874)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-19 20:26:48 +08:00
3abe26473c fix(trainbit): decode html code (#3883) 2023-03-19 15:25:06 +08:00
023107226c fix(trainbit): remove unnecessary operation (#3881) 2023-03-18 13:52:36 +08:00
8b109cfe40 fix(smb): byte alignment (close #3868) 2023-03-17 16:32:34 +08:00
b48e97d406 chore: fix release name [skip ci] 2023-03-16 22:47:01 +08:00
65 changed files with 1744 additions and 341 deletions

View File

@ -16,7 +16,7 @@ jobs:
strategy: strategy:
matrix: matrix:
platform: [ ubuntu-latest ] platform: [ ubuntu-latest ]
go-version: [ 1.19 ] go-version: [ '1.20' ]
name: auto generate lang.json name: auto generate lang.json
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:

View File

@ -11,7 +11,7 @@ jobs:
strategy: strategy:
matrix: matrix:
platform: [ubuntu-latest] platform: [ubuntu-latest]
go-version: [1.19] go-version: [ '1.20' ]
name: Build name: Build
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:

View File

@ -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自动关闭。

View File

@ -9,11 +9,11 @@ jobs:
strategy: strategy:
matrix: matrix:
platform: [ ubuntu-latest ] platform: [ ubuntu-latest ]
go-version: [ 1.19 ] go-version: [ '1.20' ]
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 }}
@ -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/*

View File

@ -1,11 +1,11 @@
FROM alpine:edge as builder FROM alpine:3.17 as builder
LABEL stage=go-builder LABEL stage=go-builder
WORKDIR /app/ WORKDIR /app/
COPY ./ ./ COPY ./ ./
RUN apk add --no-cache bash curl gcc git go musl-dev; \ RUN apk add --no-cache bash curl gcc git go musl-dev; \
bash build.sh release docker bash build.sh release docker
FROM alpine:edge FROM alpine:3.17
LABEL MAINTAINER="i@nn.ci" LABEL MAINTAINER="i@nn.ci"
VOLUME /opt/alist/data/ VOLUME /opt/alist/data/
WORKDIR /opt/alist/ WORKDIR /opt/alist/

View File

@ -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
} }

View File

@ -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")
} }
} }
}, },

View File

@ -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
} }

View File

@ -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,36 +220,41 @@ 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) { res, 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 {
return err return err
} }
log.Debugln("upload request res: ", string(res))
if resp.Data.Reuse || resp.Data.Key == "" { if resp.Data.Reuse || resp.Data.Key == "" {
return nil return nil
} }
cfg := &aws.Config{ if resp.Data.AccessKeyId == "" || resp.Data.SecretAccessKey == "" || resp.Data.SessionToken == "" {
Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken), err = d.newUpload(ctx, &resp, stream, uploadFile, up)
Region: aws.String("123pan"), } else {
Endpoint: aws.String("file.123pan.com"), cfg := &aws.Config{
S3ForcePathStyle: aws.Bool(true), Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken),
Region: aws.String("123pan"),
Endpoint: aws.String(resp.Data.EndPoint),
S3ForcePathStyle: aws.Bool(true),
}
s, err := session.NewSession(cfg)
if err != nil {
return err
}
uploader := s3manager.NewUploader(s)
input := &s3manager.UploadInput{
Bucket: &resp.Data.Bucket,
Key: &resp.Data.Key,
Body: uploadFile,
}
_, err = uploader.UploadWithContext(ctx, input)
} }
s, err := session.NewSession(cfg)
if err != nil { if err != nil {
return err return err
} }
uploader := s3manager.NewUploader(s) _, err = d.request(UploadComplete, http.MethodPost, func(req *resty.Request) {
input := &s3manager.UploadInput{
Bucket: &resp.Data.Bucket,
Key: &resp.Data.Key,
Body: uploadFile,
}
_, err = uploader.UploadWithContext(ctx, input)
if err != nil {
return err
}
_, err = d.request("https://www.123pan.com/api/file/upload_complete", 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)

View File

@ -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{

View File

@ -74,5 +74,14 @@ type UploadResp struct {
SessionToken string `json:"SessionToken"` SessionToken string `json:"SessionToken"`
FileId int64 `json:"FileId"` FileId int64 `json:"FileId"`
Reuse bool `json:"Reuse"` Reuse bool `json:"Reuse"`
EndPoint string `json:"EndPoint"`
StorageNode string `json:"StorageNode"`
UploadId string `json:"UploadId"`
} `json:"data"`
}
type S3PreSignedURLs struct {
Data struct {
PreSignedUrls map[string]string `json:"presignedUrls"`
} `json:"data"` } `json:"data"`
} }

127
drivers/123/upload.go Normal file
View File

@ -0,0 +1,127 @@
package _123
import (
"context"
"fmt"
"io"
"math"
"net/http"
"strconv"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
)
func (d *Pan123) getS3PreSignedUrls(ctx context.Context, upReq *UploadResp, start, end int) (*S3PreSignedURLs, error) {
data := base.Json{
"bucket": upReq.Data.Bucket,
"key": upReq.Data.Key,
"partNumberEnd": end,
"partNumberStart": start,
"uploadId": upReq.Data.UploadId,
"StorageNode": upReq.Data.StorageNode,
}
var s3PreSignedUrls S3PreSignedURLs
_, err := d.request(S3PreSignedUrls, http.MethodPost, func(req *resty.Request) {
req.SetBody(data).SetContext(ctx)
}, &s3PreSignedUrls)
if err != nil {
return nil, err
}
return &s3PreSignedUrls, nil
}
func (d *Pan123) completeS3(ctx context.Context, upReq *UploadResp) error {
data := base.Json{
"bucket": upReq.Data.Bucket,
"key": upReq.Data.Key,
"uploadId": upReq.Data.UploadId,
"StorageNode": upReq.Data.StorageNode,
}
_, err := d.request(S3Complete, http.MethodPost, func(req *resty.Request) {
req.SetBody(data).SetContext(ctx)
}, nil)
return err
}
func (d *Pan123) newUpload(ctx context.Context, upReq *UploadResp, file model.FileStreamer, reader io.Reader, up driver.UpdateProgress) error {
chunkSize := int64(1024 * 1024 * 5)
// fetch s3 pre signed urls
chunkCount := int(math.Ceil(float64(file.GetSize()) / float64(chunkSize)))
// upload 10 chunks each batch
batchSize := 10
for i := 1; i <= chunkCount; i += batchSize {
if utils.IsCanceled(ctx) {
return ctx.Err()
}
start := i
end := i + batchSize
if end > chunkCount+1 {
end = chunkCount + 1
}
s3PreSignedUrls, err := d.getS3PreSignedUrls(ctx, upReq, start, end)
if err != nil {
return err
}
// upload each chunk
for j := start; j < end; j++ {
if utils.IsCanceled(ctx) {
return ctx.Err()
}
curSize := chunkSize
if j == chunkCount {
curSize = file.GetSize() - (int64(chunkCount)-1)*chunkSize
}
err = d.uploadS3Chunk(ctx, upReq, s3PreSignedUrls, j, end, io.LimitReader(reader, chunkSize), curSize, false)
if err != nil {
return err
}
up(j * 100 / chunkCount)
}
}
// complete s3 upload
return d.completeS3(ctx, upReq)
}
func (d *Pan123) uploadS3Chunk(ctx context.Context, upReq *UploadResp, s3PreSignedUrls *S3PreSignedURLs, cur, end int, reader io.Reader, curSize int64, retry bool) error {
uploadUrl := s3PreSignedUrls.Data.PreSignedUrls[strconv.Itoa(cur)]
if uploadUrl == "" {
return fmt.Errorf("upload url is empty, s3PreSignedUrls: %+v", s3PreSignedUrls)
}
req, err := http.NewRequest("PUT", uploadUrl, reader)
if err != nil {
return err
}
req = req.WithContext(ctx)
req.ContentLength = curSize
//req.Header.Set("Content-Length", strconv.FormatInt(curSize, 10))
res, err := base.HttpClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode == http.StatusForbidden {
if retry {
return fmt.Errorf("upload s3 chunk %d failed, status code: %d", cur, res.StatusCode)
}
// refresh s3 pre signed urls
newS3PreSignedUrls, err := d.getS3PreSignedUrls(ctx, upReq, cur, end)
if err != nil {
return err
}
s3PreSignedUrls.Data.PreSignedUrls = newS3PreSignedUrls.Data.PreSignedUrls
// retry
return d.uploadS3Chunk(ctx, upReq, s3PreSignedUrls, cur, end, reader, curSize, true)
}
if res.StatusCode != http.StatusOK {
body, err := io.ReadAll(res.Body)
if err != nil {
return err
}
return fmt.Errorf("upload s3 chunk %d failed, status code: %d, body: %s", cur, res.StatusCode, body)
}
return nil
}

View File

@ -14,9 +14,24 @@ 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"
S3PreSignedUrls = API + "/file/s3_repare_upload_parts_batch"
S3Complete = API + "/file/s3_complete_multipart_upload"
)
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 +45,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 +105,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 {

View File

@ -51,7 +51,7 @@ func (d *Alias) getRootAndPath(path string) (string, string) {
} }
func (d *Alias) get(ctx context.Context, path string, dst, sub string) (model.Obj, error) { func (d *Alias) get(ctx context.Context, path string, dst, sub string) (model.Obj, error) {
obj, err := fs.Get(ctx, stdpath.Join(dst, sub)) obj, err := fs.Get(ctx, stdpath.Join(dst, sub), &fs.GetArgs{NoLog: true})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -65,7 +65,7 @@ func (d *Alias) get(ctx context.Context, path string, dst, sub string) (model.Ob
} }
func (d *Alias) list(ctx context.Context, dst, sub string) ([]model.Obj, error) { func (d *Alias) list(ctx context.Context, dst, sub string) ([]model.Obj, error) {
objs, err := fs.List(ctx, stdpath.Join(dst, sub)) objs, err := fs.List(ctx, stdpath.Join(dst, sub), &fs.ListArgs{NoLog: true})
// the obj must implement the model.SetPath interface // the obj must implement the model.SetPath interface
// return objs, err // return objs, err
if err != nil { if err != nil {
@ -83,11 +83,11 @@ func (d *Alias) list(ctx context.Context, dst, sub string) ([]model.Obj, error)
func (d *Alias) link(ctx context.Context, dst, sub string, args model.LinkArgs) (*model.Link, error) { func (d *Alias) link(ctx context.Context, dst, sub string, args model.LinkArgs) (*model.Link, error) {
reqPath := stdpath.Join(dst, sub) reqPath := stdpath.Join(dst, sub)
storage, err := fs.GetStorage(reqPath) storage, err := fs.GetStorage(reqPath, &fs.GetStoragesArgs{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = fs.Get(ctx, reqPath) _, err = fs.Get(ctx, reqPath, &fs.GetArgs{NoLog: true})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -5,7 +5,7 @@ import (
"io" "io"
"math" "math"
"net/http" "net/http"
"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"
@ -20,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 {
@ -33,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
@ -153,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) {
@ -167,28 +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()
} }
uploadUrl := partInfo.UploadUrl err = d.uploadPart(ctx, i, count, utils.NewMultiReadable(io.LimitReader(stream, DEFAULT)), &createResp, true)
if d.InternalUpload {
//Replace a known public Host with an internal Host
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, io.LimitReader(stream, DEFAULT))
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) {

View File

@ -14,7 +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 in Beijing, you can turn it on to boost the upload speed"` 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{

View File

@ -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"`
} }

View File

@ -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
}

View File

@ -24,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"
@ -35,6 +36,7 @@ import (
_ "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/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"

View File

@ -2,25 +2,30 @@ package baidu_share
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strconv"
"time" "time"
"github.com/Xhofe/go-cache"
"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"
"github.com/go-resty/resty/v2"
) )
type BaiduShare struct { type BaiduShare struct {
model.Storage model.Storage
Addition Addition
config map[string]string client *resty.Client
dlinkCache cache.ICache[string] info struct {
Root string
Seckey string
Shareid string
Uk string
}
} }
func (d *BaiduShare) Config() driver.Config { func (d *BaiduShare) Config() driver.Config {
@ -34,115 +39,208 @@ func (d *BaiduShare) GetAddition() driver.Additional {
func (d *BaiduShare) Init(ctx context.Context) error { func (d *BaiduShare) Init(ctx context.Context) error {
// TODO login / refresh token // TODO login / refresh token
//op.MustSaveDriverStorage(d) //op.MustSaveDriverStorage(d)
d.config = map[string]string{} d.client = resty.New().
d.dlinkCache = cache.NewMemCache(cache.WithClearInterval[string](time.Duration(d.CacheExpiration) * time.Minute)) SetBaseURL("https://pan.baidu.com").
return nil 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 { func (d *BaiduShare) Drop(ctx context.Context) error {
d.dlinkCache.Clear()
return nil return nil
} }
func (d *BaiduShare) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { func (d *BaiduShare) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
// TODO return the files list // TODO return the files list, required
body := url.Values{ reqDir := dir.GetPath()
"shorturl": {d.Surl}, isRoot := "0"
"dir": {dir.GetPath()}, if reqDir == d.RootFolderPath {
"root": {"0"}, reqDir = path.Join(d.info.Root, reqDir)
"pwd": {d.Pwd},
"num": {"1000"},
"order": {"time"},
} }
if body.Get("dir") == "" || body.Get("dir") == d.config["root"] { if reqDir == d.info.Root {
body.Set("root", "1") isRoot = "1"
} }
res := []model.Obj{} objs := []model.Obj{}
var err error var err error
var page int64 = 1 var page uint64 = 1
more := true more := true
for more { for more && err == nil {
body.Set("page", strconv.FormatInt(page, 10)) respJson := struct {
req := base.RestyClient.R(). Errno int64 `json:"errno"`
SetCookies([]*http.Cookie{{Name: "BDUSS", Value: d.BDUSS}}). Data struct {
SetBody(body.Encode()) More bool `json:"has_more"`
resp, e := req.Post("https://pan.baidu.com/share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1") 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 err = e
jsonresp := jsonResp{}
if err == nil { if err == nil {
err = base.RestyClient.JSONUnmarshal(resp.Body(), &jsonresp) if resp.IsSuccess() && respJson.Errno == 0 {
} page++
if err == nil && jsonresp.Errno == 0 { more = respJson.Data.More
more = jsonresp.Data.More for _, v := range respJson.Data.List {
page += 1 size, _ := v.Size.Int64()
for _, v := range jsonresp.Data.List { mtime, _ := v.Mtime.Int64()
size, _ := v.Size.Int64() objs = append(objs, &model.Object{
mtime, _ := v.Time.Int64() ID: v.Fsid.String(),
res = append(res, &model.Object{ Path: v.Path,
ID: v.ID.String(), Name: v.Name,
Path: v.Path, Size: size,
Name: v.Name, Modified: time.Unix(mtime, 0),
Size: size, IsFolder: v.Isdir.String() == "1",
Modified: time.Unix(mtime, 0), })
IsFolder: v.Dir.String() == "1", }
}) } else {
d.dlinkCache.Set(v.Path, v.Dlink, cache.WithEx[string](time.Duration(d.CacheExpiration/2)*time.Minute)) err = fmt.Errorf(" %s; %s; ", resp.Status(), resp.Body())
} }
if len(res) > 0 && body.Get("root") == "1" {
d.config["root"] = path.Dir(res[0].GetPath())
}
} else {
if err == nil {
err = fmt.Errorf("errno:%d", jsonresp.Errno)
}
break
} }
} }
return res, err return objs, err
} }
func (d *BaiduShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { func (d *BaiduShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
// TODO return link of file // TODO return link of file, required
var err error link := model.Link{Header: d.client.Header}
req := base.RestyClient.R().SetCookies([]*http.Cookie{{Name: "BDUSS", Value: d.BDUSS}}) sign := ""
url, found := d.dlinkCache.Get(file.GetPath()) stamp := ""
if !found { signJson := struct {
_, err = d.List(ctx, &model.Object{Path: path.Dir(file.GetPath())}, model.ListArgs{}) Errno int64 `json:"errno"`
url, found = d.dlinkCache.Get(file.GetPath()) Data struct {
if err == nil && !found { Stamp json.Number `json:"timestamp"`
err = errs.NotSupport 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())
} }
} }
return &model.Link{URL: url, Header: req.Header}, err 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 { func (d *BaiduShare) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
// TODO create folder // TODO create folder, optional
return errs.NotSupport return errs.NotSupport
} }
func (d *BaiduShare) Move(ctx context.Context, srcObj, dstDir model.Obj) error { func (d *BaiduShare) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
// TODO move obj // TODO move obj, optional
return errs.NotSupport return errs.NotSupport
} }
func (d *BaiduShare) Rename(ctx context.Context, srcObj model.Obj, newName string) error { func (d *BaiduShare) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
// TODO rename obj // TODO rename obj, optional
return errs.NotSupport return errs.NotSupport
} }
func (d *BaiduShare) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { func (d *BaiduShare) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
// TODO copy obj // TODO copy obj, optional
return errs.NotSupport return errs.NotSupport
} }
func (d *BaiduShare) Remove(ctx context.Context, obj model.Obj) error { func (d *BaiduShare) Remove(ctx context.Context, obj model.Obj) error {
// TODO remove obj // TODO remove obj, optional
return errs.NotSupport return errs.NotSupport
} }
func (d *BaiduShare) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { func (d *BaiduShare) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
// TODO upload file // TODO upload file, optional
return errs.NotSupport return errs.NotSupport
} }

View File

@ -17,14 +17,17 @@ type Addition struct {
} }
var config = driver.Config{ var config = driver.Config{
Name: "BaiduShare", Name: "BaiduShare",
LocalSort: true, LocalSort: true,
OnlyLocal: false, OnlyLocal: false,
OnlyProxy: false, OnlyProxy: false,
NoCache: false, NoCache: false,
NoUpload: true, NoUpload: true,
NeedMs: false, NeedMs: false,
DefaultRoot: "", DefaultRoot: "/",
CheckStatus: false,
Alert: "",
NoOverwriteUpload: false,
} }
func init() { func init() {

View File

@ -1,19 +1 @@
package baidu_share package baidu_share
import "encoding/json"
type jsonResp struct {
Errno int64 `json:"errno"`
Data struct {
More bool `json:"has_more"`
List []struct {
ID json.Number `json:"fs_id"`
Dir json.Number `json:"isdir"`
Path string `json:"path"`
Name string `json:"server_filename"`
Time json.Number `json:"server_mtime"`
Size json.Number `json:"size"`
Dlink string `json:"dlink"`
} `json:"list"`
} `json:"data"`
}

View File

@ -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 {

View File

@ -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"`
}

View File

@ -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{} {

View File

@ -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
} }

View File

@ -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
}

View File

@ -193,6 +193,9 @@ func (d *Onedrive) upBig(ctx context.Context, dstDir model.Obj, stream model.Fil
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())) req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
finish += byteSize finish += byteSize
res, err := base.HttpClient.Do(req) res, err := base.HttpClient.Do(req)
if err != nil {
return err
}
if res.StatusCode != 201 && res.StatusCode != 202 { if res.StatusCode != 201 && res.StatusCode != 202 {
data, _ := io.ReadAll(res.Body) data, _ := io.ReadAll(res.Body)
res.Body.Close() res.Body.Close()

View 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)

View 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{}
})
}

View 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"`
}

View File

@ -0,0 +1,199 @@
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 err != nil {
return err
}
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
}

View File

@ -56,8 +56,10 @@ func (d *Quark) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
"fids": []string{file.GetID()}, "fids": []string{file.GetID()},
} }
var resp DownResp var resp DownResp
ua := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch"
_, err := d.request("/file/download", http.MethodPost, func(req *resty.Request) { _, err := d.request("/file/download", http.MethodPost, func(req *resty.Request) {
req.SetBody(data) req.SetHeader("User-Agent", ua).
SetBody(data)
}, &resp) }, &resp)
if err != nil { if err != nil {
return nil, err return nil, err
@ -65,8 +67,9 @@ func (d *Quark) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
return &model.Link{ return &model.Link{
URL: resp.Data[0].DownloadUrl, URL: resp.Data[0].DownloadUrl,
Header: http.Header{ Header: http.Header{
"Cookie": []string{d.Cookie}, "Cookie": []string{d.Cookie},
"Referer": []string{"https://pan.quark.cn"}, "Referer": []string{"https://pan.quark.cn"},
"User-Agent": []string{ua},
}, },
}, nil }, nil
} }

View File

@ -7,6 +7,7 @@ import (
"io" "io"
"net/url" "net/url"
stdpath "path" stdpath "path"
"strings"
"time" "time"
"github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/driver"
@ -75,6 +76,9 @@ func (d *S3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*mo
if d.CustomHost != "" { if d.CustomHost != "" {
err = req.Build() err = req.Build()
link = req.HTTPRequest.URL.String() link = req.HTTPRequest.URL.String()
if d.RemoveBucket {
link = strings.Replace(link, "/"+d.Bucket, "", 1)
}
} else { } else {
link, err = req.Presign(time.Hour * time.Duration(d.SignURLExpire)) link, err = req.Presign(time.Hour * time.Duration(d.SignURLExpire))
} }
@ -128,6 +132,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{

View File

@ -17,6 +17,7 @@ type Addition struct {
Placeholder string `json:"placeholder"` Placeholder string `json:"placeholder"`
ForcePathStyle bool `json:"force_path_style"` ForcePathStyle bool `json:"force_path_style"`
ListObjectVersion string `json:"list_object_version" type:"select" options:"v1,v2" default:"v1"` ListObjectVersion string `json:"list_object_version" type:"select" options:"v1,v2" default:"v1"`
RemoveBucket bool `json:"remove_bucket" help:"Remove bucket name from path when using custom host."`
} }
var config = driver.Config{ var config = driver.Config{

View File

@ -15,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 int64
} }
func (d *SMB) Config() driver.Config { func (d *SMB) Config() driver.Config {

View File

@ -119,7 +119,7 @@ func (d *Trainbit) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
query := &url.Values{} query := &url.Values{}
query.Add("q", strings.Split(dstDir.GetID(), "_")[1]) query.Add("q", strings.Split(dstDir.GetID(), "_")[1])
query.Add("guid", guid) query.Add("guid", guid)
query.Add("name", url.QueryEscape(local2provider(stream.GetName(), false))) query.Add("name", url.QueryEscape(local2provider(stream.GetName(), false) + "."))
endpoint.RawQuery = query.Encode() endpoint.RawQuery = query.Encode()
var total int64 var total int64
total = 0 total = 0

View File

@ -1,6 +1,7 @@
package trainbit package trainbit
import ( import (
"html"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@ -88,36 +89,19 @@ func getToken(apiKey string, AUSHELLPORTAL string) (string, string, error) {
} }
func local2provider(filename string, isFolder bool) string { func local2provider(filename string, isFolder bool) string {
filename = strings.Replace(filename, "%", url.QueryEscape("%"), -1)
filename = strings.Replace(filename, "/", url.QueryEscape("/"), -1)
filename = strings.Replace(filename, ":", url.QueryEscape(":"), -1)
filename = strings.Replace(filename, "*", url.QueryEscape("*"), -1)
filename = strings.Replace(filename, "?", url.QueryEscape("?"), -1)
filename = strings.Replace(filename, "\"", url.QueryEscape("\""), -1)
filename = strings.Replace(filename, "<", url.QueryEscape("<"), -1)
filename = strings.Replace(filename, ">", url.QueryEscape(">"), -1)
filename = strings.Replace(filename, "|", url.QueryEscape("|"), -1)
if isFolder { if isFolder {
return filename return filename
} }
return strings.Join([]string{filename, ".delete_suffix."}, "") return filename + ".delete_suffix"
} }
func provider2local(filename string) string { func provider2local(filename string) string {
index := strings.LastIndex(filename, ".delete_suffix.") filename = html.UnescapeString(filename)
index := strings.LastIndex(filename, ".delete_suffix")
if index != -1 { if index != -1 {
filename = filename[:index] filename = filename[:index]
} }
rawName := strings.Replace(filename, url.QueryEscape("/"), "/", -1) return filename
rawName = strings.Replace(rawName, url.QueryEscape(":"), ":", -1)
rawName = strings.Replace(rawName, url.QueryEscape("*"), "*", -1)
rawName = strings.Replace(rawName, url.QueryEscape("?"), "?", -1)
rawName = strings.Replace(rawName, url.QueryEscape("\""), "\"", -1)
rawName = strings.Replace(rawName, url.QueryEscape("<"), "<", -1)
rawName = strings.Replace(rawName, url.QueryEscape(">"), ">", -1)
rawName = strings.Replace(rawName, url.QueryEscape("|"), "|", -1)
rawName = strings.Replace(rawName, url.QueryEscape("%"), "%", -1)
return rawName
} }
func parseRawFileObject(rawObject []any) ([]model.Obj, error) { func parseRawFileObject(rawObject []any) ([]model.Obj, error) {
@ -129,10 +113,10 @@ func parseRawFileObject(rawObject []any) ([]model.Obj, error) {
} }
isFolder := int64(object["ty"].(float64)) == 1 isFolder := int64(object["ty"].(float64)) == 1
var name string var name string
if isFolder { if object["ext"].(string) != "" {
name = object["name"].(string)
} else {
name = strings.Join([]string{object["name"].(string), 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)) modified, err := time.Parse("2006/01/02 15:04:05", object["modified"].(string))
if err != nil { if err != nil {

View 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
View 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
View 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
}

View 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
View 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
}

30
go.mod
View File

@ -1,12 +1,12 @@
module github.com/alist-org/alist/v3 module github.com/alist-org/alist/v3
go 1.19 go 1.20
require ( require (
github.com/SheltonZhu/115driver v1.0.14 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.3.0 github.com/deckarep/golang-set/v2 v2.3.0
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
@ -26,14 +26,14 @@ 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.8.0
golang.org/x/image v0.6.0 golang.org/x/image v0.7.0
golang.org/x/net v0.8.0 golang.org/x/net v0.9.0
gorm.io/driver/mysql v1.4.7 gorm.io/driver/mysql v1.4.7
gorm.io/driver/postgres v1.4.8 gorm.io/driver/postgres v1.4.8
gorm.io/driver/sqlite v1.4.4 gorm.io/driver/sqlite v1.4.4
@ -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
@ -104,8 +104,8 @@ require (
github.com/ugorji/go/codec v1.2.9 // indirect github.com/ugorji/go/codec v1.2.9 // indirect
go.etcd.io/bbolt v1.3.5 // indirect go.etcd.io/bbolt v1.3.5 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/sys v0.6.0 // indirect golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect

62
go.sum
View File

@ -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,6 @@ 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.2.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/deckarep/golang-set/v2 v2.3.0 h1:qs18EKUfHm2X9fA50Mr/M5hccg2tNnVqsiBImnyDs0g= github.com/deckarep/golang-set/v2 v2.3.0 h1:qs18EKUfHm2X9fA50Mr/M5hccg2tNnVqsiBImnyDs0g=
github.com/deckarep/golang-set/v2 v2.3.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=
@ -113,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=
@ -132,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=
@ -152,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=
@ -218,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=
@ -248,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=
@ -269,11 +265,11 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -285,8 +281,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -309,13 +305,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -323,8 +319,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=

View File

@ -13,19 +13,32 @@ 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) if !args.NoLog {
log.Errorf("failed list %s: %+v", path, err)
}
return nil, err return nil, err
} }
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 {
log.Errorf("failed get %s: %+v", path, err) if !args.NoLog {
log.Errorf("failed get %s: %+v", path, err)
}
return nil, err return nil, err
} }
return res, nil return res, nil
@ -96,7 +109,10 @@ func PutAsTask(dstDirPath string, file *model.FileStream) error {
return err return err
} }
func GetStorage(path string) (driver.Driver, error) { type GetStoragesArgs struct {
}
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 {
return nil, err return nil, err

View File

@ -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,9 +24,11 @@ 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) if !args.NoLog {
log.Errorf("fs/list: %+v", err)
}
if len(virtualFiles) == 0 { if len(virtualFiles) == 0 {
return nil, errors.WithMessage(err, "failed get objs") return nil, errors.WithMessage(err, "failed get objs")
} }

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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++ {

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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
} }

View File

@ -3,6 +3,7 @@ package task
import ( import (
"context" "context"
"runtime"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -63,11 +64,17 @@ func (t Task[K]) GetErrMsg() string {
return t.Error.Error() return t.Error.Error()
} }
func getCurrentGoroutineStack() string {
buf := make([]byte, 1<<16)
n := runtime.Stack(buf, false)
return string(buf[:n])
}
func (t *Task[K]) run() { func (t *Task[K]) run() {
t.state = RUNNING t.state = RUNNING
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
log.Errorf("error [%+v] while run task [%s]", err, t.Name) log.Errorf("error [%s] while run task [%s],stack trace:\n%s", err, t.Name, getCurrentGoroutineStack())
t.Error = errors.Errorf("panic: %+v", err) t.Error = errors.Errorf("panic: %+v", err)
t.state = ERRORED t.state = ERRORED
} }

View File

@ -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
}

View File

@ -8,9 +8,15 @@ import (
"github.com/alist-org/alist/v3/internal/conf" "github.com/alist-org/alist/v3/internal/conf"
"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/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

View File

@ -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
@ -61,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 {
@ -93,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)

View File

@ -21,7 +21,7 @@ import (
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
@ -65,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

View File

@ -133,7 +133,7 @@ func FsRecursiveMove(c *gin.Context) {
} }
c.Set("meta", meta) c.Set("meta", meta)
rootFiles, err := fs.List(c, srcDir, false) rootFiles, err := fs.List(c, srcDir, &fs.ListArgs{})
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
@ -154,7 +154,7 @@ func FsRecursiveMove(c *gin.Context) {
if movingFile.IsDir() { if movingFile.IsDir() {
// directory, recursive move // directory, recursive move
subFilePath := movingFilePath subFilePath := movingFilePath
subFiles, err := fs.List(c, subFilePath, true) subFiles, err := fs.List(c, subFilePath, &fs.ListArgs{Refresh: true})
if err != nil { if err != nil {
common.ErrorResp(c, err, 500) common.ErrorResp(c, err, 500)
return return
@ -293,7 +293,7 @@ func FsRegexRename(c *gin.Context) {
return return
} }
files, err := fs.List(c, reqPath, false) files, 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
@ -351,6 +351,105 @@ func FsRemove(c *gin.Context) {
common.SuccessResp(c) common.SuccessResp(c)
} }
type RemoveEmptyDirectoryReq struct {
SrcDir string `json:"src_dir"`
}
func FsRemoveEmptyDirectory(c *gin.Context) {
var req RemoveEmptyDirectoryReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
user := c.MustGet("user").(*model.User)
if !user.CanRemove() {
common.ErrorResp(c, errs.PermissionDenied, 403)
return
}
srcDir, err := user.JoinPath(req.SrcDir)
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)
// record the parent file
fileParentMap := make(map[model.Obj]model.Obj)
// removing files
removingFiles := generic.NewQueue[model.Obj]()
// removed files
removedFiles := make(map[string]bool)
for _, file := range rootFiles {
if !file.IsDir() {
continue
}
removingFiles.Push(file)
filePathMap[file] = srcDir
}
for !removingFiles.IsEmpty() {
removingFile := removingFiles.Pop()
removingFilePath := fmt.Sprintf("%s/%s", filePathMap[removingFile], removingFile.GetName())
if removedFiles[removingFilePath] {
continue
}
subFiles, err := fs.List(c, removingFilePath, &fs.ListArgs{Refresh: true})
if err != nil {
common.ErrorResp(c, err, 500)
return
}
if len(subFiles) == 0 {
// remove empty directory
err = fs.Remove(c, removingFilePath)
removedFiles[removingFilePath] = true
if err != nil {
common.ErrorResp(c, err, 500)
return
}
// recheck parent folder
parentFile, exist := fileParentMap[removingFile]
if exist {
removingFiles.Push(parentFile)
}
} else {
// recursive remove
for _, subFile := range subFiles {
if !subFile.IsDir() {
continue
}
removingFiles.Push(subFile)
filePathMap[subFile] = removingFilePath
fileParentMap[subFile] = removingFile
}
}
}
common.SuccessResp(c)
}
// Link return real link, just for proxy program, it may contain cookie, so just allowed for admin // Link return real link, just for proxy program, it may contain cookie, so just allowed for admin
func Link(c *gin.Context) { func Link(c *gin.Context) {
var req MkdirOrLinkReq var req MkdirOrLinkReq
@ -362,7 +461,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

View File

@ -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),

View File

@ -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

View File

@ -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
} }

View File

@ -131,6 +131,7 @@ func _fs(g *gin.RouterGroup) {
g.POST("/recursive_move", handles.FsRecursiveMove) 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.POST("/remove_empty_directory", handles.FsRemoveEmptyDirectory)
g.PUT("/put", middlewares.FsUp, handles.FsStream) g.PUT("/put", middlewares.FsUp, handles.FsStream)
g.PUT("/form", middlewares.FsUp, handles.FsForm) g.PUT("/form", middlewares.FsUp, handles.FsForm)
g.POST("/link", middlewares.AuthAdmin, handles.Link) g.POST("/link", middlewares.AuthAdmin, handles.Link)

View File

@ -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)

View File

@ -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
} }