Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
fe416ba15c | |||
de66708b24 | |||
2ca3e0b8bc | |||
ae04a0a760 | |||
c28168c970 | |||
46b2ed2507 | |||
22843ffc70 | |||
e1b6368343 | |||
62dae50d70 | |||
43a8ed472b | |||
d87878c232 | |||
ab7dee49b0 | |||
dca115506d | |||
be17fba0c6 | |||
cd58aa5efe | |||
946833d2cc | |||
eb42d09849 | |||
9d00492750 | |||
b6711d6ab9 | |||
7bc46de8aa | |||
a4f4fb2d73 | |||
a181b56ea7 | |||
d0b743d955 | |||
a985b748e9 | |||
44cb8aaafe | |||
51f5d1b3c4 | |||
36e0d6f787 | |||
3d0065bdcf | |||
7bf8071095 | |||
30d39f8e10 | |||
20d3ef7de6 | |||
86e5dae4d1 | |||
d89b1d4871 | |||
080e6fb22a | |||
e1cd71616d | |||
c92e11dad5 | |||
b52e8747fa | |||
14305748f0 | |||
44f8112e53 | |||
6a90b1d40a | |||
b42ec3e810 | |||
28875ce304 | |||
9b99e8ab70 | |||
98872a8fdb | |||
ce4a295008 | |||
bc1babb5b5 |
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@ -25,6 +25,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
sudo snap install zig --classic --beta
|
||||||
docker pull crazymax/xgo:latest
|
docker pull crazymax/xgo:latest
|
||||||
go install github.com/crazy-max/xgo@latest
|
go install github.com/crazy-max/xgo@latest
|
||||||
sudo apt install upx
|
sudo apt install upx
|
||||||
|
32
.github/workflows/build_docker.yml
vendored
32
.github/workflows/build_docker.yml
vendored
@ -6,7 +6,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_docker:
|
build_docker:
|
||||||
name: Docker
|
name: Build docker
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@ -30,10 +30,36 @@ jobs:
|
|||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
|
build_docker_with_aria2:
|
||||||
|
needs: build_docker
|
||||||
|
name: Build docker with aria2
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
repository: alist-org/with_aria2
|
||||||
|
ref: main
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Commit
|
||||||
|
run: |
|
||||||
|
git config --local user.email "i@nn.ci"
|
||||||
|
git config --local user.name "Noah Hsu"
|
||||||
|
git commit --allow-empty -m "Trigger build for ${{ github.sha }}"
|
||||||
|
|
||||||
|
- name: Push commit
|
||||||
|
uses: ad-m/github-push-action@master
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.MY_TOKEN }}
|
||||||
|
branch: main
|
||||||
|
repository: alist-org/with_aria2
|
2
.github/workflows/issue_question.yml
vendored
2
.github/workflows/issue_question.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
|||||||
if: github.event.label.name == 'question'
|
if: github.event.label.name == 'question'
|
||||||
steps:
|
steps:
|
||||||
- name: Create comment
|
- name: Create comment
|
||||||
uses: actions-cool/issues-helper@v3.3.3
|
uses: actions-cool/issues-helper@v3.4.0
|
||||||
with:
|
with:
|
||||||
actions: 'create-comment'
|
actions: 'create-comment'
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@ -38,6 +38,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
sudo snap install zig --classic --beta
|
||||||
docker pull crazymax/xgo:latest
|
docker pull crazymax/xgo:latest
|
||||||
go install github.com/crazy-max/xgo@latest
|
go install github.com/crazy-max/xgo@latest
|
||||||
sudo apt install upx
|
sudo apt install upx
|
||||||
|
32
.github/workflows/release_docker.yml
vendored
32
.github/workflows/release_docker.yml
vendored
@ -7,7 +7,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release_docker:
|
release_docker:
|
||||||
name: Docker
|
name: Release Docker
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@ -33,10 +33,36 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
||||||
|
|
||||||
|
release_docker_with_aria2:
|
||||||
|
needs: release_docker
|
||||||
|
name: Release docker with aria2
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
repository: alist-org/with_aria2
|
||||||
|
ref: main
|
||||||
|
persist-credentials: false
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Add tag
|
||||||
|
run: |
|
||||||
|
git config --local user.email "i@nn.ci"
|
||||||
|
git config --local user.name "Noah Hsu"
|
||||||
|
git tag -a ${{ github.ref_name }} -m "release ${{ github.ref_name }}"
|
||||||
|
|
||||||
|
- name: Push tags
|
||||||
|
uses: ad-m/github-push-action@master
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.MY_TOKEN }}
|
||||||
|
branch: main
|
||||||
|
repository: alist-org/with_aria2
|
||||||
|
16
build.sh
16
build.sh
@ -1,7 +1,7 @@
|
|||||||
appName="alist"
|
appName="alist"
|
||||||
builtAt="$(date +'%F %T %z')"
|
builtAt="$(date +'%F %T %z')"
|
||||||
goVersion=$(go version | sed 's/go version //')
|
goVersion=$(go version | sed 's/go version //')
|
||||||
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
|
gitAuthor="Xhofe <i@nn.ci>"
|
||||||
gitCommit=$(git log --pretty=format:"%h" -1)
|
gitCommit=$(git log --pretty=format:"%h" -1)
|
||||||
|
|
||||||
if [ "$1" = "dev" ]; then
|
if [ "$1" = "dev" ]; then
|
||||||
@ -41,6 +41,15 @@ FetchWebRelease() {
|
|||||||
rm -rf dist.tar.gz
|
rm -rf dist.tar.gz
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BuildWinArm64() {
|
||||||
|
echo building for windows-arm64
|
||||||
|
export GOOS=windows
|
||||||
|
export GOARCH=arm64
|
||||||
|
export CC=$(pwd)/wrapper/zcc-arm64
|
||||||
|
export CXX=$(pwd)/wrapper/zcxx-arm64
|
||||||
|
go build -o "$1" -ldflags="$ldflags" -tags=jsoniter .
|
||||||
|
}
|
||||||
|
|
||||||
BuildDev() {
|
BuildDev() {
|
||||||
rm -rf .git/
|
rm -rf .git/
|
||||||
xgo -targets=linux/amd64,windows/amd64,darwin/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
xgo -targets=linux/amd64,windows/amd64,darwin/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||||
@ -48,7 +57,7 @@ BuildDev() {
|
|||||||
mv alist-* dist
|
mv alist-* dist
|
||||||
cd dist
|
cd dist
|
||||||
upx -9 ./alist-linux*
|
upx -9 ./alist-linux*
|
||||||
upx -9 ./alist-windows*
|
upx -9 ./alist-windows-amd64.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
|
||||||
}
|
}
|
||||||
@ -80,10 +89,11 @@ BuildRelease() {
|
|||||||
export CGO_ENABLED=1
|
export CGO_ENABLED=1
|
||||||
go build -o ./build/$appName-$os_arch -ldflags="$muslflags" -tags=jsoniter .
|
go build -o ./build/$appName-$os_arch -ldflags="$muslflags" -tags=jsoniter .
|
||||||
done
|
done
|
||||||
|
BuildWinArm64 ./build/alist-windows-arm64.exe
|
||||||
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*
|
upx -9 ./alist-windows-amd64.exe
|
||||||
mv alist-* build
|
mv alist-* build
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ the address is defined in config file`,
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
Init()
|
Init()
|
||||||
bootstrap.InitAria2()
|
bootstrap.InitAria2()
|
||||||
|
bootstrap.InitQbittorrent()
|
||||||
bootstrap.LoadStorages()
|
bootstrap.LoadStorages()
|
||||||
if !flags.Debug && !flags.Dev {
|
if !flags.Debug && !flags.Dev {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
@ -96,14 +96,14 @@ func (d *Pan123) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
u_ := fmt.Sprintf("https://%s%s", u.Host, u.Path)
|
u_ := u.String()
|
||||||
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Head(u_)
|
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Head(u_)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Debug(res.String())
|
log.Debug(res.String())
|
||||||
link := model.Link{
|
link := model.Link{
|
||||||
URL: downloadUrl,
|
URL: u_,
|
||||||
}
|
}
|
||||||
log.Debugln("res code: ", res.StatusCode())
|
log.Debugln("res code: ", res.StatusCode())
|
||||||
if res.StatusCode() == 302 {
|
if res.StatusCode() == 302 {
|
||||||
|
@ -83,8 +83,7 @@ func (d *Yun139) MakeDir(ctx context.Context, parentDir model.Obj, dirName strin
|
|||||||
}
|
}
|
||||||
pathname = "/orchestration/familyCloud/cloudCatalog/v1.0/createCloudDoc"
|
pathname = "/orchestration/familyCloud/cloudCatalog/v1.0/createCloudDoc"
|
||||||
}
|
}
|
||||||
_, err := d.post(pathname,
|
_, err := d.post(pathname, data, nil)
|
||||||
data, nil)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +221,22 @@ func (d *Yun139) Remove(ctx context.Context, obj model.Obj) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ = iota //ignore first value by assigning to blank identifier
|
||||||
|
KB = 1 << (10 * iota)
|
||||||
|
MB
|
||||||
|
GB
|
||||||
|
TB
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPartSize(size int64) int64 {
|
||||||
|
// 网盘对于分片数量存在上限
|
||||||
|
if size/GB > 30 {
|
||||||
|
return 512 * MB
|
||||||
|
}
|
||||||
|
return 100 * MB
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||||
data := base.Json{
|
data := base.Json{
|
||||||
"manualRename": 2,
|
"manualRename": 2,
|
||||||
@ -267,17 +282,17 @@ func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
// Progress
|
// Progress
|
||||||
p := driver.NewProgress(stream.GetSize(), up)
|
p := driver.NewProgress(stream.GetSize(), up)
|
||||||
|
|
||||||
var Default int64 = 104857600
|
var partSize = getPartSize(stream.GetSize())
|
||||||
part := (stream.GetSize() + Default - 1) / Default
|
part := (stream.GetSize() + partSize - 1) / partSize
|
||||||
for i := int64(0); i < part; i++ {
|
for i := int64(0); i < part; i++ {
|
||||||
if utils.IsCanceled(ctx) {
|
if utils.IsCanceled(ctx) {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
start := i * Default
|
start := i * partSize
|
||||||
byteSize := stream.GetSize() - start
|
byteSize := stream.GetSize() - start
|
||||||
if byteSize > Default {
|
if byteSize > partSize {
|
||||||
byteSize = Default
|
byteSize = partSize
|
||||||
}
|
}
|
||||||
|
|
||||||
limitReader := io.LimitReader(stream, byteSize)
|
limitReader := io.LimitReader(stream, byteSize)
|
||||||
@ -301,6 +316,11 @@ func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Debugf("%+v", res)
|
log.Debugf("%+v", res)
|
||||||
|
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
res.Body.Close()
|
res.Body.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ func (d *AListV3) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|||||||
SetResult(&resp).
|
SetResult(&resp).
|
||||||
SetHeader("Authorization", d.AccessToken).
|
SetHeader("Authorization", d.AccessToken).
|
||||||
SetBody(MoveCopyReq{
|
SetBody(MoveCopyReq{
|
||||||
SrcDir: srcObj.GetPath(),
|
SrcDir: path.Dir(srcObj.GetPath()),
|
||||||
DstDir: dstDir.GetPath(),
|
DstDir: dstDir.GetPath(),
|
||||||
Names: []string{srcObj.GetName()},
|
Names: []string{srcObj.GetName()},
|
||||||
}).Post(url)
|
}).Post(url)
|
||||||
@ -135,7 +135,7 @@ func (d *AListV3) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
|||||||
SetResult(&resp).
|
SetResult(&resp).
|
||||||
SetHeader("Authorization", d.AccessToken).
|
SetHeader("Authorization", d.AccessToken).
|
||||||
SetBody(MoveCopyReq{
|
SetBody(MoveCopyReq{
|
||||||
SrcDir: srcObj.GetPath(),
|
SrcDir: path.Dir(srcObj.GetPath()),
|
||||||
DstDir: dstDir.GetPath(),
|
DstDir: dstDir.GetPath(),
|
||||||
Names: []string{srcObj.GetName()},
|
Names: []string{srcObj.GetName()},
|
||||||
}).Post(url)
|
}).Post(url)
|
||||||
@ -149,7 +149,7 @@ func (d *AListV3) Remove(ctx context.Context, obj model.Obj) error {
|
|||||||
SetResult(&resp).
|
SetResult(&resp).
|
||||||
SetHeader("Authorization", d.AccessToken).
|
SetHeader("Authorization", d.AccessToken).
|
||||||
SetBody(RemoveReq{
|
SetBody(RemoveReq{
|
||||||
Dir: obj.GetPath(),
|
Dir: path.Dir(obj.GetPath()),
|
||||||
Names: []string{obj.GetName()},
|
Names: []string{obj.GetName()},
|
||||||
}).Post(url)
|
}).Post(url)
|
||||||
return checkResp(resp, err)
|
return checkResp(resp, err)
|
||||||
|
@ -31,6 +31,7 @@ type AliDrive struct {
|
|||||||
AccessToken string
|
AccessToken string
|
||||||
cron *cron.Cron
|
cron *cron.Cron
|
||||||
DriveId string
|
DriveId string
|
||||||
|
UserID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliDrive) Config() driver.Config {
|
func (d *AliDrive) Config() driver.Config {
|
||||||
@ -54,6 +55,7 @@ func (d *AliDrive) Init(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.DriveId = utils.Json.Get(res, "default_drive_id").ToString()
|
d.DriveId = utils.Json.Get(res, "default_drive_id").ToString()
|
||||||
|
d.UserID = utils.Json.Get(res, "user_id").ToString()
|
||||||
d.cron = cron.NewCron(time.Hour * 2)
|
d.cron = cron.NewCron(time.Hour * 2)
|
||||||
d.cron.Do(func() {
|
d.cron.Do(func() {
|
||||||
err := d.refreshToken()
|
err := d.refreshToken()
|
||||||
@ -61,7 +63,22 @@ func (d *AliDrive) Init(ctx context.Context) error {
|
|||||||
log.Errorf("%+v", err)
|
log.Errorf("%+v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return err
|
if global.Has(d.UserID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// init deviceID
|
||||||
|
deviceID := utils.GetSHA256Encode(d.UserID)
|
||||||
|
// init privateKey
|
||||||
|
privateKey, _ := NewPrivateKeyFromHex(deviceID)
|
||||||
|
state := State{
|
||||||
|
nonce: -1,
|
||||||
|
privateKey: privateKey,
|
||||||
|
deviceID: deviceID,
|
||||||
|
}
|
||||||
|
// store state
|
||||||
|
global.Store(d.UserID, &state)
|
||||||
|
// init signature
|
||||||
|
return d.reSign()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *AliDrive) Drop(ctx context.Context) error {
|
func (d *AliDrive) Drop(ctx context.Context) error {
|
||||||
@ -169,17 +186,27 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
"type": "file",
|
"type": "file",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var localFile *os.File
|
||||||
|
if fileStream, ok := file.ReadCloser.(*model.FileStream); ok {
|
||||||
|
localFile, _ = fileStream.ReadCloser.(*os.File)
|
||||||
|
}
|
||||||
if d.RapidUpload {
|
if d.RapidUpload {
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||||
io.CopyN(buf, file, 1024)
|
io.CopyN(buf, file, 1024)
|
||||||
reqBody["pre_hash"] = utils.GetSHA1Encode(buf.String())
|
reqBody["pre_hash"] = utils.GetSHA1Encode(buf.String())
|
||||||
// 把头部拼接回去
|
if localFile != nil {
|
||||||
file.ReadCloser = struct {
|
if _, err := localFile.Seek(0, io.SeekStart); err != nil {
|
||||||
io.Reader
|
return err
|
||||||
io.Closer
|
}
|
||||||
}{
|
} else {
|
||||||
Reader: io.MultiReader(buf, file),
|
// 把头部拼接回去
|
||||||
Closer: file,
|
file.ReadCloser = struct {
|
||||||
|
io.Reader
|
||||||
|
io.Closer
|
||||||
|
}{
|
||||||
|
Reader: io.MultiReader(buf, file),
|
||||||
|
Closer: file,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reqBody["content_hash_name"] = "none"
|
reqBody["content_hash_name"] = "none"
|
||||||
@ -196,18 +223,28 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
}
|
}
|
||||||
|
|
||||||
if d.RapidUpload && e.Code == "PreHashMatched" {
|
if d.RapidUpload && e.Code == "PreHashMatched" {
|
||||||
tempFile, err := os.CreateTemp(conf.Conf.TempDir, "file-*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = tempFile.Close()
|
|
||||||
_ = os.Remove(tempFile.Name())
|
|
||||||
}()
|
|
||||||
delete(reqBody, "pre_hash")
|
delete(reqBody, "pre_hash")
|
||||||
h := sha1.New()
|
h := sha1.New()
|
||||||
if _, err = io.Copy(io.MultiWriter(tempFile, h), file); err != nil {
|
if localFile != nil {
|
||||||
return err
|
if _, err = io.Copy(h, localFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = localFile.Seek(0, io.SeekStart); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tempFile, err := os.CreateTemp(conf.Conf.TempDir, "file-*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = tempFile.Close()
|
||||||
|
_ = os.Remove(tempFile.Name())
|
||||||
|
}()
|
||||||
|
if _, err = io.Copy(io.MultiWriter(tempFile, h), file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
localFile = tempFile
|
||||||
}
|
}
|
||||||
reqBody["content_hash"] = hex.EncodeToString(h.Sum(nil))
|
reqBody["content_hash"] = hex.EncodeToString(h.Sum(nil))
|
||||||
reqBody["content_hash_name"] = "sha1"
|
reqBody["content_hash_name"] = "sha1"
|
||||||
@ -228,7 +265,7 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
if file.GetSize() > 0 {
|
if file.GetSize() > 0 {
|
||||||
o = r.Mod(r, i)
|
o = r.Mod(r, i)
|
||||||
}
|
}
|
||||||
n, _ := io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8])
|
n, _ := io.NewSectionReader(localFile, o.Int64(), 8).Read(buf[:8])
|
||||||
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
|
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
|
||||||
|
|
||||||
_, err, e := d.request("https://api.aliyundrive.com/adrive/v2/file/createWithFolders", http.MethodPost, func(req *resty.Request) {
|
_, err, e := d.request("https://api.aliyundrive.com/adrive/v2/file/createWithFolders", http.MethodPost, func(req *resty.Request) {
|
||||||
@ -241,17 +278,21 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// 秒传失败
|
// 秒传失败
|
||||||
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
|
if _, err = localFile.Seek(0, io.SeekStart); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
file.ReadCloser = tempFile
|
file.ReadCloser = localFile
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, partInfo := range resp.PartInfoList {
|
for i, partInfo := range resp.PartInfoList {
|
||||||
if utils.IsCanceled(ctx) {
|
if utils.IsCanceled(ctx) {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("PUT", partInfo.UploadUrl, io.LimitReader(file, DEFAULT))
|
url := partInfo.UploadUrl
|
||||||
|
if d.InternalUpload {
|
||||||
|
url = partInfo.InternalUploadUrl
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("PUT", url, io.LimitReader(file, DEFAULT))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
16
drivers/aliyundrive/global.go
Normal file
16
drivers/aliyundrive/global.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package aliyundrive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/pkg/generic_sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
deviceID string
|
||||||
|
signature string
|
||||||
|
nonce int
|
||||||
|
privateKey *ecdsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
var global = generic_sync.MapOf[string, *State]{}
|
66
drivers/aliyundrive/help.go
Normal file
66
drivers/aliyundrive/help.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package aliyundrive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/dustinxie/ecc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPrivateKey() (*ecdsa.PrivateKey, error) {
|
||||||
|
p256k1 := ecc.P256k1()
|
||||||
|
return ecdsa.GenerateKey(p256k1, rand.Reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivateKeyFromHex(hex_ string) (*ecdsa.PrivateKey, error) {
|
||||||
|
data, err := hex.DecodeString(hex_)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewPrivateKeyFromBytes(data), nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPrivateKeyFromBytes(priv []byte) *ecdsa.PrivateKey {
|
||||||
|
p256k1 := ecc.P256k1()
|
||||||
|
x, y := p256k1.ScalarBaseMult(priv)
|
||||||
|
return &ecdsa.PrivateKey{
|
||||||
|
PublicKey: ecdsa.PublicKey{
|
||||||
|
Curve: p256k1,
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
},
|
||||||
|
D: new(big.Int).SetBytes(priv),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivateKeyToHex(private *ecdsa.PrivateKey) string {
|
||||||
|
return hex.EncodeToString(PrivateKeyToBytes(private))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivateKeyToBytes(private *ecdsa.PrivateKey) []byte {
|
||||||
|
return private.D.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicKeyToHex(public *ecdsa.PublicKey) string {
|
||||||
|
return hex.EncodeToString(PublicKeyToBytes(public))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicKeyToBytes(public *ecdsa.PublicKey) []byte {
|
||||||
|
x := public.X.Bytes()
|
||||||
|
if len(x) < 32 {
|
||||||
|
for i := 0; i < 32-len(x); i++ {
|
||||||
|
x = append([]byte{0}, x...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
y := public.Y.Bytes()
|
||||||
|
if len(y) < 32 {
|
||||||
|
for i := 0; i < 32-len(y); i++ {
|
||||||
|
y = append([]byte{0}, y...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(x, y...)
|
||||||
|
}
|
@ -7,10 +7,12 @@ import (
|
|||||||
|
|
||||||
type Addition struct {
|
type Addition struct {
|
||||||
driver.RootID
|
driver.RootID
|
||||||
RefreshToken string `json:"refresh_token" required:"true"`
|
RefreshToken string `json:"refresh_token" required:"true"`
|
||||||
|
//DeviceID string `json:"device_id" required:"true"`
|
||||||
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"`
|
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"`
|
||||||
OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"`
|
OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"`
|
||||||
RapidUpload bool `json:"rapid_upload"`
|
RapidUpload bool `json:"rapid_upload"`
|
||||||
|
InternalUpload bool `json:"internal_upload"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = driver.Config{
|
var config = driver.Config{
|
||||||
|
@ -48,7 +48,8 @@ type UploadResp struct {
|
|||||||
FileId string `json:"file_id"`
|
FileId string `json:"file_id"`
|
||||||
UploadId string `json:"upload_id"`
|
UploadId string `json:"upload_id"`
|
||||||
PartInfoList []struct {
|
PartInfoList []struct {
|
||||||
UploadUrl string `json:"upload_url"`
|
UploadUrl string `json:"upload_url"`
|
||||||
|
InternalUploadUrl string `json:"internal_upload_url"`
|
||||||
} `json:"part_info_list"`
|
} `json:"part_info_list"`
|
||||||
|
|
||||||
RapidUpload bool `json:"rapid_upload"`
|
RapidUpload bool `json:"rapid_upload"`
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package aliyundrive
|
package aliyundrive
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -8,9 +10,69 @@ import (
|
|||||||
"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/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/dustinxie/ecc"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (d *AliDrive) createSession() error {
|
||||||
|
state, ok := global.Load(d.UserID)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't load user state, user_id: %s", d.UserID)
|
||||||
|
}
|
||||||
|
_, err, _ := d.request("https://api.aliyundrive.com/users/v1/users/device/create_session", http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetBody(base.Json{
|
||||||
|
"deviceName": "samsung",
|
||||||
|
"modelName": "SM-G9810",
|
||||||
|
"nonce": state.nonce,
|
||||||
|
"pubKey": PublicKeyToHex(&state.privateKey.PublicKey),
|
||||||
|
"refreshToken": d.RefreshToken,
|
||||||
|
})
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliDrive) renewSession() error {
|
||||||
|
_, err, _ := d.request("https://api.aliyundrive.com/users/v1/users/device/renew_session", http.MethodPost, nil, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliDrive) sign() {
|
||||||
|
state, ok := global.Load(d.UserID)
|
||||||
|
if !ok {
|
||||||
|
log.Errorf("can't load user state, user_id: %s", d.UserID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
secpAppID := "5dde4e1bdf9e4966b387ba58f4b3fdc3"
|
||||||
|
singdata := fmt.Sprintf("%s:%s:%s:%d", secpAppID, state.deviceID, d.UserID, state.nonce)
|
||||||
|
hash := sha256.Sum256([]byte(singdata))
|
||||||
|
data, _ := ecc.SignBytes(state.privateKey, hash[:], ecc.RecID|ecc.LowerS)
|
||||||
|
state.signature = hex.EncodeToString(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *AliDrive) reSign() error {
|
||||||
|
state, ok := global.Load(d.UserID)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't load user state, user_id: %s", d.UserID)
|
||||||
|
}
|
||||||
|
state.nonce++
|
||||||
|
if state.nonce >= 1073741823 {
|
||||||
|
state.nonce = 0
|
||||||
|
}
|
||||||
|
d.sign()
|
||||||
|
if state.nonce == 0 {
|
||||||
|
return d.createSession()
|
||||||
|
}
|
||||||
|
err := d.renewSession()
|
||||||
|
if err != nil && err.Error() == "device session signature error" {
|
||||||
|
state.nonce = 0
|
||||||
|
d.sign()
|
||||||
|
return d.createSession()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// do others that not defined in Driver interface
|
// do others that not defined in Driver interface
|
||||||
|
|
||||||
func (d *AliDrive) refreshToken() error {
|
func (d *AliDrive) refreshToken() error {
|
||||||
@ -39,9 +101,24 @@ func (d *AliDrive) refreshToken() error {
|
|||||||
|
|
||||||
func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) {
|
func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) {
|
||||||
req := base.RestyClient.R()
|
req := base.RestyClient.R()
|
||||||
req.SetHeader("Authorization", "Bearer\t"+d.AccessToken)
|
state, ok := global.Load(d.UserID)
|
||||||
req.SetHeader("content-type", "application/json")
|
if !ok {
|
||||||
req.SetHeader("origin", "https://www.aliyundrive.com")
|
if url == "https://api.aliyundrive.com/v2/user/get" {
|
||||||
|
state = &State{}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("can't load user state, user_id: %s", d.UserID), RespErr{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.SetHeaders(map[string]string{
|
||||||
|
"Authorization": "Bearer\t" + d.AccessToken,
|
||||||
|
"content-type": "application/json",
|
||||||
|
"origin": "https://www.aliyundrive.com",
|
||||||
|
"Referer": "https://aliyundrive.com/",
|
||||||
|
"X-Signature": state.signature,
|
||||||
|
"x-request-id": uuid.NewString(),
|
||||||
|
"X-Canary": "client=Android,app=adrive,version=v4.1.0",
|
||||||
|
"X-Device-Id": state.deviceID,
|
||||||
|
})
|
||||||
if callback != nil {
|
if callback != nil {
|
||||||
callback(req)
|
callback(req)
|
||||||
} else {
|
} else {
|
||||||
@ -57,14 +134,21 @@ func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp i
|
|||||||
return nil, err, e
|
return nil, err, e
|
||||||
}
|
}
|
||||||
if e.Code != "" {
|
if e.Code != "" {
|
||||||
if e.Code == "AccessTokenInvalid" {
|
switch e.Code {
|
||||||
|
case "AccessTokenInvalid":
|
||||||
err = d.refreshToken()
|
err = d.refreshToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err, e
|
return nil, err, e
|
||||||
}
|
}
|
||||||
return d.request(url, method, callback, resp)
|
case "DeviceSessionSignatureInvalid":
|
||||||
|
err = d.reSign()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err, e
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, errors.New(e.Message), e
|
||||||
}
|
}
|
||||||
return nil, errors.New(e.Message), e
|
return d.request(url, method, callback, resp)
|
||||||
} else if res.IsError() {
|
} else if res.IsError() {
|
||||||
return nil, errors.New("bad status code " + res.Status()), e
|
return nil, errors.New("bad status code " + res.Status()), e
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ func (d *AliyundriveShare) Drop(ctx context.Context) error {
|
|||||||
if d.cron != nil {
|
if d.cron != nil {
|
||||||
d.cron.Stop()
|
d.cron.Stop()
|
||||||
}
|
}
|
||||||
|
d.DriveId = ""
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ func (d *BaiduNetdisk) create(path string, size int64, isdir int, uploadid, bloc
|
|||||||
params := map[string]string{
|
params := map[string]string{
|
||||||
"method": "create",
|
"method": "create",
|
||||||
}
|
}
|
||||||
data := fmt.Sprintf("path=%s&size=%d&isdir=%d", encodeURIComponent(path), size, isdir)
|
data := fmt.Sprintf("path=%s&size=%d&isdir=%d&rtype=3", encodeURIComponent(path), size, isdir)
|
||||||
if uploadid != "" {
|
if uploadid != "" {
|
||||||
data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list)
|
data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list)
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewRestyClient() *resty.Client {
|
func NewRestyClient() *resty.Client {
|
||||||
return resty.New().
|
client := resty.New().
|
||||||
SetHeader("user-agent", UserAgent).
|
SetHeader("user-agent", UserAgent).
|
||||||
SetRetryCount(3).
|
SetRetryCount(3).
|
||||||
SetTimeout(DefaultTimeout)
|
SetTimeout(DefaultTimeout)
|
||||||
|
return client
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ func (d *GoogleDrive) Link(ctx context.Context, file model.Obj, args model.LinkA
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
link := model.Link{
|
link := model.Link{
|
||||||
URL: url + "&alt=media",
|
URL: url + "&alt=media&acknowledgeAbuse=true",
|
||||||
Header: http.Header{
|
Header: http.Header{
|
||||||
"Authorization": []string{"Bearer " + d.AccessToken},
|
"Authorization": []string{"Bearer " + d.AccessToken},
|
||||||
},
|
},
|
||||||
|
@ -210,9 +210,11 @@ func (d *LanZou) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
_, err := d._post(d.BaseUrl+"/fileup.php", func(req *resty.Request) {
|
_, err := d._post(d.BaseUrl+"/fileup.php", func(req *resty.Request) {
|
||||||
req.SetFormData(map[string]string{
|
req.SetFormData(map[string]string{
|
||||||
"task": "1",
|
"task": "1",
|
||||||
|
"vie": "2",
|
||||||
|
"ve": "2",
|
||||||
"id": "WU_FILE_0",
|
"id": "WU_FILE_0",
|
||||||
"name": stream.GetName(),
|
"name": stream.GetName(),
|
||||||
"folder_id": dstDir.GetID(),
|
"folder_id_bb_n": dstDir.GetID(),
|
||||||
}).SetFileReader("upload_file", stream.GetName(), stream).SetContext(ctx)
|
}).SetFileReader("upload_file", stream.GetName(), stream).SetContext(ctx)
|
||||||
}, &resp, true)
|
}, &resp, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -151,7 +151,7 @@ func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
|||||||
|
|
||||||
func (d *Local) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
func (d *Local) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||||
fullPath := filepath.Join(parentDir.GetPath(), dirName)
|
fullPath := filepath.Join(parentDir.GetPath(), dirName)
|
||||||
err := os.MkdirAll(fullPath, 0700)
|
err := os.MkdirAll(fullPath, 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ type File struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Object struct {
|
type Object struct {
|
||||||
model.ObjThumbURL
|
model.ObjThumb
|
||||||
ParentID string
|
ParentID string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ func fileToObj(f File, parentID string) *Object {
|
|||||||
thumb = f.Thumbnails[0].Medium.Url
|
thumb = f.Thumbnails[0].Medium.Url
|
||||||
}
|
}
|
||||||
return &Object{
|
return &Object{
|
||||||
ObjThumbURL: model.ObjThumbURL{
|
ObjThumb: model.ObjThumb{
|
||||||
Object: model.Object{
|
Object: model.Object{
|
||||||
ID: f.Id,
|
ID: f.Id,
|
||||||
Name: f.Name,
|
Name: f.Name,
|
||||||
@ -62,7 +62,7 @@ func fileToObj(f File, parentID string) *Object {
|
|||||||
IsFolder: f.File == nil,
|
IsFolder: f.File == nil,
|
||||||
},
|
},
|
||||||
Thumbnail: model.Thumbnail{Thumbnail: thumb},
|
Thumbnail: model.Thumbnail{Thumbnail: thumb},
|
||||||
Url: model.Url{Url: f.Url},
|
//Url: model.Url{Url: f.Url},
|
||||||
},
|
},
|
||||||
ParentID: parentID,
|
ParentID: parentID,
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ func (d *Onedrive) Request(url string, method string, callback base.ReqCallback,
|
|||||||
|
|
||||||
func (d *Onedrive) getFiles(path string) ([]File, error) {
|
func (d *Onedrive) getFiles(path string) ([]File, error) {
|
||||||
var res []File
|
var res []File
|
||||||
nextLink := d.GetMetaUrl(false, path) + "/children?$expand=thumbnails"
|
nextLink := d.GetMetaUrl(false, path) + "/children?$top=5000&$expand=thumbnails($select=medium)&$select=id,name,size,lastModifiedDateTime,content.downloadUrl,file,parentReference"
|
||||||
for nextLink != "" {
|
for nextLink != "" {
|
||||||
var files Files
|
var files Files
|
||||||
_, err := d.Request(nextLink, http.MethodGet, nil, &files)
|
_, err := d.Request(nextLink, http.MethodGet, nil, &files)
|
||||||
|
@ -148,6 +148,9 @@ func (d *S3) listV2(prefix string) ([]model.Obj, error) {
|
|||||||
files = append(files, &file)
|
files = append(files, &file)
|
||||||
}
|
}
|
||||||
for _, object := range listObjectsResult.Contents {
|
for _, object := range listObjectsResult.Contents {
|
||||||
|
if strings.HasSuffix(*object.Key, "/") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
name := path.Base(*object.Key)
|
name := path.Base(*object.Key)
|
||||||
if name == getPlaceholderName(d.Placeholder) || name == d.Placeholder {
|
if name == getPlaceholderName(d.Placeholder) || name == d.Placeholder {
|
||||||
continue
|
continue
|
||||||
|
21
go.mod
21
go.mod
@ -5,11 +5,12 @@ go 1.19
|
|||||||
require (
|
require (
|
||||||
github.com/SheltonZhu/115driver v1.0.13
|
github.com/SheltonZhu/115driver v1.0.13
|
||||||
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.174
|
github.com/aws/aws-sdk-go v1.44.194
|
||||||
github.com/blevesearch/bleve/v2 v2.3.6
|
github.com/blevesearch/bleve/v2 v2.3.6
|
||||||
github.com/caarlos0/env/v6 v6.10.1
|
github.com/caarlos0/env/v7 v7.0.0
|
||||||
github.com/deckarep/golang-set/v2 v2.1.0
|
github.com/deckarep/golang-set/v2 v2.1.0
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564
|
||||||
github.com/gin-contrib/cors v1.4.0
|
github.com/gin-contrib/cors v1.4.0
|
||||||
github.com/gin-gonic/gin v1.8.2
|
github.com/gin-gonic/gin v1.8.2
|
||||||
github.com/go-resty/resty/v2 v2.7.0
|
github.com/go-resty/resty/v2 v2.7.0
|
||||||
@ -29,13 +30,13 @@ require (
|
|||||||
github.com/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf
|
github.com/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf
|
||||||
github.com/upyun/go-sdk/v3 v3.0.3
|
github.com/upyun/go-sdk/v3 v3.0.3
|
||||||
github.com/winfsp/cgofuse v1.5.0
|
github.com/winfsp/cgofuse v1.5.0
|
||||||
golang.org/x/crypto v0.5.0
|
golang.org/x/crypto v0.6.0
|
||||||
golang.org/x/image v0.3.0
|
golang.org/x/image v0.4.0
|
||||||
golang.org/x/net v0.5.0
|
golang.org/x/net v0.6.0
|
||||||
gorm.io/driver/mysql v1.4.5
|
gorm.io/driver/mysql v1.4.6
|
||||||
gorm.io/driver/postgres v1.4.6
|
gorm.io/driver/postgres v1.4.7
|
||||||
gorm.io/driver/sqlite v1.4.4
|
gorm.io/driver/sqlite v1.4.4
|
||||||
gorm.io/gorm v1.24.3
|
gorm.io/gorm v1.24.5
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -96,8 +97,8 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||||
go.etcd.io/bbolt v1.3.5 // indirect
|
go.etcd.io/bbolt v1.3.5 // indirect
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
golang.org/x/text v0.6.0 // indirect
|
golang.org/x/text v0.7.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
|
||||||
|
46
go.sum
46
go.sum
@ -12,10 +12,8 @@ github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible h1:QoRMR0TCctLDqBCMyOu1e
|
|||||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
github.com/andreburgaud/crypt2go v1.1.0 h1:eitZxTPY1krUsxinsng3Qvt/Ud7q/aQmmYRh8p4hyPw=
|
github.com/andreburgaud/crypt2go v1.1.0 h1:eitZxTPY1krUsxinsng3Qvt/Ud7q/aQmmYRh8p4hyPw=
|
||||||
github.com/andreburgaud/crypt2go v1.1.0/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9yEt0zPe9Ayuhc=
|
github.com/andreburgaud/crypt2go v1.1.0/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9yEt0zPe9Ayuhc=
|
||||||
github.com/aws/aws-sdk-go v1.44.173 h1:8kXIxvQnBpGhmR3Eof6SnCKgR0q5/L/3Qbv9vAC5wic=
|
github.com/aws/aws-sdk-go v1.44.194 h1:1ZDK+QDcc5oRbZGgRZSz561eR8XVizXCeGpoZKo33NU=
|
||||||
github.com/aws/aws-sdk-go v1.44.173/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
github.com/aws/aws-sdk-go v1.44.194/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||||
github.com/aws/aws-sdk-go v1.44.174 h1:9lR4a6MKQW/t6YCG0ZKAt1GAkjdEPP8sWch/pfcuR0c=
|
|
||||||
github.com/aws/aws-sdk-go v1.44.174/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.6 h1:NlntUHcV5CSWIhpugx4d/BRMGCiaoI8ZZXrXlahzNq4=
|
||||||
@ -54,8 +52,8 @@ 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=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
|
github.com/caarlos0/env/v7 v7.0.0 h1:cyczlTd/zREwSr9ch/mwaDl7Hse7kJuUY8hvHfXu5WI=
|
||||||
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
|
github.com/caarlos0/env/v7 v7.0.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
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=
|
||||||
@ -65,6 +63,8 @@ github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6
|
|||||||
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
|
||||||
|
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564/go.mod h1:yekO+3ZShy19S+bsmnERmznGy9Rfg6dWWWpiGJjNAz8=
|
||||||
github.com/gaoyb7/115drive-webdav v0.1.8 h1:EJt4PSmcbvBY4KUh2zSo5p6fN9LZFNkIzuKejipubVw=
|
github.com/gaoyb7/115drive-webdav v0.1.8 h1:EJt4PSmcbvBY4KUh2zSo5p6fN9LZFNkIzuKejipubVw=
|
||||||
github.com/gaoyb7/115drive-webdav v0.1.8/go.mod h1:BKbeY6j8SKs3+rzBFFALznGxbPmefEm3vA+dGhqgOGU=
|
github.com/gaoyb7/115drive-webdav v0.1.8/go.mod h1:BKbeY6j8SKs3+rzBFFALznGxbPmefEm3vA+dGhqgOGU=
|
||||||
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
|
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
|
||||||
@ -235,11 +235,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.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
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.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
|
golang.org/x/image v0.4.0 h1:x1RWAiZIvERqkltrFjtQP1ycmiR5pmhjtCfVOtdURuQ=
|
||||||
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
|
golang.org/x/image v0.4.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||||
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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@ -249,8 +249,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.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
|
||||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
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.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -271,13 +271,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.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.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.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||||
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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@ -285,8 +285,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
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.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
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=
|
||||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@ -316,14 +316,14 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.4.5 h1:u1lytId4+o9dDaNcPCFzNv7h6wvmc92UjNk3z8enSBU=
|
gorm.io/driver/mysql v1.4.6 h1:5zS3vIKcyb46byXZNcYxaT9EWNIhXzu0gPuvvVrwZ8s=
|
||||||
gorm.io/driver/mysql v1.4.5/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
|
gorm.io/driver/mysql v1.4.6/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=
|
||||||
gorm.io/driver/postgres v1.4.6 h1:1FPESNXqIKG5JmraaH2bfCVlMQ7paLoCreFxDtqzwdc=
|
gorm.io/driver/postgres v1.4.7 h1:J06jXZCNq7Pdf7LIPn8tZn9LsWjd81BRSKveKNr0ZfA=
|
||||||
gorm.io/driver/postgres v1.4.6/go.mod h1:UJChCNLFKeBqQRE+HrkFUbKbq9idPXmTOk2u4Wok8S4=
|
gorm.io/driver/postgres v1.4.7/go.mod h1:UJChCNLFKeBqQRE+HrkFUbKbq9idPXmTOk2u4Wok8S4=
|
||||||
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
|
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
|
||||||
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||||
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||||
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||||
gorm.io/gorm v1.24.3 h1:WL2ifUmzR/SLp85CSURAfybcHnGZ+yLSGSxgYXlFBHg=
|
gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
|
||||||
gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
package bootstrap
|
package bootstrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/alist-org/alist/v3/cmd/flags"
|
"github.com/alist-org/alist/v3/cmd/flags"
|
||||||
|
"github.com/alist-org/alist/v3/drivers/base"
|
||||||
"github.com/alist-org/alist/v3/internal/conf"
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
"github.com/alist-org/alist/v3/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
"github.com/caarlos0/env/v6"
|
"github.com/caarlos0/env/v7"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,7 +51,7 @@ func InitConfig() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("marshal config error: %+v", err)
|
log.Fatalf("marshal config error: %+v", err)
|
||||||
}
|
}
|
||||||
err = os.WriteFile(configPath, confBody, 0777)
|
err = os.WriteFile(configPath, confBody, 0o777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("update config struct error: %+v", err)
|
log.Fatalf("update config struct error: %+v", err)
|
||||||
}
|
}
|
||||||
@ -69,11 +71,14 @@ func InitConfig() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("failed delete temp file:", err)
|
log.Errorln("failed delete temp file:", err)
|
||||||
}
|
}
|
||||||
err = os.MkdirAll(conf.Conf.TempDir, 0777)
|
err = os.MkdirAll(conf.Conf.TempDir, 0o777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("create temp dir error: %+v", err)
|
log.Fatalf("create temp dir error: %+v", err)
|
||||||
}
|
}
|
||||||
log.Debugf("config: %+v", conf.Conf)
|
log.Debugf("config: %+v", conf.Conf)
|
||||||
|
if conf.Conf.TlsInsecureSkipVerify {
|
||||||
|
base.RestyClient = base.RestyClient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func confFromEnv() {
|
func confFromEnv() {
|
||||||
|
@ -129,13 +129,14 @@ func InitialSettings() []model.SettingItem {
|
|||||||
{Key: conf.CustomizeHead, Value: `<script src="https://polyfill.io/v3/polyfill.min.js?features=String.prototype.replaceAll"></script>`, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
{Key: conf.CustomizeHead, Value: `<script src="https://polyfill.io/v3/polyfill.min.js?features=String.prototype.replaceAll"></script>`, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
{Key: conf.CustomizeBody, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
{Key: conf.CustomizeBody, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
{Key: conf.LinkExpiration, Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
|
{Key: conf.LinkExpiration, Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
{Key: conf.SignAll, Value: "true", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE},
|
{Key: conf.SignAll, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
{Key: conf.PrivacyRegs, Value: `(?:(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])
|
{Key: conf.PrivacyRegs, Value: `(?:(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])
|
||||||
([[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}|::|:(?::[[:xdigit:]]{1,4}){1,6}|[[:xdigit:]]{1,4}:(?::[[:xdigit:]]{1,4}){1,5}|(?:[[:xdigit:]]{1,4}:){2}(?::[[:xdigit:]]{1,4}){1,4}|(?:[[:xdigit:]]{1,4}:){3}(?::[[:xdigit:]]{1,4}){1,3}|(?:[[:xdigit:]]{1,4}:){4}(?::[[:xdigit:]]{1,4}){1,2}|(?:[[:xdigit:]]{1,4}:){5}:[[:xdigit:]]{1,4}|(?:[[:xdigit:]]{1,4}:){1,6}:)
|
([[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}|::|:(?::[[:xdigit:]]{1,4}){1,6}|[[:xdigit:]]{1,4}:(?::[[:xdigit:]]{1,4}){1,5}|(?:[[:xdigit:]]{1,4}:){2}(?::[[:xdigit:]]{1,4}){1,4}|(?:[[:xdigit:]]{1,4}:){3}(?::[[:xdigit:]]{1,4}){1,3}|(?:[[:xdigit:]]{1,4}:){4}(?::[[:xdigit:]]{1,4}){1,2}|(?:[[:xdigit:]]{1,4}:){5}:[[:xdigit:]]{1,4}|(?:[[:xdigit:]]{1,4}:){1,6}:)
|
||||||
(?U)access_token=(.*)&`,
|
(?U)access_token=(.*)&`,
|
||||||
Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||||
{Key: conf.OcrApi, Value: "https://api.nn.ci/ocr/file/json", Type: conf.TypeString, Group: model.GLOBAL},
|
{Key: conf.OcrApi, Value: "https://api.nn.ci/ocr/file/json", Type: conf.TypeString, Group: model.GLOBAL},
|
||||||
{Key: conf.FilenameCharMapping, Value: `{"/": "|"}`, Type: conf.TypeText, Group: model.GLOBAL},
|
{Key: conf.FilenameCharMapping, Value: `{"/": "|"}`, Type: conf.TypeText, Group: model.GLOBAL},
|
||||||
|
{Key: conf.ForwardDirectLinkParams, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL},
|
||||||
|
|
||||||
// aria2 settings
|
// aria2 settings
|
||||||
{Key: conf.Aria2Uri, Value: "http://localhost:6800/jsonrpc", Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
|
{Key: conf.Aria2Uri, Value: "http://localhost:6800/jsonrpc", Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
|
||||||
@ -153,6 +154,9 @@ func InitialSettings() []model.SettingItem {
|
|||||||
{Key: conf.GithubClientId, Value: "", Type: conf.TypeString, Group: model.GITHUB, Flag: model.PRIVATE},
|
{Key: conf.GithubClientId, Value: "", Type: conf.TypeString, Group: model.GITHUB, Flag: model.PRIVATE},
|
||||||
{Key: conf.GithubClientSecrets, Value: "", Type: conf.TypeString, Group: model.GITHUB, Flag: model.PRIVATE},
|
{Key: conf.GithubClientSecrets, Value: "", Type: conf.TypeString, Group: model.GITHUB, Flag: model.PRIVATE},
|
||||||
{Key: conf.GithubLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GITHUB, Flag: model.PUBLIC},
|
{Key: conf.GithubLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GITHUB, Flag: model.PUBLIC},
|
||||||
|
|
||||||
|
// qbittorrent settings
|
||||||
|
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.QBITTORRENT, Flag: model.PRIVATE},
|
||||||
}
|
}
|
||||||
if flags.Dev {
|
if flags.Dev {
|
||||||
initialSettingItems = append(initialSettingItems, []model.SettingItem{
|
initialSettingItems = append(initialSettingItems, []model.SettingItem{
|
||||||
|
@ -48,6 +48,7 @@ func initUser() {
|
|||||||
Role: model.GUEST,
|
Role: model.GUEST,
|
||||||
BasePath: "/",
|
BasePath: "/",
|
||||||
Permission: 0,
|
Permission: 0,
|
||||||
|
Disabled: true,
|
||||||
}
|
}
|
||||||
if err := db.CreateUser(guest); err != nil {
|
if err := db.CreateUser(guest); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -51,7 +51,7 @@ func InitDB() {
|
|||||||
if !(strings.HasSuffix(database.DBFile, ".db") && len(database.DBFile) > 3) {
|
if !(strings.HasSuffix(database.DBFile, ".db") && len(database.DBFile) > 3) {
|
||||||
log.Fatalf("db name error.")
|
log.Fatalf("db name error.")
|
||||||
}
|
}
|
||||||
dB, err = gorm.Open(sqlite.Open(fmt.Sprintf("%s?_journal=WAL&_locking=EXCLUSIVE&_vacuum=incremental",
|
dB, err = gorm.Open(sqlite.Open(fmt.Sprintf("%s?_journal=WAL&_vacuum=incremental",
|
||||||
database.DBFile)), gormConfig)
|
database.DBFile)), gormConfig)
|
||||||
}
|
}
|
||||||
case "mysql":
|
case "mysql":
|
||||||
|
@ -35,19 +35,22 @@ func setLog(l *logrus.Logger) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Log() {
|
func Log() {
|
||||||
log.SetOutput(logrus.StandardLogger().Out)
|
|
||||||
setLog(logrus.StandardLogger())
|
setLog(logrus.StandardLogger())
|
||||||
setLog(utils.Log)
|
setLog(utils.Log)
|
||||||
logConfig := conf.Conf.Log
|
logConfig := conf.Conf.Log
|
||||||
if logConfig.Enable {
|
if logConfig.Enable {
|
||||||
mw := io.MultiWriter(os.Stdout, &lumberjack.Logger{
|
var w io.Writer = &lumberjack.Logger{
|
||||||
Filename: logConfig.Name,
|
Filename: logConfig.Name,
|
||||||
MaxSize: logConfig.MaxSize, // megabytes
|
MaxSize: logConfig.MaxSize, // megabytes
|
||||||
MaxBackups: logConfig.MaxBackups,
|
MaxBackups: logConfig.MaxBackups,
|
||||||
MaxAge: logConfig.MaxAge, //days
|
MaxAge: logConfig.MaxAge, //days
|
||||||
Compress: logConfig.Compress, // disabled by default
|
Compress: logConfig.Compress, // disabled by default
|
||||||
})
|
}
|
||||||
logrus.SetOutput(mw)
|
if flags.Debug || flags.Dev {
|
||||||
|
w = io.MultiWriter(os.Stdout, w)
|
||||||
|
}
|
||||||
|
logrus.SetOutput(w)
|
||||||
}
|
}
|
||||||
|
log.SetOutput(logrus.StandardLogger().Out)
|
||||||
utils.Log.Infof("init logrus...")
|
utils.Log.Infof("init logrus...")
|
||||||
}
|
}
|
||||||
|
15
internal/bootstrap/qbittorrent.go
Normal file
15
internal/bootstrap/qbittorrent.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/qbittorrent"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitQbittorrent() {
|
||||||
|
go func() {
|
||||||
|
err := qbittorrent.InitClient()
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.Infof("qbittorrent not ready.")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
@ -35,19 +35,20 @@ type LogConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Force bool `json:"force" env:"FORCE"`
|
Force bool `json:"force" env:"FORCE"`
|
||||||
Address string `json:"address" env:"ADDR"`
|
Address string `json:"address" env:"ADDR"`
|
||||||
Port int `json:"port" env:"PORT"`
|
Port int `json:"port" env:"PORT"`
|
||||||
SiteURL string `json:"site_url" env:"SITE_URL"`
|
SiteURL string `json:"site_url" env:"SITE_URL"`
|
||||||
Cdn string `json:"cdn" env:"CDN"`
|
Cdn string `json:"cdn" env:"CDN"`
|
||||||
JwtSecret string `json:"jwt_secret" env:"JWT_SECRET"`
|
JwtSecret string `json:"jwt_secret" env:"JWT_SECRET"`
|
||||||
TokenExpiresIn int `json:"token_expires_in" env:"TOKEN_EXPIRES_IN"`
|
TokenExpiresIn int `json:"token_expires_in" env:"TOKEN_EXPIRES_IN"`
|
||||||
Database Database `json:"database"`
|
Database Database `json:"database"`
|
||||||
Scheme Scheme `json:"scheme"`
|
Scheme Scheme `json:"scheme"`
|
||||||
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
||||||
BleveDir string `json:"bleve_dir" env:"BLEVE_DIR"`
|
BleveDir string `json:"bleve_dir" env:"BLEVE_DIR"`
|
||||||
Log LogConfig `json:"log"`
|
Log LogConfig `json:"log"`
|
||||||
MaxConnections int `json:"max_connections" env:"MAX_CONNECTIONS"`
|
MaxConnections int `json:"max_connections" env:"MAX_CONNECTIONS"`
|
||||||
|
TlsInsecureSkipVerify bool `json:"tls_insecure_skip_verify" env:"TLS_INSECURE_SKIP_VERIFY"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
@ -75,6 +76,7 @@ func DefaultConfig() *Config {
|
|||||||
MaxBackups: 5,
|
MaxBackups: 5,
|
||||||
MaxAge: 28,
|
MaxAge: 28,
|
||||||
},
|
},
|
||||||
MaxConnections: 0,
|
MaxConnections: 0,
|
||||||
|
TlsInsecureSkipVerify: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,72 +1,74 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeString = "string"
|
TypeString = "string"
|
||||||
TypeSelect = "select"
|
TypeSelect = "select"
|
||||||
TypeBool = "bool"
|
TypeBool = "bool"
|
||||||
TypeText = "text"
|
TypeText = "text"
|
||||||
TypeNumber = "number"
|
TypeNumber = "number"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// site
|
// site
|
||||||
VERSION = "version"
|
VERSION = "version"
|
||||||
ApiUrl = "api_url"
|
SiteTitle = "site_title"
|
||||||
BasePath = "base_path"
|
Announcement = "announcement"
|
||||||
SiteTitle = "site_title"
|
AllowIndexed = "allow_indexed"
|
||||||
Announcement = "announcement"
|
|
||||||
AllowIndexed = "allow_indexed"
|
|
||||||
|
|
||||||
Logo = "logo"
|
Logo = "logo"
|
||||||
Favicon = "favicon"
|
Favicon = "favicon"
|
||||||
MainColor = "main_color"
|
MainColor = "main_color"
|
||||||
|
|
||||||
// preview
|
// preview
|
||||||
TextTypes = "text_types"
|
TextTypes = "text_types"
|
||||||
AudioTypes = "audio_types"
|
AudioTypes = "audio_types"
|
||||||
VideoTypes = "video_types"
|
VideoTypes = "video_types"
|
||||||
ImageTypes = "image_types"
|
ImageTypes = "image_types"
|
||||||
ProxyTypes = "proxy_types"
|
ProxyTypes = "proxy_types"
|
||||||
ProxyIgnoreHeaders = "proxy_ignore_headers"
|
ProxyIgnoreHeaders = "proxy_ignore_headers"
|
||||||
AudioAutoplay = "audio_autoplay"
|
AudioAutoplay = "audio_autoplay"
|
||||||
VideoAutoplay = "video_autoplay"
|
VideoAutoplay = "video_autoplay"
|
||||||
|
|
||||||
// global
|
// global
|
||||||
HideFiles = "hide_files"
|
HideFiles = "hide_files"
|
||||||
CustomizeHead = "customize_head"
|
CustomizeHead = "customize_head"
|
||||||
CustomizeBody = "customize_body"
|
CustomizeBody = "customize_body"
|
||||||
LinkExpiration = "link_expiration"
|
LinkExpiration = "link_expiration"
|
||||||
SignAll = "sign_all"
|
SignAll = "sign_all"
|
||||||
PrivacyRegs = "privacy_regs"
|
PrivacyRegs = "privacy_regs"
|
||||||
OcrApi = "ocr_api"
|
OcrApi = "ocr_api"
|
||||||
FilenameCharMapping = "filename_char_mapping"
|
FilenameCharMapping = "filename_char_mapping"
|
||||||
|
ForwardDirectLinkParams = "forward_direct_link_params"
|
||||||
|
|
||||||
// index
|
// index
|
||||||
SearchIndex = "search_index"
|
SearchIndex = "search_index"
|
||||||
AutoUpdateIndex = "auto_update_index"
|
AutoUpdateIndex = "auto_update_index"
|
||||||
IgnorePaths = "ignore_paths"
|
IgnorePaths = "ignore_paths"
|
||||||
MaxIndexDepth = "max_index_depth"
|
MaxIndexDepth = "max_index_depth"
|
||||||
|
|
||||||
// aria2
|
// aria2
|
||||||
Aria2Uri = "aria2_uri"
|
Aria2Uri = "aria2_uri"
|
||||||
Aria2Secret = "aria2_secret"
|
Aria2Secret = "aria2_secret"
|
||||||
|
|
||||||
// single
|
// single
|
||||||
Token = "token"
|
Token = "token"
|
||||||
IndexProgress = "index_progress"
|
IndexProgress = "index_progress"
|
||||||
|
|
||||||
//Github
|
//Github
|
||||||
GithubClientId = "github_client_id"
|
GithubClientId = "github_client_id"
|
||||||
GithubClientSecrets = "github_client_secrets"
|
GithubClientSecrets = "github_client_secrets"
|
||||||
GithubLoginEnabled = "github_login_enabled"
|
GithubLoginEnabled = "github_login_enabled"
|
||||||
|
|
||||||
|
// qbittorrent
|
||||||
|
QbittorrentUrl = "qbittorrent_url"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UNKNOWN = iota
|
UNKNOWN = iota
|
||||||
FOLDER
|
FOLDER
|
||||||
//OFFICE
|
//OFFICE
|
||||||
VIDEO
|
VIDEO
|
||||||
AUDIO
|
AUDIO
|
||||||
TEXT
|
TEXT
|
||||||
IMAGE
|
IMAGE
|
||||||
)
|
)
|
||||||
|
@ -41,9 +41,9 @@ func getFileStreamFromLink(file model.Obj, link *model.Link) (*model.FileStream,
|
|||||||
if link.Data != nil {
|
if link.Data != nil {
|
||||||
rc = link.Data
|
rc = link.Data
|
||||||
} else if link.FilePath != nil {
|
} else if link.FilePath != nil {
|
||||||
// copy a new temp, because will be deleted after upload
|
// create a new temp symbolic link, because it will be deleted after upload
|
||||||
newFilePath := stdpath.Join(conf.Conf.TempDir, fmt.Sprintf("%s-%s", uuid.NewString(), file.GetName()))
|
newFilePath := stdpath.Join(conf.Conf.TempDir, fmt.Sprintf("%s-%s", uuid.NewString(), file.GetName()))
|
||||||
err := utils.CopyFile(*link.FilePath, newFilePath)
|
err := utils.SymlinkOrCopyFile(*link.FilePath, newFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ const (
|
|||||||
ARIA2
|
ARIA2
|
||||||
INDEX
|
INDEX
|
||||||
GITHUB
|
GITHUB
|
||||||
|
QBITTORRENT
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -18,17 +18,19 @@ type User struct {
|
|||||||
Password string `json:"password"` // password
|
Password string `json:"password"` // password
|
||||||
BasePath string `json:"base_path"` // base path
|
BasePath string `json:"base_path"` // base path
|
||||||
Role int `json:"role"` // user's role
|
Role int `json:"role"` // user's role
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
// Determine permissions by bit
|
// Determine permissions by bit
|
||||||
// 0: can see hidden files
|
// 0: can see hidden files
|
||||||
// 1: can access without password
|
// 1: can access without password
|
||||||
// 2: can add aria2 tasks
|
// 2: can add aria2 tasks
|
||||||
// 3: can mkdir and upload
|
// 3: can mkdir and upload
|
||||||
// 4: can rename
|
// 4: can rename
|
||||||
// 5: can move
|
// 5: can move
|
||||||
// 6: can copy
|
// 6: can copy
|
||||||
// 7: can remove
|
// 7: can remove
|
||||||
// 8: webdav read
|
// 8: webdav read
|
||||||
// 9: webdav write
|
// 9: webdav write
|
||||||
|
// 10: can add qbittorrent tasks
|
||||||
Permission int32 `json:"permission"`
|
Permission int32 `json:"permission"`
|
||||||
OtpSecret string `json:"-"`
|
OtpSecret string `json:"-"`
|
||||||
GithubID int `json:"github_id"`
|
GithubID int `json:"github_id"`
|
||||||
@ -92,6 +94,10 @@ func (u User) CanWebdavManage() bool {
|
|||||||
return u.IsAdmin() || (u.Permission>>9)&1 == 1
|
return u.IsAdmin() || (u.Permission>>9)&1 == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u User) CanAddQbittorrentTasks() bool {
|
||||||
|
return u.IsAdmin() || (u.Permission>>10)&1 == 1
|
||||||
|
}
|
||||||
|
|
||||||
func (u User) JoinPath(reqPath string) (string, error) {
|
func (u User) JoinPath(reqPath string) (string, error) {
|
||||||
return utils.JoinBasePath(u.BasePath, reqPath)
|
return utils.JoinBasePath(u.BasePath, reqPath)
|
||||||
}
|
}
|
||||||
|
58
internal/qbittorrent/add.go
Normal file
58
internal/qbittorrent/add.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package qbittorrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/errs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/task"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddURL(ctx context.Context, url string, dstDirPath string) error {
|
||||||
|
// check storage
|
||||||
|
storage, dstDirActualPath, err := op.GetStorageAndActualPath(dstDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed get storage")
|
||||||
|
}
|
||||||
|
// check is it could upload
|
||||||
|
if storage.Config().NoUpload {
|
||||||
|
return errors.WithStack(errs.UploadNotSupported)
|
||||||
|
}
|
||||||
|
// check path is valid
|
||||||
|
obj, err := op.Get(ctx, storage, dstDirActualPath)
|
||||||
|
if err != nil {
|
||||||
|
if !errs.IsObjectNotFound(err) {
|
||||||
|
return errors.WithMessage(err, "failed get object")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !obj.IsDir() {
|
||||||
|
// can't add to a file
|
||||||
|
return errors.WithStack(errs.NotFolder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// call qbittorrent
|
||||||
|
id := uuid.NewString()
|
||||||
|
tempDir := filepath.Join(conf.Conf.TempDir, "qbittorrent", id)
|
||||||
|
err = qbclient.AddFromLink(url, tempDir, id)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to add url %s", url)
|
||||||
|
}
|
||||||
|
DownTaskManager.Submit(task.WithCancelCtx(&task.Task[string]{
|
||||||
|
ID: id,
|
||||||
|
Name: fmt.Sprintf("download %s to [%s](%s)", url, storage.GetStorage().MountPath, dstDirActualPath),
|
||||||
|
Func: func(tsk *task.Task[string]) error {
|
||||||
|
m := &Monitor{
|
||||||
|
tsk: tsk,
|
||||||
|
tempDir: tempDir,
|
||||||
|
dstDirPath: dstDirPath,
|
||||||
|
}
|
||||||
|
return m.Loop()
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
return nil
|
||||||
|
}
|
337
internal/qbittorrent/client.go
Normal file
337
internal/qbittorrent/client.go
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
package qbittorrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
AddFromLink(link string, savePath string, id string) error
|
||||||
|
GetInfo(id string) (TorrentInfo, error)
|
||||||
|
GetFiles(id string) ([]FileInfo, error)
|
||||||
|
Delete(id string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
url *url.URL
|
||||||
|
client http.Client
|
||||||
|
Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(webuiUrl string) (Client, error) {
|
||||||
|
u, err := url.Parse(webuiUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
jar, err := cookiejar.New(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var c = &client{
|
||||||
|
url: u,
|
||||||
|
client: http.Client{Jar: jar},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.checkAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) checkAuthorization() error {
|
||||||
|
// check authorization
|
||||||
|
if c.authorized() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check authorization after logging in
|
||||||
|
err := c.login()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if c.authorized() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("unauthorized qbittorrent url")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) authorized() bool {
|
||||||
|
resp, err := c.post("/api/v2/app/version", nil)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return resp.StatusCode == 200 // the status code will be 403 if not authorized
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) login() error {
|
||||||
|
// prepare HTTP request
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("username", c.url.User.Username())
|
||||||
|
passwd, _ := c.url.User.Password()
|
||||||
|
v.Set("password", passwd)
|
||||||
|
resp, err := c.post("/api/v2/auth/login", v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check result
|
||||||
|
body := make([]byte, 2)
|
||||||
|
_, err = resp.Body.Read(body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if string(body) != "Ok" {
|
||||||
|
return errors.New("failed to login into qBittorrent webui with url: " + c.url.String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) post(path string, data url.Values) (*http.Response, error) {
|
||||||
|
u := c.url.JoinPath(path)
|
||||||
|
u.User = nil // remove userinfo for requests
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", u.String(), bytes.NewReader([]byte(data.Encode())))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if data != nil {
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Cookies() != nil {
|
||||||
|
c.client.Jar.SetCookies(u, resp.Cookies())
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) AddFromLink(link string, savePath string, id string) error {
|
||||||
|
err := c.checkAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
writer := multipart.NewWriter(buf)
|
||||||
|
|
||||||
|
addField := func(name string, value string) {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = writer.WriteField(name, value)
|
||||||
|
}
|
||||||
|
addField("urls", link)
|
||||||
|
addField("savepath", savePath)
|
||||||
|
addField("tags", "alist-"+id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
u := c.url.JoinPath("/api/v2/torrents/add")
|
||||||
|
u.User = nil // remove userinfo for requests
|
||||||
|
req, err := http.NewRequest("POST", u.String(), buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", writer.FormDataContentType())
|
||||||
|
|
||||||
|
resp, err := c.client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check result
|
||||||
|
body := make([]byte, 2)
|
||||||
|
_, err = resp.Body.Read(body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != 200 || string(body) != "Ok" {
|
||||||
|
return errors.New("failed to add qBittorrent task: " + link)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TorrentStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ERROR TorrentStatus = "error"
|
||||||
|
MISSINGFILES TorrentStatus = "missingFiles"
|
||||||
|
UPLOADING TorrentStatus = "uploading"
|
||||||
|
PAUSEDUP TorrentStatus = "pausedUP"
|
||||||
|
QUEUEDUP TorrentStatus = "queuedUP"
|
||||||
|
STALLEDUP TorrentStatus = "stalledUP"
|
||||||
|
CHECKINGUP TorrentStatus = "checkingUP"
|
||||||
|
FORCEDUP TorrentStatus = "forcedUP"
|
||||||
|
ALLOCATING TorrentStatus = "allocating"
|
||||||
|
DOWNLOADING TorrentStatus = "downloading"
|
||||||
|
METADL TorrentStatus = "metaDL"
|
||||||
|
PAUSEDDL TorrentStatus = "pausedDL"
|
||||||
|
QUEUEDDL TorrentStatus = "queuedDL"
|
||||||
|
STALLEDDL TorrentStatus = "stalledDL"
|
||||||
|
CHECKINGDL TorrentStatus = "checkingDL"
|
||||||
|
FORCEDDL TorrentStatus = "forcedDL"
|
||||||
|
CHECKINGRESUMEDATA TorrentStatus = "checkingResumeData"
|
||||||
|
MOVING TorrentStatus = "moving"
|
||||||
|
UNKNOWN TorrentStatus = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://github.com/DGuang21/PTGo/blob/main/app/client/client_distributer.go
|
||||||
|
type TorrentInfo struct {
|
||||||
|
AddedOn int `json:"added_on"` // 将 torrent 添加到客户端的时间(Unix Epoch)
|
||||||
|
AmountLeft int64 `json:"amount_left"` // 剩余大小(字节)
|
||||||
|
AutoTmm bool `json:"auto_tmm"` // 此 torrent 是否由 Automatic Torrent Management 管理
|
||||||
|
Availability float64 `json:"availability"` // 当前百分比
|
||||||
|
Category string `json:"category"` //
|
||||||
|
Completed int64 `json:"completed"` // 完成的传输数据量(字节)
|
||||||
|
CompletionOn int `json:"completion_on"` // Torrent 完成的时间(Unix Epoch)
|
||||||
|
ContentPath string `json:"content_path"` // torrent 内容的绝对路径(多文件 torrent 的根路径,单文件 torrent 的绝对文件路径)
|
||||||
|
DlLimit int `json:"dl_limit"` // Torrent 下载速度限制(字节/秒)
|
||||||
|
Dlspeed int `json:"dlspeed"` // Torrent 下载速度(字节/秒)
|
||||||
|
Downloaded int64 `json:"downloaded"` // 已经下载大小
|
||||||
|
DownloadedSession int64 `json:"downloaded_session"` // 此会话下载的数据量
|
||||||
|
Eta int `json:"eta"` //
|
||||||
|
FLPiecePrio bool `json:"f_l_piece_prio"` // 如果第一个最后一块被优先考虑,则为true
|
||||||
|
ForceStart bool `json:"force_start"` // 如果为此 torrent 启用了强制启动,则为true
|
||||||
|
Hash string `json:"hash"` //
|
||||||
|
LastActivity int `json:"last_activity"` // 上次活跃的时间(Unix Epoch)
|
||||||
|
MagnetURI string `json:"magnet_uri"` // 与此 torrent 对应的 Magnet URI
|
||||||
|
MaxRatio int `json:"max_ratio"` // 种子/上传停止种子前的最大共享比率
|
||||||
|
MaxSeedingTime int `json:"max_seeding_time"` // 停止种子种子前的最长种子时间(秒)
|
||||||
|
Name string `json:"name"` //
|
||||||
|
NumComplete int `json:"num_complete"` //
|
||||||
|
NumIncomplete int `json:"num_incomplete"` //
|
||||||
|
NumLeechs int `json:"num_leechs"` // 连接到的 leechers 的数量
|
||||||
|
NumSeeds int `json:"num_seeds"` // 连接到的种子数
|
||||||
|
Priority int `json:"priority"` // 速度优先。如果队列被禁用或 torrent 处于种子模式,则返回 -1
|
||||||
|
Progress float64 `json:"progress"` // 进度
|
||||||
|
Ratio float64 `json:"ratio"` // Torrent 共享比率
|
||||||
|
RatioLimit int `json:"ratio_limit"` //
|
||||||
|
SavePath string `json:"save_path"`
|
||||||
|
SeedingTime int `json:"seeding_time"` // Torrent 完成用时(秒)
|
||||||
|
SeedingTimeLimit int `json:"seeding_time_limit"` // max_seeding_time
|
||||||
|
SeenComplete int `json:"seen_complete"` // 上次 torrent 完成的时间
|
||||||
|
SeqDl bool `json:"seq_dl"` // 如果启用顺序下载,则为true
|
||||||
|
Size int64 `json:"size"` //
|
||||||
|
State TorrentStatus `json:"state"` // 参见https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)#get-torrent-list
|
||||||
|
SuperSeeding bool `json:"super_seeding"` // 如果启用超级播种,则为true
|
||||||
|
Tags string `json:"tags"` // Torrent 的逗号连接标签列表
|
||||||
|
TimeActive int `json:"time_active"` // 总活动时间(秒)
|
||||||
|
TotalSize int64 `json:"total_size"` // 此 torrent 中所有文件的总大小(字节)(包括未选择的文件)
|
||||||
|
Tracker string `json:"tracker"` // 第一个具有工作状态的tracker。如果没有tracker在工作,则返回空字符串。
|
||||||
|
TrackersCount int `json:"trackers_count"` //
|
||||||
|
UpLimit int `json:"up_limit"` // 上传限制
|
||||||
|
Uploaded int64 `json:"uploaded"` // 累计上传
|
||||||
|
UploadedSession int64 `json:"uploaded_session"` // 当前session累计上传
|
||||||
|
Upspeed int `json:"upspeed"` // 上传速度(字节/秒)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) GetInfo(id string) (TorrentInfo, error) {
|
||||||
|
var infos []TorrentInfo
|
||||||
|
|
||||||
|
err := c.checkAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return TorrentInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("tag", "alist-"+id)
|
||||||
|
response, err := c.post("/api/v2/torrents/info", v)
|
||||||
|
if err != nil {
|
||||||
|
return TorrentInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return TorrentInfo{}, err
|
||||||
|
}
|
||||||
|
err = utils.Json.Unmarshal(body, &infos)
|
||||||
|
if err != nil {
|
||||||
|
return TorrentInfo{}, err
|
||||||
|
}
|
||||||
|
if len(infos) != 1 {
|
||||||
|
return TorrentInfo{}, errors.New("there should be exactly one task with tag \"alist-" + id + "\"")
|
||||||
|
}
|
||||||
|
return infos[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileInfo struct {
|
||||||
|
Index int `json:"index"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
Progress float32 `json:"progress"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
IsSeed bool `json:"is_seed"`
|
||||||
|
PieceRange []int `json:"piece_range"`
|
||||||
|
Availability float32 `json:"availability"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) GetFiles(id string) ([]FileInfo, error) {
|
||||||
|
var infos []FileInfo
|
||||||
|
|
||||||
|
err := c.checkAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return []FileInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tInfo, err := c.GetInfo(id)
|
||||||
|
if err != nil {
|
||||||
|
return []FileInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("hash", tInfo.Hash)
|
||||||
|
response, err := c.post("/api/v2/torrents/files", v)
|
||||||
|
if err != nil {
|
||||||
|
return []FileInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []FileInfo{}, err
|
||||||
|
}
|
||||||
|
err = utils.Json.Unmarshal(body, &infos)
|
||||||
|
if err != nil {
|
||||||
|
return []FileInfo{}, err
|
||||||
|
}
|
||||||
|
return infos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Delete(id string) error {
|
||||||
|
err := c.checkAuthorization()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := c.GetInfo(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("hashes", info.Hash)
|
||||||
|
v.Set("deleteFiles", "false")
|
||||||
|
response, err := c.post("/api/v2/torrents/delete", v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.StatusCode != 200 {
|
||||||
|
return errors.New("failed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
152
internal/qbittorrent/client_test.go
Normal file
152
internal/qbittorrent/client_test.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package qbittorrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLogin(t *testing.T) {
|
||||||
|
// test logging in with wrong password
|
||||||
|
u, err := url.Parse("http://admin:admin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
jar, err := cookiejar.New(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
var c = &client{
|
||||||
|
url: u,
|
||||||
|
client: http.Client{Jar: jar},
|
||||||
|
}
|
||||||
|
err = c.login()
|
||||||
|
if err == nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test logging in with correct password
|
||||||
|
u, err = url.Parse("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
c.url = u
|
||||||
|
err = c.login()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in this test, the `Bypass authentication for clients on localhost` option in qBittorrent webui should be disabled
|
||||||
|
func TestAuthorized(t *testing.T) {
|
||||||
|
// init client
|
||||||
|
u, err := url.Parse("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
jar, err := cookiejar.New(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
var c = &client{
|
||||||
|
url: u,
|
||||||
|
client: http.Client{Jar: jar},
|
||||||
|
}
|
||||||
|
|
||||||
|
// test without logging in, which should be unauthorized
|
||||||
|
authorized := c.authorized()
|
||||||
|
if authorized {
|
||||||
|
t.Error("Should not be authorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test after logging in
|
||||||
|
err = c.login()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
authorized = c.authorized()
|
||||||
|
if !authorized {
|
||||||
|
t.Error("Should be authorized")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
_, err := New("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, err = New("http://admin:wrong_password@127.0.0.1:8080/")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Should get an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdd(t *testing.T) {
|
||||||
|
// init client
|
||||||
|
c, err := New("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test add
|
||||||
|
err = c.login()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = c.AddFromLink(
|
||||||
|
"https://releases.ubuntu.com/22.04/ubuntu-22.04.1-desktop-amd64.iso.torrent",
|
||||||
|
"D:\\qBittorrentDownload\\alist",
|
||||||
|
"uuid-1",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = c.AddFromLink(
|
||||||
|
"magnet:?xt=urn:btih:375ae3280cd80a8e9d7212e11dfaf7c45069dd35&dn=archlinux-2023.02.01-x86_64.iso",
|
||||||
|
"D:\\qBittorrentDownload\\alist",
|
||||||
|
"uuid-2",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInfo(t *testing.T) {
|
||||||
|
// init client
|
||||||
|
c, err := New("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, err = c.GetInfo("uuid-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFiles(t *testing.T) {
|
||||||
|
// init client
|
||||||
|
c, err := New("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
files, err := c.GetFiles("uuid-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(files) != 1 {
|
||||||
|
t.Error("should have exactly one file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
// init client
|
||||||
|
c, err := New("http://admin:adminadmin@127.0.0.1:8080/")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
err = c.Delete("uuid-2")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
147
internal/qbittorrent/monitor.go
Normal file
147
internal/qbittorrent/monitor.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package qbittorrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/task"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Monitor struct {
|
||||||
|
tsk *task.Task[string]
|
||||||
|
tempDir string
|
||||||
|
dstDirPath string
|
||||||
|
finish chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) Loop() error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
completed bool
|
||||||
|
)
|
||||||
|
m.finish = make(chan struct{})
|
||||||
|
outer:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-m.tsk.Ctx.Done():
|
||||||
|
err = qbclient.Delete(m.tsk.ID)
|
||||||
|
return err
|
||||||
|
case <-time.After(time.Second * 2):
|
||||||
|
completed, err = m.update()
|
||||||
|
if completed {
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.tsk.SetStatus("qbittorrent download completed, transferring")
|
||||||
|
<-m.finish
|
||||||
|
m.tsk.SetStatus("completed")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) update() (bool, error) {
|
||||||
|
info, err := qbclient.GetInfo(m.tsk.ID)
|
||||||
|
if err != nil {
|
||||||
|
m.tsk.SetStatus("qbittorrent " + string(info.State))
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
progress := float64(info.Completed) / float64(info.Size) * 100
|
||||||
|
m.tsk.SetProgress(int(progress))
|
||||||
|
switch info.State {
|
||||||
|
case UPLOADING:
|
||||||
|
case PAUSEDUP:
|
||||||
|
case QUEUEDUP:
|
||||||
|
case STALLEDUP:
|
||||||
|
case FORCEDUP:
|
||||||
|
case CHECKINGUP:
|
||||||
|
err = m.complete()
|
||||||
|
return true, errors.WithMessage(err, "failed to transfer file")
|
||||||
|
case ALLOCATING:
|
||||||
|
case DOWNLOADING:
|
||||||
|
case METADL:
|
||||||
|
case PAUSEDDL:
|
||||||
|
case QUEUEDDL:
|
||||||
|
case STALLEDDL:
|
||||||
|
case CHECKINGDL:
|
||||||
|
case FORCEDDL:
|
||||||
|
case CHECKINGRESUMEDATA:
|
||||||
|
case MOVING:
|
||||||
|
case UNKNOWN: // or maybe should return an error for UNKNOWN?
|
||||||
|
m.tsk.SetStatus("qbittorrent downloading")
|
||||||
|
return false, nil
|
||||||
|
case ERROR:
|
||||||
|
case MISSINGFILES:
|
||||||
|
return true, errors.Errorf("failed to download %s, error: %s", m.tsk.ID, info.State)
|
||||||
|
}
|
||||||
|
return true, errors.New("unknown error occurred downloading qbittorrent") // should never happen
|
||||||
|
}
|
||||||
|
|
||||||
|
var TransferTaskManager = task.NewTaskManager(3, func(k *uint64) {
|
||||||
|
atomic.AddUint64(k, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
func (m *Monitor) complete() error {
|
||||||
|
// check dstDir again
|
||||||
|
storage, dstDirActualPath, err := op.GetStorageAndActualPath(m.dstDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.WithMessage(err, "failed get storage")
|
||||||
|
}
|
||||||
|
// get files
|
||||||
|
files, err := qbclient.GetFiles(m.tsk.ID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to get files of %s", m.tsk.ID)
|
||||||
|
}
|
||||||
|
log.Debugf("files len: %d", len(files))
|
||||||
|
// upload files
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(files))
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
err := os.RemoveAll(m.tempDir)
|
||||||
|
m.finish <- struct{}{}
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to remove qbittorrent temp dir: %+v", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for _, file := range files {
|
||||||
|
filePath := filepath.Join(m.tempDir, file.Name)
|
||||||
|
TransferTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
|
||||||
|
Name: fmt.Sprintf("transfer %s to [%s](%s)", filePath, storage.GetStorage().MountPath, dstDirActualPath),
|
||||||
|
Func: func(tsk *task.Task[uint64]) error {
|
||||||
|
defer wg.Done()
|
||||||
|
size := file.Size
|
||||||
|
mimetype := utils.GetMimeType(filePath)
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to open file %s", filePath)
|
||||||
|
}
|
||||||
|
stream := &model.FileStream{
|
||||||
|
Obj: &model.Object{
|
||||||
|
Name: path.Base(filePath),
|
||||||
|
Size: size,
|
||||||
|
Modified: time.Now(),
|
||||||
|
IsFolder: false,
|
||||||
|
},
|
||||||
|
ReadCloser: f,
|
||||||
|
Mimetype: mimetype,
|
||||||
|
}
|
||||||
|
newDistDir := filepath.Join(dstDirActualPath, file.Name)
|
||||||
|
return op.Put(tsk.Ctx, storage, newDistDir, stream, tsk.SetProgress)
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
23
internal/qbittorrent/qbittorrent.go
Normal file
23
internal/qbittorrent/qbittorrent.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package qbittorrent
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/setting"
|
||||||
|
"github.com/alist-org/alist/v3/pkg/task"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DownTaskManager = task.NewTaskManager[string](3)
|
||||||
|
var qbclient Client
|
||||||
|
|
||||||
|
func InitClient() error {
|
||||||
|
var err error
|
||||||
|
qbclient = nil
|
||||||
|
|
||||||
|
url := setting.GetStr(conf.QbittorrentUrl)
|
||||||
|
qbclient, err = New(url)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsQbittorrentReady() bool {
|
||||||
|
return qbclient != nil
|
||||||
|
}
|
@ -31,6 +31,8 @@ func BuildIndex(ctx context.Context, indexPaths, ignorePaths []string, maxDepth
|
|||||||
objCount uint64 = 0
|
objCount uint64 = 0
|
||||||
fi model.Obj
|
fi model.Obj
|
||||||
)
|
)
|
||||||
|
log.Infof("build index for: %+v", indexPaths)
|
||||||
|
log.Infof("ignore paths: %+v", ignorePaths)
|
||||||
Running.Store(true)
|
Running.Store(true)
|
||||||
Quit = make(chan struct{}, 1)
|
Quit = make(chan struct{}, 1)
|
||||||
indexMQ := mq.NewInMemoryMQ[ObjWithParent]()
|
indexMQ := mq.NewInMemoryMQ[ObjWithParent]()
|
||||||
|
@ -52,10 +52,13 @@ func updateIgnorePaths() {
|
|||||||
url := addition.Address + "/api/public/settings"
|
url := addition.Address + "/api/public/settings"
|
||||||
res, err := base.RestyClient.R().Get(url)
|
res, err := base.RestyClient.R().Get(url)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
allowIndexed = utils.Json.Get(res.Body(), "data", conf.AllowIndexed).ToBool()
|
log.Debugf("allow_indexed body: %+v", res.String())
|
||||||
|
allowIndexed = utils.Json.Get(res.Body(), "data", conf.AllowIndexed).ToString() == "true"
|
||||||
|
v3Visited[addition.Address] = allowIndexed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if allowIndexed {
|
log.Debugf("%s allow_indexed: %v", addition.Address, allowIndexed)
|
||||||
|
if !allowIndexed {
|
||||||
ignorePaths = append(ignorePaths, storage.GetStorage().MountPath)
|
ignorePaths = append(ignorePaths, storage.GetStorage().MountPath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,7 +40,7 @@ func CopyFile(src, dst string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CopyDir Dir copies a whole directory recursively
|
// CopyDir Dir copies a whole directory recursively
|
||||||
func CopyDir(src string, dst string) error {
|
func CopyDir(src, dst string) error {
|
||||||
var err error
|
var err error
|
||||||
var fds []os.DirEntry
|
var fds []os.DirEntry
|
||||||
var srcinfo os.FileInfo
|
var srcinfo os.FileInfo
|
||||||
@ -71,6 +71,17 @@ func CopyDir(src string, dst string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SymlinkOrCopyFile symlinks a file or copy if symlink failed
|
||||||
|
func SymlinkOrCopyFile(src, dst string) error {
|
||||||
|
if err := CreateNestedDirectory(filepath.Dir(dst)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Symlink(src, dst); err != nil {
|
||||||
|
return CopyFile(src, dst)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Exists determine whether the file exists
|
// Exists determine whether the file exists
|
||||||
func Exists(name string) bool {
|
func Exists(name string) bool {
|
||||||
if _, err := os.Stat(name); err != nil {
|
if _, err := os.Stat(name); err != nil {
|
||||||
@ -81,15 +92,20 @@ func Exists(name string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateNestedDirectory create nested directory
|
||||||
|
func CreateNestedDirectory(path string) error {
|
||||||
|
err := os.MkdirAll(path, 0700)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("can't create folder, %s", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// CreateNestedFile create nested file
|
// CreateNestedFile create nested file
|
||||||
func CreateNestedFile(path string) (*os.File, error) {
|
func CreateNestedFile(path string) (*os.File, error) {
|
||||||
basePath := filepath.Dir(path)
|
basePath := filepath.Dir(path)
|
||||||
if !Exists(basePath) {
|
if err := CreateNestedDirectory(basePath); err != nil {
|
||||||
err := os.MkdirAll(basePath, 0700)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
log.Errorf("can't create folder, %s", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return os.Create(path)
|
return os.Create(path)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"strings"
|
"strings"
|
||||||
@ -14,6 +15,12 @@ func GetSHA1Encode(data string) string {
|
|||||||
return hex.EncodeToString(h.Sum(nil))
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSHA256Encode(data string) string {
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(data))
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
func GetMD5Encode(data string) string {
|
func GetMD5Encode(data string) string {
|
||||||
h := md5.New()
|
h := md5.New()
|
||||||
h.Write([]byte(data))
|
h.Write([]byte(data))
|
||||||
|
@ -75,7 +75,17 @@ func EncodePath(path string, all ...bool) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func JoinBasePath(basePath, reqPath string) (string, error) {
|
func JoinBasePath(basePath, reqPath string) (string, error) {
|
||||||
if strings.HasSuffix(reqPath, "..") || strings.Contains(reqPath, "../") {
|
/** relative path:
|
||||||
|
* 1. ..
|
||||||
|
* 2. ../
|
||||||
|
* 3. /..
|
||||||
|
* 4. /../
|
||||||
|
* 5. /a/b/..
|
||||||
|
*/
|
||||||
|
if reqPath == ".." ||
|
||||||
|
strings.HasSuffix(reqPath, "/..") ||
|
||||||
|
strings.HasPrefix(reqPath, "../") ||
|
||||||
|
strings.Contains(reqPath, "/../") {
|
||||||
return "", errs.RelativePath
|
return "", errs.RelativePath
|
||||||
}
|
}
|
||||||
return stdpath.Join(FixAndCleanPath(basePath), FixAndCleanPath(reqPath)), nil
|
return stdpath.Join(FixAndCleanPath(basePath), FixAndCleanPath(reqPath)), nil
|
||||||
|
@ -3,24 +3,27 @@ package common
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
stdpath "path"
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetApiUrl(r *http.Request) string {
|
func GetApiUrl(r *http.Request) string {
|
||||||
api := conf.Conf.SiteURL
|
api := conf.Conf.SiteURL
|
||||||
if api == "" {
|
if strings.HasPrefix(api, "http") {
|
||||||
api = setting.GetStr(conf.ApiUrl)
|
return api
|
||||||
}
|
}
|
||||||
if r != nil && api == "" {
|
if r != nil && api == "" {
|
||||||
protocol := "http"
|
protocol := "http"
|
||||||
if r.TLS != nil {
|
if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" {
|
||||||
protocol = "https"
|
protocol = "https"
|
||||||
}
|
}
|
||||||
api = fmt.Sprintf("%s://%s", protocol, r.Host)
|
host := r.Host
|
||||||
|
if r.Header.Get("X-Forwarded-Host") != "" {
|
||||||
|
host = r.Header.Get("X-Forwarded-Host")
|
||||||
|
}
|
||||||
|
api = fmt.Sprintf("%s://%s", protocol, stdpath.Join(host, api))
|
||||||
}
|
}
|
||||||
strings.TrimSuffix(api, "/")
|
strings.TrimSuffix(api, "/")
|
||||||
return api
|
return api
|
||||||
|
@ -2,6 +2,7 @@ package handles
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
stdpath "path"
|
stdpath "path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -9,6 +10,7 @@ import (
|
|||||||
"github.com/alist-org/alist/v3/internal/driver"
|
"github.com/alist-org/alist/v3/internal/driver"
|
||||||
"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/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"
|
||||||
@ -27,6 +29,7 @@ func Down(c *gin.Context) {
|
|||||||
Proxy(c)
|
Proxy(c)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
link, _, err := fs.Link(c, rawPath, model.LinkArgs{
|
link, _, err := fs.Link(c, rawPath, model.LinkArgs{
|
||||||
IP: c.ClientIP(),
|
IP: c.ClientIP(),
|
||||||
Header: c.Request.Header,
|
Header: c.Request.Header,
|
||||||
@ -38,6 +41,23 @@ func Down(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
c.Header("Referrer-Policy", "no-referrer")
|
c.Header("Referrer-Policy", "no-referrer")
|
||||||
c.Header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
|
c.Header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
|
||||||
|
if setting.GetBool(conf.ForwardDirectLinkParams) {
|
||||||
|
params := c.Request.URL.Query()
|
||||||
|
params.Del("sign")
|
||||||
|
u, err := url.Parse(link.URL)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
values := u.Query()
|
||||||
|
for k := range params {
|
||||||
|
for i := range params[k] {
|
||||||
|
values.Set(k, params[k][i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.RawQuery = values.Encode()
|
||||||
|
link.URL = u.String()
|
||||||
|
}
|
||||||
c.Redirect(302, link.URL)
|
c.Redirect(302, link.URL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,6 +91,23 @@ func Proxy(c *gin.Context) {
|
|||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if link.URL != "" && setting.GetBool(conf.ForwardDirectLinkParams) {
|
||||||
|
params := c.Request.URL.Query()
|
||||||
|
params.Del("sign")
|
||||||
|
u, err := url.Parse(link.URL)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
values := u.Query()
|
||||||
|
for k := range params {
|
||||||
|
for i := range params[k] {
|
||||||
|
values.Set(k, params[k][i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
u.RawQuery = values.Encode()
|
||||||
|
link.URL = u.String()
|
||||||
|
}
|
||||||
err = common.Proxy(c.Writer, c.Request, link, file)
|
err = common.Proxy(c.Writer, c.Request, link, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.ErrorResp(c, err, 500, true)
|
common.ErrorResp(c, err, 500, true)
|
||||||
|
69
server/handles/qbittorrent.go
Normal file
69
server/handles/qbittorrent.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package handles
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/alist-org/alist/v3/internal/conf"
|
||||||
|
"github.com/alist-org/alist/v3/internal/model"
|
||||||
|
"github.com/alist-org/alist/v3/internal/op"
|
||||||
|
"github.com/alist-org/alist/v3/internal/qbittorrent"
|
||||||
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SetQbittorrentReq struct {
|
||||||
|
Url string `json:"url" form:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetQbittorrent(c *gin.Context) {
|
||||||
|
var req SetQbittorrentReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
items := []model.SettingItem{
|
||||||
|
{Key: conf.QbittorrentUrl, Value: req.Url, Type: conf.TypeString, Group: model.QBITTORRENT, Flag: model.PRIVATE},
|
||||||
|
}
|
||||||
|
if err := op.SaveSettingItems(items); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := qbittorrent.InitClient(); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.SuccessResp(c, "ok")
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddQbittorrentReq struct {
|
||||||
|
Urls []string `json:"urls"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddQbittorrent(c *gin.Context) {
|
||||||
|
user := c.MustGet("user").(*model.User)
|
||||||
|
if !user.CanAddQbittorrentTasks() {
|
||||||
|
common.ErrorStrResp(c, "permission denied", 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !qbittorrent.IsQbittorrentReady() {
|
||||||
|
common.ErrorStrResp(c, "qbittorrent not ready", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req AddQbittorrentReq
|
||||||
|
if err := c.ShouldBind(&req); err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reqPath, err := user.JoinPath(req.Path)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 403)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, url := range req.Urls {
|
||||||
|
err := qbittorrent.AddURL(c, url, reqPath)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alist-org/alist/v3/internal/aria2"
|
"github.com/alist-org/alist/v3/internal/aria2"
|
||||||
"github.com/alist-org/alist/v3/internal/fs"
|
"github.com/alist-org/alist/v3/internal/fs"
|
||||||
|
"github.com/alist-org/alist/v3/internal/qbittorrent"
|
||||||
"github.com/alist-org/alist/v3/pkg/task"
|
"github.com/alist-org/alist/v3/pkg/task"
|
||||||
"github.com/alist-org/alist/v3/server/common"
|
"github.com/alist-org/alist/v3/server/common"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -19,9 +20,19 @@ type TaskInfo struct {
|
|||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTaskInfoUint(task *task.Task[uint64]) TaskInfo {
|
type K2Str[K comparable] func(k K) string
|
||||||
|
|
||||||
|
func uint64K2Str(k uint64) string {
|
||||||
|
return strconv.FormatUint(k, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func strK2Str(str string) string {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTaskInfo[K comparable](task *task.Task[K], k2Str K2Str[K]) TaskInfo {
|
||||||
return TaskInfo{
|
return TaskInfo{
|
||||||
ID: strconv.FormatUint(task.ID, 10),
|
ID: k2Str(task.ID),
|
||||||
Name: task.Name,
|
Name: task.Name,
|
||||||
State: task.GetState(),
|
State: task.GetState(),
|
||||||
Status: task.GetStatus(),
|
Status: task.GetStatus(),
|
||||||
@ -30,183 +41,68 @@ func getTaskInfoUint(task *task.Task[uint64]) TaskInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTaskInfoStr(task *task.Task[string]) TaskInfo {
|
func getTaskInfos[K comparable](tasks []*task.Task[K], k2Str K2Str[K]) []TaskInfo {
|
||||||
return TaskInfo{
|
|
||||||
ID: task.ID,
|
|
||||||
Name: task.Name,
|
|
||||||
State: task.GetState(),
|
|
||||||
Status: task.GetStatus(),
|
|
||||||
Progress: task.GetProgress(),
|
|
||||||
Error: task.GetErrMsg(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTaskInfosUint(tasks []*task.Task[uint64]) []TaskInfo {
|
|
||||||
var infos []TaskInfo
|
var infos []TaskInfo
|
||||||
for _, t := range tasks {
|
for _, t := range tasks {
|
||||||
infos = append(infos, getTaskInfoUint(t))
|
infos = append(infos, getTaskInfo(t, k2Str))
|
||||||
}
|
}
|
||||||
return infos
|
return infos
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTaskInfosStr(tasks []*task.Task[string]) []TaskInfo {
|
type Str2K[K comparable] func(str string) (K, error)
|
||||||
var infos []TaskInfo
|
|
||||||
for _, t := range tasks {
|
func str2Uint64K(str string) (uint64, error) {
|
||||||
infos = append(infos, getTaskInfoStr(t))
|
return strconv.ParseUint(str, 10, 64)
|
||||||
}
|
|
||||||
return infos
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UndoneDownTask(c *gin.Context) {
|
func str2StrK(str string) (string, error) {
|
||||||
common.SuccessResp(c, getTaskInfosStr(aria2.DownTaskManager.ListUndone()))
|
return str, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DoneDownTask(c *gin.Context) {
|
func taskRoute[K comparable](g *gin.RouterGroup, manager *task.Manager[K], k2Str K2Str[K], str2K Str2K[K]) {
|
||||||
common.SuccessResp(c, getTaskInfosStr(aria2.DownTaskManager.ListDone()))
|
g.GET("/undone", func(c *gin.Context) {
|
||||||
}
|
common.SuccessResp(c, getTaskInfos(manager.ListUndone(), k2Str))
|
||||||
|
})
|
||||||
func CancelDownTask(c *gin.Context) {
|
g.GET("/done", func(c *gin.Context) {
|
||||||
tid := c.Query("tid")
|
common.SuccessResp(c, getTaskInfos(manager.ListDone(), k2Str))
|
||||||
if err := aria2.DownTaskManager.Cancel(tid); err != nil {
|
})
|
||||||
common.ErrorResp(c, err, 500)
|
g.POST("/cancel", func(c *gin.Context) {
|
||||||
} else {
|
tid := c.Query("tid")
|
||||||
|
id, err := str2K(tid)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := manager.Cancel(id); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
g.POST("/delete", func(c *gin.Context) {
|
||||||
|
tid := c.Query("tid")
|
||||||
|
id, err := str2K(tid)
|
||||||
|
if err != nil {
|
||||||
|
common.ErrorResp(c, err, 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := manager.Remove(id); err != nil {
|
||||||
|
common.ErrorResp(c, err, 500)
|
||||||
|
} else {
|
||||||
|
common.SuccessResp(c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
g.POST("/clear_done", func(c *gin.Context) {
|
||||||
|
manager.ClearDone()
|
||||||
common.SuccessResp(c)
|
common.SuccessResp(c)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteDownTask(c *gin.Context) {
|
func SetupTaskRoute(g *gin.RouterGroup) {
|
||||||
tid := c.Query("tid")
|
taskRoute(g.Group("/aria2_down"), aria2.DownTaskManager, strK2Str, str2StrK)
|
||||||
if err := aria2.DownTaskManager.Remove(tid); err != nil {
|
taskRoute(g.Group("/aria2_transfer"), aria2.TransferTaskManager, uint64K2Str, str2Uint64K)
|
||||||
common.ErrorResp(c, err, 500)
|
taskRoute(g.Group("/upload"), fs.UploadTaskManager, uint64K2Str, str2Uint64K)
|
||||||
} else {
|
taskRoute(g.Group("/copy"), fs.CopyTaskManager, uint64K2Str, str2Uint64K)
|
||||||
common.SuccessResp(c)
|
taskRoute(g.Group("/qbit_down"), qbittorrent.DownTaskManager, strK2Str, str2StrK)
|
||||||
}
|
taskRoute(g.Group("/qbit_transfer"), qbittorrent.TransferTaskManager, uint64K2Str, str2Uint64K)
|
||||||
}
|
|
||||||
|
|
||||||
func ClearDoneDownTasks(c *gin.Context) {
|
|
||||||
aria2.DownTaskManager.ClearDone()
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UndoneTransferTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(aria2.TransferTaskManager.ListUndone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoneTransferTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(aria2.TransferTaskManager.ListDone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func CancelTransferTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := aria2.TransferTaskManager.Cancel(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTransferTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := aria2.TransferTaskManager.Remove(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClearDoneTransferTasks(c *gin.Context) {
|
|
||||||
aria2.TransferTaskManager.ClearDone()
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UndoneUploadTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(fs.UploadTaskManager.ListUndone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoneUploadTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(fs.UploadTaskManager.ListDone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func CancelUploadTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := fs.UploadTaskManager.Cancel(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteUploadTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := fs.UploadTaskManager.Remove(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClearDoneUploadTasks(c *gin.Context) {
|
|
||||||
fs.UploadTaskManager.ClearDone()
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func UndoneCopyTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(fs.CopyTaskManager.ListUndone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoneCopyTask(c *gin.Context) {
|
|
||||||
common.SuccessResp(c, getTaskInfosUint(fs.CopyTaskManager.ListDone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func CancelCopyTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := fs.CopyTaskManager.Cancel(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteCopyTask(c *gin.Context) {
|
|
||||||
id := c.Query("tid")
|
|
||||||
tid, err := strconv.ParseUint(id, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
common.ErrorResp(c, err, 400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := fs.CopyTaskManager.Remove(tid); err != nil {
|
|
||||||
common.ErrorResp(c, err, 500)
|
|
||||||
} else {
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ClearDoneCopyTasks(c *gin.Context) {
|
|
||||||
fs.CopyTaskManager.ClearDone()
|
|
||||||
common.SuccessResp(c)
|
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,10 @@ func UpdateUser(c *gin.Context) {
|
|||||||
if req.OtpSecret == "" {
|
if req.OtpSecret == "" {
|
||||||
req.OtpSecret = user.OtpSecret
|
req.OtpSecret = user.OtpSecret
|
||||||
}
|
}
|
||||||
|
if req.Disabled && req.IsAdmin() {
|
||||||
|
common.ErrorStrResp(c, "admin user can not be disabled", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
if err := op.UpdateUser(&req); err != nil {
|
if err := op.UpdateUser(&req); err != nil {
|
||||||
common.ErrorResp(c, err, 500)
|
common.ErrorResp(c, err, 500)
|
||||||
} else {
|
} else {
|
||||||
|
@ -33,6 +33,11 @@ func Auth(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if guest.Disabled {
|
||||||
|
common.ErrorStrResp(c, "Guest user is disabled, login please", 401)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
c.Set("user", guest)
|
c.Set("user", guest)
|
||||||
log.Debugf("use empty token: %+v", guest)
|
log.Debugf("use empty token: %+v", guest)
|
||||||
c.Next()
|
c.Next()
|
||||||
@ -50,6 +55,11 @@ func Auth(c *gin.Context) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if user.Disabled {
|
||||||
|
common.ErrorStrResp(c, "Current user is disabled, replace please", 401)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
c.Set("user", user)
|
c.Set("user", user)
|
||||||
log.Debugf("use login token: %+v", user)
|
log.Debugf("use login token: %+v", user)
|
||||||
c.Next()
|
c.Next()
|
||||||
|
@ -34,8 +34,10 @@ func Init(r *gin.Engine) {
|
|||||||
auth.POST("/me/update", handles.UpdateCurrent)
|
auth.POST("/me/update", handles.UpdateCurrent)
|
||||||
auth.POST("/auth/2fa/generate", handles.Generate2FA)
|
auth.POST("/auth/2fa/generate", handles.Generate2FA)
|
||||||
auth.POST("/auth/2fa/verify", handles.Verify2FA)
|
auth.POST("/auth/2fa/verify", handles.Verify2FA)
|
||||||
auth.GET("/auth/github", handles.GithubLoginRedirect)
|
|
||||||
auth.GET("/auth/github_callback", handles.GithubLoginCallback)
|
// github auth
|
||||||
|
api.GET("/auth/github", handles.GithubLoginRedirect)
|
||||||
|
api.GET("/auth/github_callback", handles.GithubLoginCallback)
|
||||||
|
|
||||||
// no need auth
|
// no need auth
|
||||||
public := api.Group("/public")
|
public := api.Group("/public")
|
||||||
@ -87,28 +89,10 @@ func admin(g *gin.RouterGroup) {
|
|||||||
setting.POST("/delete", handles.DeleteSetting)
|
setting.POST("/delete", handles.DeleteSetting)
|
||||||
setting.POST("/reset_token", handles.ResetToken)
|
setting.POST("/reset_token", handles.ResetToken)
|
||||||
setting.POST("/set_aria2", handles.SetAria2)
|
setting.POST("/set_aria2", handles.SetAria2)
|
||||||
|
setting.POST("/set_qbittorrent", handles.SetQbittorrent)
|
||||||
|
|
||||||
task := g.Group("/task")
|
task := g.Group("/task")
|
||||||
task.GET("/down/undone", handles.UndoneDownTask)
|
handles.SetupTaskRoute(task)
|
||||||
task.GET("/down/done", handles.DoneDownTask)
|
|
||||||
task.POST("/down/cancel", handles.CancelDownTask)
|
|
||||||
task.POST("/down/delete", handles.DeleteDownTask)
|
|
||||||
task.POST("/down/clear_done", handles.ClearDoneDownTasks)
|
|
||||||
task.GET("/transfer/undone", handles.UndoneTransferTask)
|
|
||||||
task.GET("/transfer/done", handles.DoneTransferTask)
|
|
||||||
task.POST("/transfer/cancel", handles.CancelTransferTask)
|
|
||||||
task.POST("/transfer/delete", handles.DeleteTransferTask)
|
|
||||||
task.POST("/transfer/clear_done", handles.ClearDoneTransferTasks)
|
|
||||||
task.GET("/upload/undone", handles.UndoneUploadTask)
|
|
||||||
task.GET("/upload/done", handles.DoneUploadTask)
|
|
||||||
task.POST("/upload/cancel", handles.CancelUploadTask)
|
|
||||||
task.POST("/upload/delete", handles.DeleteUploadTask)
|
|
||||||
task.POST("/upload/clear_done", handles.ClearDoneUploadTasks)
|
|
||||||
task.GET("/copy/undone", handles.UndoneCopyTask)
|
|
||||||
task.GET("/copy/done", handles.DoneCopyTask)
|
|
||||||
task.POST("/copy/cancel", handles.CancelCopyTask)
|
|
||||||
task.POST("/copy/delete", handles.DeleteCopyTask)
|
|
||||||
task.POST("/copy/clear_done", handles.ClearDoneCopyTasks)
|
|
||||||
|
|
||||||
ms := g.Group("/message")
|
ms := g.Group("/message")
|
||||||
ms.POST("/get", message.HttpInstance.GetHandle)
|
ms.POST("/get", message.HttpInstance.GetHandle)
|
||||||
@ -137,6 +121,7 @@ func _fs(g *gin.RouterGroup) {
|
|||||||
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)
|
||||||
g.POST("/add_aria2", handles.AddAria2)
|
g.POST("/add_aria2", handles.AddAria2)
|
||||||
|
g.POST("/add_qbit", handles.AddQbittorrent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Cors(r *gin.Engine) {
|
func Cors(r *gin.Engine) {
|
||||||
|
@ -5,7 +5,6 @@ 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/pkg/utils"
|
"github.com/alist-org/alist/v3/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,11 +24,6 @@ func getSiteConfig() SiteConfig {
|
|||||||
BasePath: u.Path,
|
BasePath: u.Path,
|
||||||
Cdn: strings.ReplaceAll(strings.TrimSuffix(conf.Conf.Cdn, "/"), "$version", conf.WebVersion),
|
Cdn: strings.ReplaceAll(strings.TrimSuffix(conf.Conf.Cdn, "/"), "$version", conf.WebVersion),
|
||||||
}
|
}
|
||||||
// try to get old config
|
|
||||||
if siteConfig.ApiURL == "" {
|
|
||||||
siteConfig.ApiURL = setting.GetStr(conf.ApiUrl)
|
|
||||||
siteConfig.BasePath = setting.GetStr(conf.BasePath)
|
|
||||||
}
|
|
||||||
if siteConfig.BasePath != "" {
|
if siteConfig.BasePath != "" {
|
||||||
siteConfig.BasePath = utils.FixAndCleanPath(siteConfig.BasePath)
|
siteConfig.BasePath = utils.FixAndCleanPath(siteConfig.BasePath)
|
||||||
}
|
}
|
||||||
|
2
wrapper/zcc-arm64
Normal file
2
wrapper/zcc-arm64
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
zig cc -target aarch64-windows-gnu $@
|
2
wrapper/zcxx-arm64
Normal file
2
wrapper/zcxx-arm64
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
zig c++ -target aarch64-windows-gnu $@
|
Reference in New Issue
Block a user