Compare commits
38 Commits
refactor/o
...
v3.29.1
Author | SHA1 | Date | |
---|---|---|---|
d142fc3449 | |||
f23567199b | |||
1420492d81 | |||
b88067ea2f | |||
d5f381ef6f | |||
68af284dad | |||
d26887d211 | |||
3f405de6a9 | |||
6100647310 | |||
34746e951c | |||
b6134dc515 | |||
d455a232ef | |||
fe34d30d17 | |||
0fbb986ba9 | |||
1280070438 | |||
d7f66138eb | |||
b2890f05ab | |||
7583c4d734 | |||
11a30c5044 | |||
de9647a5fa | |||
8d5283604c | |||
867accafd1 | |||
6fc6751463 | |||
f904596cbc | |||
3d51845f57 | |||
a7421d8fc2 | |||
55a14bc271 | |||
91f51f17d0 | |||
4355dae491 | |||
da1c7a4c23 | |||
769281bd40 | |||
3bbdd4fa89 | |||
68f440abdb | |||
65c5ec0c34 | |||
a6325967d0 | |||
4dff49470a | |||
cc86d6f3d1 | |||
c0f9c8ebaf |
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
go-version: [ '1.20' ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Build
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
@ -27,6 +27,9 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: benjlevesque/short-sha@v2.2
|
||||
id: short-sha
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo snap install zig --classic --beta
|
||||
@ -41,5 +44,5 @@ jobs:
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: alist
|
||||
name: alist_${{ env.SHA }}
|
||||
path: dist
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.20' ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
2
.github/workflows/release_linux_musl.yml
vendored
2
.github/workflows/release_linux_musl.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.20' ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
2
.github/workflows/release_linux_musl_arm.yml
vendored
2
.github/workflows/release_linux_musl_arm.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.20' ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
@ -1,11 +1,11 @@
|
||||
FROM alpine:3.18 as builder
|
||||
FROM alpine:edge as builder
|
||||
LABEL stage=go-builder
|
||||
WORKDIR /app/
|
||||
COPY ./ ./
|
||||
RUN apk add --no-cache bash curl gcc git go musl-dev; \
|
||||
bash build.sh release docker
|
||||
|
||||
FROM alpine:3.18
|
||||
FROM alpine:edge
|
||||
LABEL MAINTAINER="i@nn.ci"
|
||||
VOLUME /opt/alist/data/
|
||||
WORKDIR /opt/alist/
|
||||
|
@ -112,9 +112,10 @@ https://alist.nn.ci/guide/sponsor.html
|
||||
|
||||
### Special sponsors
|
||||
|
||||
- [亚洲云 - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商](https://www.asiayun.com/aff/QQCOOQKZ) (sponsored Chinese API server)
|
||||
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.pw/)
|
||||
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
||||
- [VidHub](https://okaapps.com/product/1659622164?ref=alist) - An elegant cloud video player within the Apple ecosystem. Support for iPhone, iPad, Mac, and Apple TV.
|
||||
- [亚洲云](https://www.asiayun.com/aff/QQCOOQKZ) - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商 (sponsored Chinese API server)
|
||||
- [找资源](https://zhaoziyuan.pw/) - 阿里云盘资源搜索引擎
|
||||
- [JetBrains](https://www.jetbrains.com/) - Essential tools for software developers and teams
|
||||
|
||||
## Contributors
|
||||
|
||||
|
@ -110,9 +110,10 @@ AList 是一个开源软件,如果你碰巧喜欢这个项目,并希望我
|
||||
|
||||
### 特别赞助
|
||||
|
||||
- [亚洲云 - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商](https://www.asiayun.com/aff/QQCOOQKZ) (国内API服务器赞助)
|
||||
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.pw/)
|
||||
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
||||
- [VidHub](https://zh.okaapps.com/product/1659622164?ref=alist) - 苹果生态下优雅的网盘视频播放器,iPhone,iPad,Mac,Apple TV全平台支持。
|
||||
- [亚洲云](https://www.asiayun.com/aff/QQCOOQKZ) - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商 (国内API服务器赞助)
|
||||
- [找资源](https://zhaoziyuan.pw/) - 阿里云盘资源搜索引擎
|
||||
- [JetBrains](https://www.jetbrains.com/) - Essential tools for software developers and teams
|
||||
|
||||
## 贡献者
|
||||
|
||||
|
@ -112,9 +112,10 @@ https://alist.nn.ci/guide/sponsor.html
|
||||
|
||||
### スペシャルスポンサー
|
||||
|
||||
- [亚洲云 - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商](https://www.asiayun.com/aff/QQCOOQKZ) (sponsored Chinese API server)
|
||||
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.pw/)
|
||||
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
||||
- [VidHub](https://okaapps.com/product/1659622164?ref=alist) - An elegant cloud video player within the Apple ecosystem. Support for iPhone, iPad, Mac, and Apple TV.
|
||||
- [亚洲云](https://www.asiayun.com/aff/QQCOOQKZ) - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商 (sponsored Chinese API server)
|
||||
- [找资源](https://zhaoziyuan.pw/) - 阿里云盘资源搜索引擎
|
||||
- [JetBrains](https://www.jetbrains.com/) - Essential tools for software developers and teams
|
||||
|
||||
## コントリビューター
|
||||
|
||||
|
2
build.sh
2
build.sh
@ -85,6 +85,8 @@ BuildDev() {
|
||||
}
|
||||
|
||||
BuildDocker() {
|
||||
echo "replace github.com/mattn/go-sqlite3 => github.com/leso-kn/go-sqlite3 v0.0.0-20230710125852-03158dc838ed" >>go.mod
|
||||
go get gorm.io/driver/sqlite@v1.4.4
|
||||
go build -o ./bin/alist -ldflags="$ldflags" -tags=jsoniter .
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/alist-org/alist/v3/cmd/flags"
|
||||
_ "github.com/alist-org/alist/v3/drivers"
|
||||
_ "github.com/alist-org/alist/v3/internal/offline_download"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -13,7 +14,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/cmd/flags"
|
||||
_ "github.com/alist-org/alist/v3/drivers"
|
||||
"github.com/alist-org/alist/v3/internal/bootstrap"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
@ -35,9 +35,9 @@ the address is defined in config file`,
|
||||
utils.Log.Infof("delayed start for %d seconds", conf.Conf.DelayedStart)
|
||||
time.Sleep(time.Duration(conf.Conf.DelayedStart) * time.Second)
|
||||
}
|
||||
bootstrap.InitAria2()
|
||||
bootstrap.InitQbittorrent()
|
||||
bootstrap.InitOfflineDownloadTools()
|
||||
bootstrap.LoadStorages()
|
||||
bootstrap.InitTaskManager()
|
||||
if !flags.Debug && !flags.Dev {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
@ -51,7 +51,7 @@ the address is defined in config file`,
|
||||
httpSrv = &http.Server{Addr: httpBase, Handler: r}
|
||||
go func() {
|
||||
err := httpSrv.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
utils.Log.Fatalf("failed to start http: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
@ -62,7 +62,7 @@ the address is defined in config file`,
|
||||
httpsSrv = &http.Server{Addr: httpsBase, Handler: r}
|
||||
go func() {
|
||||
err := httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile)
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
utils.Log.Fatalf("failed to start https: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
@ -86,7 +86,7 @@ the address is defined in config file`,
|
||||
}
|
||||
}
|
||||
err = unixSrv.Serve(listener)
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
utils.Log.Fatalf("failed to start unix: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
|
@ -18,26 +18,25 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/SheltonZhu/115driver/pkg/driver"
|
||||
driver115 "github.com/SheltonZhu/115driver/pkg/driver"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var UserAgent = driver.UA115Desktop
|
||||
var UserAgent = driver115.UA115Desktop
|
||||
|
||||
func (d *Pan115) login() error {
|
||||
var err error
|
||||
opts := []driver.Option{
|
||||
driver.UA(UserAgent),
|
||||
func(c *driver.Pan115Client) {
|
||||
opts := []driver115.Option{
|
||||
driver115.UA(UserAgent),
|
||||
func(c *driver115.Pan115Client) {
|
||||
c.Client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify})
|
||||
},
|
||||
}
|
||||
d.client = driver.New(opts...)
|
||||
cr := &driver.Credential{}
|
||||
d.client = driver115.New(opts...)
|
||||
cr := &driver115.Credential{}
|
||||
if d.Addition.QRCodeToken != "" {
|
||||
s := &driver.QRCodeSession{
|
||||
s := &driver115.QRCodeSession{
|
||||
UID: d.Addition.QRCodeToken,
|
||||
}
|
||||
if cr, err = d.client.QRCodeLogin(s); err != nil {
|
||||
@ -59,7 +58,7 @@ func (d *Pan115) login() error {
|
||||
func (d *Pan115) getFiles(fileId string) ([]FileObj, error) {
|
||||
res := make([]FileObj, 0)
|
||||
if d.PageSize <= 0 {
|
||||
d.PageSize = driver.FileListLimit
|
||||
d.PageSize = driver115.FileListLimit
|
||||
}
|
||||
files, err := d.client.ListWithLimit(fileId, d.PageSize)
|
||||
if err != nil {
|
||||
@ -249,7 +248,7 @@ func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize i
|
||||
go func(threadId int) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errCh <- fmt.Errorf("Recovered in %v", r)
|
||||
errCh <- fmt.Errorf("recovered in %v", r)
|
||||
}
|
||||
}()
|
||||
for chunk := range chunksCh {
|
||||
|
112
drivers/115_share/driver.go
Normal file
112
drivers/115_share/driver.go
Normal file
@ -0,0 +1,112 @@
|
||||
package _115_share
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
driver115 "github.com/SheltonZhu/115driver/pkg/driver"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
type Pan115Share struct {
|
||||
model.Storage
|
||||
Addition
|
||||
client *driver115.Pan115Client
|
||||
limiter *rate.Limiter
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *Pan115Share) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Init(ctx context.Context) error {
|
||||
if d.LimitRate > 0 {
|
||||
d.limiter = rate.NewLimiter(rate.Limit(d.LimitRate), 1)
|
||||
}
|
||||
|
||||
return d.login()
|
||||
}
|
||||
|
||||
func (d *Pan115Share) WaitLimit(ctx context.Context) error {
|
||||
if d.limiter != nil {
|
||||
return d.limiter.Wait(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Pan115Share) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
if err := d.WaitLimit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := make([]driver115.ShareFile, 0)
|
||||
fileResp, err := d.client.GetShareSnap(d.ShareCode, d.ReceiveCode, dir.GetID(), driver115.QueryLimit(int(d.PageSize)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, fileResp.Data.List...)
|
||||
total := fileResp.Data.Count
|
||||
count := len(fileResp.Data.List)
|
||||
for total > count {
|
||||
fileResp, err := d.client.GetShareSnap(
|
||||
d.ShareCode, d.ReceiveCode, dir.GetID(),
|
||||
driver115.QueryLimit(int(d.PageSize)), driver115.QueryOffset(count),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, fileResp.Data.List...)
|
||||
count += len(fileResp.Data.List)
|
||||
}
|
||||
|
||||
return utils.SliceConvert(files, transFunc)
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
if err := d.WaitLimit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
downloadInfo, err := d.client.DownloadByShareCode(d.ShareCode, d.ReceiveCode, file.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.Link{URL: downloadInfo.URL.URL}, nil
|
||||
}
|
||||
|
||||
func (d *Pan115Share) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Remove(ctx context.Context, obj model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*Pan115Share)(nil)
|
33
drivers/115_share/meta.go
Normal file
33
drivers/115_share/meta.go
Normal file
@ -0,0 +1,33 @@
|
||||
package _115_share
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
Cookie string `json:"cookie" type:"text" help:"one of QR code token and cookie required"`
|
||||
QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
|
||||
PageSize int64 `json:"page_size" type:"number" default:"20" help:"list api per page size of 115 driver"`
|
||||
LimitRate float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"`
|
||||
ShareCode string `json:"share_code" type:"text" required:"true" help:"share code of 115 share link"`
|
||||
ReceiveCode string `json:"receive_code" type:"text" required:"true" help:"receive code of 115 share link"`
|
||||
driver.RootID
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "115 Share",
|
||||
DefaultRoot: "",
|
||||
// OnlyProxy: true,
|
||||
// OnlyLocal: true,
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: true,
|
||||
NoUpload: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Pan115Share{}
|
||||
})
|
||||
}
|
111
drivers/115_share/utils.go
Normal file
111
drivers/115_share/utils.go
Normal file
@ -0,0 +1,111 @@
|
||||
package _115_share
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
driver115 "github.com/SheltonZhu/115driver/pkg/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ model.Obj = (*FileObj)(nil)
|
||||
|
||||
type FileObj struct {
|
||||
Size int64
|
||||
Sha1 string
|
||||
Utm time.Time
|
||||
FileName string
|
||||
isDir bool
|
||||
FileID string
|
||||
}
|
||||
|
||||
func (f *FileObj) CreateTime() time.Time {
|
||||
return f.Utm
|
||||
}
|
||||
|
||||
func (f *FileObj) GetHash() utils.HashInfo {
|
||||
return utils.NewHashInfo(utils.SHA1, f.Sha1)
|
||||
}
|
||||
|
||||
func (f *FileObj) GetSize() int64 {
|
||||
return f.Size
|
||||
}
|
||||
|
||||
func (f *FileObj) GetName() string {
|
||||
return f.FileName
|
||||
}
|
||||
|
||||
func (f *FileObj) ModTime() time.Time {
|
||||
return f.Utm
|
||||
}
|
||||
|
||||
func (f *FileObj) IsDir() bool {
|
||||
return f.isDir
|
||||
}
|
||||
|
||||
func (f *FileObj) GetID() string {
|
||||
return f.FileID
|
||||
}
|
||||
|
||||
func (f *FileObj) GetPath() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func transFunc(sf driver115.ShareFile) (model.Obj, error) {
|
||||
timeInt, err := strconv.ParseInt(sf.UpdateTime, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
utm = time.Unix(timeInt, 0)
|
||||
isDir = (sf.IsFile == 0)
|
||||
fileID = string(sf.FileID)
|
||||
)
|
||||
if isDir {
|
||||
fileID = string(sf.CategoryID)
|
||||
}
|
||||
return &FileObj{
|
||||
Size: int64(sf.Size),
|
||||
Sha1: sf.Sha1,
|
||||
Utm: utm,
|
||||
FileName: string(sf.FileName),
|
||||
isDir: isDir,
|
||||
FileID: fileID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var UserAgent = driver115.UA115Browser
|
||||
|
||||
func (d *Pan115Share) login() error {
|
||||
var err error
|
||||
opts := []driver115.Option{
|
||||
driver115.UA(UserAgent),
|
||||
}
|
||||
d.client = driver115.New(opts...)
|
||||
if _, err := d.client.GetShareSnap(d.ShareCode, d.ReceiveCode, ""); err != nil {
|
||||
return errors.Wrap(err, "failed to get share snap")
|
||||
}
|
||||
cr := &driver115.Credential{}
|
||||
if d.QRCodeToken != "" {
|
||||
s := &driver115.QRCodeSession{
|
||||
UID: d.QRCodeToken,
|
||||
}
|
||||
if cr, err = d.client.QRCodeLogin(s); err != nil {
|
||||
return errors.Wrap(err, "failed to login by qrcode")
|
||||
}
|
||||
d.Cookie = fmt.Sprintf("UID=%s;CID=%s;SEID=%s", cr.UID, cr.CID, cr.SEID)
|
||||
d.QRCodeToken = ""
|
||||
} else if d.Cookie != "" {
|
||||
if err = cr.FromCookie(d.Cookie); err != nil {
|
||||
return errors.Wrap(err, "failed to login by cookies")
|
||||
}
|
||||
d.client.ImportCredential(cr)
|
||||
} else {
|
||||
return errors.New("missing cookie or qrcode account")
|
||||
}
|
||||
|
||||
return d.client.LoginCheck()
|
||||
}
|
@ -107,7 +107,7 @@ func (d *Pan123) newUpload(ctx context.Context, upReq *UploadResp, file model.Fi
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
up(j * 100 / chunkCount)
|
||||
up(float64(j) * 100 / float64(chunkCount))
|
||||
}
|
||||
}
|
||||
// complete s3 upload
|
||||
|
@ -380,7 +380,7 @@ func (d *Cloud189) newUpload(ctx context.Context, dstDir model.Obj, file model.F
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
up(int(i * 100 / count))
|
||||
up(float64(i) * 100 / float64(count))
|
||||
}
|
||||
fileMd5 := hex.EncodeToString(md5Sum.Sum(nil))
|
||||
sliceMd5 := fileMd5
|
||||
|
@ -513,7 +513,7 @@ func (y *Cloud189PC) StreamUpload(ctx context.Context, dstDir model.Obj, file mo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
up(int(threadG.Success()) * 100 / count)
|
||||
up(float64(threadG.Success()) * 100 / float64(count))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@ -676,7 +676,7 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
return err
|
||||
}
|
||||
|
||||
up(int(threadG.Success()) * 100 / len(uploadUrls))
|
||||
up(float64(threadG.Success()) * 100 / float64(len(uploadUrls)))
|
||||
uploadProgress.UploadParts[i] = ""
|
||||
return nil
|
||||
})
|
||||
@ -812,7 +812,7 @@ func (y *Cloud189PC) OldUpload(ctx context.Context, dstDir model.Obj, file model
|
||||
if _, err := tempFile.Seek(status.GetSize(), io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
up(int(status.GetSize()/file.GetSize()) * 100)
|
||||
up(float64(status.GetSize()) / float64(file.GetSize()) * 100)
|
||||
}
|
||||
|
||||
return y.OldUploadCommit(ctx, status.FileCommitUrl, status.UploadFileId)
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
@ -15,6 +14,8 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
@ -304,7 +305,7 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, streamer model.Fil
|
||||
}
|
||||
res.Body.Close()
|
||||
if count > 0 {
|
||||
up(i * 100 / count)
|
||||
up(float64(i) * 100 / float64(count))
|
||||
}
|
||||
}
|
||||
var resp2 base.Json
|
||||
|
@ -80,7 +80,7 @@ func (d *AliyundriveOpen) link(ctx context.Context, file model.Obj) (*model.Link
|
||||
req.SetBody(base.Json{
|
||||
"drive_id": d.DriveId,
|
||||
"file_id": file.GetID(),
|
||||
"expire_sec": 14400,
|
||||
"expire_sec": 900,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
@ -93,7 +93,7 @@ func (d *AliyundriveOpen) link(ctx context.Context, file model.Obj) (*model.Link
|
||||
}
|
||||
url = utils.Json.Get(res, "streamsUrl", d.LIVPDownloadFormat).ToString()
|
||||
}
|
||||
exp := time.Hour
|
||||
exp := time.Minute
|
||||
return &model.Link{
|
||||
URL: url,
|
||||
Expiration: &exp,
|
||||
@ -207,7 +207,7 @@ func (d *AliyundriveOpen) Other(ctx context.Context, args model.OtherArgs) (inte
|
||||
case "video_preview":
|
||||
uri = "/adrive/v1.0/openFile/getVideoPreviewPlayInfo"
|
||||
data["category"] = "live_transcoding"
|
||||
data["url_expire_sec"] = 14400
|
||||
data["url_expire_sec"] = 900
|
||||
default:
|
||||
return nil, errs.NotSupport
|
||||
}
|
||||
|
@ -258,7 +258,7 @@ func (d *AliyundriveOpen) upload(ctx context.Context, dstDir model.Obj, stream m
|
||||
return nil, err
|
||||
}
|
||||
offset += partSize
|
||||
up(i * 100 / count)
|
||||
up(float64(i*100) / float64(count))
|
||||
}
|
||||
} else {
|
||||
log.Debugf("[aliyundrive_open] rapid upload success, file id: %s", createResp.FileId)
|
||||
|
@ -2,6 +2,7 @@ package drivers
|
||||
|
||||
import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/115"
|
||||
_ "github.com/alist-org/alist/v3/drivers/115_share"
|
||||
_ "github.com/alist-org/alist/v3/drivers/123"
|
||||
_ "github.com/alist-org/alist/v3/drivers/123_link"
|
||||
_ "github.com/alist-org/alist/v3/drivers/123_share"
|
||||
@ -17,6 +18,7 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/baidu_netdisk"
|
||||
_ "github.com/alist-org/alist/v3/drivers/baidu_photo"
|
||||
_ "github.com/alist-org/alist/v3/drivers/baidu_share"
|
||||
_ "github.com/alist-org/alist/v3/drivers/chaoxing"
|
||||
_ "github.com/alist-org/alist/v3/drivers/cloudreve"
|
||||
_ "github.com/alist-org/alist/v3/drivers/crypt"
|
||||
_ "github.com/alist-org/alist/v3/drivers/dropbox"
|
||||
@ -45,6 +47,7 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/url_tree"
|
||||
_ "github.com/alist-org/alist/v3/drivers/uss"
|
||||
_ "github.com/alist-org/alist/v3/drivers/virtual"
|
||||
_ "github.com/alist-org/alist/v3/drivers/vtencent"
|
||||
_ "github.com/alist-org/alist/v3/drivers/webdav"
|
||||
_ "github.com/alist-org/alist/v3/drivers/weiyun"
|
||||
_ "github.com/alist-org/alist/v3/drivers/wopan"
|
||||
|
@ -278,7 +278,7 @@ func (d *BaiduNetdisk) Put(ctx context.Context, dstDir model.Obj, stream model.F
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
up(int(threadG.Success()) * 100 / len(precreateResp.BlockList))
|
||||
up(float64(threadG.Success()) * 100 / float64(len(precreateResp.BlockList)))
|
||||
precreateResp.BlockList[i] = -1
|
||||
return nil
|
||||
})
|
||||
|
@ -329,7 +329,7 @@ func (d *BaiduPhoto) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
up(int(threadG.Success()) * 100 / len(precreateResp.BlockList))
|
||||
up(float64(threadG.Success()) * 100 / float64(len(precreateResp.BlockList)))
|
||||
precreateResp.BlockList[i] = -1
|
||||
return nil
|
||||
})
|
||||
|
297
drivers/chaoxing/driver.go
Normal file
297
drivers/chaoxing/driver.go
Normal file
@ -0,0 +1,297 @@
|
||||
package chaoxing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/cron"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"google.golang.org/appengine/log"
|
||||
)
|
||||
|
||||
type ChaoXing struct {
|
||||
model.Storage
|
||||
Addition
|
||||
cron *cron.Cron
|
||||
config driver.Config
|
||||
conf Conf
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Config() driver.Config {
|
||||
return d.config
|
||||
}
|
||||
|
||||
func (d *ChaoXing) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *ChaoXing) refreshCookie() error {
|
||||
cookie, err := d.Login()
|
||||
if err != nil {
|
||||
d.Status = err.Error()
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
d.Addition.Cookie = cookie
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Init(ctx context.Context) error {
|
||||
err := d.refreshCookie()
|
||||
if err != nil {
|
||||
log.Errorf(ctx, err.Error())
|
||||
}
|
||||
d.cron = cron.NewCron(time.Hour * 12)
|
||||
d.cron.Do(func() {
|
||||
err = d.refreshCookie()
|
||||
if err != nil {
|
||||
log.Errorf(ctx, err.Error())
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Drop(ctx context.Context) error {
|
||||
d.cron.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
files, err := d.GetFiles(dir.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
||||
return fileToObj(src), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
var resp DownResp
|
||||
ua := d.conf.ua
|
||||
fileId := strings.Split(file.GetID(), "$")[1]
|
||||
_, err := d.requestDownload("/screen/note_note/files/status/"+fileId, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetHeader("User-Agent", ua)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u := resp.Download
|
||||
return &model.Link{
|
||||
URL: u,
|
||||
Header: http.Header{
|
||||
"Cookie": []string{d.Cookie},
|
||||
"Referer": []string{d.conf.referer},
|
||||
"User-Agent": []string{ua},
|
||||
},
|
||||
Concurrency: 2,
|
||||
PartSize: 10 * utils.MB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"name": dirName,
|
||||
"pid": parentDir.GetID(),
|
||||
}
|
||||
var resp ListFileResp
|
||||
_, err := d.request("/pc/resource/addResourceFolder", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Result != 1 {
|
||||
msg := fmt.Sprintf("error:%s", resp.Msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"folderIds": srcObj.GetID(),
|
||||
"targetId": dstDir.GetID(),
|
||||
}
|
||||
if !srcObj.IsDir() {
|
||||
query = map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"recIds": strings.Split(srcObj.GetID(), "$")[0],
|
||||
"targetId": dstDir.GetID(),
|
||||
}
|
||||
}
|
||||
var resp ListFileResp
|
||||
_, err := d.request("/pc/resource/moveResource", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !resp.Status {
|
||||
msg := fmt.Sprintf("error:%s", resp.Msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"folderId": srcObj.GetID(),
|
||||
"name": newName,
|
||||
}
|
||||
path := "/pc/resource/updateResourceFolderName"
|
||||
if !srcObj.IsDir() {
|
||||
// path = "/pc/resource/updateResourceFileName"
|
||||
// query = map[string]string{
|
||||
// "bbsid": d.Addition.Bbsid,
|
||||
// "recIds": strings.Split(srcObj.GetID(), "$")[0],
|
||||
// "name": newName,
|
||||
// }
|
||||
return errors.New("此网盘不支持修改文件名")
|
||||
}
|
||||
var resp ListFileResp
|
||||
_, err := d.request(path, http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Result != 1 {
|
||||
msg := fmt.Sprintf("error:%s", resp.Msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO copy obj, optional
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Remove(ctx context.Context, obj model.Obj) error {
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"folderIds": obj.GetID(),
|
||||
}
|
||||
path := "/pc/resource/deleteResourceFolder"
|
||||
var resp ListFileResp
|
||||
if !obj.IsDir() {
|
||||
path = "/pc/resource/deleteResourceFile"
|
||||
query = map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"recIds": strings.Split(obj.GetID(), "$")[0],
|
||||
}
|
||||
}
|
||||
_, err := d.request(path, http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Result != 1 {
|
||||
msg := fmt.Sprintf("error:%s", resp.Msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
var resp UploadDataRsp
|
||||
_, err := d.request("https://noteyd.chaoxing.com/pc/files/getUploadConfig", http.MethodGet, func(req *resty.Request) {
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Result != 1 {
|
||||
return errors.New("get upload data error")
|
||||
}
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
filePart, err := writer.CreateFormFile("file", stream.GetName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(filePart, stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writer.WriteField("_token", resp.Msg.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writer.WriteField("puid", fmt.Sprintf("%d", resp.Msg.Puid))
|
||||
if err != nil {
|
||||
fmt.Println("Error writing param2 to request body:", err)
|
||||
return err
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("POST", "https://pan-yz.chaoxing.com/upload", body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
req.Header.Set("Content-Length", fmt.Sprintf("%d", body.Len()))
|
||||
resps, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resps.Body.Close()
|
||||
bodys, err := io.ReadAll(resps.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var fileRsp UploadFileDataRsp
|
||||
err = json.Unmarshal(bodys, &fileRsp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fileRsp.Msg != "success" {
|
||||
return errors.New(fileRsp.Msg)
|
||||
}
|
||||
uploadDoneParam := UploadDoneParam{Key: fileRsp.ObjectID, Cataid: "100000019", Param: fileRsp.Data}
|
||||
params, err := json.Marshal(uploadDoneParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"pid": dstDir.GetID(),
|
||||
"type": "yunpan",
|
||||
"params": url.QueryEscape("[" + string(params) + "]"),
|
||||
}
|
||||
var respd ListFileResp
|
||||
_, err = d.request("/pc/resource/addResource", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &respd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if respd.Result != 1 {
|
||||
msg := fmt.Sprintf("error:%v", resp.Msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*ChaoXing)(nil)
|
47
drivers/chaoxing/meta.go
Normal file
47
drivers/chaoxing/meta.go
Normal file
@ -0,0 +1,47 @@
|
||||
package chaoxing
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
// 此程序挂载的是超星小组网盘,需要代理才能使用;
|
||||
// 登录超星后进入个人空间,进入小组,新建小组,点击进去。
|
||||
// url中就有bbsid的参数,系统限制单文件大小2G,没有总容量限制
|
||||
type Addition struct {
|
||||
// 超星用户名及密码
|
||||
UserName string `json:"user_name" required:"true"`
|
||||
Password string `json:"password" required:"true"`
|
||||
// 从自己新建的小组url里获取
|
||||
Bbsid string `json:"bbsid" required:"true"`
|
||||
driver.RootID
|
||||
// 可不填,程序会自动登录获取
|
||||
Cookie string `json:"cookie"`
|
||||
}
|
||||
|
||||
type Conf struct {
|
||||
ua string
|
||||
referer string
|
||||
api string
|
||||
DowloadApi string
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &ChaoXing{
|
||||
config: driver.Config{
|
||||
Name: "ChaoXingGroupDrive",
|
||||
OnlyProxy: true,
|
||||
OnlyLocal: false,
|
||||
DefaultRoot: "-1",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
conf: Conf{
|
||||
ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch",
|
||||
referer: "https://chaoxing.com/",
|
||||
api: "https://groupweb.chaoxing.com",
|
||||
DowloadApi: "https://noteyd.chaoxing.com",
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
263
drivers/chaoxing/types.go
Normal file
263
drivers/chaoxing/types.go
Normal file
@ -0,0 +1,263 @@
|
||||
package chaoxing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type Resp struct {
|
||||
Result int `json:"result"`
|
||||
}
|
||||
|
||||
type UserAuth struct {
|
||||
GroupAuth struct {
|
||||
AddData int `json:"addData"`
|
||||
AddDataFolder int `json:"addDataFolder"`
|
||||
AddLebel int `json:"addLebel"`
|
||||
AddManager int `json:"addManager"`
|
||||
AddMem int `json:"addMem"`
|
||||
AddTopicFolder int `json:"addTopicFolder"`
|
||||
AnonymousAddReply int `json:"anonymousAddReply"`
|
||||
AnonymousAddTopic int `json:"anonymousAddTopic"`
|
||||
BatchOperation int `json:"batchOperation"`
|
||||
DelData int `json:"delData"`
|
||||
DelDataFolder int `json:"delDataFolder"`
|
||||
DelMem int `json:"delMem"`
|
||||
DelTopicFolder int `json:"delTopicFolder"`
|
||||
Dismiss int `json:"dismiss"`
|
||||
ExamEnc string `json:"examEnc"`
|
||||
GroupChat int `json:"groupChat"`
|
||||
IsShowCircleChatButton int `json:"isShowCircleChatButton"`
|
||||
IsShowCircleCloudButton int `json:"isShowCircleCloudButton"`
|
||||
IsShowCompanyButton int `json:"isShowCompanyButton"`
|
||||
Join int `json:"join"`
|
||||
MemberShowRankSet int `json:"memberShowRankSet"`
|
||||
ModifyDataFolder int `json:"modifyDataFolder"`
|
||||
ModifyExpose int `json:"modifyExpose"`
|
||||
ModifyName int `json:"modifyName"`
|
||||
ModifyShowPic int `json:"modifyShowPic"`
|
||||
ModifyTopicFolder int `json:"modifyTopicFolder"`
|
||||
ModifyVisibleState int `json:"modifyVisibleState"`
|
||||
OnlyMgrScoreSet int `json:"onlyMgrScoreSet"`
|
||||
Quit int `json:"quit"`
|
||||
SendNotice int `json:"sendNotice"`
|
||||
ShowActivityManage int `json:"showActivityManage"`
|
||||
ShowActivitySet int `json:"showActivitySet"`
|
||||
ShowAttentionSet int `json:"showAttentionSet"`
|
||||
ShowAutoClearStatus int `json:"showAutoClearStatus"`
|
||||
ShowBarcode int `json:"showBarcode"`
|
||||
ShowChatRoomSet int `json:"showChatRoomSet"`
|
||||
ShowCircleActivitySet int `json:"showCircleActivitySet"`
|
||||
ShowCircleSet int `json:"showCircleSet"`
|
||||
ShowCmem int `json:"showCmem"`
|
||||
ShowDataFolder int `json:"showDataFolder"`
|
||||
ShowDelReason int `json:"showDelReason"`
|
||||
ShowForward int `json:"showForward"`
|
||||
ShowGroupChat int `json:"showGroupChat"`
|
||||
ShowGroupChatSet int `json:"showGroupChatSet"`
|
||||
ShowGroupSquareSet int `json:"showGroupSquareSet"`
|
||||
ShowLockAddSet int `json:"showLockAddSet"`
|
||||
ShowManager int `json:"showManager"`
|
||||
ShowManagerIdentitySet int `json:"showManagerIdentitySet"`
|
||||
ShowNeedDelReasonSet int `json:"showNeedDelReasonSet"`
|
||||
ShowNotice int `json:"showNotice"`
|
||||
ShowOnlyManagerReplySet int `json:"showOnlyManagerReplySet"`
|
||||
ShowRank int `json:"showRank"`
|
||||
ShowRank2 int `json:"showRank2"`
|
||||
ShowRecycleBin int `json:"showRecycleBin"`
|
||||
ShowReplyByClass int `json:"showReplyByClass"`
|
||||
ShowReplyNeedCheck int `json:"showReplyNeedCheck"`
|
||||
ShowSignbanSet int `json:"showSignbanSet"`
|
||||
ShowSpeechSet int `json:"showSpeechSet"`
|
||||
ShowTopicCheck int `json:"showTopicCheck"`
|
||||
ShowTopicNeedCheck int `json:"showTopicNeedCheck"`
|
||||
ShowTransferSet int `json:"showTransferSet"`
|
||||
} `json:"groupAuth"`
|
||||
OperationAuth struct {
|
||||
Add int `json:"add"`
|
||||
AddTopicToFolder int `json:"addTopicToFolder"`
|
||||
ChoiceSet int `json:"choiceSet"`
|
||||
DelTopicFromFolder int `json:"delTopicFromFolder"`
|
||||
Delete int `json:"delete"`
|
||||
Reply int `json:"reply"`
|
||||
ScoreSet int `json:"scoreSet"`
|
||||
TopSet int `json:"topSet"`
|
||||
Update int `json:"update"`
|
||||
} `json:"operationAuth"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Cataid int `json:"cataid"`
|
||||
Cfid int `json:"cfid"`
|
||||
Content struct {
|
||||
Cfid int `json:"cfid"`
|
||||
Pid int `json:"pid"`
|
||||
FolderName string `json:"folderName"`
|
||||
ShareType int `json:"shareType"`
|
||||
Preview string `json:"preview"`
|
||||
Filetype string `json:"filetype"`
|
||||
PreviewURL string `json:"previewUrl"`
|
||||
IsImg bool `json:"isImg"`
|
||||
ParentPath string `json:"parentPath"`
|
||||
Icon string `json:"icon"`
|
||||
Suffix string `json:"suffix"`
|
||||
Duration int `json:"duration"`
|
||||
Pantype string `json:"pantype"`
|
||||
Puid int `json:"puid"`
|
||||
Filepath string `json:"filepath"`
|
||||
Crc string `json:"crc"`
|
||||
Isfile bool `json:"isfile"`
|
||||
Residstr string `json:"residstr"`
|
||||
ObjectID string `json:"objectId"`
|
||||
Extinfo string `json:"extinfo"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Creator int `json:"creator"`
|
||||
ResTypeValue int `json:"resTypeValue"`
|
||||
UploadDateFormat string `json:"uploadDateFormat"`
|
||||
DisableOpt bool `json:"disableOpt"`
|
||||
DownPath string `json:"downPath"`
|
||||
Sort int `json:"sort"`
|
||||
Topsort int `json:"topsort"`
|
||||
Restype string `json:"restype"`
|
||||
Size int `json:"size"`
|
||||
UploadDate string `json:"uploadDate"`
|
||||
FileSize string `json:"fileSize"`
|
||||
Name string `json:"name"`
|
||||
FileID string `json:"fileId"`
|
||||
} `json:"content"`
|
||||
CreatorID int `json:"creatorId"`
|
||||
DesID string `json:"des_id"`
|
||||
ID int `json:"id"`
|
||||
Inserttime int64 `json:"inserttime"`
|
||||
Key string `json:"key"`
|
||||
Norder int `json:"norder"`
|
||||
OwnerID int `json:"ownerId"`
|
||||
OwnerType int `json:"ownerType"`
|
||||
Path string `json:"path"`
|
||||
Rid int `json:"rid"`
|
||||
Status int `json:"status"`
|
||||
Topsign int `json:"topsign"`
|
||||
}
|
||||
|
||||
type ListFileResp struct {
|
||||
Msg string `json:"msg"`
|
||||
Result int `json:"result"`
|
||||
Status bool `json:"status"`
|
||||
UserAuth UserAuth `json:"userAuth"`
|
||||
List []File `json:"list"`
|
||||
}
|
||||
|
||||
type DownResp struct {
|
||||
Msg string `json:"msg"`
|
||||
Duration int `json:"duration"`
|
||||
Download string `json:"download"`
|
||||
FileStatus string `json:"fileStatus"`
|
||||
URL string `json:"url"`
|
||||
Status bool `json:"status"`
|
||||
}
|
||||
|
||||
type UploadDataRsp struct {
|
||||
Result int `json:"result"`
|
||||
Msg struct {
|
||||
Puid int `json:"puid"`
|
||||
Token string `json:"token"`
|
||||
} `json:"msg"`
|
||||
}
|
||||
|
||||
type UploadFileDataRsp struct {
|
||||
Result bool `json:"result"`
|
||||
Msg string `json:"msg"`
|
||||
Crc string `json:"crc"`
|
||||
ObjectID string `json:"objectId"`
|
||||
Resid int64 `json:"resid"`
|
||||
Puid int `json:"puid"`
|
||||
Data struct {
|
||||
DisableOpt bool `json:"disableOpt"`
|
||||
Resid int64 `json:"resid"`
|
||||
Crc string `json:"crc"`
|
||||
Puid int `json:"puid"`
|
||||
Isfile bool `json:"isfile"`
|
||||
Pantype string `json:"pantype"`
|
||||
Size int `json:"size"`
|
||||
Name string `json:"name"`
|
||||
ObjectID string `json:"objectId"`
|
||||
Restype string `json:"restype"`
|
||||
UploadDate time.Time `json:"uploadDate"`
|
||||
ModifyDate time.Time `json:"modifyDate"`
|
||||
UploadDateFormat string `json:"uploadDateFormat"`
|
||||
Residstr string `json:"residstr"`
|
||||
Suffix string `json:"suffix"`
|
||||
Preview string `json:"preview"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Creator int `json:"creator"`
|
||||
Duration int `json:"duration"`
|
||||
IsImg bool `json:"isImg"`
|
||||
PreviewURL string `json:"previewUrl"`
|
||||
Filetype string `json:"filetype"`
|
||||
Filepath string `json:"filepath"`
|
||||
Sort int `json:"sort"`
|
||||
Topsort int `json:"topsort"`
|
||||
ResTypeValue int `json:"resTypeValue"`
|
||||
Extinfo string `json:"extinfo"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
|
||||
type UploadDoneParam struct {
|
||||
Cataid string `json:"cataid"`
|
||||
Key string `json:"key"`
|
||||
Param struct {
|
||||
DisableOpt bool `json:"disableOpt"`
|
||||
Resid int64 `json:"resid"`
|
||||
Crc string `json:"crc"`
|
||||
Puid int `json:"puid"`
|
||||
Isfile bool `json:"isfile"`
|
||||
Pantype string `json:"pantype"`
|
||||
Size int `json:"size"`
|
||||
Name string `json:"name"`
|
||||
ObjectID string `json:"objectId"`
|
||||
Restype string `json:"restype"`
|
||||
UploadDate time.Time `json:"uploadDate"`
|
||||
ModifyDate time.Time `json:"modifyDate"`
|
||||
UploadDateFormat string `json:"uploadDateFormat"`
|
||||
Residstr string `json:"residstr"`
|
||||
Suffix string `json:"suffix"`
|
||||
Preview string `json:"preview"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Creator int `json:"creator"`
|
||||
Duration int `json:"duration"`
|
||||
IsImg bool `json:"isImg"`
|
||||
PreviewURL string `json:"previewUrl"`
|
||||
Filetype string `json:"filetype"`
|
||||
Filepath string `json:"filepath"`
|
||||
Sort int `json:"sort"`
|
||||
Topsort int `json:"topsort"`
|
||||
ResTypeValue int `json:"resTypeValue"`
|
||||
Extinfo string `json:"extinfo"`
|
||||
} `json:"param"`
|
||||
}
|
||||
|
||||
func fileToObj(f File) *model.Object {
|
||||
if len(f.Content.FolderName) > 0 {
|
||||
return &model.Object{
|
||||
ID: fmt.Sprintf("%d", f.ID),
|
||||
Name: f.Content.FolderName,
|
||||
Size: 0,
|
||||
Modified: time.UnixMilli(f.Inserttime),
|
||||
IsFolder: true,
|
||||
}
|
||||
}
|
||||
paserTime, err := time.Parse("2006-01-02 15:04", f.Content.UploadDate)
|
||||
if err != nil {
|
||||
paserTime = time.Now()
|
||||
}
|
||||
return &model.Object{
|
||||
ID: fmt.Sprintf("%d$%s", f.ID, f.Content.FileID),
|
||||
Name: f.Content.Name,
|
||||
Size: int64(f.Content.Size),
|
||||
Modified: paserTime,
|
||||
IsFolder: false,
|
||||
}
|
||||
}
|
179
drivers/chaoxing/util.go
Normal file
179
drivers/chaoxing/util.go
Normal file
@ -0,0 +1,179 @@
|
||||
package chaoxing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
func (d *ChaoXing) requestDownload(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
u := d.conf.DowloadApi + pathname
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"Cookie": d.Cookie,
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Referer": d.conf.referer,
|
||||
})
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
var e Resp
|
||||
req.SetError(&e)
|
||||
res, err := req.Execute(method, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
u := d.conf.api + pathname
|
||||
if strings.Contains(pathname, "getUploadConfig") {
|
||||
u = pathname
|
||||
}
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"Cookie": d.Cookie,
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Referer": d.conf.referer,
|
||||
})
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
var e Resp
|
||||
req.SetError(&e)
|
||||
res, err := req.Execute(method, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) GetFiles(parent string) ([]File, error) {
|
||||
files := make([]File, 0)
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"folderId": parent,
|
||||
"recType": "1",
|
||||
}
|
||||
var resp ListFileResp
|
||||
_, err := d.request("/pc/resource/getResourceList", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Result != 1 {
|
||||
msg:=fmt.Sprintf("error code is:%d", resp.Result)
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
if len(resp.List) > 0 {
|
||||
files = append(files, resp.List...)
|
||||
}
|
||||
querys := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"folderId": parent,
|
||||
"recType": "2",
|
||||
}
|
||||
var resps ListFileResp
|
||||
_, err = d.request("/pc/resource/getResourceList", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(querys)
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resps.List) > 0 {
|
||||
files = append(files, resps.List...)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func EncryptByAES(message, key string) (string, error) {
|
||||
aesKey := []byte(key)
|
||||
plainText := []byte(message)
|
||||
block, err := aes.NewCipher(aesKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
iv := aesKey[:aes.BlockSize]
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
padding := aes.BlockSize - len(plainText)%aes.BlockSize
|
||||
paddedText := append(plainText, byte(padding))
|
||||
for i := 0; i < padding-1; i++ {
|
||||
paddedText = append(paddedText, byte(padding))
|
||||
}
|
||||
ciphertext := make([]byte, len(paddedText))
|
||||
mode.CryptBlocks(ciphertext, paddedText)
|
||||
encrypted := base64.StdEncoding.EncodeToString(ciphertext)
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
func CookiesToString(cookies []*http.Cookie) string {
|
||||
var cookieStr string
|
||||
for _, cookie := range cookies {
|
||||
cookieStr += cookie.Name + "=" + cookie.Value + "; "
|
||||
}
|
||||
if len(cookieStr) > 2 {
|
||||
cookieStr = cookieStr[:len(cookieStr)-2]
|
||||
}
|
||||
return cookieStr
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Login() (string, error) {
|
||||
transferKey := "u2oh6Vu^HWe4_AES"
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
uname, err := EncryptByAES(d.Addition.UserName, transferKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
password, err := EncryptByAES(d.Addition.Password, transferKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = writer.WriteField("uname", uname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = writer.WriteField("password", password)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = writer.WriteField("t", "true")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Create the request
|
||||
req, err := http.NewRequest("POST", "https://passport2.chaoxing.com/fanyalogin", body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
req.Header.Set("Content-Length", fmt.Sprintf("%d", body.Len()))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return CookiesToString(resp.Cookies()), nil
|
||||
|
||||
}
|
@ -53,6 +53,14 @@ func (d *Cloudreve) List(ctx context.Context, dir model.Obj, args model.ListArgs
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if src.Type == "dir" && d.EnableThumbAndFolderSize {
|
||||
var dprop DirectoryProp
|
||||
err = d.request(http.MethodGet, "/object/property/"+src.Id+"?is_folder=true", nil, &dprop)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
src.Size = dprop.Size
|
||||
}
|
||||
return objectToObj(src, thumb), nil
|
||||
})
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ type Addition struct {
|
||||
Password string `json:"password"`
|
||||
Cookie string `json:"cookie"`
|
||||
CustomUA string `json:"custom_ua"`
|
||||
EnableThumbAndFolderSize bool `json:"enable_thumb_and_folder_size"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -44,6 +44,10 @@ type Object struct {
|
||||
SourceEnabled bool `json:"source_enabled"`
|
||||
}
|
||||
|
||||
type DirectoryProp struct {
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
func objectToObj(f Object, t model.Thumbnail) *model.ObjThumb {
|
||||
return &model.ObjThumb{
|
||||
Object: model.Object{
|
||||
|
@ -151,6 +151,9 @@ func convertSrc(obj model.Obj) map[string]interface{} {
|
||||
}
|
||||
|
||||
func (d *Cloudreve) GetThumb(file Object) (model.Thumbnail, error) {
|
||||
if !d.Addition.EnableThumbAndFolderSize {
|
||||
return model.Thumbnail{}, nil
|
||||
}
|
||||
ua := d.CustomUA
|
||||
if ua == "" {
|
||||
ua = base.UserAgent
|
||||
|
@ -124,6 +124,9 @@ func (d *Crypt) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([
|
||||
//filter illegal files
|
||||
continue
|
||||
}
|
||||
if !d.ShowHidden && strings.HasPrefix(name, ".") {
|
||||
continue
|
||||
}
|
||||
objRes := model.Object{
|
||||
Name: name,
|
||||
Size: 0,
|
||||
@ -145,6 +148,9 @@ func (d *Crypt) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([
|
||||
//filter illegal files
|
||||
continue
|
||||
}
|
||||
if !d.ShowHidden && strings.HasPrefix(name, ".") {
|
||||
continue
|
||||
}
|
||||
objRes := model.Object{
|
||||
Name: name,
|
||||
Size: size,
|
||||
|
@ -21,6 +21,8 @@ type Addition struct {
|
||||
FileNameEncoding string `json:"filename_encoding" type:"select" required:"true" options:"base64,base32,base32768" default:"base64" help:"for advanced user only!"`
|
||||
|
||||
Thumbnail bool `json:"thumbnail" required:"true" default:"false" help:"enable thumbnail which pre-generated under .thumbnails folder"`
|
||||
|
||||
ShowHidden bool `json:"show_hidden" default:"true" required:"false" help:"show hidden directories and files"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -203,7 +203,7 @@ func (d *Dropbox) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
|
||||
_ = res.Body.Close()
|
||||
|
||||
if count > 0 {
|
||||
up((i + 1) * 100 / count)
|
||||
up(float64(i+1) * 100 / float64(count))
|
||||
}
|
||||
|
||||
offset += byteSize
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
@ -44,7 +43,7 @@ func (d *GoogleDrive) refreshToken() error {
|
||||
gdsaFileThis := d.RefreshToken
|
||||
if gdsaFile.IsDir() {
|
||||
if len(d.ServiceAccountFileList) <= 0 {
|
||||
gdsaReadDir, gdsaDirErr := ioutil.ReadDir(d.RefreshToken)
|
||||
gdsaReadDir, gdsaDirErr := os.ReadDir(d.RefreshToken)
|
||||
if gdsaDirErr != nil {
|
||||
log.Error("read dir fail")
|
||||
return gdsaDirErr
|
||||
@ -76,7 +75,7 @@ func (d *GoogleDrive) refreshToken() error {
|
||||
}
|
||||
}
|
||||
|
||||
gdsaFileThisContent, err := ioutil.ReadFile(gdsaFileThis)
|
||||
gdsaFileThisContent, err := os.ReadFile(gdsaFileThis)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -4,11 +4,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||
"github.com/rclone/rclone/lib/readers"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||
"github.com/rclone/rclone/lib/readers"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
@ -169,7 +170,7 @@ func (d *Mega) Put(ctx context.Context, dstDir model.Obj, stream model.FileStrea
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
up(id * 100 / u.Chunks())
|
||||
up(float64(id) * 100 / float64(u.Chunks()))
|
||||
}
|
||||
|
||||
_, err = u.Finish()
|
||||
|
@ -119,10 +119,13 @@ func (d *MoPan) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
||||
}
|
||||
|
||||
data.DownloadUrl = strings.Replace(strings.ReplaceAll(data.DownloadUrl, "&", "&"), "http://", "https://", 1)
|
||||
res, err := base.NoRedirectClient.R().SetContext(ctx).Head(data.DownloadUrl)
|
||||
res, err := base.NoRedirectClient.R().SetDoNotParseResponse(true).SetContext(ctx).Get(data.DownloadUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = res.RawBody().Close()
|
||||
}()
|
||||
if res.StatusCode() == 302 {
|
||||
data.DownloadUrl = res.Header().Get("location")
|
||||
}
|
||||
@ -308,7 +311,7 @@ func (d *MoPan) Put(ctx context.Context, dstDir model.Obj, stream model.FileStre
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("upload err,code=%d", resp.StatusCode)
|
||||
}
|
||||
up(100 * int(threadG.Success()) / len(parts))
|
||||
up(100 * float64(threadG.Success()) / float64(len(parts)))
|
||||
initUpdload.PartInfos[i] = ""
|
||||
return nil
|
||||
})
|
||||
|
@ -203,7 +203,7 @@ func (d *Onedrive) upBig(ctx context.Context, dstDir model.Obj, stream model.Fil
|
||||
return errors.New(string(data))
|
||||
}
|
||||
res.Body.Close()
|
||||
up(int(finish * 100 / stream.GetSize()))
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ func (d *OnedriveAPP) upBig(ctx context.Context, dstDir model.Obj, stream model.
|
||||
return errors.New(string(data))
|
||||
}
|
||||
res.Body.Close()
|
||||
up(int(finish * 100 / stream.GetSize()))
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ func (d *QuarkOrUC) Put(ctx context.Context, dstDir model.Obj, stream model.File
|
||||
}
|
||||
md5s = append(md5s, m)
|
||||
partNumber++
|
||||
up(int(100 * (total - left) / total))
|
||||
up(100 * float64(total-left) / float64(total))
|
||||
}
|
||||
err = d.upCommit(pre, md5s)
|
||||
if err != nil {
|
||||
|
@ -4,13 +4,14 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
"io"
|
||||
"net/url"
|
||||
stdpath "path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
@ -104,7 +105,7 @@ func (d *S3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) e
|
||||
},
|
||||
Reader: io.NopCloser(bytes.NewReader([]byte{})),
|
||||
Mimetype: "application/octet-stream",
|
||||
}, func(int) {})
|
||||
}, func(float64) {})
|
||||
}
|
||||
|
||||
func (d *S3) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
|
@ -189,7 +189,7 @@ func (d *Teambition) chunkUpload(ctx context.Context, file model.FileStreamer, t
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
up(i * 100 / newChunk.Chunks)
|
||||
up(float64(i) * 100 / float64(newChunk.Chunks))
|
||||
}
|
||||
_, err = base.RestyClient.R().SetHeader("Authorization", token).Post(
|
||||
fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s",
|
||||
|
@ -213,7 +213,7 @@ func (d *Terabox) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
|
||||
}
|
||||
log.Debugln(res.String())
|
||||
if len(precreateResp.BlockList) > 0 {
|
||||
up(i * 100 / len(precreateResp.BlockList))
|
||||
up(float64(i) * 100 / float64(len(precreateResp.BlockList)))
|
||||
}
|
||||
}
|
||||
_, err = d.create(rawPath, stream.GetSize(), 0, precreateResp.Uploadid, block_list_str)
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -128,7 +127,7 @@ func (d *Trainbit) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
||||
stream,
|
||||
func(byteNum int) {
|
||||
total += int64(byteNum)
|
||||
up(int(math.Round(float64(total) / float64(stream.GetSize()) * 100)))
|
||||
up(float64(total) / float64(stream.GetSize()) * 100)
|
||||
},
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPost, endpoint.String(), progressReader)
|
||||
|
208
drivers/vtencent/drive.go
Normal file
208
drivers/vtencent/drive.go
Normal file
@ -0,0 +1,208 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/cron"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type Vtencent struct {
|
||||
model.Storage
|
||||
Addition
|
||||
cron *cron.Cron
|
||||
config driver.Config
|
||||
conf Conf
|
||||
}
|
||||
|
||||
func (d *Vtencent) Config() driver.Config {
|
||||
return d.config
|
||||
}
|
||||
|
||||
func (d *Vtencent) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *Vtencent) Init(ctx context.Context) error {
|
||||
tfUid, err := d.LoadUser()
|
||||
if err != nil {
|
||||
d.Status = err.Error()
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
d.Addition.TfUid = tfUid
|
||||
op.MustSaveDriverStorage(d)
|
||||
d.cron = cron.NewCron(time.Hour * 12)
|
||||
d.cron.Do(func() {
|
||||
_, err := d.LoadUser()
|
||||
if err != nil {
|
||||
d.Status = err.Error()
|
||||
op.MustSaveDriverStorage(d)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) Drop(ctx context.Context) error {
|
||||
d.cron.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
files, err := d.GetFiles(dir.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
||||
return fileToObj(src), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Vtencent) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
form := fmt.Sprintf(`{"MaterialIds":["%s"]}`, file.GetID())
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resps RspDown
|
||||
api := "https://api.vs.tencent.com/SaaS/Material/DescribeMaterialDownloadUrl"
|
||||
rsp, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(rsp, &resps); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resps.Data.DownloadURLInfoSet) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
u := resps.Data.DownloadURLInfoSet[0].DownloadURL
|
||||
link := &model.Link{
|
||||
URL: u,
|
||||
Header: http.Header{
|
||||
"Referer": []string{d.conf.referer},
|
||||
"User-Agent": []string{d.conf.ua},
|
||||
},
|
||||
Concurrency: 2,
|
||||
PartSize: 10 * utils.MB,
|
||||
}
|
||||
if file.GetSize() == 0 {
|
||||
link.Concurrency = 0
|
||||
link.PartSize = 0
|
||||
}
|
||||
return link, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
classId, err := strconv.Atoi(parentDir.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.request("https://api.vs.tencent.com/PaaS/Material/CreateClass", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"Owner": base.Json{
|
||||
"Type": "PERSON",
|
||||
"Id": d.TfUid,
|
||||
},
|
||||
"ParentClassId": classId,
|
||||
"Name": dirName,
|
||||
"VerifySign": ""})
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
srcType := "MATERIAL"
|
||||
if srcObj.IsDir() {
|
||||
srcType = "CLASS"
|
||||
}
|
||||
form := fmt.Sprintf(`{"SourceInfos":[
|
||||
{"Owner":{"Id":"%s","Type":"PERSON"},
|
||||
"Resource":{"Type":"%s","Id":"%s"}}
|
||||
],
|
||||
"Destination":{"Owner":{"Id":"%s","Type":"PERSON"},
|
||||
"Resource":{"Type":"CLASS","Id":"%s"}}
|
||||
}`, d.TfUid, srcType, srcObj.GetID(), d.TfUid, dstDir.GetID())
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := d.request("https://api.vs.tencent.com/PaaS/Material/MoveResource", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/ModifyMaterial"
|
||||
form := fmt.Sprintf(`{
|
||||
"Owner":{"Type":"PERSON","Id":"%s"},
|
||||
"MaterialId":"%s","Name":"%s"}`, d.TfUid, srcObj.GetID(), newName)
|
||||
if srcObj.IsDir() {
|
||||
classId, err := strconv.Atoi(srcObj.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
api = "https://api.vs.tencent.com/PaaS/Material/ModifyClass"
|
||||
form = fmt.Sprintf(`{"Owner":{"Type":"PERSON","Id":"%s"},
|
||||
"ClassId":%d,"Name":"%s"}`, d.TfUid, classId, newName)
|
||||
}
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO copy obj, optional
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Vtencent) Remove(ctx context.Context, obj model.Obj) error {
|
||||
srcType := "MATERIAL"
|
||||
if obj.IsDir() {
|
||||
srcType = "CLASS"
|
||||
}
|
||||
form := fmt.Sprintf(`{
|
||||
"SourceInfos":[
|
||||
{"Owner":{"Type":"PERSON","Id":"%s"},
|
||||
"Resource":{"Type":"%s","Id":"%s"}}
|
||||
]
|
||||
}`, d.TfUid, srcType, obj.GetID())
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := d.request("https://api.vs.tencent.com/PaaS/Material/DeleteResource", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
err := d.FileUpload(ctx, dstDir, stream, up)
|
||||
return err
|
||||
}
|
||||
|
||||
//func (d *Vtencent) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
var _ driver.Driver = (*Vtencent)(nil)
|
39
drivers/vtencent/meta.go
Normal file
39
drivers/vtencent/meta.go
Normal file
@ -0,0 +1,39 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
driver.RootID
|
||||
Cookie string `json:"cookie" required:"true"`
|
||||
TfUid string `json:"tf_uid"`
|
||||
OrderBy string `json:"order_by" type:"select" options:"Name,Size,UpdateTime,CreatTime"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"Asc,Desc"`
|
||||
}
|
||||
|
||||
type Conf struct {
|
||||
ua string
|
||||
referer string
|
||||
origin string
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Vtencent{
|
||||
config: driver.Config{
|
||||
Name: "VTencent",
|
||||
OnlyProxy: true,
|
||||
OnlyLocal: false,
|
||||
DefaultRoot: "9",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
conf: Conf{
|
||||
ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch",
|
||||
referer: "https://app.v.tencent.com/",
|
||||
origin: "https://app.v.tencent.com",
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
33
drivers/vtencent/signature.go
Normal file
33
drivers/vtencent/signature.go
Normal file
@ -0,0 +1,33 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func QSignatureKey(timeKey string, signPath string, key string) string {
|
||||
signKey := hmac.New(sha1.New, []byte(key))
|
||||
signKey.Write([]byte(timeKey))
|
||||
signKeyBytes := signKey.Sum(nil)
|
||||
signKeyHex := hex.EncodeToString(signKeyBytes)
|
||||
sha := sha1.New()
|
||||
sha.Write([]byte(signPath))
|
||||
shaBytes := sha.Sum(nil)
|
||||
shaHex := hex.EncodeToString(shaBytes)
|
||||
|
||||
O := "sha1\n" + timeKey + "\n" + shaHex + "\n"
|
||||
dataSignKey := hmac.New(sha1.New, []byte(signKeyHex))
|
||||
dataSignKey.Write([]byte(O))
|
||||
dataSignKeyBytes := dataSignKey.Sum(nil)
|
||||
dataSignKeyHex := hex.EncodeToString(dataSignKeyBytes)
|
||||
return dataSignKeyHex
|
||||
}
|
||||
|
||||
func QTwoSignatureKey(timeKey string, key string) string {
|
||||
signKey := hmac.New(sha1.New, []byte(key))
|
||||
signKey.Write([]byte(timeKey))
|
||||
signKeyBytes := signKey.Sum(nil)
|
||||
signKeyHex := hex.EncodeToString(signKeyBytes)
|
||||
return signKeyHex
|
||||
}
|
252
drivers/vtencent/types.go
Normal file
252
drivers/vtencent/types.go
Normal file
@ -0,0 +1,252 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type RespErr struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
}
|
||||
|
||||
type Reqfiles struct {
|
||||
ScrollToken string `json:"ScrollToken"`
|
||||
Text string `json:"Text"`
|
||||
Offset int `json:"Offset"`
|
||||
Limit int `json:"Limit"`
|
||||
Sort struct {
|
||||
Field string `json:"Field"`
|
||||
Order string `json:"Order"`
|
||||
} `json:"Sort"`
|
||||
CreateTimeRanges []any `json:"CreateTimeRanges"`
|
||||
MaterialTypes []any `json:"MaterialTypes"`
|
||||
ReviewStatuses []any `json:"ReviewStatuses"`
|
||||
Tags []any `json:"Tags"`
|
||||
SearchScopes []struct {
|
||||
Owner struct {
|
||||
Type string `json:"Type"`
|
||||
ID string `json:"Id"`
|
||||
} `json:"Owner"`
|
||||
ClassID int `json:"ClassId"`
|
||||
SearchOneDepth bool `json:"SearchOneDepth"`
|
||||
} `json:"SearchScopes"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Type string `json:"Type"`
|
||||
ClassInfo struct {
|
||||
ClassID int `json:"ClassId"`
|
||||
Name string `json:"Name"`
|
||||
UpdateTime time.Time `json:"UpdateTime"`
|
||||
CreateTime time.Time `json:"CreateTime"`
|
||||
FileInboxID string `json:"FileInboxId"`
|
||||
Owner struct {
|
||||
Type string `json:"Type"`
|
||||
ID string `json:"Id"`
|
||||
} `json:"Owner"`
|
||||
ClassPath string `json:"ClassPath"`
|
||||
ParentClassID int `json:"ParentClassId"`
|
||||
AttachmentInfo struct {
|
||||
SubClassCount int `json:"SubClassCount"`
|
||||
MaterialCount int `json:"MaterialCount"`
|
||||
Size int64 `json:"Size"`
|
||||
} `json:"AttachmentInfo"`
|
||||
ClassPreviewURLSet []string `json:"ClassPreviewUrlSet"`
|
||||
} `json:"ClassInfo"`
|
||||
MaterialInfo struct {
|
||||
BasicInfo struct {
|
||||
MaterialID string `json:"MaterialId"`
|
||||
MaterialType string `json:"MaterialType"`
|
||||
Name string `json:"Name"`
|
||||
CreateTime time.Time `json:"CreateTime"`
|
||||
UpdateTime time.Time `json:"UpdateTime"`
|
||||
ClassPath string `json:"ClassPath"`
|
||||
ClassID int `json:"ClassId"`
|
||||
TagInfoSet []any `json:"TagInfoSet"`
|
||||
TagSet []any `json:"TagSet"`
|
||||
PreviewURL string `json:"PreviewUrl"`
|
||||
MediaURL string `json:"MediaUrl"`
|
||||
UnifiedMediaPreviewURL string `json:"UnifiedMediaPreviewUrl"`
|
||||
Owner struct {
|
||||
Type string `json:"Type"`
|
||||
ID string `json:"Id"`
|
||||
} `json:"Owner"`
|
||||
PermissionSet any `json:"PermissionSet"`
|
||||
PermissionInfoSet []any `json:"PermissionInfoSet"`
|
||||
TfUID string `json:"TfUid"`
|
||||
GroupID string `json:"GroupId"`
|
||||
VersionMaterialIDSet []any `json:"VersionMaterialIdSet"`
|
||||
FileType string `json:"FileType"`
|
||||
CmeMaterialPlayList []any `json:"CmeMaterialPlayList"`
|
||||
Status string `json:"Status"`
|
||||
DownloadSwitch string `json:"DownloadSwitch"`
|
||||
} `json:"BasicInfo"`
|
||||
MediaInfo struct {
|
||||
Width int `json:"Width"`
|
||||
Height int `json:"Height"`
|
||||
Size int `json:"Size"`
|
||||
Duration float64 `json:"Duration"`
|
||||
Fps int `json:"Fps"`
|
||||
BitRate int `json:"BitRate"`
|
||||
Codec string `json:"Codec"`
|
||||
MediaType string `json:"MediaType"`
|
||||
FavoriteStatus string `json:"FavoriteStatus"`
|
||||
} `json:"MediaInfo"`
|
||||
MaterialStatus struct {
|
||||
ContentReviewStatus string `json:"ContentReviewStatus"`
|
||||
EditorUsableStatus string `json:"EditorUsableStatus"`
|
||||
UnifiedPreviewStatus string `json:"UnifiedPreviewStatus"`
|
||||
EditPreviewImageSpiritStatus string `json:"EditPreviewImageSpiritStatus"`
|
||||
TranscodeStatus string `json:"TranscodeStatus"`
|
||||
AdaptiveStreamingStatus string `json:"AdaptiveStreamingStatus"`
|
||||
StreamConnectable string `json:"StreamConnectable"`
|
||||
AiAnalysisStatus string `json:"AiAnalysisStatus"`
|
||||
AiRecognitionStatus string `json:"AiRecognitionStatus"`
|
||||
} `json:"MaterialStatus"`
|
||||
ImageMaterial struct {
|
||||
Height int `json:"Height"`
|
||||
Width int `json:"Width"`
|
||||
Size int `json:"Size"`
|
||||
MaterialURL string `json:"MaterialUrl"`
|
||||
Resolution string `json:"Resolution"`
|
||||
VodFileID string `json:"VodFileId"`
|
||||
OriginalURL string `json:"OriginalUrl"`
|
||||
} `json:"ImageMaterial"`
|
||||
VideoMaterial struct {
|
||||
MetaData struct {
|
||||
Size int `json:"Size"`
|
||||
Container string `json:"Container"`
|
||||
Bitrate int `json:"Bitrate"`
|
||||
Height int `json:"Height"`
|
||||
Width int `json:"Width"`
|
||||
Duration float64 `json:"Duration"`
|
||||
Rotate int `json:"Rotate"`
|
||||
VideoStreamInfoSet []struct {
|
||||
Bitrate int `json:"Bitrate"`
|
||||
Height int `json:"Height"`
|
||||
Width int `json:"Width"`
|
||||
Codec string `json:"Codec"`
|
||||
Fps int `json:"Fps"`
|
||||
} `json:"VideoStreamInfoSet"`
|
||||
AudioStreamInfoSet []struct {
|
||||
Bitrate int `json:"Bitrate"`
|
||||
SamplingRate int `json:"SamplingRate"`
|
||||
Codec string `json:"Codec"`
|
||||
} `json:"AudioStreamInfoSet"`
|
||||
} `json:"MetaData"`
|
||||
ImageSpriteInfo any `json:"ImageSpriteInfo"`
|
||||
MaterialURL string `json:"MaterialUrl"`
|
||||
CoverURL string `json:"CoverUrl"`
|
||||
Resolution string `json:"Resolution"`
|
||||
VodFileID string `json:"VodFileId"`
|
||||
OriginalURL string `json:"OriginalUrl"`
|
||||
AudioWaveformURL string `json:"AudioWaveformUrl"`
|
||||
SubtitleURL string `json:"SubtitleUrl"`
|
||||
TranscodeInfoSet []any `json:"TranscodeInfoSet"`
|
||||
ImageSpriteInfoSet []any `json:"ImageSpriteInfoSet"`
|
||||
} `json:"VideoMaterial"`
|
||||
} `json:"MaterialInfo"`
|
||||
}
|
||||
|
||||
type RspFiles struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
TotalCount int `json:"TotalCount"`
|
||||
ResourceInfoSet []File `json:"ResourceInfoSet"`
|
||||
ScrollToken string `json:"ScrollToken"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
type RspDown struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
DownloadURLInfoSet []struct {
|
||||
MaterialID string `json:"MaterialId"`
|
||||
DownloadURL string `json:"DownloadUrl"`
|
||||
} `json:"DownloadUrlInfoSet"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
type RspCreatrMaterial struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
UploadContext string `json:"UploadContext"`
|
||||
VodUploadSign string `json:"VodUploadSign"`
|
||||
QuickUpload bool `json:"QuickUpload"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
type RspApplyUploadUGC struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Video struct {
|
||||
StorageSignature string `json:"storageSignature"`
|
||||
StoragePath string `json:"storagePath"`
|
||||
} `json:"video"`
|
||||
StorageAppID int `json:"storageAppId"`
|
||||
StorageBucket string `json:"storageBucket"`
|
||||
StorageRegion string `json:"storageRegion"`
|
||||
StorageRegionV5 string `json:"storageRegionV5"`
|
||||
Domain string `json:"domain"`
|
||||
VodSessionKey string `json:"vodSessionKey"`
|
||||
TempCertificate struct {
|
||||
SecretID string `json:"secretId"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Token string `json:"token"`
|
||||
ExpiredTime int `json:"expiredTime"`
|
||||
} `json:"tempCertificate"`
|
||||
AppID int `json:"appId"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
StorageRegionV50 string `json:"StorageRegionV5"`
|
||||
MiniProgramAccelerateHost string `json:"MiniProgramAccelerateHost"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type RspCommitUploadUGC struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Video struct {
|
||||
URL string `json:"url"`
|
||||
VerifyContent string `json:"verify_content"`
|
||||
} `json:"video"`
|
||||
FileID string `json:"fileId"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type RspFinishUpload struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
MaterialID string `json:"MaterialId"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
func fileToObj(f File) *model.Object {
|
||||
obj := &model.Object{}
|
||||
if f.Type == "CLASS" {
|
||||
obj.Name = f.ClassInfo.Name
|
||||
obj.ID = strconv.Itoa(f.ClassInfo.ClassID)
|
||||
obj.IsFolder = true
|
||||
obj.Modified = f.ClassInfo.CreateTime
|
||||
obj.Size = 0
|
||||
} else if f.Type == "MATERIAL" {
|
||||
obj.Name = f.MaterialInfo.BasicInfo.Name
|
||||
obj.ID = f.MaterialInfo.BasicInfo.MaterialID
|
||||
obj.IsFolder = false
|
||||
obj.Modified = f.MaterialInfo.BasicInfo.CreateTime
|
||||
obj.Size = int64(f.MaterialInfo.MediaInfo.Size)
|
||||
}
|
||||
return obj
|
||||
}
|
289
drivers/vtencent/util.go
Normal file
289
drivers/vtencent/util.go
Normal file
@ -0,0 +1,289 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
func (d *Vtencent) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"cookie": d.Cookie,
|
||||
"content-type": "application/json",
|
||||
"origin": d.conf.origin,
|
||||
"referer": d.conf.referer,
|
||||
})
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
} else {
|
||||
req.SetBody("{}")
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
res, err := req.Execute(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
code := utils.Json.Get(res.Body(), "Code").ToString()
|
||||
if code != "Success" {
|
||||
switch code {
|
||||
case "AuthFailure.SessionInvalid":
|
||||
if err != nil {
|
||||
return nil, errors.New(code)
|
||||
}
|
||||
default:
|
||||
return nil, errors.New(code)
|
||||
}
|
||||
return d.request(url, method, callback, resp)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) ugcRequest(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"cookie": d.Cookie,
|
||||
"content-type": "application/json",
|
||||
"origin": d.conf.origin,
|
||||
"referer": d.conf.referer,
|
||||
})
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
} else {
|
||||
req.SetBody("{}")
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
res, err := req.Execute(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
code := utils.Json.Get(res.Body(), "Code").ToInt()
|
||||
if code != 0 {
|
||||
message := utils.Json.Get(res.Body(), "message").ToString()
|
||||
if len(message) == 0 {
|
||||
message = utils.Json.Get(res.Body(), "msg").ToString()
|
||||
}
|
||||
return nil, errors.New(message)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) LoadUser() (string, error) {
|
||||
api := "https://api.vs.tencent.com/SaaS/Account/DescribeAccount"
|
||||
res, err := d.request(api, http.MethodPost, func(req *resty.Request) {}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return utils.Json.Get(res, "Data", "TfUid").ToString(), nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) GetFiles(dirId string) ([]File, error) {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/SearchResource"
|
||||
form := fmt.Sprintf(`{
|
||||
"Text":"",
|
||||
"Text":"",
|
||||
"Offset":0,
|
||||
"Limit":20000,
|
||||
"Sort":{"Field":"%s","Order":"%s"},
|
||||
"CreateTimeRanges":[],
|
||||
"MaterialTypes":[],
|
||||
"ReviewStatuses":[],
|
||||
"Tags":[],
|
||||
"SearchScopes":[{"Owner":{"Type":"PERSON","Id":"%s"},"ClassId":%s,"SearchOneDepth":true}]
|
||||
}`, d.Addition.OrderBy, d.Addition.OrderDirection, d.TfUid, dirId)
|
||||
var resps RspFiles
|
||||
_, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return []File{}, err
|
||||
}
|
||||
return resps.Data.ResourceInfoSet, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) CreateUploadMaterial(classId int, fileName string, UploadSummaryKey string) (RspCreatrMaterial, error) {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/CreateUploadMaterial"
|
||||
form := base.Json{"Owner": base.Json{"Type": "PERSON", "Id": d.TfUid},
|
||||
"MaterialType": "VIDEO", "Name": fileName, "ClassId": classId,
|
||||
"UploadSummaryKey": UploadSummaryKey}
|
||||
var resps RspCreatrMaterial
|
||||
_, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspCreatrMaterial{}, err
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) ApplyUploadUGC(signature string, stream model.FileStreamer) (RspApplyUploadUGC, error) {
|
||||
api := "https://vod2.qcloud.com/v3/index.php?Action=ApplyUploadUGC"
|
||||
form := base.Json{
|
||||
"signature": signature,
|
||||
"videoName": stream.GetName(),
|
||||
"videoType": strings.ReplaceAll(path.Ext(stream.GetName()), ".", ""),
|
||||
"videoSize": stream.GetSize(),
|
||||
}
|
||||
var resps RspApplyUploadUGC
|
||||
_, err := d.ugcRequest(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspApplyUploadUGC{}, err
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) CommitUploadUGC(signature string, vodSessionKey string) (RspCommitUploadUGC, error) {
|
||||
api := "https://vod2.qcloud.com/v3/index.php?Action=CommitUploadUGC"
|
||||
form := base.Json{
|
||||
"signature": signature,
|
||||
"vodSessionKey": vodSessionKey,
|
||||
}
|
||||
var resps RspCommitUploadUGC
|
||||
rsp, err := d.ugcRequest(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspCommitUploadUGC{}, err
|
||||
}
|
||||
if len(resps.Data.Video.URL) == 0 {
|
||||
return RspCommitUploadUGC{}, errors.New(string(rsp))
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) FinishUploadMaterial(SummaryKey string, VodVerifyKey string, UploadContext, VodFileId string) (RspFinishUpload, error) {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/FinishUploadMaterial"
|
||||
form := base.Json{
|
||||
"UploadContext": UploadContext,
|
||||
"VodVerifyKey": VodVerifyKey,
|
||||
"VodFileId": VodFileId,
|
||||
"UploadFullKey": SummaryKey}
|
||||
var resps RspFinishUpload
|
||||
rsp, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspFinishUpload{}, err
|
||||
}
|
||||
if len(resps.Data.MaterialID) == 0 {
|
||||
return RspFinishUpload{}, errors.New(string(rsp))
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) FinishHashUploadMaterial(SummaryKey string, UploadContext string) (RspFinishUpload, error) {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/FinishUploadMaterial"
|
||||
var resps RspFinishUpload
|
||||
form := base.Json{
|
||||
"UploadContext": UploadContext,
|
||||
"UploadFullKey": SummaryKey}
|
||||
rsp, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspFinishUpload{}, err
|
||||
}
|
||||
if len(resps.Data.MaterialID) == 0 {
|
||||
return RspFinishUpload{}, errors.New(string(rsp))
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) FileUpload(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
classId, err := strconv.Atoi(dstDir.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
const chunkLength int64 = 1024 * 1024 * 10
|
||||
reader, err := stream.RangeRead(http_range.Range{Start: 0, Length: chunkLength})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chunkHash, err := utils.HashReader(utils.SHA1, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rspCreatrMaterial, err := d.CreateUploadMaterial(classId, stream.GetName(), chunkHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rspCreatrMaterial.Data.QuickUpload {
|
||||
SummaryKey := stream.GetHash().GetHash(utils.SHA1)
|
||||
if len(SummaryKey) < utils.SHA1.Width {
|
||||
if SummaryKey, err = utils.HashReader(utils.SHA1, stream); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
UploadContext := rspCreatrMaterial.Data.UploadContext
|
||||
_, err = d.FinishHashUploadMaterial(SummaryKey, UploadContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
hash := sha1.New()
|
||||
rspUGC, err := d.ApplyUploadUGC(rspCreatrMaterial.Data.VodUploadSign, stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params := rspUGC.Data
|
||||
certificate := params.TempCertificate
|
||||
cfg := &aws.Config{
|
||||
HTTPClient: base.HttpClient,
|
||||
// S3ForcePathStyle: aws.Bool(true),
|
||||
Credentials: credentials.NewStaticCredentials(certificate.SecretID, certificate.SecretKey, certificate.Token),
|
||||
Region: aws.String(params.StorageRegionV5),
|
||||
Endpoint: aws.String(fmt.Sprintf("cos.%s.myqcloud.com", params.StorageRegionV5)),
|
||||
}
|
||||
ss, err := session.NewSession(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploader := s3manager.NewUploader(ss)
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: aws.String(fmt.Sprintf("%s-%d", params.StorageBucket, params.StorageAppID)),
|
||||
Key: ¶ms.Video.StoragePath,
|
||||
Body: io.TeeReader(stream, io.MultiWriter(hash, driver.NewProgress(stream.GetSize(), up))),
|
||||
}
|
||||
_, err = uploader.UploadWithContext(ctx, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rspCommitUGC, err := d.CommitUploadUGC(rspCreatrMaterial.Data.VodUploadSign, rspUGC.Data.VodSessionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
VodVerifyKey := rspCommitUGC.Data.Video.VerifyContent
|
||||
VodFileId := rspCommitUGC.Data.FileID
|
||||
UploadContext := rspCreatrMaterial.Data.UploadContext
|
||||
SummaryKey := hex.EncodeToString(hash.Sum(nil))
|
||||
_, err = d.FinishUploadMaterial(SummaryKey, VodVerifyKey, UploadContext, VodFileId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -11,6 +11,7 @@ type Addition struct {
|
||||
Username string `json:"username" required:"true"`
|
||||
Password string `json:"password" required:"true"`
|
||||
driver.RootPath
|
||||
TlsInsecureSkipVerify bool `json:"tls_insecure_skip_verify" default:"false"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -1,6 +1,7 @@
|
||||
package webdav
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
|
||||
@ -17,6 +18,10 @@ func (d *WebDav) isSharepoint() bool {
|
||||
|
||||
func (d *WebDav) setClient() error {
|
||||
c := gowebdav.NewClient(d.Address, d.Username, d.Password)
|
||||
c.SetTransport(&http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: d.TlsInsecureSkipVerify},
|
||||
})
|
||||
if d.isSharepoint() {
|
||||
cookie, err := odrvcookie.GetCookie(d.Username, d.Password, d.Address)
|
||||
if err == nil {
|
||||
|
@ -159,7 +159,7 @@ func (d *Wopan) Put(ctx context.Context, dstDir model.Obj, stream model.FileStre
|
||||
ContentType: stream.GetMimetype(),
|
||||
}, dstDir.GetID(), d.FamilyID, wopan.Upload2COption{
|
||||
OnProgress: func(current, total int64) {
|
||||
up(int(100 * current / total))
|
||||
up(100 * float64(current) / float64(total))
|
||||
},
|
||||
})
|
||||
return err
|
||||
|
25
go.mod
25
go.mod
@ -1,27 +1,27 @@
|
||||
module github.com/alist-org/alist/v3
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/SheltonZhu/115driver v1.0.16
|
||||
github.com/SheltonZhu/115driver v1.0.21
|
||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
||||
github.com/Xhofe/rateg v0.0.0-20230728072201-251a4e1adad4
|
||||
github.com/Xhofe/wopan-sdk-go v0.1.2
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible
|
||||
github.com/avast/retry-go v3.0.0+incompatible
|
||||
github.com/aws/aws-sdk-go v1.44.327
|
||||
github.com/aws/aws-sdk-go v1.46.7
|
||||
github.com/blevesearch/bleve/v2 v2.3.10
|
||||
github.com/caarlos0/env/v9 v9.0.0
|
||||
github.com/charmbracelet/bubbles v0.16.1
|
||||
github.com/charmbracelet/bubbletea v0.24.2
|
||||
github.com/charmbracelet/lipgloss v0.7.1
|
||||
github.com/charmbracelet/lipgloss v0.9.1
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/deckarep/golang-set/v2 v2.3.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/djherbis/times v1.5.0
|
||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.4
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.2
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.3
|
||||
github.com/gin-contrib/cors v1.4.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-resty/resty/v2 v2.9.1
|
||||
@ -47,11 +47,14 @@ require (
|
||||
github.com/u2takey/ffmpeg-go v0.5.0
|
||||
github.com/upyun/go-sdk/v3 v3.0.4
|
||||
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5
|
||||
github.com/xhofe/tache v0.1.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
golang.org/x/image v0.11.0
|
||||
golang.org/x/net v0.16.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.12.0
|
||||
golang.org/x/time v0.3.0
|
||||
google.golang.org/appengine v1.6.7
|
||||
gorm.io/driver/mysql v1.4.7
|
||||
gorm.io/driver/postgres v1.4.8
|
||||
gorm.io/driver/sqlite v1.4.4
|
||||
@ -122,6 +125,7 @@ require (
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.3.0 // indirect
|
||||
github.com/jaevor/go-nanoid v1.3.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
@ -137,7 +141,7 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
@ -150,7 +154,7 @@ require (
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.1 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/multiformats/go-base32 v0.1.0 // indirect
|
||||
github.com/multiformats/go-base36 v0.2.0 // indirect
|
||||
github.com/multiformats/go-multiaddr v0.9.0 // indirect
|
||||
@ -182,6 +186,7 @@ require (
|
||||
github.com/u2takey/go-utils v0.3.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xhofe/gsync v0.0.0-20230917091818-2111ceb38a25 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
@ -189,9 +194,7 @@ require (
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
google.golang.org/api v0.134.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect
|
||||
google.golang.org/grpc v1.57.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
|
139
go.sum
139
go.sum
@ -2,44 +2,36 @@ cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA=
|
||||
cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
|
||||
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE=
|
||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc=
|
||||
github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY=
|
||||
github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
|
||||
github.com/SheltonZhu/115driver v1.0.15 h1:RRvgXvXEzvrPwkRno0CUIg7ucEphbsfwct2mQxfNOdQ=
|
||||
github.com/SheltonZhu/115driver v1.0.15/go.mod h1:e3fPOBANbH/FsTya8FquJwOR3ErhCQgEab3q6CVY2k4=
|
||||
github.com/SheltonZhu/115driver v1.0.16 h1:XOhqRtKF9huTCobM5rWVd9DtJyBKLJSnBwWPF3+GM+k=
|
||||
github.com/SheltonZhu/115driver v1.0.16/go.mod h1:e3fPOBANbH/FsTya8FquJwOR3ErhCQgEab3q6CVY2k4=
|
||||
github.com/SheltonZhu/115driver v1.0.21 h1:Pz6r14VwIiuSyHj+OmJe57FHhbmWB/6IfnXAFL2iXbU=
|
||||
github.com/SheltonZhu/115driver v1.0.21/go.mod h1:e3fPOBANbH/FsTya8FquJwOR3ErhCQgEab3q6CVY2k4=
|
||||
github.com/Unknwon/goconfig v1.0.0 h1:9IAu/BYbSLQi8puFjUQApZTxIHqSwrj5d8vpP8vTq4A=
|
||||
github.com/Unknwon/goconfig v1.0.0/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw=
|
||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a h1:RenIAa2q4H8UcS/cqmwdT1WCWIAH5aumP8m8RpbqVsE=
|
||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a/go.mod h1:sSBbaOg90XwWKtpT56kVujF0bIeVITnPlssLclogS04=
|
||||
github.com/Xhofe/rateg v0.0.0-20230728072201-251a4e1adad4 h1:WnvifFgYyogPz2ZFvaVLk4gI/Co0paF92FmxSR6U1zY=
|
||||
github.com/Xhofe/rateg v0.0.0-20230728072201-251a4e1adad4/go.mod h1:8pWlL2rpusvx7Xa6yYaIWOJ8bR3gPdFBUT7OystyGOY=
|
||||
github.com/Xhofe/wopan-sdk-go v0.1.1 h1:dSrTxNYclqNuo9libjtC+R6C4RCen/inh/dUXd12vpM=
|
||||
github.com/Xhofe/wopan-sdk-go v0.1.1/go.mod h1:xWcUS7PoFLDD9gy2BK2VQfilEsZngLMz2Vkx3oF2zJY=
|
||||
github.com/Xhofe/wopan-sdk-go v0.1.2 h1:6Gh4YTT7b7YHN0OoJ33j7Jm9ru/ckuvcDxPnRmH07jc=
|
||||
github.com/Xhofe/wopan-sdk-go v0.1.2/go.mod h1:ktLYb4t7rnPFq1AshLaPXq5kZER+DkEagT6/i/in0uo=
|
||||
github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0=
|
||||
github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM=
|
||||
github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
|
||||
github.com/aead/ecdh v0.2.0/go.mod h1:a9HHtXuSo8J1Js1MwLQx2mBhkXMT6YwUmVVEY4tTB8U=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible h1:QoRMR0TCctLDqBCMyOu1eXdZyMw3F7uGA9qPn2J4+R8=
|
||||
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.7+incompatible h1:KpbJFXwhVeuxNtBJ74MCGbIoaBok2uZvkD7QXp2+Wis=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiwbXTpUEinBpHsN7mG21Rc2k=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+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/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9yEt0zPe9Ayuhc=
|
||||
github.com/andreburgaud/crypt2go v1.2.0 h1:oly/ENAodeqTYpUafgd4r3v+VKLQnmOKUyfpj+TxHbE=
|
||||
github.com/andreburgaud/crypt2go v1.2.0/go.mod h1:kKRqlrX/3Q9Ki7HdUsoh0cX1Urq14/Hcta4l4VrIXrI=
|
||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.44.327 h1:ZS8oO4+7MOBLhkdwIhgtVeDzCeWOlTfKJS7EgggbIEY=
|
||||
github.com/aws/aws-sdk-go v1.44.327/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go v1.46.7 h1:IjvAWeiJZlbETOemOwvheN5L17CvKvKW0T1xOC6d3Sc=
|
||||
github.com/aws/aws-sdk-go v1.46.7/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
@ -50,16 +42,10 @@ github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjL
|
||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/blevesearch/bleve/v2 v2.3.9 h1:pUMvK0mxAexqasZcVj8lazmWnEW5XiV0tASIqANiNTQ=
|
||||
github.com/blevesearch/bleve/v2 v2.3.9/go.mod h1:1PibElcjlQMQHF9uS9mRv58ODQgj4pCWHA1Wfd+qagU=
|
||||
github.com/blevesearch/bleve/v2 v2.3.10 h1:z8V0wwGoL4rp7nG/O3qVVLYxUqCbEwskMt4iRJsPLgg=
|
||||
github.com/blevesearch/bleve/v2 v2.3.10/go.mod h1:RJzeoeHC+vNHsoLR54+crS1HmOWpnH87fL70HAUCzIA=
|
||||
github.com/blevesearch/bleve_index_api v1.0.5 h1:Lc986kpC4Z0/n1g3gg8ul7H+lxgOQPcXb9SxvQGu+tw=
|
||||
github.com/blevesearch/bleve_index_api v1.0.5/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||
github.com/blevesearch/bleve_index_api v1.0.6 h1:gyUUxdsrvmW3jVhhYdCVL6h9dCjNT/geNU7PxGn37p8=
|
||||
github.com/blevesearch/bleve_index_api v1.0.6/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||
github.com/blevesearch/geo v0.1.17 h1:AguzI6/5mHXapzB0gE9IKWo+wWPHZmXZoscHcjFgAFA=
|
||||
github.com/blevesearch/geo v0.1.17/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM=
|
||||
github.com/blevesearch/geo v0.1.18 h1:Np8jycHTZ5scFe7VEPLrDoHnnb9C4j636ue/CGrhtDw=
|
||||
github.com/blevesearch/geo v0.1.18/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
@ -68,8 +54,6 @@ github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZG
|
||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.5 h1:1g713kpCQZ8u4a3stRGBfrwVOuGRnmxOVU5MQkUPrHU=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.5/go.mod h1:f2nOkKS1HcjgIWZgDAErgBdxmr2eyt0Kn7IY+FU1Xe4=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.6 h1:CdekX/Ob6YCYmeHzD72cKpwzBjvkOGegHOqhAkXp6yA=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.6/go.mod h1:nQQYlp51XvoSVxcciBjtvuHPIVjlWrN1hX4qwK2cqdc=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
@ -80,24 +64,14 @@ github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMG
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||
github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI=
|
||||
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||
github.com/blevesearch/zapx/v11 v11.3.9 h1:y3ijS4h4MJdmQ07MHASxat4owAixreK2xdo76w9ncrw=
|
||||
github.com/blevesearch/zapx/v11 v11.3.9/go.mod h1:jcAYnQwlr+LqD2vLjDWjWiZDXDXGFqPbpPDRTd3XmS4=
|
||||
github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk=
|
||||
github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ=
|
||||
github.com/blevesearch/zapx/v12 v12.3.9 h1:MXGLlZ03oxXH3DMJTZaBaRj2xb6t4wQVZeZK/wu1M6w=
|
||||
github.com/blevesearch/zapx/v12 v12.3.9/go.mod h1:QXCMwmOkdLnMDgTN1P4CcuX5F851iUOtOwXbw0HMBYs=
|
||||
github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s=
|
||||
github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs=
|
||||
github.com/blevesearch/zapx/v13 v13.3.9 h1:+VAz9V0VmllHXlZV4DCvfYj0nqaZHgF3MeEHwOyRBwQ=
|
||||
github.com/blevesearch/zapx/v13 v13.3.9/go.mod h1:s+WjNp4WSDtrBVBpa37DUOd7S/Gr/jTZ7ST/MbCVj/0=
|
||||
github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8=
|
||||
github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk=
|
||||
github.com/blevesearch/zapx/v14 v14.3.9 h1:wuqxATgsTCNHM9xsOFOeFp8H2heZ/gMX/tsl9lRK8U4=
|
||||
github.com/blevesearch/zapx/v14 v14.3.9/go.mod h1:MWZ4v8AzFBRurhDzkLvokFW8ljcq9Evm27mkWe8OGbM=
|
||||
github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU=
|
||||
github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns=
|
||||
github.com/blevesearch/zapx/v15 v15.3.12 h1:w/kU9aHyfMDEdwHGZzCiakC3HZ9z5gYlXaALDC4Dct8=
|
||||
github.com/blevesearch/zapx/v15 v15.3.12/go.mod h1:tx53gDJS/7Oa3Je820cmVurqCuJ4dqdAy1kiDMV/IUo=
|
||||
github.com/blevesearch/zapx/v15 v15.3.13 h1:6EkfaZiPlAxqXz0neniq35my6S48QI94W/wyhnpDHHQ=
|
||||
github.com/blevesearch/zapx/v15 v15.3.13/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
@ -115,9 +89,10 @@ github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5
|
||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
||||
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
|
||||
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
||||
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
|
||||
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
|
||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
@ -137,6 +112,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/deckarep/golang-set/v2 v2.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A=
|
||||
github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
@ -145,12 +121,10 @@ github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU=
|
||||
github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0=
|
||||
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/foxxorcat/mopan-sdk-go v0.1.3 h1:6ww0ulyLDh6neXZBqUM2PDbxQ6lfdkQbr0FCh9BTY0Y=
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.3/go.mod h1:iWHA2JFhzmKR28ySp1ON0g6DjLaYtvb5jhTqPVTDW9A=
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.4 h1:6utvPiBv8KDRDVKB7A4FERdrVxcHKZd2fBFCNuKcXzU=
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.4/go.mod h1:iWHA2JFhzmKR28ySp1ON0g6DjLaYtvb5jhTqPVTDW9A=
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.2 h1:waRWIBmjL9GCcndJ8HvOYrrVB4hhoPYzRrn3I/Cnzqw=
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.2/go.mod h1:AKsLFuWhWlClpGrg1zxTdMejugZEZtmhIuElAk3W83s=
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.3 h1:I5c5nfGErhq9DBumyjCVCggRA74jhgriMqRRFu5jeeY=
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.3/go.mod h1:TPxzN0d2PahweUEHlOBWlwZSA+rELSUlGYMWgXRn9ps=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
@ -174,6 +148,7 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
@ -184,10 +159,7 @@ github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXS
|
||||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/go-resty/resty/v2 v2.8.0 h1:J29d0JFWwSWrDCysnOK/YjsPMLQTx0TvgJEHVGvf2L8=
|
||||
github.com/go-resty/resty/v2 v2.8.0/go.mod h1:UCui0cMHekLrSntoMyofdSTaPpinlRHFtPpizuyDW2w=
|
||||
github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM=
|
||||
github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
@ -207,7 +179,7 @@ github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
@ -223,13 +195,14 @@ github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
||||
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
||||
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@ -242,14 +215,10 @@ github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRK
|
||||
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/ipfs/boxo v0.8.0 h1:UdjAJmHzQHo/j3g3b1bAcAXCj/GM6iTwvSlBDvPBNBs=
|
||||
github.com/ipfs/boxo v0.8.0/go.mod h1:RIsi4CnTyQ7AUsNn5gXljJYZlQrHBMnJp94p73liFiA=
|
||||
github.com/ipfs/boxo v0.12.0 h1:AXHg/1ONZdRQHQLgG5JHsSC3XoE4DjCAMgK+asZvUcQ=
|
||||
github.com/ipfs/boxo v0.12.0/go.mod h1:xAnfiU6PtxWCnRqu7dcXQ10bB5/kvI1kXRotuGqGBhg=
|
||||
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
|
||||
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
|
||||
github.com/ipfs/go-ipfs-api v0.6.1 h1:nK5oeFOdMh1ogT+GCOcyBFOOcFGNuudSb1rg9YDyAKE=
|
||||
github.com/ipfs/go-ipfs-api v0.6.1/go.mod h1:8pl+ZMF2LX42szbqGbpOBEiI1/rYaImvTvJtG0g+rL4=
|
||||
github.com/ipfs/go-ipfs-api v0.7.0 h1:CMBNCUl0b45coC+lQCXEVpMhwoqjiaCwUIrM+coYW2Q=
|
||||
github.com/ipfs/go-ipfs-api v0.7.0/go.mod h1:AIxsTNB0+ZhkqIfTZpdZ0VR/cpX5zrXjATa3prSay3g=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
@ -259,6 +228,8 @@ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZ
|
||||
github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA=
|
||||
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
||||
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jaevor/go-nanoid v1.3.0 h1:nD+iepesZS6pr3uOVf20vR9GdGgJW1HPaR46gtrxzkg=
|
||||
github.com/jaevor/go-nanoid v1.3.0/go.mod h1:SI+jFaPuddYkqkVQoNGHs81navCtH388TcrH0RqFKgY=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
@ -270,11 +241,9 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 h1:G+9t9cEtnC9jFiTxyptEKuNIAbiN5ZCQzX2a74lj3xg=
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004/go.mod h1:KmHnJWQrgEvbuy0vcvj00gtMqbvNn1L+3YUZLK/B92c=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
@ -289,6 +258,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@ -318,8 +288,8 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
@ -347,8 +317,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
|
||||
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
|
||||
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
|
||||
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
|
||||
@ -357,44 +327,34 @@ github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sgg
|
||||
github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0=
|
||||
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
|
||||
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
|
||||
github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8=
|
||||
github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
|
||||
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
|
||||
github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
|
||||
github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108=
|
||||
github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc=
|
||||
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
|
||||
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
|
||||
github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo=
|
||||
github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q=
|
||||
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
|
||||
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
||||
github.com/ncw/swift/v2 v2.0.2 h1:jx282pcAKFhmoZBSdMcCRFn9VWkoBIRsCpe+yZq7vEk=
|
||||
github.com/ncw/swift/v2 v2.0.2/go.mod h1:z0A9RVdYPjNjXVo2pDOPxZ4eu3oarO1P91fTItcb+Kg=
|
||||
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 h1:dg/EaaJLPIg4xn2kaZil7Ax3wfoxcFXaBwyOTlcz5AI=
|
||||
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77/go.mod h1:FD9a09Vw07CSMTdT0Y7ttStOa1WZsnPBslliMw2DkeM=
|
||||
github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831 h1:K3T3eu4h5aYIOzUtLjN08L4Qt4WGaJONMgcaD0ayBJQ=
|
||||
github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831/go.mod h1:lSHD4lC4zlMl+zcoysdJcd5KFzsWwOD8BJbyg1Ws9Ng=
|
||||
github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
|
||||
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6 h1:5TvW1dv00Y13njmQ1AWkxSWtPkwE7ZEF6yDuv9q+Als=
|
||||
github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
|
||||
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
@ -423,6 +383,7 @@ github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||
@ -436,6 +397,7 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
@ -469,8 +431,6 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/u2takey/ffmpeg-go v0.4.1 h1:l5ClIwL3N2LaH1zF3xivb3kP2HW95eyG5xhHE1JdZ9Y=
|
||||
github.com/u2takey/ffmpeg-go v0.4.1/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc=
|
||||
github.com/u2takey/ffmpeg-go v0.5.0 h1:r7d86XuL7uLWJ5mzSeQ03uvjfIhiJYvsRAJFCW4uklU=
|
||||
github.com/u2takey/ffmpeg-go v0.5.0/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc=
|
||||
github.com/u2takey/go-utils v0.3.1 h1:TaQTgmEZZeDHQFYfd+AdUT1cT4QJgJn/XVPELhHw4ys=
|
||||
@ -481,17 +441,23 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/upyun/go-sdk/v3 v3.0.4 h1:2DCJa/Yi7/3ZybT9UCPATSzvU3wpPPxhXinNlb1Hi8Q=
|
||||
github.com/upyun/go-sdk/v3 v3.0.4/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
|
||||
github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
|
||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5 h1:jxZvjx8Ve5sOXorZG0KzTxbp0Cr1n3FEegfmyd9br1k=
|
||||
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5/go.mod h1:uxjoF2jEYT3+x+vC2KJddEGdk/LU8pRowXmyVMHSV5I=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xhofe/gsync v0.0.0-20230917091818-2111ceb38a25 h1:eDfebW/yfq9DtG9RO3KP7BT2dot2CvJGIvrB0NEoDXI=
|
||||
github.com/xhofe/gsync v0.0.0-20230917091818-2111ceb38a25/go.mod h1:fH4oNm5F9NfI5dLi0oIMtsLNKQOirUDbEMCIBb/7SU0=
|
||||
github.com/xhofe/tache v0.1.0 h1:W0uoyLWCmUEQudXwB93owdlGSlN8gwZmiiDlKFCerKA=
|
||||
github.com/xhofe/tache v0.1.0/go.mod h1:iKumPFvywf30FRpAHHCt64G0JHLMzT0K+wyGedHsmTQ=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
@ -506,23 +472,12 @@ 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.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M=
|
||||
golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
|
||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@ -538,16 +493,9 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
|
||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
|
||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -579,9 +527,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -590,11 +535,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
@ -606,9 +546,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
|
@ -1,61 +0,0 @@
|
||||
package aria2
|
||||
|
||||
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 AddURI(ctx context.Context, uri 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 aria2 rpc
|
||||
tempDir := filepath.Join(conf.Conf.TempDir, "aria2", uuid.NewString())
|
||||
options := map[string]interface{}{
|
||||
"dir": tempDir,
|
||||
}
|
||||
gid, err := client.AddURI([]string{uri}, options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to add uri %s", uri)
|
||||
}
|
||||
DownTaskManager.Submit(task.WithCancelCtx(&task.Task[string]{
|
||||
ID: gid,
|
||||
Name: fmt.Sprintf("download %s to [%s](%s)", uri, storage.GetStorage().MountPath, dstDirActualPath),
|
||||
Func: func(tsk *task.Task[string]) error {
|
||||
m := &Monitor{
|
||||
tsk: tsk,
|
||||
tempDir: tempDir,
|
||||
retried: 0,
|
||||
dstDirPath: dstDirPath,
|
||||
}
|
||||
return m.Loop()
|
||||
},
|
||||
}))
|
||||
return nil
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package aria2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/pkg/aria2/rpc"
|
||||
"github.com/alist-org/alist/v3/pkg/task"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var DownTaskManager = task.NewTaskManager[string](3)
|
||||
var notify = NewNotify()
|
||||
var client rpc.Client
|
||||
|
||||
func InitClient(timeout int) (string, error) {
|
||||
client = nil
|
||||
uri := setting.GetStr(conf.Aria2Uri)
|
||||
secret := setting.GetStr(conf.Aria2Secret)
|
||||
return InitAria2Client(uri, secret, timeout)
|
||||
}
|
||||
|
||||
func InitAria2Client(uri string, secret string, timeout int) (string, error) {
|
||||
c, err := rpc.New(context.Background(), uri, secret, time.Duration(timeout)*time.Second, notify)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to init aria2 client")
|
||||
}
|
||||
version, err := c.GetVersion()
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed get aria2 version")
|
||||
}
|
||||
client = c
|
||||
log.Infof("using aria2 version: %s", version.Version)
|
||||
return version.Version, nil
|
||||
}
|
||||
|
||||
func IsAria2Ready() bool {
|
||||
return client != nil
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package aria2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "github.com/alist-org/alist/v3/drivers"
|
||||
conf2 "github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/task"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
conf2.Conf = conf2.DefaultConfig()
|
||||
absPath, err := filepath.Abs("../../data/temp")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
conf2.Conf.TempDir = absPath
|
||||
dB, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
||||
if err != nil {
|
||||
panic("failed to connect database")
|
||||
}
|
||||
db.Init(dB)
|
||||
}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
_, err := InitAria2Client("http://localhost:16800/jsonrpc", "secret", 3)
|
||||
if err != nil {
|
||||
t.Errorf("failed to init aria2: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDown(t *testing.T) {
|
||||
TestConnect(t)
|
||||
_, err := op.CreateStorage(context.Background(), model.Storage{
|
||||
ID: 0,
|
||||
MountPath: "/",
|
||||
Order: 0,
|
||||
Driver: "Local",
|
||||
Status: "",
|
||||
Addition: `{"root_folder":"../../data"}`,
|
||||
Remark: "",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create storage: %+v", err)
|
||||
}
|
||||
err = AddURI(context.Background(), "https://nodejs.org/dist/index.json", "/test")
|
||||
if err != nil {
|
||||
t.Errorf("failed to add uri: %+v", err)
|
||||
}
|
||||
tasks := DownTaskManager.GetAll()
|
||||
if len(tasks) != 1 {
|
||||
t.Errorf("failed to get tasks: %+v", tasks)
|
||||
}
|
||||
for {
|
||||
tsk := tasks[0]
|
||||
t.Logf("task: %+v", tsk)
|
||||
if tsk.GetState() == task.SUCCEEDED {
|
||||
break
|
||||
}
|
||||
if tsk.GetState() == task.ERRORED {
|
||||
t.Fatalf("failed to download: %+v", tsk)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
for {
|
||||
if len(TransferTaskManager.GetAll()) == 0 {
|
||||
continue
|
||||
}
|
||||
tsk := TransferTaskManager.GetAll()[0]
|
||||
t.Logf("task: %+v", tsk)
|
||||
if tsk.GetState() == task.SUCCEEDED {
|
||||
break
|
||||
}
|
||||
if tsk.GetState() == task.ERRORED {
|
||||
t.Fatalf("failed to download: %+v", tsk)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
@ -1,191 +0,0 @@
|
||||
package aria2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
tsk *task.Task[string]
|
||||
tempDir string
|
||||
retried int
|
||||
c chan int
|
||||
dstDirPath string
|
||||
finish chan struct{}
|
||||
}
|
||||
|
||||
func (m *Monitor) Loop() error {
|
||||
defer func() {
|
||||
notify.Signals.Delete(m.tsk.ID)
|
||||
// clear temp dir, should do while complete
|
||||
//_ = os.RemoveAll(m.tempDir)
|
||||
}()
|
||||
m.c = make(chan int)
|
||||
m.finish = make(chan struct{})
|
||||
notify.Signals.Store(m.tsk.ID, m.c)
|
||||
var (
|
||||
err error
|
||||
ok bool
|
||||
)
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-m.tsk.Ctx.Done():
|
||||
_, err := client.Remove(m.tsk.ID)
|
||||
return err
|
||||
case <-m.c:
|
||||
ok, err = m.Update()
|
||||
if ok {
|
||||
break outer
|
||||
}
|
||||
case <-time.After(time.Second * 2):
|
||||
ok, err = m.Update()
|
||||
if ok {
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.tsk.SetStatus("aria2 download completed, transferring")
|
||||
<-m.finish
|
||||
m.tsk.SetStatus("completed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Monitor) Update() (bool, error) {
|
||||
info, err := client.TellStatus(m.tsk.ID)
|
||||
if err != nil {
|
||||
m.retried++
|
||||
log.Errorf("failed to get status of %s, retried %d times", m.tsk.ID, m.retried)
|
||||
return false, nil
|
||||
}
|
||||
if m.retried > 5 {
|
||||
return true, errors.Errorf("failed to get status of %s, retried %d times", m.tsk.ID, m.retried)
|
||||
}
|
||||
m.retried = 0
|
||||
if len(info.FollowedBy) != 0 {
|
||||
log.Debugf("followen by: %+v", info.FollowedBy)
|
||||
gid := info.FollowedBy[0]
|
||||
notify.Signals.Delete(m.tsk.ID)
|
||||
oldId := m.tsk.ID
|
||||
m.tsk.ID = gid
|
||||
DownTaskManager.RawTasks().Delete(oldId)
|
||||
DownTaskManager.RawTasks().Store(m.tsk.ID, m.tsk)
|
||||
notify.Signals.Store(gid, m.c)
|
||||
return false, nil
|
||||
}
|
||||
// update download status
|
||||
total, err := strconv.ParseUint(info.TotalLength, 10, 64)
|
||||
if err != nil {
|
||||
total = 0
|
||||
}
|
||||
downloaded, err := strconv.ParseUint(info.CompletedLength, 10, 64)
|
||||
if err != nil {
|
||||
downloaded = 0
|
||||
}
|
||||
progress := float64(downloaded) / float64(total) * 100
|
||||
m.tsk.SetProgress(int(progress))
|
||||
switch info.Status {
|
||||
case "complete":
|
||||
err := m.Complete()
|
||||
return true, errors.WithMessage(err, "failed to transfer file")
|
||||
case "error":
|
||||
return true, errors.Errorf("failed to download %s, error: %s", m.tsk.ID, info.ErrorMessage)
|
||||
case "active":
|
||||
m.tsk.SetStatus("aria2: " + info.Status)
|
||||
if info.Seeder == "true" {
|
||||
err := m.Complete()
|
||||
return true, errors.WithMessage(err, "failed to transfer file")
|
||||
}
|
||||
return false, nil
|
||||
case "waiting", "paused":
|
||||
m.tsk.SetStatus("aria2: " + info.Status)
|
||||
return false, nil
|
||||
case "removed":
|
||||
return true, errors.Errorf("failed to download %s, removed", m.tsk.ID)
|
||||
default:
|
||||
return true, errors.Errorf("failed to download %s, unknown status %s", m.tsk.ID, info.Status)
|
||||
}
|
||||
}
|
||||
|
||||
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 := client.GetFiles(m.tsk.ID)
|
||||
log.Debugf("files len: %d", len(files))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get files of %s", m.tsk.ID)
|
||||
}
|
||||
// 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 aria2 temp dir: %+v", err.Error())
|
||||
}
|
||||
}()
|
||||
for i, _ := range files {
|
||||
file := files[i]
|
||||
TransferTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
|
||||
Name: fmt.Sprintf("transfer %s to [%s](%s)", file.Path, storage.GetStorage().MountPath, dstDirActualPath),
|
||||
Func: func(tsk *task.Task[uint64]) error {
|
||||
defer wg.Done()
|
||||
size, _ := strconv.ParseInt(file.Length, 10, 64)
|
||||
mimetype := utils.GetMimeType(file.Path)
|
||||
f, err := os.Open(file.Path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open file %s", file.Path)
|
||||
}
|
||||
s := stream.FileStream{
|
||||
Obj: &model.Object{
|
||||
Name: path.Base(file.Path),
|
||||
Size: size,
|
||||
Modified: time.Now(),
|
||||
IsFolder: false,
|
||||
},
|
||||
Reader: f,
|
||||
Closers: utils.NewClosers(f),
|
||||
Mimetype: mimetype,
|
||||
}
|
||||
ss, err := stream.NewSeekableStream(s, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relDir, err := filepath.Rel(m.tempDir, filepath.Dir(file.Path))
|
||||
if err != nil {
|
||||
log.Errorf("find relation directory error: %v", err)
|
||||
}
|
||||
newDistDir := filepath.Join(dstDirActualPath, relDir)
|
||||
return op.Put(tsk.Ctx, storage, newDistDir, ss, tsk.SetProgress)
|
||||
},
|
||||
}))
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/aria2"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
func InitAria2() {
|
||||
go func() {
|
||||
_, err := aria2.InitClient(2)
|
||||
if err != nil {
|
||||
//utils.Log.Errorf("failed to init aria2 client: %+v", err)
|
||||
utils.Log.Infof("Aria2 not ready.")
|
||||
}
|
||||
}()
|
||||
}
|
@ -4,6 +4,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/cmd/flags"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/pkg/utils/random"
|
||||
@ -139,12 +140,9 @@ func InitialSettings() []model.SettingItem {
|
||||
{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.ForwardDirectLinkParams, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL},
|
||||
{Key: conf.IgnoreDirectLinkParams, Value: "sign,alist_ts", Type: conf.TypeString, Group: model.GLOBAL},
|
||||
{Key: conf.WebauthnLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||||
|
||||
// aria2 settings
|
||||
{Key: conf.Aria2Uri, Value: "http://localhost:6800/jsonrpc", Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
|
||||
{Key: conf.Aria2Secret, Value: "", Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
|
||||
|
||||
// single settings
|
||||
{Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||
{Key: conf.SearchIndex, Value: "none", Type: conf.TypeSelect, Options: "database,database_non_full_text,bleve,none", Group: model.INDEX},
|
||||
@ -167,11 +165,8 @@ func InitialSettings() []model.SettingItem {
|
||||
{Key: conf.SSODefaultDir, Value: "/", Type: conf.TypeString, Group: model.SSO, Flag: model.PRIVATE},
|
||||
{Key: conf.SSODefaultPermission, Value: "0", Type: conf.TypeNumber, Group: model.SSO, Flag: model.PRIVATE},
|
||||
{Key: conf.SSOCompatibilityMode, Value: "false", Type: conf.TypeBool, Group: model.SSO, Flag: model.PUBLIC},
|
||||
|
||||
// qbittorrent settings
|
||||
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||
{Key: conf.QbittorrentSeedtime, Value: "0", Type: conf.TypeNumber, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||
}
|
||||
initialSettingItems = append(initialSettingItems, tool.Tools.Items()...)
|
||||
if flags.Dev {
|
||||
initialSettingItems = append(initialSettingItems, []model.SettingItem{
|
||||
{Key: "test_deprecated", Value: "test_value", Type: conf.TypeString, Flag: model.DEPRECATED},
|
||||
|
17
internal/bootstrap/offline_download.go
Normal file
17
internal/bootstrap/offline_download.go
Normal file
@ -0,0 +1,17 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
func InitOfflineDownloadTools() {
|
||||
for k, v := range tool.Tools {
|
||||
res, err := v.Init()
|
||||
if err != nil {
|
||||
utils.Log.Warnf("init tool %s failed: %s", k, err)
|
||||
} else {
|
||||
utils.Log.Infof("init tool %s success: %s", k, res)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
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.")
|
||||
}
|
||||
}()
|
||||
}
|
15
internal/bootstrap/task.go
Normal file
15
internal/bootstrap/task.go
Normal file
@ -0,0 +1,15 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/fs"
|
||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||
"github.com/xhofe/tache"
|
||||
)
|
||||
|
||||
func InitTaskManager() {
|
||||
fs.UploadTaskManager = tache.NewManager[*fs.UploadTask](tache.WithWorks(conf.Conf.Tasks.Upload.Workers), tache.WithMaxRetry(conf.Conf.Tasks.Upload.MaxRetry))
|
||||
fs.CopyTaskManager = tache.NewManager[*fs.CopyTask](tache.WithWorks(conf.Conf.Tasks.Copy.Workers), tache.WithMaxRetry(conf.Conf.Tasks.Copy.MaxRetry))
|
||||
tool.DownloadTaskManager = tache.NewManager[*tool.DownloadTask](tache.WithWorks(conf.Conf.Tasks.Download.Workers), tache.WithMaxRetry(conf.Conf.Tasks.Download.MaxRetry))
|
||||
tool.TransferTaskManager = tache.NewManager[*tool.TransferTask](tache.WithWorks(conf.Conf.Tasks.Transfer.Workers), tache.WithMaxRetry(conf.Conf.Tasks.Transfer.MaxRetry))
|
||||
}
|
@ -8,15 +8,15 @@ import (
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
Type string `json:"type" env:"DB_TYPE"`
|
||||
Host string `json:"host" env:"DB_HOST"`
|
||||
Port int `json:"port" env:"DB_PORT"`
|
||||
User string `json:"user" env:"DB_USER"`
|
||||
Password string `json:"password" env:"DB_PASS"`
|
||||
Name string `json:"name" env:"DB_NAME"`
|
||||
DBFile string `json:"db_file" env:"DB_FILE"`
|
||||
TablePrefix string `json:"table_prefix" env:"DB_TABLE_PREFIX"`
|
||||
SSLMode string `json:"ssl_mode" env:"DB_SSL_MODE"`
|
||||
Type string `json:"type" env:"TYPE"`
|
||||
Host string `json:"host" env:"HOST"`
|
||||
Port int `json:"port" env:"PORT"`
|
||||
User string `json:"user" env:"USER"`
|
||||
Password string `json:"password" env:"PASS"`
|
||||
Name string `json:"name" env:"NAME"`
|
||||
DBFile string `json:"db_file" env:"FILE"`
|
||||
TablePrefix string `json:"table_prefix" env:"TABLE_PREFIX"`
|
||||
SSLMode string `json:"ssl_mode" env:"SSL_MODE"`
|
||||
}
|
||||
|
||||
type Scheme struct {
|
||||
@ -39,20 +39,41 @@ type LogConfig struct {
|
||||
Compress bool `json:"compress" env:"COMPRESS"`
|
||||
}
|
||||
|
||||
type TaskConfig struct {
|
||||
Workers int `json:"workers" env:"WORKERS"`
|
||||
MaxRetry int `json:"max_retry" env:"MAX_RETRY"`
|
||||
}
|
||||
|
||||
type TasksConfig struct {
|
||||
Download TaskConfig `json:"download" envPrefix:"DOWNLOAD_"`
|
||||
Transfer TaskConfig `json:"transfer" envPrefix:"TRANSFER_"`
|
||||
Upload TaskConfig `json:"upload" envPrefix:"UPLOAD_"`
|
||||
Copy TaskConfig `json:"copy" envPrefix:"COPY_"`
|
||||
}
|
||||
|
||||
type Cors struct {
|
||||
AllowOrigins []string `json:"allow_origins" env:"ALLOW_ORIGINS"`
|
||||
AllowMethods []string `json:"allow_methods" env:"ALLOW_METHODS"`
|
||||
AllowHeaders []string `json:"allow_headers" env:"ALLOW_HEADERS"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Force bool `json:"force" env:"FORCE"`
|
||||
SiteURL string `json:"site_url" env:"SITE_URL"`
|
||||
Cdn string `json:"cdn" env:"CDN"`
|
||||
JwtSecret string `json:"jwt_secret" env:"JWT_SECRET"`
|
||||
TokenExpiresIn int `json:"token_expires_in" env:"TOKEN_EXPIRES_IN"`
|
||||
Database Database `json:"database"`
|
||||
Database Database `json:"database" envPrefix:"DB_"`
|
||||
Scheme Scheme `json:"scheme"`
|
||||
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
||||
BleveDir string `json:"bleve_dir" env:"BLEVE_DIR"`
|
||||
DistDir string `json:"dist_dir"`
|
||||
Log LogConfig `json:"log"`
|
||||
DelayedStart int `json:"delayed_start" env:"DELAYED_START"`
|
||||
MaxConnections int `json:"max_connections" env:"MAX_CONNECTIONS"`
|
||||
TlsInsecureSkipVerify bool `json:"tls_insecure_skip_verify" env:"TLS_INSECURE_SKIP_VERIFY"`
|
||||
Tasks TasksConfig `json:"tasks" envPrefix:"TASKS_"`
|
||||
Cors Cors `json:"cors" envPrefix:"CORS_"`
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
@ -89,5 +110,27 @@ func DefaultConfig() *Config {
|
||||
},
|
||||
MaxConnections: 0,
|
||||
TlsInsecureSkipVerify: true,
|
||||
Tasks: TasksConfig{
|
||||
Download: TaskConfig{
|
||||
Workers: 5,
|
||||
MaxRetry: 1,
|
||||
},
|
||||
Transfer: TaskConfig{
|
||||
Workers: 5,
|
||||
MaxRetry: 2,
|
||||
},
|
||||
Upload: TaskConfig{
|
||||
Workers: 5,
|
||||
},
|
||||
Copy: TaskConfig{
|
||||
Workers: 5,
|
||||
MaxRetry: 2,
|
||||
},
|
||||
},
|
||||
Cors: Cors{
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{"*"},
|
||||
AllowHeaders: []string{"*"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ const (
|
||||
OcrApi = "ocr_api"
|
||||
FilenameCharMapping = "filename_char_mapping"
|
||||
ForwardDirectLinkParams = "forward_direct_link_params"
|
||||
IgnoreDirectLinkParams = "ignore_direct_link_params"
|
||||
WebauthnLoginEnabled = "webauthn_login_enabled"
|
||||
|
||||
// index
|
||||
|
@ -11,7 +11,7 @@ type Config struct {
|
||||
DefaultRoot string `json:"default_root"`
|
||||
CheckStatus bool `json:"-"`
|
||||
Alert string `json:"alert"` //info,success,warning,danger
|
||||
NoOverwriteUpload bool `json:"-"`
|
||||
NoOverwriteUpload bool `json:"-"` // whether to support overwrite upload
|
||||
}
|
||||
|
||||
func (c Config) MustProxy() bool {
|
||||
|
@ -109,7 +109,7 @@ type PutResult interface {
|
||||
Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up UpdateProgress) (model.Obj, error)
|
||||
}
|
||||
|
||||
type UpdateProgress func(percentage int)
|
||||
type UpdateProgress func(percentage float64)
|
||||
|
||||
type Progress struct {
|
||||
Total int64
|
||||
@ -120,7 +120,7 @@ type Progress struct {
|
||||
func (p *Progress) Write(b []byte) (n int, err error) {
|
||||
n = len(b)
|
||||
p.Done += int64(n)
|
||||
p.up(int(float64(p.Done) / float64(p.Total) * 100))
|
||||
p.up(float64(p.Done) / float64(p.Total) * 100)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -29,3 +29,7 @@ func NewErr(err error, format string, a ...any) error {
|
||||
func IsNotFoundError(err error) bool {
|
||||
return errors.Is(pkgerr.Cause(err), ObjectNotFound) || errors.Is(pkgerr.Cause(err), StorageNotFound)
|
||||
}
|
||||
|
||||
func IsNotSupportError(err error) bool {
|
||||
return errors.Is(pkgerr.Cause(err), NotSupport)
|
||||
}
|
||||
|
@ -3,24 +3,39 @@ package fs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
stdpath "path"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
"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"
|
||||
"github.com/xhofe/tache"
|
||||
"net/http"
|
||||
stdpath "path"
|
||||
)
|
||||
|
||||
var CopyTaskManager = task.NewTaskManager(3, func(tid *uint64) {
|
||||
atomic.AddUint64(tid, 1)
|
||||
})
|
||||
type CopyTask struct {
|
||||
tache.Base
|
||||
Status string `json:"status"`
|
||||
srcStorage, dstStorage driver.Driver
|
||||
srcObjPath, dstDirPath string
|
||||
}
|
||||
|
||||
func (t *CopyTask) GetName() string {
|
||||
return fmt.Sprintf("copy [%s](%s) to [%s](%s)",
|
||||
t.srcStorage.GetStorage().MountPath, t.srcObjPath, t.dstStorage.GetStorage().MountPath, t.dstDirPath)
|
||||
}
|
||||
|
||||
func (t *CopyTask) GetStatus() string {
|
||||
return t.Status
|
||||
}
|
||||
|
||||
func (t *CopyTask) Run() error {
|
||||
return copyBetween2Storages(t, t.srcStorage, t.dstStorage, t.srcObjPath, t.dstDirPath)
|
||||
}
|
||||
|
||||
var CopyTaskManager *tache.Manager[*CopyTask]
|
||||
|
||||
// Copy if in the same storage, call move method
|
||||
// if not, add copy task
|
||||
@ -63,59 +78,52 @@ func _copy(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool
|
||||
}
|
||||
}
|
||||
// not in the same storage
|
||||
CopyTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
|
||||
Name: fmt.Sprintf("copy [%s](%s) to [%s](%s)", srcStorage.GetStorage().MountPath, srcObjActualPath, dstStorage.GetStorage().MountPath, dstDirActualPath),
|
||||
Func: func(task *task.Task[uint64]) error {
|
||||
return copyBetween2Storages(task, srcStorage, dstStorage, srcObjActualPath, dstDirActualPath)
|
||||
},
|
||||
}))
|
||||
CopyTaskManager.Add(&CopyTask{
|
||||
srcStorage: srcStorage,
|
||||
dstStorage: dstStorage,
|
||||
srcObjPath: srcObjActualPath,
|
||||
dstDirPath: dstDirActualPath,
|
||||
})
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func copyBetween2Storages(t *task.Task[uint64], srcStorage, dstStorage driver.Driver, srcObjPath, dstDirPath string) error {
|
||||
t.SetStatus("getting src object")
|
||||
srcObj, err := op.Get(t.Ctx, srcStorage, srcObjPath)
|
||||
func copyBetween2Storages(t *CopyTask, srcStorage, dstStorage driver.Driver, srcObjPath, dstDirPath string) error {
|
||||
t.Status = "getting src object"
|
||||
srcObj, err := op.Get(t.Ctx(), srcStorage, srcObjPath)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed get src [%s] file", srcObjPath)
|
||||
}
|
||||
if srcObj.IsDir() {
|
||||
t.SetStatus("src object is dir, listing objs")
|
||||
objs, err := op.List(t.Ctx, srcStorage, srcObjPath, model.ListArgs{})
|
||||
t.Status = "src object is dir, listing objs"
|
||||
objs, err := op.List(t.Ctx(), srcStorage, srcObjPath, model.ListArgs{})
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed list src [%s] objs", srcObjPath)
|
||||
}
|
||||
for _, obj := range objs {
|
||||
if utils.IsCanceled(t.Ctx) {
|
||||
if utils.IsCanceled(t.Ctx()) {
|
||||
return nil
|
||||
}
|
||||
srcObjPath := stdpath.Join(srcObjPath, obj.GetName())
|
||||
dstObjPath := stdpath.Join(dstDirPath, srcObj.GetName())
|
||||
CopyTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
|
||||
Name: fmt.Sprintf("copy [%s](%s) to [%s](%s)", srcStorage.GetStorage().MountPath, srcObjPath, dstStorage.GetStorage().MountPath, dstObjPath),
|
||||
Func: func(t *task.Task[uint64]) error {
|
||||
return copyBetween2Storages(t, srcStorage, dstStorage, srcObjPath, dstObjPath)
|
||||
},
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
CopyTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
|
||||
Name: fmt.Sprintf("copy [%s](%s) to [%s](%s)", srcStorage.GetStorage().MountPath, srcObjPath, dstStorage.GetStorage().MountPath, dstDirPath),
|
||||
Func: func(t *task.Task[uint64]) error {
|
||||
err := copyFileBetween2Storages(t, srcStorage, dstStorage, srcObjPath, dstDirPath)
|
||||
log.Debugf("copy file between storages: %+v", err)
|
||||
return err
|
||||
},
|
||||
}))
|
||||
CopyTaskManager.Add(&CopyTask{
|
||||
srcStorage: srcStorage,
|
||||
dstStorage: dstStorage,
|
||||
srcObjPath: srcObjPath,
|
||||
dstDirPath: dstObjPath,
|
||||
})
|
||||
}
|
||||
t.Status = "src object is dir, added all copy tasks of objs"
|
||||
return nil
|
||||
}
|
||||
return copyFileBetween2Storages(t, srcStorage, dstStorage, srcObjPath, dstDirPath)
|
||||
}
|
||||
|
||||
func copyFileBetween2Storages(tsk *task.Task[uint64], srcStorage, dstStorage driver.Driver, srcFilePath, dstDirPath string) error {
|
||||
srcFile, err := op.Get(tsk.Ctx, srcStorage, srcFilePath)
|
||||
func copyFileBetween2Storages(tsk *CopyTask, srcStorage, dstStorage driver.Driver, srcFilePath, dstDirPath string) error {
|
||||
srcFile, err := op.Get(tsk.Ctx(), srcStorage, srcFilePath)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed get src [%s] file", srcFilePath)
|
||||
}
|
||||
link, _, err := op.Link(tsk.Ctx, srcStorage, srcFilePath, model.LinkArgs{
|
||||
link, _, err := op.Link(tsk.Ctx(), srcStorage, srcFilePath, model.LinkArgs{
|
||||
Header: http.Header{},
|
||||
})
|
||||
if err != nil {
|
||||
@ -123,12 +131,12 @@ func copyFileBetween2Storages(tsk *task.Task[uint64], srcStorage, dstStorage dri
|
||||
}
|
||||
fs := stream.FileStream{
|
||||
Obj: srcFile,
|
||||
Ctx: tsk.Ctx,
|
||||
Ctx: tsk.Ctx(),
|
||||
}
|
||||
// any link provided is seekable
|
||||
ss, err := stream.NewSeekableStream(fs, link)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed get [%s] stream", srcFilePath)
|
||||
}
|
||||
return op.Put(tsk.Ctx, dstStorage, dstDirPath, ss, tsk.SetProgress, true)
|
||||
return op.Put(tsk.Ctx(), dstStorage, dstDirPath, ss, tsk.SetProgress, true)
|
||||
}
|
||||
|
@ -3,18 +3,34 @@ package fs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/task"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/xhofe/tache"
|
||||
)
|
||||
|
||||
var UploadTaskManager = task.NewTaskManager(3, func(tid *uint64) {
|
||||
atomic.AddUint64(tid, 1)
|
||||
})
|
||||
type UploadTask struct {
|
||||
tache.Base
|
||||
storage driver.Driver
|
||||
dstDirActualPath string
|
||||
file model.FileStreamer
|
||||
}
|
||||
|
||||
func (t *UploadTask) GetName() string {
|
||||
return fmt.Sprintf("upload %s to [%s](%s)", t.file.GetName(), t.storage.GetStorage().MountPath, t.dstDirActualPath)
|
||||
}
|
||||
|
||||
func (t *UploadTask) GetStatus() string {
|
||||
return "uploading"
|
||||
}
|
||||
|
||||
func (t *UploadTask) Run() error {
|
||||
return op.Put(t.Ctx(), t.storage, t.dstDirActualPath, t.file, t.SetProgress, true)
|
||||
}
|
||||
|
||||
var UploadTaskManager *tache.Manager[*UploadTask]
|
||||
|
||||
// putAsTask add as a put task and return immediately
|
||||
func putAsTask(dstDirPath string, file model.FileStreamer) error {
|
||||
@ -33,12 +49,11 @@ func putAsTask(dstDirPath string, file model.FileStreamer) error {
|
||||
//file.SetReader(tempFile)
|
||||
//file.SetTmpFile(tempFile)
|
||||
}
|
||||
UploadTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
|
||||
Name: fmt.Sprintf("upload %s to [%s](%s)", file.GetName(), storage.GetStorage().MountPath, dstDirActualPath),
|
||||
Func: func(task *task.Task[uint64]) error {
|
||||
return op.Put(task.Ctx, storage, dstDirActualPath, file, task.SetProgress, true)
|
||||
},
|
||||
}))
|
||||
UploadTaskManager.Add(&UploadTask{
|
||||
storage: storage,
|
||||
dstDirActualPath: dstDirActualPath,
|
||||
file: file,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ const (
|
||||
STYLE
|
||||
PREVIEW
|
||||
GLOBAL
|
||||
ARIA2
|
||||
OFFLINE_DOWNLOAD
|
||||
INDEX
|
||||
SSO
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
@ -24,6 +25,7 @@ type User struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"` // unique key
|
||||
Username string `json:"username" gorm:"unique" binding:"required"` // username
|
||||
PwdHash string `json:"-"` // password hash
|
||||
PwdTS int64 `json:"-"` // password timestamp
|
||||
Salt string `json:"-"` // unique salt
|
||||
Password string `json:"password"` // password
|
||||
BasePath string `json:"base_path"` // base path
|
||||
@ -32,7 +34,7 @@ type User struct {
|
||||
// Determine permissions by bit
|
||||
// 0: can see hidden files
|
||||
// 1: can access without password
|
||||
// 2: can add aria2 tasks
|
||||
// 2: can add offline download tasks
|
||||
// 3: can mkdir and upload
|
||||
// 4: can rename
|
||||
// 5: can move
|
||||
@ -40,7 +42,6 @@ type User struct {
|
||||
// 7: can remove
|
||||
// 8: webdav read
|
||||
// 9: webdav write
|
||||
// 10: can add qbittorrent tasks
|
||||
Permission int32 `json:"permission"`
|
||||
OtpSecret string `json:"-"`
|
||||
SsoID string `json:"sso_id"` // unique by sso platform
|
||||
@ -72,6 +73,7 @@ func (u *User) ValidatePwdStaticHash(pwdStaticHash string) error {
|
||||
func (u *User) SetPassword(pwd string) *User {
|
||||
u.Salt = random.String(16)
|
||||
u.PwdHash = TwoHashPwd(pwd, u.Salt)
|
||||
u.PwdTS = time.Now().Unix()
|
||||
return u
|
||||
}
|
||||
|
||||
@ -83,7 +85,7 @@ func (u *User) CanAccessWithoutPassword() bool {
|
||||
return u.IsAdmin() || (u.Permission>>1)&1 == 1
|
||||
}
|
||||
|
||||
func (u *User) CanAddAria2Tasks() bool {
|
||||
func (u *User) CanAddOfflineDownloadTasks() bool {
|
||||
return u.IsAdmin() || (u.Permission>>2)&1 == 1
|
||||
}
|
||||
|
||||
@ -115,10 +117,6 @@ func (u *User) CanWebdavManage() bool {
|
||||
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) {
|
||||
return utils.JoinBasePath(u.BasePath, reqPath)
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
@ -169,7 +168,7 @@ func newDownloadRangeClient(data []byte) (*downloadCaptureClient, *int, *[]strin
|
||||
header := &http.Header{}
|
||||
header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, fin-1, len(data)))
|
||||
return &http.Response{
|
||||
Body: ioutil.NopCloser(bytes.NewReader(bodyBytes)),
|
||||
Body: io.NopCloser(bytes.NewReader(bodyBytes)),
|
||||
Header: *header,
|
||||
ContentLength: int64(len(bodyBytes)),
|
||||
}, nil
|
||||
|
7
internal/offline_download/all.go
Normal file
7
internal/offline_download/all.go
Normal file
@ -0,0 +1,7 @@
|
||||
package offline_download
|
||||
|
||||
import (
|
||||
_ "github.com/alist-org/alist/v3/internal/offline_download/aria2"
|
||||
_ "github.com/alist-org/alist/v3/internal/offline_download/http"
|
||||
_ "github.com/alist-org/alist/v3/internal/offline_download/qbit"
|
||||
)
|
126
internal/offline_download/aria2/aria2.go
Normal file
126
internal/offline_download/aria2/aria2.go
Normal file
@ -0,0 +1,126 @@
|
||||
package aria2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/pkg/aria2/rpc"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var notify = NewNotify()
|
||||
|
||||
type Aria2 struct {
|
||||
client rpc.Client
|
||||
}
|
||||
|
||||
func (a *Aria2) Run(task *tool.DownloadTask) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (a *Aria2) Name() string {
|
||||
return "aria2"
|
||||
}
|
||||
|
||||
func (a *Aria2) Items() []model.SettingItem {
|
||||
// aria2 settings
|
||||
return []model.SettingItem{
|
||||
{Key: conf.Aria2Uri, Value: "http://localhost:6800/jsonrpc", Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
|
||||
{Key: conf.Aria2Secret, Value: "", Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Aria2) Init() (string, error) {
|
||||
a.client = nil
|
||||
uri := setting.GetStr(conf.Aria2Uri)
|
||||
secret := setting.GetStr(conf.Aria2Secret)
|
||||
c, err := rpc.New(context.Background(), uri, secret, 4*time.Second, notify)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to init aria2 client")
|
||||
}
|
||||
version, err := c.GetVersion()
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed get aria2 version")
|
||||
}
|
||||
a.client = c
|
||||
log.Infof("using aria2 version: %s", version.Version)
|
||||
return fmt.Sprintf("aria2 version: %s", version.Version), nil
|
||||
}
|
||||
|
||||
func (a *Aria2) IsReady() bool {
|
||||
return a.client != nil
|
||||
}
|
||||
|
||||
func (a *Aria2) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||
options := map[string]interface{}{
|
||||
"dir": args.TempDir,
|
||||
}
|
||||
gid, err := a.client.AddURI([]string{args.Url}, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
notify.Signals.Store(gid, args.Signal)
|
||||
return gid, nil
|
||||
}
|
||||
|
||||
func (a *Aria2) Remove(task *tool.DownloadTask) error {
|
||||
_, err := a.client.Remove(task.GID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Aria2) Status(task *tool.DownloadTask) (*tool.Status, error) {
|
||||
info, err := a.client.TellStatus(task.GID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
total, err := strconv.ParseUint(info.TotalLength, 10, 64)
|
||||
if err != nil {
|
||||
total = 0
|
||||
}
|
||||
downloaded, err := strconv.ParseUint(info.CompletedLength, 10, 64)
|
||||
if err != nil {
|
||||
downloaded = 0
|
||||
}
|
||||
s := &tool.Status{
|
||||
Completed: info.Status == "complete",
|
||||
Err: err,
|
||||
}
|
||||
s.Progress = float64(downloaded) / float64(total) * 100
|
||||
if len(info.FollowedBy) != 0 {
|
||||
s.NewGID = info.FollowedBy[0]
|
||||
notify.Signals.Delete(task.GID)
|
||||
notify.Signals.Store(s.NewGID, task.Signal)
|
||||
}
|
||||
switch info.Status {
|
||||
case "complete":
|
||||
s.Completed = true
|
||||
case "error":
|
||||
s.Err = errors.Errorf("failed to download %s, error: %s", task.GID, info.ErrorMessage)
|
||||
case "active":
|
||||
s.Status = "aria2: " + info.Status
|
||||
if info.Seeder == "true" {
|
||||
s.Completed = true
|
||||
}
|
||||
case "waiting", "paused":
|
||||
s.Status = "aria2: " + info.Status
|
||||
case "removed":
|
||||
s.Err = errors.Errorf("failed to download %s, removed", task.GID)
|
||||
default:
|
||||
return nil, errors.Errorf("[aria2] unknown status %s", info.Status)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
var _ tool.Tool = (*Aria2)(nil)
|
||||
|
||||
func init() {
|
||||
tool.Tools.Add(&Aria2{})
|
||||
}
|
85
internal/offline_download/http/client.go
Normal file
85
internal/offline_download/http/client.go
Normal file
@ -0,0 +1,85 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type SimpleHttp struct {
|
||||
client http.Client
|
||||
}
|
||||
|
||||
func (s SimpleHttp) Name() string {
|
||||
return "SimpleHttp"
|
||||
}
|
||||
|
||||
func (s SimpleHttp) Items() []model.SettingItem {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SimpleHttp) Init() (string, error) {
|
||||
return "ok", nil
|
||||
}
|
||||
|
||||
func (s SimpleHttp) IsReady() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s SimpleHttp) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (s SimpleHttp) Remove(task *tool.DownloadTask) error {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (s SimpleHttp) Status(task *tool.DownloadTask) (*tool.Status, error) {
|
||||
panic("should not be called")
|
||||
}
|
||||
|
||||
func (s SimpleHttp) Run(task *tool.DownloadTask) error {
|
||||
u := task.Url
|
||||
// parse url
|
||||
_u, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(task.Ctx(), http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 400 {
|
||||
return fmt.Errorf("http status code %d", resp.StatusCode)
|
||||
}
|
||||
filename := path.Base(_u.Path)
|
||||
if n, err := parseFilenameFromContentDisposition(resp.Header.Get("Content-Disposition")); err == nil {
|
||||
filename = n
|
||||
}
|
||||
// save to temp dir
|
||||
_ = os.MkdirAll(task.TempDir, os.ModePerm)
|
||||
filePath := filepath.Join(task.TempDir, filename)
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
fileSize := resp.ContentLength
|
||||
err = utils.CopyWithCtx(task.Ctx(), file, resp.Body, fileSize, task.SetProgress)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
tool.Tools.Add(&SimpleHttp{})
|
||||
}
|
21
internal/offline_download/http/util.go
Normal file
21
internal/offline_download/http/util.go
Normal file
@ -0,0 +1,21 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
)
|
||||
|
||||
func parseFilenameFromContentDisposition(contentDisposition string) (string, error) {
|
||||
if contentDisposition == "" {
|
||||
return "", fmt.Errorf("Content-Disposition is empty")
|
||||
}
|
||||
_, params, err := mime.ParseMediaType(contentDisposition)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
filename := params["filename"]
|
||||
if filename == "" {
|
||||
return "", fmt.Errorf("filename not found in Content-Disposition: [%s]", contentDisposition)
|
||||
}
|
||||
return filename, nil
|
||||
}
|
85
internal/offline_download/qbit/qbit.go
Normal file
85
internal/offline_download/qbit/qbit.go
Normal file
@ -0,0 +1,85 @@
|
||||
package qbit
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/pkg/qbittorrent"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type QBittorrent struct {
|
||||
client qbittorrent.Client
|
||||
}
|
||||
|
||||
func (a *QBittorrent) Run(task *tool.DownloadTask) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (a *QBittorrent) Name() string {
|
||||
return "qBittorrent"
|
||||
}
|
||||
|
||||
func (a *QBittorrent) Items() []model.SettingItem {
|
||||
// qBittorrent settings
|
||||
return []model.SettingItem{
|
||||
{Key: conf.QbittorrentUrl, Value: "http://admin:adminadmin@localhost:8080/", Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
|
||||
{Key: conf.QbittorrentSeedtime, Value: "0", Type: conf.TypeNumber, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *QBittorrent) Init() (string, error) {
|
||||
a.client = nil
|
||||
url := setting.GetStr(conf.QbittorrentUrl)
|
||||
qbClient, err := qbittorrent.New(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
a.client = qbClient
|
||||
return "ok", nil
|
||||
}
|
||||
|
||||
func (a *QBittorrent) IsReady() bool {
|
||||
return a.client != nil
|
||||
}
|
||||
|
||||
func (a *QBittorrent) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||
err := a.client.AddFromLink(args.Url, args.TempDir, args.UID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return args.UID, nil
|
||||
}
|
||||
|
||||
func (a *QBittorrent) Remove(task *tool.DownloadTask) error {
|
||||
err := a.client.Delete(task.GID, true)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *QBittorrent) Status(task *tool.DownloadTask) (*tool.Status, error) {
|
||||
info, err := a.client.GetInfo(task.GID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &tool.Status{}
|
||||
s.Progress = float64(info.Completed) / float64(info.Size) * 100
|
||||
switch info.State {
|
||||
case qbittorrent.UPLOADING, qbittorrent.PAUSEDUP, qbittorrent.QUEUEDUP, qbittorrent.STALLEDUP, qbittorrent.FORCEDUP, qbittorrent.CHECKINGUP:
|
||||
s.Completed = true
|
||||
case qbittorrent.ALLOCATING, qbittorrent.DOWNLOADING, qbittorrent.METADL, qbittorrent.PAUSEDDL, qbittorrent.QUEUEDDL, qbittorrent.STALLEDDL, qbittorrent.CHECKINGDL, qbittorrent.FORCEDDL, qbittorrent.CHECKINGRESUMEDATA, qbittorrent.MOVING:
|
||||
s.Status = "[qBittorrent] downloading"
|
||||
case qbittorrent.ERROR, qbittorrent.MISSINGFILES, qbittorrent.UNKNOWN:
|
||||
s.Err = errors.Errorf("[qBittorrent] failed to download %s, error: %s", task.GID, info.State)
|
||||
default:
|
||||
s.Err = errors.Errorf("[qBittorrent] unknown error occurred downloading %s", task.GID)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
var _ tool.Tool = (*QBittorrent)(nil)
|
||||
|
||||
func init() {
|
||||
tool.Tools.Add(&QBittorrent{})
|
||||
}
|
75
internal/offline_download/tool/add.go
Normal file
75
internal/offline_download/tool/add.go
Normal file
@ -0,0 +1,75 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type DeletePolicy string
|
||||
|
||||
const (
|
||||
DeleteOnUploadSucceed DeletePolicy = "delete_on_upload_succeed"
|
||||
DeleteOnUploadFailed DeletePolicy = "delete_on_upload_failed"
|
||||
DeleteNever DeletePolicy = "delete_never"
|
||||
DeleteAlways DeletePolicy = "delete_always"
|
||||
)
|
||||
|
||||
type AddURLArgs struct {
|
||||
URL string
|
||||
DstDirPath string
|
||||
Tool string
|
||||
DeletePolicy DeletePolicy
|
||||
}
|
||||
|
||||
func AddURL(ctx context.Context, args *AddURLArgs) error {
|
||||
// get tool
|
||||
tool, err := Tools.Get(args.Tool)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed get tool")
|
||||
}
|
||||
// check tool is ready
|
||||
if !tool.IsReady() {
|
||||
// try to init tool
|
||||
if _, err := tool.Init(); err != nil {
|
||||
return errors.Wrapf(err, "failed init tool %s", args.Tool)
|
||||
}
|
||||
}
|
||||
// check storage
|
||||
storage, dstDirActualPath, err := op.GetStorageAndActualPath(args.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)
|
||||
}
|
||||
}
|
||||
|
||||
uid := uuid.NewString()
|
||||
tempDir := filepath.Join(conf.Conf.TempDir, args.Tool, uid)
|
||||
t := &DownloadTask{
|
||||
Url: args.URL,
|
||||
DstDirPath: args.DstDirPath,
|
||||
TempDir: tempDir,
|
||||
DeletePolicy: args.DeletePolicy,
|
||||
tool: tool,
|
||||
}
|
||||
DownloadTaskManager.Add(t)
|
||||
return nil
|
||||
}
|
17
internal/offline_download/tool/all_test.go
Normal file
17
internal/offline_download/tool/all_test.go
Normal file
@ -0,0 +1,17 @@
|
||||
package tool_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/offline_download/tool"
|
||||
)
|
||||
|
||||
func TestGetFiles(t *testing.T) {
|
||||
files, err := tool.GetFiles("..")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, file := range files {
|
||||
t.Log(file.Name, file.Size, file.Path, file.Modified)
|
||||
}
|
||||
}
|
66
internal/offline_download/tool/base.go
Normal file
66
internal/offline_download/tool/base.go
Normal file
@ -0,0 +1,66 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type AddUrlArgs struct {
|
||||
Url string
|
||||
UID string
|
||||
TempDir string
|
||||
Signal chan int
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Progress float64
|
||||
NewGID string
|
||||
Completed bool
|
||||
Status string
|
||||
Err error
|
||||
}
|
||||
|
||||
type Tool interface {
|
||||
Name() string
|
||||
// Items return the setting items the tool need
|
||||
Items() []model.SettingItem
|
||||
Init() (string, error)
|
||||
IsReady() bool
|
||||
// AddURL add an uri to download, return the task id
|
||||
AddURL(args *AddUrlArgs) (string, error)
|
||||
// Remove the download if task been canceled
|
||||
Remove(task *DownloadTask) error
|
||||
// Status return the status of the download task, if an error occurred, return the error in Status.Err
|
||||
Status(task *DownloadTask) (*Status, error)
|
||||
|
||||
// Run for simple http download
|
||||
Run(task *DownloadTask) error
|
||||
}
|
||||
|
||||
type GetFileser interface {
|
||||
// GetFiles return the files of the download task, if nil, means walk the temp dir to get the files
|
||||
GetFiles(task *DownloadTask) []File
|
||||
}
|
||||
|
||||
type File struct {
|
||||
// ReadCloser for http client
|
||||
ReadCloser io.ReadCloser
|
||||
Name string
|
||||
Size int64
|
||||
Path string
|
||||
Modified time.Time
|
||||
}
|
||||
|
||||
func (f *File) GetReadCloser() (io.ReadCloser, error) {
|
||||
if f.ReadCloser != nil {
|
||||
return f.ReadCloser, nil
|
||||
}
|
||||
file, err := os.Open(f.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
}
|
155
internal/offline_download/tool/download.go
Normal file
155
internal/offline_download/tool/download.go
Normal file
@ -0,0 +1,155 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/xhofe/tache"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DownloadTask struct {
|
||||
tache.Base
|
||||
Url string `json:"url"`
|
||||
DstDirPath string `json:"dst_dir_path"`
|
||||
TempDir string `json:"temp_dir"`
|
||||
DeletePolicy DeletePolicy `json:"delete_policy"`
|
||||
|
||||
Status string `json:"status"`
|
||||
Signal chan int `json:"-"`
|
||||
GID string `json:"-"`
|
||||
finish chan struct{}
|
||||
tool Tool
|
||||
callStatusRetried int
|
||||
}
|
||||
|
||||
func (t *DownloadTask) Run() error {
|
||||
if err := t.tool.Run(t); !errs.IsNotSupportError(err) {
|
||||
if err == nil {
|
||||
return t.Complete()
|
||||
}
|
||||
return err
|
||||
}
|
||||
t.Signal = make(chan int)
|
||||
t.finish = make(chan struct{})
|
||||
defer func() {
|
||||
t.Signal = nil
|
||||
t.finish = nil
|
||||
}()
|
||||
gid, err := t.tool.AddURL(&AddUrlArgs{
|
||||
Url: t.Url,
|
||||
UID: t.ID,
|
||||
TempDir: t.TempDir,
|
||||
Signal: t.Signal,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.GID = gid
|
||||
var (
|
||||
ok bool
|
||||
)
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-t.CtxDone():
|
||||
err := t.tool.Remove(t)
|
||||
return err
|
||||
case <-t.Signal:
|
||||
ok, err = t.Update()
|
||||
if ok {
|
||||
break outer
|
||||
}
|
||||
case <-time.After(time.Second * 3):
|
||||
ok, err = t.Update()
|
||||
if ok {
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Status = "aria2 download completed, maybe transferring"
|
||||
t.finish <- struct{}{}
|
||||
t.Status = "offline download completed"
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update download status, return true if download completed
|
||||
func (t *DownloadTask) Update() (bool, error) {
|
||||
info, err := t.tool.Status(t)
|
||||
if err != nil {
|
||||
t.callStatusRetried++
|
||||
log.Errorf("failed to get status of %s, retried %d times", t.ID, t.callStatusRetried)
|
||||
return false, nil
|
||||
}
|
||||
if t.callStatusRetried > 5 {
|
||||
return true, errors.Errorf("failed to get status of %s, retried %d times", t.ID, t.callStatusRetried)
|
||||
}
|
||||
t.callStatusRetried = 0
|
||||
t.SetProgress(info.Progress)
|
||||
t.Status = fmt.Sprintf("[%s]: %s", t.tool.Name(), info.Status)
|
||||
if info.NewGID != "" {
|
||||
log.Debugf("followen by: %+v", info.NewGID)
|
||||
t.GID = info.NewGID
|
||||
return false, nil
|
||||
}
|
||||
// if download completed
|
||||
if info.Completed {
|
||||
err := t.Complete()
|
||||
return true, errors.WithMessage(err, "failed to transfer file")
|
||||
}
|
||||
// if download failed
|
||||
if info.Err != nil {
|
||||
return true, errors.Errorf("failed to download %s, error: %s", t.ID, info.Err.Error())
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (t *DownloadTask) Complete() error {
|
||||
var (
|
||||
files []File
|
||||
err error
|
||||
)
|
||||
if getFileser, ok := t.tool.(GetFileser); ok {
|
||||
files = getFileser.GetFiles(t)
|
||||
} else {
|
||||
files, err = GetFiles(t.TempDir)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get files")
|
||||
}
|
||||
}
|
||||
// upload files
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(files))
|
||||
go func() {
|
||||
wg.Wait()
|
||||
t.finish <- struct{}{}
|
||||
}()
|
||||
for i, _ := range files {
|
||||
file := files[i]
|
||||
TransferTaskManager.Add(&TransferTask{
|
||||
file: file,
|
||||
dstDirPath: t.DstDirPath,
|
||||
wg: &wg,
|
||||
tempDir: t.TempDir,
|
||||
deletePolicy: t.DeletePolicy,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *DownloadTask) GetName() string {
|
||||
return fmt.Sprintf("download %s to (%s)", t.Url, t.DstDirPath)
|
||||
}
|
||||
|
||||
func (t *DownloadTask) GetStatus() string {
|
||||
return t.Status
|
||||
}
|
||||
|
||||
var (
|
||||
DownloadTaskManager *tache.Manager[*DownloadTask]
|
||||
)
|
39
internal/offline_download/tool/tools.go
Normal file
39
internal/offline_download/tool/tools.go
Normal file
@ -0,0 +1,39 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
var (
|
||||
Tools = make(ToolsManager)
|
||||
)
|
||||
|
||||
type ToolsManager map[string]Tool
|
||||
|
||||
func (t ToolsManager) Get(name string) (Tool, error) {
|
||||
if tool, ok := t[name]; ok {
|
||||
return tool, nil
|
||||
}
|
||||
return nil, fmt.Errorf("tool %s not found", name)
|
||||
}
|
||||
|
||||
func (t ToolsManager) Add(tool Tool) {
|
||||
t[tool.Name()] = tool
|
||||
}
|
||||
|
||||
func (t ToolsManager) Names() []string {
|
||||
names := make([]string, 0, len(t))
|
||||
for name := range t {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (t ToolsManager) Items() []model.SettingItem {
|
||||
var items []model.SettingItem
|
||||
for _, tool := range t {
|
||||
items = append(items, tool.Items()...)
|
||||
}
|
||||
return items
|
||||
}
|
86
internal/offline_download/tool/transfer.go
Normal file
86
internal/offline_download/tool/transfer.go
Normal file
@ -0,0 +1,86 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/xhofe/tache"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type TransferTask struct {
|
||||
tache.Base
|
||||
file File
|
||||
dstDirPath string
|
||||
wg *sync.WaitGroup
|
||||
tempDir string
|
||||
deletePolicy DeletePolicy
|
||||
}
|
||||
|
||||
func (t *TransferTask) Run() error {
|
||||
defer t.wg.Done()
|
||||
// check dstDir again
|
||||
storage, dstDirActualPath, err := op.GetStorageAndActualPath(t.dstDirPath)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed get storage")
|
||||
}
|
||||
mimetype := utils.GetMimeType(t.file.Path)
|
||||
rc, err := t.file.GetReadCloser()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open file %s", t.file.Path)
|
||||
}
|
||||
s := &stream.FileStream{
|
||||
Ctx: nil,
|
||||
Obj: &model.Object{
|
||||
Name: filepath.Base(t.file.Path),
|
||||
Size: t.file.Size,
|
||||
Modified: t.file.Modified,
|
||||
IsFolder: false,
|
||||
},
|
||||
Reader: rc,
|
||||
Mimetype: mimetype,
|
||||
Closers: utils.NewClosers(rc),
|
||||
}
|
||||
relDir, err := filepath.Rel(t.tempDir, filepath.Dir(t.file.Path))
|
||||
if err != nil {
|
||||
log.Errorf("find relation directory error: %v", err)
|
||||
}
|
||||
newDistDir := filepath.Join(dstDirActualPath, relDir)
|
||||
return op.Put(t.Ctx(), storage, newDistDir, s, t.SetProgress)
|
||||
}
|
||||
|
||||
func (t *TransferTask) GetName() string {
|
||||
return fmt.Sprintf("transfer %s to [%s]", t.file.Path, t.dstDirPath)
|
||||
}
|
||||
|
||||
func (t *TransferTask) GetStatus() string {
|
||||
return "transferring"
|
||||
}
|
||||
|
||||
func (t *TransferTask) OnSucceeded() {
|
||||
if t.deletePolicy == DeleteOnUploadSucceed || t.deletePolicy == DeleteAlways {
|
||||
err := os.Remove(t.file.Path)
|
||||
if err != nil {
|
||||
log.Errorf("failed to delete file %s, error: %s", t.file.Path, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TransferTask) OnFailed() {
|
||||
if t.deletePolicy == DeleteOnUploadFailed || t.deletePolicy == DeleteAlways {
|
||||
err := os.Remove(t.file.Path)
|
||||
if err != nil {
|
||||
log.Errorf("failed to delete file %s, error: %s", t.file.Path, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
TransferTaskManager *tache.Manager[*TransferTask]
|
||||
)
|
28
internal/offline_download/tool/util.go
Normal file
28
internal/offline_download/tool/util.go
Normal file
@ -0,0 +1,28 @@
|
||||
package tool
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func GetFiles(dir string) ([]File, error) {
|
||||
var files []File
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
files = append(files, File{
|
||||
Name: info.Name(),
|
||||
Size: info.Size(),
|
||||
Path: path,
|
||||
Modified: info.ModTime(),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return files, nil
|
||||
}
|
@ -534,7 +534,7 @@ func Put(ctx context.Context, storage driver.Driver, dstDirPath string, file mod
|
||||
}
|
||||
// if up is nil, set a default to prevent panic
|
||||
if up == nil {
|
||||
up = func(p int) {}
|
||||
up = func(p float64) {}
|
||||
}
|
||||
|
||||
switch s := storage.(type) {
|
||||
|
@ -78,6 +78,10 @@ var settingItemHooks = map[string]SettingItemHook{
|
||||
log.Debugf("filename char mapping: %+v", conf.FilenameCharMap)
|
||||
return nil
|
||||
},
|
||||
conf.IgnoreDirectLinkParams: func(item *model.SettingItem) error {
|
||||
conf.SlicesMap[conf.IgnoreDirectLinkParams] = strings.Split(item.Value, ",")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func RegisterSettingItemHook(key string, hook SettingItemHook) {
|
||||
|
@ -1,60 +0,0 @@
|
||||
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/internal/setting"
|
||||
"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,
|
||||
seedtime: setting.GetInt(conf.QbittorrentSeedtime, 0),
|
||||
}
|
||||
return m.Loop()
|
||||
},
|
||||
}))
|
||||
return nil
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
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)
|
||||
}
|
||||
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.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.Delete("uuid-1", true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
package qbittorrent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
tsk *task.Task[string]
|
||||
tempDir string
|
||||
dstDirPath string
|
||||
seedtime int
|
||||
finish chan struct{}
|
||||
}
|
||||
|
||||
func (m *Monitor) Loop() error {
|
||||
var (
|
||||
err error
|
||||
completed bool
|
||||
)
|
||||
m.finish = make(chan struct{})
|
||||
|
||||
// wait for qbittorrent to parse torrent and create task
|
||||
m.tsk.SetStatus("waiting for qbittorrent to parse torrent and create task")
|
||||
waitCount := 0
|
||||
for {
|
||||
_, err := qbclient.GetInfo(m.tsk.ID)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
switch err.(type) {
|
||||
case InfoNotFoundError:
|
||||
break
|
||||
default:
|
||||
return err
|
||||
}
|
||||
|
||||
waitCount += 1
|
||||
if waitCount >= 60 {
|
||||
return errors.New("torrent parse timeout")
|
||||
}
|
||||
timer := time.NewTimer(time.Second)
|
||||
<-timer.C
|
||||
}
|
||||
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-m.tsk.Ctx.Done():
|
||||
// delete qbittorrent task and downloaded files when the task exits with error
|
||||
return qbclient.Delete(m.tsk.ID, true)
|
||||
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, PAUSEDUP, QUEUEDUP, STALLEDUP, FORCEDUP, CHECKINGUP:
|
||||
err = m.complete()
|
||||
return true, errors.WithMessage(err, "failed to transfer file")
|
||||
case ALLOCATING, DOWNLOADING, METADL, PAUSEDDL, QUEUEDDL, STALLEDDL, CHECKINGDL, FORCEDDL, CHECKINGRESUMEDATA, MOVING:
|
||||
m.tsk.SetStatus("qbittorrent downloading")
|
||||
return false, nil
|
||||
case ERROR, MISSINGFILES, UNKNOWN:
|
||||
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, dstBaseDir, 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))
|
||||
// delete qbittorrent task but do not delete the files before transferring to avoid qbittorrent
|
||||
// accessing downloaded files and throw `cannot access the file because it is being used by another process` error
|
||||
// err = qbclient.Delete(m.tsk.ID, false)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// upload files
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(files))
|
||||
go func() {
|
||||
wg.Wait()
|
||||
m.finish <- struct{}{}
|
||||
if m.seedtime < 0 {
|
||||
log.Debugf("do not delete qb task %s", m.tsk.ID)
|
||||
return
|
||||
}
|
||||
log.Debugf("delete qb task %s after %d minutes", m.tsk.ID, m.seedtime)
|
||||
<-time.After(time.Duration(m.seedtime) * time.Minute)
|
||||
err := qbclient.Delete(m.tsk.ID, true)
|
||||
if err != nil {
|
||||
log.Errorln(err.Error())
|
||||
}
|
||||
err = os.RemoveAll(m.tempDir)
|
||||
if err != nil {
|
||||
log.Errorf("failed to remove qbittorrent temp dir: %+v", err.Error())
|
||||
}
|
||||
}()
|
||||
for _, file := range files {
|
||||
tempPath := filepath.Join(m.tempDir, file.Name)
|
||||
dstPath := filepath.Join(dstBaseDir, file.Name)
|
||||
dstDir := filepath.Dir(dstPath)
|
||||
fileName := filepath.Base(dstPath)
|
||||
TransferTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
|
||||
Name: fmt.Sprintf("transfer %s to [%s](%s)", tempPath, storage.GetStorage().MountPath, dstPath),
|
||||
Func: func(tsk *task.Task[uint64]) error {
|
||||
defer wg.Done()
|
||||
size := file.Size
|
||||
mimetype := utils.GetMimeType(tempPath)
|
||||
f, err := os.Open(tempPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open file %s", tempPath)
|
||||
}
|
||||
s := stream.FileStream{
|
||||
Obj: &model.Object{
|
||||
Name: fileName,
|
||||
Size: size,
|
||||
Modified: time.Now(),
|
||||
IsFolder: false,
|
||||
},
|
||||
Reader: f,
|
||||
Closers: utils.NewClosers(f),
|
||||
Mimetype: mimetype,
|
||||
}
|
||||
ss, err := stream.NewSeekableStream(s, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return op.Put(tsk.Ctx, storage, dstDir, ss, tsk.SetProgress)
|
||||
},
|
||||
}))
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
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
|
||||
}
|
@ -26,7 +26,7 @@ type Task[K comparable] struct {
|
||||
Name string
|
||||
state string // pending, running, finished, canceling, canceled, errored
|
||||
status string
|
||||
progress int
|
||||
progress float64
|
||||
|
||||
Error error
|
||||
|
||||
@ -41,11 +41,11 @@ func (t *Task[K]) SetStatus(status string) {
|
||||
t.status = status
|
||||
}
|
||||
|
||||
func (t *Task[K]) SetProgress(percentage int) {
|
||||
func (t *Task[K]) SetProgress(percentage float64) {
|
||||
t.progress = percentage
|
||||
}
|
||||
|
||||
func (t Task[K]) GetProgress() int {
|
||||
func (t Task[K]) GetProgress() float64 {
|
||||
return t.progress
|
||||
}
|
||||
|
||||
|
@ -163,8 +163,15 @@ func GetObjType(filename string, isDir bool) int {
|
||||
return GetFileType(filename)
|
||||
}
|
||||
|
||||
var extraMimeTypes = map[string]string{
|
||||
".apk": "application/vnd.android.package-archive",
|
||||
}
|
||||
|
||||
func GetMimeType(name string) string {
|
||||
ext := path.Ext(name)
|
||||
if m, ok := extraMimeTypes[ext]; ok {
|
||||
return m
|
||||
}
|
||||
m := mime.TypeByExtension(ext)
|
||||
if m != "" {
|
||||
return m
|
||||
|
@ -5,10 +5,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/exp/constraints"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -21,7 +22,7 @@ func (rf readerFunc) Read(p []byte) (n int, err error) { return rf(p) }
|
||||
// CopyWithCtx slightly modified function signature:
|
||||
// - context has been added in order to propagate cancellation
|
||||
// - I do not return the number of bytes written, has it is not useful in my use case
|
||||
func CopyWithCtx(ctx context.Context, out io.Writer, in io.Reader, size int64, progress func(percentage int)) error {
|
||||
func CopyWithCtx(ctx context.Context, out io.Writer, in io.Reader, size int64, progress func(percentage float64)) error {
|
||||
// Copy will call the Reader and Writer interface multiple time, in order
|
||||
// to copy by chunk (avoiding loading the whole file in memory).
|
||||
// I insert the ability to cancel before read time as it is the earliest
|
||||
@ -40,7 +41,7 @@ func CopyWithCtx(ctx context.Context, out io.Writer, in io.Reader, size int64, p
|
||||
n, err := in.Read(p)
|
||||
if s > 0 && (err == nil || err == io.EOF) {
|
||||
finish += int64(n)
|
||||
progress(int(finish / s))
|
||||
progress(float64(finish) / float64(s))
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -12,12 +13,14 @@ var SecretKey []byte
|
||||
|
||||
type UserClaims struct {
|
||||
Username string `json:"username"`
|
||||
PwdTS int64 `json:"pwd_ts"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func GenerateToken(username string) (tokenString string, err error) {
|
||||
func GenerateToken(user *model.User) (tokenString string, err error) {
|
||||
claim := UserClaims{
|
||||
Username: username,
|
||||
Username: user.Username,
|
||||
PwdTS: user.PwdTS,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(conf.Conf.TokenExpiresIn) * time.Hour)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
|
@ -17,6 +17,10 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
||||
if link.MFile != nil {
|
||||
defer link.MFile.Close()
|
||||
attachFileName(w, file)
|
||||
contentType := link.Header.Get("Content-Type")
|
||||
if contentType != "" {
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
}
|
||||
http.ServeContent(w, r, file.GetName(), file.ModTime(), link.MFile)
|
||||
return nil
|
||||
} else if link.RangeReadCloser != nil {
|
||||
@ -76,4 +80,5 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
||||
func attachFileName(w http.ResponseWriter, file model.Obj) {
|
||||
fileName := file.GetName()
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, fileName, url.PathEscape(fileName)))
|
||||
w.Header().Set("Content-Type", utils.GetMimeType(fileName))
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
package handles
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/aria2"
|
||||
"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/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SetAria2Req struct {
|
||||
Uri string `json:"uri" form:"uri"`
|
||||
Secret string `json:"secret" form:"secret"`
|
||||
}
|
||||
|
||||
func SetAria2(c *gin.Context) {
|
||||
var req SetAria2Req
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
items := []model.SettingItem{
|
||||
{Key: conf.Aria2Uri, Value: req.Uri, Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
|
||||
{Key: conf.Aria2Secret, Value: req.Secret, Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
|
||||
}
|
||||
if err := op.SaveSettingItems(items); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
version, err := aria2.InitClient(2)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c, version)
|
||||
}
|
||||
|
||||
type AddAria2Req struct {
|
||||
Urls []string `json:"urls"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
func AddAria2(c *gin.Context) {
|
||||
user := c.MustGet("user").(*model.User)
|
||||
if !user.CanAddAria2Tasks() {
|
||||
common.ErrorStrResp(c, "permission denied", 403)
|
||||
return
|
||||
}
|
||||
if !aria2.IsAria2Ready() {
|
||||
// try to init client
|
||||
_, err := aria2.InitClient(2)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if !aria2.IsAria2Ready() {
|
||||
common.ErrorStrResp(c, "aria2 still not ready after init", 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
var req AddAria2Req
|
||||
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 := aria2.AddURI(c, url, reqPath)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
common.SuccessResp(c)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user