Compare commits

..

14 Commits

Author SHA1 Message Date
579f0c06af ci: delete file after decompression
fix: no space left on device
2023-07-30 18:25:52 +08:00
b12d92acc9 perf(baidu_netdisk): optimize memory allocate 2023-07-29 17:12:43 +08:00
e700ce15e5 fix: missed progress in upload task 2023-07-29 17:09:26 +08:00
7dbef7d559 chore(deps): update actions-cool/issues-helper action to v3.5.1 (#4855)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-28 16:16:42 +08:00
7e9cdd8b07 fix(aliyundrive_open): fail limit on concurrently call (#4851) 2023-07-28 15:55:39 +08:00
cee6bc6b5d fix(terabox): slice out of range (close #4858 in #4860) 2023-07-28 15:52:20 +08:00
cfd23c05b4 fix(139): upload empty file (close #4711) 2023-07-27 19:26:22 +08:00
0c1acd72ca fix: link cache not deleted after overwriting file (close #4852) 2023-07-27 19:07:53 +08:00
e2ca06dcca docs: update go version 2023-07-27 18:32:33 +08:00
0828fd787d chore: update placeholder of version in bug_report issue template 2023-07-27 18:31:16 +08:00
2e23ea68d4 fix(aliyundrive_open): increase limit interval (close #4851) 2023-07-27 18:26:11 +08:00
4afa822bec fix(123): Use APP-side API (close #4834 in #4856) 2023-07-27 15:51:59 +08:00
f2ca9b40db fix(qbittorrent): incorrect field type (close #4843) 2023-07-25 13:31:41 +08:00
4c2535cb22 fix(115): user-agent lost on upload (close #4831) 2023-07-23 15:18:33 +08:00
21 changed files with 107 additions and 249 deletions

View File

@ -21,7 +21,7 @@ body:
attributes:
label: Alist Version / Alist 版本
description: What version of our software are you running?
placeholder: v2.0.0
placeholder: v3.xx.xx
validations:
required: true
- type: input

View File

@ -10,7 +10,7 @@ jobs:
if: github.event.label.name == 'question'
steps:
- name: Create comment
uses: actions-cool/issues-helper@v3.5.0
uses: actions-cool/issues-helper@v3.5.1
with:
actions: 'create-comment'
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -7,7 +7,7 @@
Prerequisites:
- [git](https://git-scm.com)
- [Go 1.19+](https://golang.org/doc/install)
- [Go 1.20+](https://golang.org/doc/install)
- [gcc](https://gcc.gnu.org/)
- [nodejs](https://nodejs.org/)

View File

@ -98,6 +98,7 @@ BuildRelease() {
url="${BASE}${i}.tgz"
curl -L -o "${i}.tgz" "${url}"
sudo tar xf "${i}.tgz" --strip-components 1 -C /usr/local
rm -f "${i}.tgz"
done
OS_ARCHES=(linux-musl-amd64 linux-musl-arm64 linux-musl-arm linux-musl-mips linux-musl-mips64 linux-musl-mips64le linux-musl-mipsle linux-musl-ppc64le linux-musl-s390x)
CGO_ARGS=(x86_64-linux-musl-gcc aarch64-linux-musl-gcc arm-linux-musleabihf-gcc mips-linux-musl-gcc mips64-linux-musl-gcc mips64el-linux-musl-gcc mipsel-linux-musl-gcc powerpc64le-linux-musl-gcc s390x-linux-musl-gcc)

View File

@ -1,10 +1,11 @@
package _115
import (
"crypto/tls"
"fmt"
"github.com/SheltonZhu/115driver/pkg/driver"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/pkg/errors"
)
@ -14,9 +15,11 @@ func (d *Pan115) login() error {
var err error
opts := []driver.Option{
driver.UA(UserAgent),
func(c *driver.Pan115Client) {
c.Client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify})
},
}
d.client = driver.New(opts...)
d.client.SetHttpClient(base.HttpClient)
cr := &driver.Credential{}
if d.Addition.QRCodeToken != "" {
s := &driver.QRCodeSession{

View File

@ -1,14 +1,10 @@
package _123
import (
"crypto/md5"
"errors"
"fmt"
"math/rand"
"net/http"
"net/url"
"strconv"
"time"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/pkg/utils"
@ -19,9 +15,10 @@ import (
// do others that not defined in Driver interface
const (
Api = "https://www.123pan.com/api"
AApi = "https://www.123pan.com/a/api"
BApi = "https://www.123pan.com/b/api"
MainApi = BApi
MainApi = Api
SignIn = MainApi + "/user/sign_in"
Logout = MainApi + "/user/logout"
UserInfo = MainApi + "/user/info"
@ -37,7 +34,7 @@ const (
S3Auth = MainApi + "/file/s3_upload_object/auth"
UploadCompleteV2 = MainApi + "/file/upload_complete/v2"
S3Complete = MainApi + "/file/s3_complete_multipart_upload"
AuthKeySalt = "8-8D$sL8gPjom7bk#cY"
//AuthKeySalt = "8-8D$sL8gPjom7bk#cY"
)
func (d *Pan123) login() error {
@ -59,9 +56,10 @@ func (d *Pan123) login() error {
SetHeaders(map[string]string{
"origin": "https://www.123pan.com",
"referer": "https://www.123pan.com/",
"platform": "web",
"app-version": "3",
"user-agent": base.UserAgent,
"user-agent": "Dart/2.19(dart:io)",
"platform": "android",
"app-version": "36",
//"user-agent": base.UserAgent,
}).
SetBody(body).Post(SignIn)
if err != nil {
@ -75,19 +73,19 @@ func (d *Pan123) login() error {
return err
}
func authKey(reqUrl string) (*string, error) {
reqURL, err := url.Parse(reqUrl)
if err != nil {
return nil, err
}
nowUnix := time.Now().Unix()
random := rand.Intn(0x989680)
p4 := fmt.Sprintf("%d|%d|%s|%s|%s|%s", nowUnix, random, reqURL.Path, "web", "3", AuthKeySalt)
authKey := fmt.Sprintf("%d-%d-%x", nowUnix, random, md5.Sum([]byte(p4)))
return &authKey, nil
}
//func authKey(reqUrl string) (*string, error) {
// reqURL, err := url.Parse(reqUrl)
// if err != nil {
// return nil, err
// }
//
// nowUnix := time.Now().Unix()
// random := rand.Intn(0x989680)
//
// p4 := fmt.Sprintf("%d|%d|%s|%s|%s|%s", nowUnix, random, reqURL.Path, "web", "3", AuthKeySalt)
// authKey := fmt.Sprintf("%d-%d-%x", nowUnix, random, md5.Sum([]byte(p4)))
// return &authKey, nil
//}
func (d *Pan123) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R()
@ -95,9 +93,10 @@ func (d *Pan123) request(url string, method string, callback base.ReqCallback, r
"origin": "https://www.123pan.com",
"referer": "https://www.123pan.com/",
"authorization": "Bearer " + d.AccessToken,
"platform": "web",
"app-version": "3",
"user-agent": base.UserAgent,
"user-agent": "Dart/2.19(dart:io)",
"platform": "android",
"app-version": "36",
//"user-agent": base.UserAgent,
})
if callback != nil {
callback(req)
@ -105,11 +104,11 @@ func (d *Pan123) request(url string, method string, callback base.ReqCallback, r
if resp != nil {
req.SetResult(resp)
}
authKey, err := authKey(url)
if err != nil {
return nil, err
}
req.SetQueryParam("auth-key", *authKey)
//authKey, err := authKey(url)
//if err != nil {
// return nil, err
//}
//req.SetQueryParam("auth-key", *authKey)
res, err := req.Execute(method, url)
if err != nil {
return nil, err

View File

@ -300,6 +300,9 @@ func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
var partSize = getPartSize(stream.GetSize())
part := (stream.GetSize() + partSize - 1) / partSize
if part == 0 {
part = 1
}
for i := int64(0); i < part; i++ {
if utils.IsCanceled(ctx) {
return ctx.Err()
@ -331,13 +334,11 @@ func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
if err != nil {
return err
}
_ = res.Body.Close()
log.Debugf("%+v", res)
if res.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
res.Body.Close()
}
return nil

View File

@ -6,6 +6,7 @@ import (
"net/http"
"time"
"github.com/Xhofe/rateg"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
@ -39,8 +40,14 @@ func (d *AliyundriveOpen) Init(ctx context.Context) error {
return err
}
d.DriveId = utils.Json.Get(res, "default_drive_id").ToString()
d.limitList = utils.LimitRateCtx(d.list, time.Second/4)
d.limitLink = utils.LimitRateCtx(d.link, time.Second)
d.limitList = rateg.LimitFnCtx(d.list, rateg.LimitFnOption{
Limit: 4,
Bucket: 1,
})
d.limitLink = rateg.LimitFnCtx(d.link, rateg.LimitFnOption{
Limit: 1,
Bucket: 1,
})
return nil
}

View File

@ -10,6 +10,7 @@ import (
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
)
// do others that not defined in Driver interface
@ -19,9 +20,9 @@ func (d *AliyundriveOpen) refreshToken() error {
if d.OauthTokenURL != "" && d.ClientID == "" {
url = d.OauthTokenURL
}
var resp base.TokenResp
//var resp base.TokenResp
var e ErrResp
_, err := base.RestyClient.R().
res, err := base.RestyClient.R().
ForceContentType("application/json").
SetBody(base.Json{
"client_id": d.ClientID,
@ -29,19 +30,21 @@ func (d *AliyundriveOpen) refreshToken() error {
"grant_type": "refresh_token",
"refresh_token": d.RefreshToken,
}).
SetResult(&resp).
//SetResult(&resp).
SetError(&e).
Post(url)
if err != nil {
return err
}
log.Debugf("[ali_open] refresh token response: %s", res.String())
if e.Code != "" {
return fmt.Errorf("failed to refresh token: %s", e.Message)
}
if resp.RefreshToken == "" {
refresh, access := utils.Json.Get(res.Body(), "refresh_token").ToString(), utils.Json.Get(res.Body(), "access_token").ToString()
if refresh == "" {
return errors.New("failed to refresh token: refresh token is empty")
}
d.RefreshToken, d.AccessToken = resp.RefreshToken, resp.AccessToken
d.RefreshToken, d.AccessToken = refresh, access
op.MustSaveDriverStorage(d)
return nil
}
@ -65,6 +68,9 @@ func (d *AliyundriveOpen) requestReturnErrResp(uri, method string, callback base
req.SetError(&e)
res, err := req.Execute(method, d.base+uri)
if err != nil {
if res != nil {
log.Errorf("[aliyundrive_open] request error: %s", res.String())
}
return nil, err, nil
}
isRetry := len(retry) > 0 && retry[0]

View File

@ -6,6 +6,7 @@ import (
"net/http"
"time"
"github.com/Xhofe/rateg"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
@ -52,8 +53,14 @@ func (d *AliyundriveShare) Init(ctx context.Context) error {
log.Errorf("%+v", err)
}
})
d.limitList = utils.LimitRateCtx(d.list, time.Second/4)
d.limitLink = utils.LimitRateCtx(d.link, time.Second)
d.limitList = rateg.LimitFnCtx(d.list, rateg.LimitFnOption{
Limit: 4,
Bucket: 1,
})
d.limitLink = rateg.LimitFnCtx(d.link, rateg.LimitFnOption{
Limit: 1,
Bucket: 1,
})
return nil
}

View File

@ -1,7 +1,6 @@
package baidu_netdisk
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
@ -118,7 +117,6 @@ func (d *BaiduNetdisk) Put(ctx context.Context, dstDir model.Obj, stream model.F
_ = os.Remove(tempFile.Name())
}()
var Default int64 = 4 * 1024 * 1024
defaultByteData := make([]byte, Default)
count := int(math.Ceil(float64(stream.GetSize()) / float64(Default)))
var SliceSize int64 = 256 * 1024
// cal md5
@ -130,20 +128,14 @@ func (d *BaiduNetdisk) Put(ctx context.Context, dstDir model.Obj, stream model.F
left := stream.GetSize()
for i := 0; i < count; i++ {
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
_, err = io.Copy(io.MultiWriter(h1, h2), io.LimitReader(tempFile, byteSize))
if err != nil {
return err
}
h1.Write(byteData)
h2.Write(byteData)
block_list = append(block_list, fmt.Sprintf("\"%s\"", hex.EncodeToString(h2.Sum(nil))))
h2.Reset()
}
@ -177,6 +169,7 @@ func (d *BaiduNetdisk) Put(ctx context.Context, dstDir model.Obj, stream model.F
params := map[string]string{
"method": "precreate",
}
log.Debugf("[baidu_netdisk] precreate data: %s", data)
var precreateResp PrecreateResp
_, err = d.post("/xpan/file", params, data, &precreateResp)
if err != nil {
@ -199,24 +192,16 @@ func (d *BaiduNetdisk) Put(ctx context.Context, dstDir model.Obj, stream model.F
return ctx.Err()
}
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
u := "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2"
params["partseq"] = strconv.Itoa(partseq)
res, err := base.RestyClient.R().
SetContext(ctx).
SetQueryParams(params).
SetFileReader("file", stream.GetName(), bytes.NewReader(byteData)).
SetFileReader("file", stream.GetName(), io.LimitReader(tempFile, byteSize)).
Post(u)
if err != nil {
return err

View File

@ -13,6 +13,7 @@ import (
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
)
// do others that not defined in Driver interface
@ -62,16 +63,17 @@ func (d *BaiduNetdisk) request(furl string, method string, callback base.ReqCall
if err != nil {
return nil, err
}
log.Debugf("[baidu_netdisk] req: %s, resp: %s", furl, res.String())
errno := utils.Json.Get(res.Body(), "errno").ToInt()
if errno != 0 {
if errno == -6 {
if utils.SliceContains([]int{111, -6}, errno) {
err = d.refreshToken()
if err != nil {
return nil, err
}
return d.request(furl, method, callback, resp)
}
return nil, fmt.Errorf("errno: %d, refer to https://pan.baidu.com/union/doc/", errno)
return nil, fmt.Errorf("req: [%s] ,errno: %d, refer to https://pan.baidu.com/union/doc/", furl, errno)
}
return res.Body(), nil
}

View File

@ -3,15 +3,16 @@ package terbox
import (
"encoding/base64"
"fmt"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/alist-org/alist/v3/drivers/base"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/go-resty/resty/v2"
)
func (d *Terabox) request(furl string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
@ -139,6 +140,11 @@ func (d *Terabox) linkOfficial(file model.Obj, args model.LinkArgs) (*model.Link
if err != nil {
return nil, err
}
if len(resp.Dlink) == 0 {
return nil, fmt.Errorf("fid %s no dlink found, errno: %d", file.GetID(), resp.Errno)
}
res, err := base.NoRedirectClient.R().SetHeader("Cookie", d.Cookie).SetHeader("User-Agent", base.UserAgent).Get(resp.Dlink[0].Dlink)
if err != nil {
return nil, err

3
go.mod
View File

@ -49,6 +49,7 @@ require (
require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/RoaringBitmap/roaring v1.2.3 // indirect
github.com/Xhofe/rateg v0.0.0-20230728072201-251a4e1adad4 // indirect
github.com/aead/ecdh v0.2.0 // indirect
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible // indirect
github.com/andreburgaud/crypt2go v1.1.0 // indirect
@ -135,7 +136,7 @@ require (
golang.org/x/arch v0.3.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect

5
go.sum
View File

@ -6,6 +6,8 @@ github.com/SheltonZhu/115driver v1.0.14 h1:uW3dl8J9KDMw+3gPxQdhTysoGhw0/uI1484GT
github.com/SheltonZhu/115driver v1.0.14/go.mod h1:00ixivHH5HqDj4S7kAWbkuUrjtsJTxc7cGv5RMw3RVs=
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a h1:RenIAa2q4H8UcS/cqmwdT1WCWIAH5aumP8m8RpbqVsE=
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a/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/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
@ -210,6 +212,7 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@ -394,6 +397,8 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=

View File

@ -36,7 +36,7 @@ func putAsTask(dstDirPath string, file *model.FileStream) error {
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, nil, true)
return op.Put(task.Ctx, storage, dstDirActualPath, file, task.SetProgress, true)
},
}))
return nil

View File

@ -25,6 +25,7 @@ type Link struct {
Status int // status maybe 200 or 206, etc
FilePath *string // local file, return the filepath
Expiration *time.Duration // url expiration time
IPCacheKey bool // add ip to cache key
//Handle func(w http.ResponseWriter, r *http.Request) error `json:"-"` // custom handler
Writer WriterFunc `json:"-"` // custom writer
}

View File

@ -243,7 +243,7 @@ func Link(ctx context.Context, storage driver.Driver, path string, args model.Li
if file.IsDir() {
return nil, nil, errors.WithStack(errs.NotFile)
}
key := Key(storage, path) + ":" + args.IP
key := Key(storage, path)
if link, ok := linkCache.Get(key); ok {
return link, file, nil
}
@ -253,6 +253,9 @@ func Link(ctx context.Context, storage driver.Driver, path string, args model.Li
return nil, errors.Wrapf(err, "failed get link")
}
if link.Expiration != nil {
if link.IPCacheKey {
key = key + ":" + args.IP
}
linkCache.Set(key, link, cache.WithEx[*model.Link](*link.Expiration))
}
return link, nil
@ -563,6 +566,9 @@ func Put(ctx context.Context, storage driver.Driver, dstDirPath string, file *mo
err := Remove(ctx, storage, tempPath)
if err != nil {
return err
} else {
key := Key(storage, stdpath.Join(dstDirPath, file.GetName()))
linkCache.Del(key)
}
}
}

View File

@ -3,12 +3,13 @@ package qbittorrent
import (
"bytes"
"errors"
"github.com/alist-org/alist/v3/pkg/utils"
"io"
"mime/multipart"
"net/http"
"net/http/cookiejar"
"net/url"
"github.com/alist-org/alist/v3/pkg/utils"
)
type Client interface {
@ -213,7 +214,7 @@ type TorrentInfo struct {
Hash string `json:"hash"` //
LastActivity int `json:"last_activity"` // 上次活跃的时间Unix Epoch
MagnetURI string `json:"magnet_uri"` // 与此 torrent 对应的 Magnet URI
MaxRatio int `json:"max_ratio"` // 种子/上传停止种子前的最大共享比率
MaxRatio float64 `json:"max_ratio"` // 种子/上传停止种子前的最大共享比率
MaxSeedingTime int `json:"max_seeding_time"` // 停止种子种子前的最长种子时间(秒)
Name string `json:"name"` //
NumComplete int `json:"num_complete"` //

View File

@ -1,114 +0,0 @@
package utils
import (
"context"
"reflect"
"time"
)
func LimitRateReflect(f interface{}, interval time.Duration) func(...interface{}) []interface{} {
// Use closures to save the time of the last function call
var lastCall time.Time
fValue := reflect.ValueOf(f)
fType := fValue.Type()
if fType.Kind() != reflect.Func {
panic("f must be a function")
}
//if fType.NumOut() == 0 {
// panic("f must have at least one output parameter")
//}
outCount := fType.NumOut()
outTypes := make([]reflect.Type, outCount)
for i := 0; i < outCount; i++ {
outTypes[i] = fType.Out(i)
}
// Returns a new function, which is used to limit the function to be called only once at a specified time interval
return func(args ...interface{}) []interface{} {
// Calculate the time interval since the last function call
elapsed := time.Since(lastCall)
// If the interval is less than the specified time, wait for the remaining time
if elapsed < interval {
time.Sleep(interval - elapsed)
}
// Update the time of the last function call
lastCall = time.Now()
inCount := fType.NumIn()
in := make([]reflect.Value, inCount)
if len(args) != inCount {
panic("wrong number of arguments")
}
for i := 0; i < inCount; i++ {
in[i] = reflect.ValueOf(args[i])
}
out := fValue.Call(in)
if len(out) != outCount {
panic("function returned wrong number of values")
}
result := make([]interface{}, outCount)
for i := 0; i < outCount; i++ {
result[i] = out[i].Interface()
}
return result
}
}
type Fn[T any, R any] func(T) (R, error)
type FnCtx[T any, R any] func(context.Context, T) (R, error)
func LimitRate[T any, R any](f Fn[T, R], interval time.Duration) Fn[T, R] {
// Use closures to save the time of the last function call
var lastCall time.Time
// Returns a new function, which is used to limit the function to be called only once at a specified time interval
return func(t T) (R, error) {
// Calculate the time interval since the last function call
elapsed := time.Since(lastCall)
// If the interval is less than the specified time, wait for the remaining time
if elapsed < interval {
time.Sleep(interval - elapsed)
}
// Update the time of the last function call
lastCall = time.Now()
// Execute the function that needs to be limited
return f(t)
}
}
func LimitRateCtx[T any, R any](f FnCtx[T, R], interval time.Duration) FnCtx[T, R] {
// Use closures to save the time of the last function call
var lastCall time.Time
// Returns a new function, which is used to limit the function to be called only once at a specified time interval
return func(ctx context.Context, t T) (R, error) {
// Calculate the time interval since the last function call
elapsed := time.Since(lastCall)
// If the interval is less than the specified time, wait for the remaining time
if elapsed < interval {
t := time.NewTimer(interval - elapsed)
select {
case <-ctx.Done():
t.Stop()
var zero R
return zero, ctx.Err()
case <-t.C:
}
}
// Update the time of the last function call
lastCall = time.Now()
// Execute the function that needs to be limited
return f(ctx, t)
}
}

View File

@ -1,59 +0,0 @@
package utils_test
import (
"context"
"testing"
"time"
"github.com/alist-org/alist/v3/pkg/utils"
)
func myFunction(a int) (int, error) {
// do something
return a + 1, nil
}
func TestLimitRate(t *testing.T) {
myLimitedFunction := utils.LimitRate(myFunction, time.Second)
result, _ := myLimitedFunction(1)
t.Log(result) // Output: 2
result, _ = myLimitedFunction(2)
t.Log(result) // Output: 3
}
type Test struct {
limitFn func(string) (string, error)
}
func (t *Test) myFunction(a string) (string, error) {
// do something
return a + " world", nil
}
func TestLimitRateStruct(t *testing.T) {
test := &Test{}
test.limitFn = utils.LimitRate(test.myFunction, time.Second)
result, _ := test.limitFn("hello")
t.Log(result) // Output: hello world
result, _ = test.limitFn("hi")
t.Log(result) // Output: hi world
}
func myFunctionCtx(ctx context.Context, a int) (int, error) {
// do something
return a + 1, nil
}
func TestLimitRateCtx(t *testing.T) {
myLimitedFunction := utils.LimitRateCtx(myFunctionCtx, time.Second)
result, _ := myLimitedFunction(context.Background(), 1)
t.Log(result) // Output: 2
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(500 * time.Millisecond)
cancel()
}()
result, err := myLimitedFunction(ctx, 2)
t.Log(result, err) // Output: 0 context canceled
result, _ = myLimitedFunction(context.Background(), 3)
t.Log(result) // Output: 4
}