Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
6c91cfeb90 | |||
bfd1f25972 | |||
8c0defce09 | |||
a1e88cfa05 | |||
443f5ffbcc | |||
b8bc94306d | |||
d9795ff22f | |||
c4108007cd | |||
f3db23a41e | |||
4741a75c92 | |||
301756ba03 | |||
3b2703a5e5 | |||
2a601f06cb | |||
adc3a56552 | |||
4d9a29bddd | |||
666e02f0c3 | |||
6aaec19c1c | |||
1091e1b740 | |||
d06c605421 | |||
43de823058 | |||
02d0aef611 | |||
5596661ce8 | |||
2379cb8d67 | |||
8c0ebe0841 | |||
fd868bac84 | |||
ebcbb29a0f | |||
00ff0a43a7 | |||
3d3f23ec9e | |||
d484219c48 |
2
.github/workflows/auto_lang.yml
vendored
2
.github/workflows/auto_lang.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
prerelease: true
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
|
@ -15,4 +15,4 @@ RUN apk add --no-cache bash ca-certificates su-exec tzdata; \
|
||||
chmod +x /entrypoint.sh
|
||||
ENV PUID=0 PGID=0 UMASK=022
|
||||
EXPOSE 5244
|
||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||
CMD [ "/entrypoint.sh" ]
|
||||
|
52
cmd/storage.go
Normal file
52
cmd/storage.go
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// storageCmd represents the storage command
|
||||
var storageCmd = &cobra.Command{
|
||||
Use: "storage",
|
||||
Short: "Manage storage",
|
||||
}
|
||||
|
||||
func init() {
|
||||
var mountPath string
|
||||
var disable = &cobra.Command{
|
||||
Use: "disable",
|
||||
Short: "Disable a storage",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
Init()
|
||||
storage, err := db.GetStorageByMountPath(mountPath)
|
||||
if err != nil {
|
||||
utils.Log.Errorf("failed to query storage: %+v", err)
|
||||
} else {
|
||||
storage.Disabled = true
|
||||
err = db.UpdateStorage(storage)
|
||||
if err != nil {
|
||||
utils.Log.Errorf("failed to update storage: %+v", err)
|
||||
} else {
|
||||
utils.Log.Infof("Storage with mount path [%s] have been disabled", mountPath)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
disable.Flags().StringVarP(&mountPath, "mount-path", "m", "", "The mountPath of storage")
|
||||
RootCmd.AddCommand(storageCmd)
|
||||
storageCmd.AddCommand(disable)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// storageCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// storageCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
@ -6,8 +6,9 @@ import (
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
Cookie string `json:"cookie"`
|
||||
QRCodeToken string `json:"qrcode_token"`
|
||||
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:"56" help:"list api per page size of 115 driver"`
|
||||
driver.RootID
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 115Browser/23.9.3.2 115disk/30.1.0"
|
||||
var UserAgent = driver.UA115Desktop
|
||||
|
||||
func (d *Pan115) login() error {
|
||||
var err error
|
||||
@ -38,7 +38,10 @@ func (d *Pan115) login() error {
|
||||
|
||||
func (d *Pan115) getFiles(fileId string) ([]driver.File, error) {
|
||||
res := make([]driver.File, 0)
|
||||
files, err := d.client.List(fileId)
|
||||
if d.PageSize <= 0 {
|
||||
d.PageSize = driver.FileListLimit
|
||||
}
|
||||
files, err := d.client.ListWithLimit(fileId, d.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
114
drivers/alias/driver.go
Normal file
114
drivers/alias/driver.go
Normal file
@ -0,0 +1,114 @@
|
||||
package alias
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
type Alias struct {
|
||||
model.Storage
|
||||
Addition
|
||||
pathMap map[string][]string
|
||||
autoFlatten bool
|
||||
oneKey string
|
||||
}
|
||||
|
||||
func (d *Alias) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *Alias) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *Alias) Init(ctx context.Context) error {
|
||||
if d.Paths == "" {
|
||||
return errors.New("paths is required")
|
||||
}
|
||||
d.pathMap = make(map[string][]string)
|
||||
for _, path := range strings.Split(d.Paths, "\n") {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" {
|
||||
continue
|
||||
}
|
||||
k, v := getPair(path)
|
||||
d.pathMap[k] = append(d.pathMap[k], v)
|
||||
}
|
||||
if len(d.pathMap) == 1 {
|
||||
for k := range d.pathMap {
|
||||
d.oneKey = k
|
||||
}
|
||||
d.autoFlatten = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Alias) Drop(ctx context.Context) error {
|
||||
d.pathMap = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Alias) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||
if utils.PathEqual(path, "/") {
|
||||
return &model.Object{
|
||||
Name: "Root",
|
||||
IsFolder: true,
|
||||
Path: "/",
|
||||
}, nil
|
||||
}
|
||||
root, sub := d.getRootAndPath(path)
|
||||
dsts, ok := d.pathMap[root]
|
||||
if !ok {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
for _, dst := range dsts {
|
||||
obj, err := d.get(ctx, path, dst, sub)
|
||||
if err == nil {
|
||||
return obj, nil
|
||||
}
|
||||
}
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
|
||||
func (d *Alias) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
path := dir.GetPath()
|
||||
if utils.PathEqual(path, "/") && !d.autoFlatten {
|
||||
return d.listRoot(), nil
|
||||
}
|
||||
root, sub := d.getRootAndPath(path)
|
||||
dsts, ok := d.pathMap[root]
|
||||
if !ok {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
var objs []model.Obj
|
||||
for _, dst := range dsts {
|
||||
tmp, err := d.list(ctx, dst, sub)
|
||||
if err == nil {
|
||||
objs = append(objs, tmp...)
|
||||
}
|
||||
}
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
func (d *Alias) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
root, sub := d.getRootAndPath(file.GetPath())
|
||||
dsts, ok := d.pathMap[root]
|
||||
if !ok {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
for _, dst := range dsts {
|
||||
link, err := d.link(ctx, dst, sub, args)
|
||||
if err == nil {
|
||||
return link, nil
|
||||
}
|
||||
}
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*Alias)(nil)
|
27
drivers/alias/meta.go
Normal file
27
drivers/alias/meta.go
Normal file
@ -0,0 +1,27 @@
|
||||
package alias
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
// Usually one of two
|
||||
// driver.RootPath
|
||||
// define other
|
||||
Paths string `json:"paths" required:"true" type:"text"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Alias",
|
||||
LocalSort: true,
|
||||
NoCache: true,
|
||||
NoUpload: true,
|
||||
DefaultRoot: "/",
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Alias{}
|
||||
})
|
||||
}
|
1
drivers/alias/types.go
Normal file
1
drivers/alias/types.go
Normal file
@ -0,0 +1 @@
|
||||
package alias
|
103
drivers/alias/util.go
Normal file
103
drivers/alias/util.go
Normal file
@ -0,0 +1,103 @@
|
||||
package alias
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
stdpath "path"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/fs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/sign"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
)
|
||||
|
||||
func (d *Alias) listRoot() []model.Obj {
|
||||
var objs []model.Obj
|
||||
for k, _ := range d.pathMap {
|
||||
obj := model.Object{
|
||||
Name: k,
|
||||
IsFolder: true,
|
||||
Modified: d.Modified,
|
||||
}
|
||||
objs = append(objs, &obj)
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
||||
// do others that not defined in Driver interface
|
||||
func getPair(path string) (string, string) {
|
||||
//path = strings.TrimSpace(path)
|
||||
if strings.Contains(path, ":") {
|
||||
pair := strings.SplitN(path, ":", 2)
|
||||
if !strings.Contains(pair[0], "/") {
|
||||
return pair[0], pair[1]
|
||||
}
|
||||
}
|
||||
return stdpath.Base(path), path
|
||||
}
|
||||
|
||||
func (d *Alias) getRootAndPath(path string) (string, string) {
|
||||
if d.autoFlatten {
|
||||
return d.oneKey, path
|
||||
}
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
parts := strings.SplitN(path, "/", 2)
|
||||
if len(parts) == 1 {
|
||||
return parts[0], ""
|
||||
}
|
||||
return parts[0], parts[1]
|
||||
}
|
||||
|
||||
func (d *Alias) get(ctx context.Context, path string, dst, sub string) (model.Obj, error) {
|
||||
obj, err := fs.Get(ctx, stdpath.Join(dst, sub))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.Object{
|
||||
Path: path,
|
||||
Name: obj.GetName(),
|
||||
Size: obj.GetSize(),
|
||||
Modified: obj.ModTime(),
|
||||
IsFolder: obj.IsDir(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Alias) list(ctx context.Context, dst, sub string) ([]model.Obj, error) {
|
||||
objs, err := fs.List(ctx, stdpath.Join(dst, sub))
|
||||
// the obj must implement the model.SetPath interface
|
||||
// return objs, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SliceConvert(objs, func(obj model.Obj) (model.Obj, error) {
|
||||
return &model.Object{
|
||||
Name: obj.GetName(),
|
||||
Size: obj.GetSize(),
|
||||
Modified: obj.ModTime(),
|
||||
IsFolder: obj.IsDir(),
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Alias) link(ctx context.Context, dst, sub string, args model.LinkArgs) (*model.Link, error) {
|
||||
reqPath := stdpath.Join(dst, sub)
|
||||
storage, err := fs.GetStorage(reqPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = fs.Get(ctx, reqPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if common.ShouldProxy(storage, stdpath.Base(sub)) {
|
||||
return &model.Link{
|
||||
URL: fmt.Sprintf("/p%s?sign=%s",
|
||||
utils.EncodePath(reqPath, true),
|
||||
sign.Sign(reqPath)),
|
||||
}, nil
|
||||
}
|
||||
link, _, err := fs.Link(ctx, reqPath, args)
|
||||
return link, err
|
||||
}
|
@ -226,7 +226,7 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
||||
delete(reqBody, "pre_hash")
|
||||
h := sha1.New()
|
||||
if localFile != nil {
|
||||
if _, err = io.Copy(h, localFile); err != nil {
|
||||
if err = utils.CopyWithCtx(ctx, h, localFile, 0, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = localFile.Seek(0, io.SeekStart); err != nil {
|
||||
@ -241,7 +241,7 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileS
|
||||
_ = tempFile.Close()
|
||||
_ = os.Remove(tempFile.Name())
|
||||
}()
|
||||
if _, err = io.Copy(io.MultiWriter(tempFile, h), file); err != nil {
|
||||
if err = utils.CopyWithCtx(ctx, io.MultiWriter(tempFile, h), file, 0, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
localFile = tempFile
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
@ -125,7 +126,11 @@ func (d *AliyundriveOpen) Copy(ctx context.Context, srcObj, dstDir model.Obj) er
|
||||
}
|
||||
|
||||
func (d *AliyundriveOpen) Remove(ctx context.Context, obj model.Obj) error {
|
||||
_, err := d.request("/adrive/v1.0/openFile/recyclebin/trash", http.MethodPost, func(req *resty.Request) {
|
||||
uri := "/adrive/v1.0/openFile/recyclebin/trash"
|
||||
if d.RemoveWay == "delete" {
|
||||
uri = "/adrive/v1.0/openFile/delete"
|
||||
}
|
||||
_, err := d.request(uri, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"drive_id": d.DriveId,
|
||||
"file_id": obj.GetID(),
|
||||
@ -166,7 +171,12 @@ func (d *AliyundriveOpen) Put(ctx context.Context, dstDir model.Obj, stream mode
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
req, err := http.NewRequest("PUT", partInfo.UploadUrl, io.LimitReader(stream, DEFAULT))
|
||||
uploadUrl := partInfo.UploadUrl
|
||||
if d.InternalUpload {
|
||||
//Replace a known public Host with an internal Host
|
||||
uploadUrl = strings.ReplaceAll(uploadUrl, "https://cn-beijing-data.aliyundrive.net/", "http://ccp-bj29-bj-1592982087.oss-cn-beijing-internal.aliyuncs.com/")
|
||||
}
|
||||
req, err := http.NewRequest("PUT", uploadUrl, io.LimitReader(stream, DEFAULT))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ type Addition struct {
|
||||
OauthTokenURL string `json:"oauth_token_url" default:"https://api.nn.ci/alist/ali_open/token"`
|
||||
ClientID string `json:"client_id" required:"false" help:"Keep it empty if you don't have one"`
|
||||
ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"`
|
||||
RemoveWay string `json:"remove_way" required:"true" type:"select" options:"trash,delete"`
|
||||
InternalUpload bool `json:"internal_upload" help:"If you are using Aliyun ECS in Beijing, you can turn it on to boost the upload speed"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/139"
|
||||
_ "github.com/alist-org/alist/v3/drivers/189"
|
||||
_ "github.com/alist-org/alist/v3/drivers/189pc"
|
||||
_ "github.com/alist-org/alist/v3/drivers/alias"
|
||||
_ "github.com/alist-org/alist/v3/drivers/alist_v2"
|
||||
_ "github.com/alist-org/alist/v3/drivers/alist_v3"
|
||||
_ "github.com/alist-org/alist/v3/drivers/aliyundrive"
|
||||
@ -13,6 +14,7 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/aliyundrive_share"
|
||||
_ "github.com/alist-org/alist/v3/drivers/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/cloudreve"
|
||||
_ "github.com/alist-org/alist/v3/drivers/ftp"
|
||||
_ "github.com/alist-org/alist/v3/drivers/google_drive"
|
||||
@ -32,6 +34,7 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/teambition"
|
||||
_ "github.com/alist-org/alist/v3/drivers/terabox"
|
||||
_ "github.com/alist-org/alist/v3/drivers/thunder"
|
||||
_ "github.com/alist-org/alist/v3/drivers/trainbit"
|
||||
_ "github.com/alist-org/alist/v3/drivers/uss"
|
||||
_ "github.com/alist-org/alist/v3/drivers/virtual"
|
||||
_ "github.com/alist-org/alist/v3/drivers/webdav"
|
||||
|
153
drivers/baidu_share/driver.go
Normal file
153
drivers/baidu_share/driver.go
Normal file
@ -0,0 +1,153 @@
|
||||
package baidu_share
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Xhofe/go-cache"
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type BaiduShare struct {
|
||||
model.Storage
|
||||
Addition
|
||||
config map[string]string
|
||||
dlinkCache cache.ICache[string]
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *BaiduShare) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Init(ctx context.Context) error {
|
||||
// TODO login / refresh token
|
||||
//op.MustSaveDriverStorage(d)
|
||||
d.config = map[string]string{}
|
||||
d.dlinkCache = cache.NewMemCache(cache.WithClearInterval[string](time.Duration(d.CacheExpiration) * time.Minute))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Drop(ctx context.Context) error {
|
||||
d.dlinkCache.Clear()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *BaiduShare) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
// TODO return the files list
|
||||
body := url.Values{
|
||||
"shorturl": {d.Surl},
|
||||
"dir": {dir.GetPath()},
|
||||
"root": {"0"},
|
||||
"pwd": {d.Pwd},
|
||||
"num": {"1000"},
|
||||
"order": {"time"},
|
||||
}
|
||||
if body.Get("dir") == "" || body.Get("dir") == d.config["root"] {
|
||||
body.Set("root", "1")
|
||||
}
|
||||
res := []model.Obj{}
|
||||
var err error
|
||||
var page int64 = 1
|
||||
more := true
|
||||
for more {
|
||||
body.Set("page", strconv.FormatInt(page, 10))
|
||||
req := base.RestyClient.R().
|
||||
SetCookies([]*http.Cookie{{Name: "BDUSS", Value: d.BDUSS}}).
|
||||
SetBody(body.Encode())
|
||||
resp, e := req.Post("https://pan.baidu.com/share/wxlist?channel=weixin&version=2.2.2&clienttype=25&web=1")
|
||||
err = e
|
||||
jsonresp := jsonResp{}
|
||||
if err == nil {
|
||||
err = base.RestyClient.JSONUnmarshal(resp.Body(), &jsonresp)
|
||||
}
|
||||
if err == nil && jsonresp.Errno == 0 {
|
||||
more = jsonresp.Data.More
|
||||
page += 1
|
||||
for _, v := range jsonresp.Data.List {
|
||||
size, _ := v.Size.Int64()
|
||||
mtime, _ := v.Time.Int64()
|
||||
res = append(res, &model.Object{
|
||||
ID: v.ID.String(),
|
||||
Path: v.Path,
|
||||
Name: v.Name,
|
||||
Size: size,
|
||||
Modified: time.Unix(mtime, 0),
|
||||
IsFolder: v.Dir.String() == "1",
|
||||
})
|
||||
d.dlinkCache.Set(v.Path, v.Dlink, cache.WithEx[string](time.Duration(d.CacheExpiration/2)*time.Minute))
|
||||
}
|
||||
if len(res) > 0 && body.Get("root") == "1" {
|
||||
d.config["root"] = path.Dir(res[0].GetPath())
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("errno:%d", jsonresp.Errno)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
// TODO return link of file
|
||||
var err error
|
||||
req := base.RestyClient.R().SetCookies([]*http.Cookie{{Name: "BDUSS", Value: d.BDUSS}})
|
||||
url, found := d.dlinkCache.Get(file.GetPath())
|
||||
if !found {
|
||||
_, err = d.List(ctx, &model.Object{Path: path.Dir(file.GetPath())}, model.ListArgs{})
|
||||
url, found = d.dlinkCache.Get(file.GetPath())
|
||||
if err == nil && !found {
|
||||
err = errs.NotSupport
|
||||
}
|
||||
}
|
||||
return &model.Link{URL: url, Header: req.Header}, err
|
||||
}
|
||||
|
||||
func (d *BaiduShare) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
// TODO create folder
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO move obj
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
// TODO rename obj
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO copy obj
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Remove(ctx context.Context, obj model.Obj) error {
|
||||
// TODO remove obj
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *BaiduShare) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
// TODO upload file
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
var _ driver.Driver = (*BaiduShare)(nil)
|
34
drivers/baidu_share/meta.go
Normal file
34
drivers/baidu_share/meta.go
Normal file
@ -0,0 +1,34 @@
|
||||
package baidu_share
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
// Usually one of two
|
||||
driver.RootPath
|
||||
// driver.RootID
|
||||
// define other
|
||||
// Field string `json:"field" type:"select" required:"true" options:"a,b,c" default:"a"`
|
||||
Surl string `json:"surl"`
|
||||
Pwd string `json:"pwd"`
|
||||
BDUSS string `json:"BDUSS"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "BaiduShare",
|
||||
LocalSort: true,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: true,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "",
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &BaiduShare{}
|
||||
})
|
||||
}
|
19
drivers/baidu_share/types.go
Normal file
19
drivers/baidu_share/types.go
Normal file
@ -0,0 +1,19 @@
|
||||
package baidu_share
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type jsonResp struct {
|
||||
Errno int64 `json:"errno"`
|
||||
Data struct {
|
||||
More bool `json:"has_more"`
|
||||
List []struct {
|
||||
ID json.Number `json:"fs_id"`
|
||||
Dir json.Number `json:"isdir"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"server_filename"`
|
||||
Time json.Number `json:"server_mtime"`
|
||||
Size json.Number `json:"size"`
|
||||
Dlink string `json:"dlink"`
|
||||
} `json:"list"`
|
||||
} `json:"data"`
|
||||
}
|
3
drivers/baidu_share/util.go
Normal file
3
drivers/baidu_share/util.go
Normal file
@ -0,0 +1,3 @@
|
||||
package baidu_share
|
||||
|
||||
// do others that not defined in Driver interface
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
stdpath "path"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
@ -44,8 +45,7 @@ func (d *FTP) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]m
|
||||
return nil, err
|
||||
}
|
||||
res := make([]model.Obj, 0)
|
||||
for i, _ := range entries {
|
||||
entry := entries[i]
|
||||
for _, entry := range entries {
|
||||
if entry.Name == "." || entry.Name == ".." {
|
||||
continue
|
||||
}
|
||||
@ -64,13 +64,13 @@ func (d *FTP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*m
|
||||
if err := d.login(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := d.conn.Retr(file.GetPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
r := NewFTPFileReader(d.conn, file.GetPath())
|
||||
link := &model.Link{
|
||||
Data: r,
|
||||
}
|
||||
return &model.Link{
|
||||
Data: resp,
|
||||
}, nil
|
||||
base.HandleRange(link, r, args.Header, file.GetSize())
|
||||
return link, nil
|
||||
}
|
||||
|
||||
func (d *FTP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
|
@ -1,6 +1,13 @@
|
||||
package ftp
|
||||
|
||||
import "github.com/jlaffaye/ftp"
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jlaffaye/ftp"
|
||||
)
|
||||
|
||||
// do others that not defined in Driver interface
|
||||
|
||||
@ -11,7 +18,7 @@ func (d *FTP) login() error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
conn, err := ftp.Dial(d.Address)
|
||||
conn, err := ftp.Dial(d.Address, ftp.DialWithShutTimeout(10*time.Second))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -22,3 +29,81 @@ func (d *FTP) login() error {
|
||||
d.conn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
// An FTP file reader that implements io.ReadSeekCloser for seeking.
|
||||
type FTPFileReader struct {
|
||||
conn *ftp.ServerConn
|
||||
resp *ftp.Response
|
||||
offset int64
|
||||
mu sync.Mutex
|
||||
path string
|
||||
}
|
||||
|
||||
func NewFTPFileReader(conn *ftp.ServerConn, path string) *FTPFileReader {
|
||||
return &FTPFileReader{
|
||||
conn: conn,
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *FTPFileReader) Read(buf []byte) (n int, err error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if r.resp == nil {
|
||||
r.resp, err = r.conn.RetrFrom(r.path, uint64(r.offset))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
n, err = r.resp.Read(buf)
|
||||
r.offset += int64(n)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *FTPFileReader) Seek(offset int64, whence int) (int64, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
oldOffset := r.offset
|
||||
var newOffset int64
|
||||
switch whence {
|
||||
case io.SeekStart:
|
||||
newOffset = offset
|
||||
case io.SeekCurrent:
|
||||
newOffset = oldOffset + offset
|
||||
case io.SeekEnd:
|
||||
size, err := r.conn.FileSize(r.path)
|
||||
if err != nil {
|
||||
return oldOffset, err
|
||||
}
|
||||
newOffset = offset + int64(size)
|
||||
default:
|
||||
return -1, os.ErrInvalid
|
||||
}
|
||||
|
||||
if newOffset < 0 {
|
||||
// offset out of range
|
||||
return oldOffset, os.ErrInvalid
|
||||
}
|
||||
if newOffset == oldOffset {
|
||||
// offset not changed, so return directly
|
||||
return oldOffset, nil
|
||||
}
|
||||
r.offset = newOffset
|
||||
|
||||
if r.resp != nil {
|
||||
// close the existing ftp data connection, otherwise the next read will be blocked
|
||||
_ = r.resp.Close() // we do not care about whether it returns an error
|
||||
r.resp = nil
|
||||
}
|
||||
return newOffset, nil
|
||||
}
|
||||
|
||||
func (r *FTPFileReader) Close() error {
|
||||
if r.resp != nil {
|
||||
return r.resp.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
||||
}
|
||||
srcBuf = videoBuf
|
||||
} else {
|
||||
imgData, err := ioutil.ReadFile(fullPath)
|
||||
imgData, err := os.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -59,7 +59,8 @@ func (d *S3) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]mo
|
||||
|
||||
func (d *S3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
path := getKey(file.GetPath(), false)
|
||||
disposition := fmt.Sprintf(`attachment;filename="%s"`, url.QueryEscape(stdpath.Base(path)))
|
||||
filename := stdpath.Base(path)
|
||||
disposition := fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename))
|
||||
input := &s3.GetObjectInput{
|
||||
Bucket: &d.Bucket,
|
||||
Key: &path,
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
@ -19,7 +18,7 @@ type SMB struct {
|
||||
model.Storage
|
||||
Addition
|
||||
fs *smb2.Share
|
||||
lastConnTime time.Time
|
||||
lastConnTime int64
|
||||
}
|
||||
|
||||
func (d *SMB) Config() driver.Config {
|
||||
|
@ -6,17 +6,22 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/hirochachacha/go-smb2"
|
||||
)
|
||||
|
||||
func (d *SMB) updateLastConnTime() {
|
||||
d.lastConnTime = time.Now()
|
||||
atomic.StoreInt64(&d.lastConnTime, time.Now().Unix())
|
||||
}
|
||||
|
||||
func (d *SMB) cleanLastConnTime() {
|
||||
d.lastConnTime = time.Now().AddDate(0, 0, -1)
|
||||
atomic.StoreInt64(&d.lastConnTime, 0)
|
||||
}
|
||||
|
||||
func (d *SMB) getLastConnTime() time.Time {
|
||||
return time.Unix(atomic.LoadInt64(&d.lastConnTime), 0)
|
||||
}
|
||||
|
||||
func (d *SMB) initFS() error {
|
||||
@ -43,7 +48,7 @@ func (d *SMB) initFS() error {
|
||||
}
|
||||
|
||||
func (d *SMB) checkConn() error {
|
||||
if time.Since(d.lastConnTime) < 5*time.Minute {
|
||||
if time.Since(d.getLastConnTime()) < 5*time.Minute {
|
||||
return nil
|
||||
}
|
||||
if d.fs != nil {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
@ -124,11 +125,11 @@ func (d *Teambition) Remove(ctx context.Context, obj model.Obj) error {
|
||||
}
|
||||
|
||||
func (d *Teambition) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
res, err := d.request("/projects", http.MethodGet, nil, nil)
|
||||
res, err := d.request("/api/v2/users/me", http.MethodGet, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token := GetBetweenStr(string(res), "strikerAuth":"", "","phoneForLogin")
|
||||
token := utils.Json.Get(res, "strikerAuth").ToString()
|
||||
var newFile *FileUpload
|
||||
if stream.GetSize() <= 20971520 {
|
||||
// post upload
|
||||
|
@ -210,7 +210,7 @@ func (d *Teambition) finishUpload(file *FileUpload, parentId string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func GetBetweenStr(str, start, end string) string {
|
||||
func getBetweenStr(str, start, end string) string {
|
||||
n := strings.Index(str, start)
|
||||
if n == -1 {
|
||||
return ""
|
||||
|
@ -32,42 +32,42 @@ func (d *Template) Drop(ctx context.Context) error {
|
||||
}
|
||||
|
||||
func (d *Template) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
// TODO return the files list
|
||||
// TODO return the files list, required
|
||||
return nil, errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Template) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
// TODO return link of file
|
||||
// TODO return link of file, required
|
||||
return nil, errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Template) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
// TODO create folder
|
||||
// TODO create folder, optional
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Template) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO move obj
|
||||
// TODO move obj, optional
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Template) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
// TODO rename obj
|
||||
// TODO rename obj, optional
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Template) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO copy obj
|
||||
// TODO copy obj, optional
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Template) Remove(ctx context.Context, obj model.Obj) error {
|
||||
// TODO remove obj
|
||||
// TODO remove obj, optional
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Template) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
// TODO upload file
|
||||
// TODO upload file, optional
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,9 @@ var config = driver.Config{
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "root, / or other",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
142
drivers/trainbit/driver.go
Normal file
142
drivers/trainbit/driver.go
Normal file
@ -0,0 +1,142 @@
|
||||
package trainbit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type Trainbit struct {
|
||||
model.Storage
|
||||
Addition
|
||||
}
|
||||
|
||||
var apiExpiredate, guid string
|
||||
|
||||
func (d *Trainbit) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *Trainbit) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *Trainbit) Init(ctx context.Context) error {
|
||||
http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
var err error
|
||||
apiExpiredate, guid, err = getToken(d.ApiKey, d.AUSHELLPORTAL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Trainbit) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Trainbit) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
form := make(url.Values)
|
||||
form.Set("parentid", strings.Split(dir.GetID(), "_")[0])
|
||||
res, err := postForm("https://trainbit.com/lib/api/v1/listoffiles", form, apiExpiredate, d.ApiKey, d.AUSHELLPORTAL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonData any
|
||||
json.Unmarshal(data, &jsonData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
object, err := parseRawFileObject(jsonData.(map[string]any)["items"].([]any))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return object, nil
|
||||
}
|
||||
|
||||
func (d *Trainbit) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
res, err := get(fmt.Sprintf("https://trainbit.com/files/%s/", strings.Split(file.GetID(), "_")[0]), d.ApiKey, d.AUSHELLPORTAL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.Link{
|
||||
URL: res.Header.Get("Location"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Trainbit) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
form := make(url.Values)
|
||||
form.Set("name", local2provider(dirName, true))
|
||||
form.Set("parentid", strings.Split(parentDir.GetID(), "_")[0])
|
||||
_, err := postForm("https://trainbit.com/lib/api/v1/createfolder", form, apiExpiredate, d.ApiKey, d.AUSHELLPORTAL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Trainbit) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
form := make(url.Values)
|
||||
form.Set("sourceid", strings.Split(srcObj.GetID(), "_")[0])
|
||||
form.Set("destinationid", strings.Split(dstDir.GetID(), "_")[0])
|
||||
_, err := postForm("https://trainbit.com/lib/api/v1/move", form, apiExpiredate, d.ApiKey, d.AUSHELLPORTAL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Trainbit) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
form := make(url.Values)
|
||||
form.Set("id", strings.Split(srcObj.GetID(), "_")[0])
|
||||
form.Set("name", local2provider(newName, srcObj.IsDir()))
|
||||
_, err := postForm("https://trainbit.com/lib/api/v1/edit", form, apiExpiredate, d.ApiKey, d.AUSHELLPORTAL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Trainbit) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Trainbit) Remove(ctx context.Context, obj model.Obj) error {
|
||||
form := make(url.Values)
|
||||
form.Set("id", strings.Split(obj.GetID(), "_")[0])
|
||||
_, err := postForm("https://trainbit.com/lib/api/v1/delete", form, apiExpiredate, d.ApiKey, d.AUSHELLPORTAL)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Trainbit) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
endpoint, _ := url.Parse("https://tb28.trainbit.com/api/upload/send_raw/")
|
||||
query := &url.Values{}
|
||||
query.Add("q", strings.Split(dstDir.GetID(), "_")[1])
|
||||
query.Add("guid", guid)
|
||||
query.Add("name", url.QueryEscape(local2provider(stream.GetName(), false)))
|
||||
endpoint.RawQuery = query.Encode()
|
||||
var total int64
|
||||
total = 0
|
||||
progressReader := &ProgressReader{
|
||||
stream,
|
||||
func(byteNum int) {
|
||||
total += int64(byteNum)
|
||||
up(int(math.Round(float64(total) / float64(stream.GetSize()) * 100)))
|
||||
},
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPost, endpoint.String(), progressReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "text/json; charset=UTF-8")
|
||||
_, err = http.DefaultClient.Do(req)
|
||||
return err
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*Trainbit)(nil)
|
29
drivers/trainbit/meta.go
Normal file
29
drivers/trainbit/meta.go
Normal file
@ -0,0 +1,29 @@
|
||||
package trainbit
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
driver.RootID
|
||||
AUSHELLPORTAL string `json:"AUSHELLPORTAL" required:"true"`
|
||||
ApiKey string `json:"apikey" required:"true"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Trainbit",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "0_000",
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Trainbit{}
|
||||
})
|
||||
}
|
1
drivers/trainbit/types.go
Normal file
1
drivers/trainbit/types.go
Normal file
@ -0,0 +1 @@
|
||||
package trainbit
|
150
drivers/trainbit/util.go
Normal file
150
drivers/trainbit/util.go
Normal file
@ -0,0 +1,150 @@
|
||||
package trainbit
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type ProgressReader struct {
|
||||
io.Reader
|
||||
reporter func(byteNum int)
|
||||
}
|
||||
|
||||
func (progressReader *ProgressReader) Read(data []byte) (int, error) {
|
||||
byteNum, err := progressReader.Reader.Read(data)
|
||||
progressReader.reporter(byteNum)
|
||||
return byteNum, err
|
||||
}
|
||||
|
||||
func get(url string, apiKey string, AUSHELLPORTAL string) (*http.Response, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: ".AUSHELLPORTAL",
|
||||
Value: AUSHELLPORTAL,
|
||||
MaxAge: 2 * 60,
|
||||
})
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "retkeyapi",
|
||||
Value: apiKey,
|
||||
MaxAge: 2 * 60,
|
||||
})
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func postForm(endpoint string, data url.Values, apiExpiredate string, apiKey string, AUSHELLPORTAL string) (*http.Response, error) {
|
||||
extData := make(url.Values)
|
||||
for key, value := range data {
|
||||
extData[key] = make([]string, len(value))
|
||||
copy(extData[key], value)
|
||||
}
|
||||
extData.Set("apikey", apiKey)
|
||||
extData.Set("expiredate", apiExpiredate)
|
||||
req, err := http.NewRequest(http.MethodPost, endpoint, strings.NewReader(extData.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: ".AUSHELLPORTAL",
|
||||
Value: AUSHELLPORTAL,
|
||||
MaxAge: 2 * 60,
|
||||
})
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "retkeyapi",
|
||||
Value: apiKey,
|
||||
MaxAge: 2 * 60,
|
||||
})
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func getToken(apiKey string, AUSHELLPORTAL string) (string, string, error) {
|
||||
res, err := get("https://trainbit.com/files/", apiKey, AUSHELLPORTAL)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
data, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
text := string(data)
|
||||
apiExpiredateReg := regexp.MustCompile(`core.api.expiredate = '([^']*)';`)
|
||||
result := apiExpiredateReg.FindAllStringSubmatch(text, -1)
|
||||
apiExpiredate := result[0][1]
|
||||
guidReg := regexp.MustCompile(`app.vars.upload.guid = '([^']*)';`)
|
||||
result = guidReg.FindAllStringSubmatch(text, -1)
|
||||
guid := result[0][1]
|
||||
return apiExpiredate, guid, nil
|
||||
}
|
||||
|
||||
func local2provider(filename string, isFolder bool) string {
|
||||
filename = strings.Replace(filename, "%", url.QueryEscape("%"), -1)
|
||||
filename = strings.Replace(filename, "/", url.QueryEscape("/"), -1)
|
||||
filename = strings.Replace(filename, ":", url.QueryEscape(":"), -1)
|
||||
filename = strings.Replace(filename, "*", url.QueryEscape("*"), -1)
|
||||
filename = strings.Replace(filename, "?", url.QueryEscape("?"), -1)
|
||||
filename = strings.Replace(filename, "\"", url.QueryEscape("\""), -1)
|
||||
filename = strings.Replace(filename, "<", url.QueryEscape("<"), -1)
|
||||
filename = strings.Replace(filename, ">", url.QueryEscape(">"), -1)
|
||||
filename = strings.Replace(filename, "|", url.QueryEscape("|"), -1)
|
||||
if isFolder {
|
||||
return filename
|
||||
}
|
||||
return strings.Join([]string{filename, ".delete_suffix."}, "")
|
||||
}
|
||||
|
||||
func provider2local(filename string) string {
|
||||
index := strings.LastIndex(filename, ".delete_suffix.")
|
||||
if index != -1 {
|
||||
filename = filename[:index]
|
||||
}
|
||||
rawName := strings.Replace(filename, url.QueryEscape("/"), "/", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape(":"), ":", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("*"), "*", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("?"), "?", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("\""), "\"", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("<"), "<", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape(">"), ">", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("|"), "|", -1)
|
||||
rawName = strings.Replace(rawName, url.QueryEscape("%"), "%", -1)
|
||||
return rawName
|
||||
}
|
||||
|
||||
func parseRawFileObject(rawObject []any) ([]model.Obj, error) {
|
||||
objectList := make([]model.Obj, 0)
|
||||
for _, each := range rawObject {
|
||||
object := each.(map[string]any)
|
||||
if object["id"].(string) == "0" {
|
||||
continue
|
||||
}
|
||||
isFolder := int64(object["ty"].(float64)) == 1
|
||||
var name string
|
||||
if isFolder {
|
||||
name = object["name"].(string)
|
||||
} else {
|
||||
name = strings.Join([]string{object["name"].(string), object["ext"].(string)}, ".")
|
||||
}
|
||||
modified, err := time.Parse("2006/01/02 15:04:05", object["modified"].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objectList = append(objectList, model.Obj(&model.Object{
|
||||
ID: strings.Join([]string{object["id"].(string), strings.Split(object["uploadurl"].(string), "=")[1]}, "_"),
|
||||
Name: provider2local(name),
|
||||
Size: int64(object["byte"].(float64)),
|
||||
Modified: modified.Add(-210 * time.Minute),
|
||||
IsFolder: isFolder,
|
||||
}))
|
||||
}
|
||||
return objectList, nil
|
||||
}
|
9
go.mod
9
go.mod
@ -3,12 +3,12 @@ module github.com/alist-org/alist/v3
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/SheltonZhu/115driver v1.0.13
|
||||
github.com/SheltonZhu/115driver v1.0.14
|
||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
||||
github.com/aws/aws-sdk-go v1.44.194
|
||||
github.com/blevesearch/bleve/v2 v2.3.6
|
||||
github.com/caarlos0/env/v7 v7.0.0
|
||||
github.com/deckarep/golang-set/v2 v2.2.0
|
||||
github.com/caarlos0/env/v7 v7.1.0
|
||||
github.com/deckarep/golang-set/v2 v2.3.0
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564
|
||||
github.com/gin-contrib/cors v1.4.0
|
||||
@ -37,7 +37,7 @@ require (
|
||||
gorm.io/driver/mysql v1.4.7
|
||||
gorm.io/driver/postgres v1.4.8
|
||||
gorm.io/driver/sqlite v1.4.4
|
||||
gorm.io/gorm v1.24.6
|
||||
gorm.io/gorm v1.24.5
|
||||
)
|
||||
|
||||
require (
|
||||
@ -110,6 +110,5 @@ require (
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
41
go.sum
41
go.sum
@ -2,8 +2,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXlp4riwo=
|
||||
github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA=
|
||||
github.com/SheltonZhu/115driver v1.0.13 h1:YEhQ3iMvd5TD6Xp1wKg+73KgdCMjc0pDoT1eCNXnA3M=
|
||||
github.com/SheltonZhu/115driver v1.0.13/go.mod h1:00ixivHH5HqDj4S7kAWbkuUrjtsJTxc7cGv5RMw3RVs=
|
||||
github.com/SheltonZhu/115driver v1.0.14 h1:uW3dl8J9KDMw+3gPxQdhTysoGhw0/uI1484GT9xhfU4=
|
||||
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/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
|
||||
@ -56,8 +56,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBW
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA=
|
||||
github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/caarlos0/env/v7 v7.0.0 h1:cyczlTd/zREwSr9ch/mwaDl7Hse7kJuUY8hvHfXu5WI=
|
||||
github.com/caarlos0/env/v7 v7.0.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
|
||||
github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
|
||||
github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
|
||||
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=
|
||||
@ -66,10 +66,10 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
|
||||
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/deckarep/golang-set/v2 v2.2.0 h1:2pMQd3Soi6qfw7E5MMKaEh5W5ES18bW3AbFFnGl6LgQ=
|
||||
github.com/deckarep/golang-set/v2 v2.2.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/deckarep/golang-set/v2 v2.3.0 h1:qs18EKUfHm2X9fA50Mr/M5hccg2tNnVqsiBImnyDs0g=
|
||||
github.com/deckarep/golang-set/v2 v2.3.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
|
||||
@ -84,25 +84,19 @@ github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURU
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY=
|
||||
github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398=
|
||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
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=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
|
||||
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
|
||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||
@ -110,8 +104,6 @@ github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSM
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
@ -183,8 +175,6 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
|
||||
github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ=
|
||||
github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
@ -246,10 +236,6 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf h1:Y43S3e9P1NPs/QF4R5/SdlXj2d31540hP4Gk8VKNvDg=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20220725095014-c4e0c2b5debf/go.mod h1:c+cGNU1qi9bO7ZF4IRMYk+KaZTNiQ/gQrSbyMmGFq1Q=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20230220145126-b87ebf5801d8 h1:Jg3qJLX/qhwrh4MdB75+Z9l/JkCODVHG8nXY187qa1E=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20230220145126-b87ebf5801d8/go.mod h1:c+cGNU1qi9bO7ZF4IRMYk+KaZTNiQ/gQrSbyMmGFq1Q=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca h1:I9rVnNXdIkij4UvMT7OmKhH9sOIvS8iXkxfPdnn9wQA=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca/go.mod h1:suDIky6yrK07NnaBadCB4sS0CqFOvUK91lH7CR+JlDA=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
@ -259,7 +245,6 @@ github.com/u2takey/ffmpeg-go v0.4.1/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFo
|
||||
github.com/u2takey/go-utils v0.3.1 h1:TaQTgmEZZeDHQFYfd+AdUT1cT4QJgJn/XVPELhHw4ys=
|
||||
github.com/u2takey/go-utils v0.3.1/go.mod h1:6e+v5vEZ/6gu12w/DC2ixZdZtCrNokVxD0JUklcqdCs=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
|
||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
@ -274,7 +259,6 @@ go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@ -284,13 +268,10 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
|
||||
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@ -304,8 +285,6 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.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.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -329,22 +308,20 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
@ -392,7 +369,5 @@ gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
|
||||
gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s=
|
||||
gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
|
@ -158,6 +158,7 @@ func InitialSettings() []model.SettingItem {
|
||||
|
||||
// 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},
|
||||
}
|
||||
if flags.Dev {
|
||||
initialSettingItems = append(initialSettingItems, []model.SettingItem{
|
||||
|
@ -62,6 +62,7 @@ const (
|
||||
|
||||
// qbittorrent
|
||||
QbittorrentUrl = "qbittorrent_url"
|
||||
QbittorrentSeedtime = "qbittorrent_seedtime"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -51,6 +51,15 @@ func GetStorageById(id uint) (*model.Storage, error) {
|
||||
return &storage, nil
|
||||
}
|
||||
|
||||
// GetStorageByMountPath Get Storage by mountPath, used to update storage usually
|
||||
func GetStorageByMountPath(mountPath string) (*model.Storage, error) {
|
||||
var storage model.Storage
|
||||
if err := db.Where("mount_path = ?", mountPath).First(&storage).Error; err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
return &storage, nil
|
||||
}
|
||||
|
||||
func GetEnabledStorages() ([]model.Storage, error) {
|
||||
var storages []model.Storage
|
||||
if err := db.Where(fmt.Sprintf("%s = ?", columnName("disabled")), false).Find(&storages).Error; err != nil {
|
||||
|
@ -2,9 +2,12 @@ package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -13,5 +16,14 @@ func link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, m
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithMessage(err, "failed get storage")
|
||||
}
|
||||
return op.Link(ctx, storage, actualPath, args)
|
||||
l, obj, err := op.Link(ctx, storage, actualPath, args)
|
||||
if err != nil {
|
||||
return nil, nil, errors.WithMessage(err, "failed link")
|
||||
}
|
||||
if l.URL != "" && !strings.HasPrefix(l.URL, "http://") && !strings.HasPrefix(l.URL, "https://") {
|
||||
if c, ok := ctx.(*gin.Context); ok {
|
||||
l.URL = common.GetApiUrl(c.Request) + l.URL
|
||||
}
|
||||
}
|
||||
return l, obj, nil
|
||||
}
|
||||
|
@ -2,5 +2,6 @@ package op
|
||||
|
||||
const (
|
||||
WORK = "work"
|
||||
DISABLED = "disabled"
|
||||
RootName = "root"
|
||||
)
|
||||
|
@ -142,6 +142,7 @@ func DisableStorage(ctx context.Context, id uint) error {
|
||||
}
|
||||
// delete the storage in the memory
|
||||
storage.Disabled = true
|
||||
storage.SetStatus(DISABLED)
|
||||
err = db.UpdateStorage(storage)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed update storage in db")
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/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"
|
||||
@ -50,6 +51,7 @@ func AddURL(ctx context.Context, url string, dstDirPath string) error {
|
||||
tsk: tsk,
|
||||
tempDir: tempDir,
|
||||
dstDirPath: dstDirPath,
|
||||
seedtime: setting.GetInt(conf.QbittorrentSeedtime, 0),
|
||||
}
|
||||
return m.Loop()
|
||||
},
|
||||
|
@ -2,23 +2,26 @@ package qbittorrent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
tsk *task.Task[string]
|
||||
tempDir string
|
||||
dstDirPath string
|
||||
seedtime int
|
||||
finish chan struct{}
|
||||
}
|
||||
|
||||
@ -114,17 +117,27 @@ func (m *Monitor) complete() error {
|
||||
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
|
||||
}
|
||||
// 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()
|
||||
err := os.RemoveAll(m.tempDir)
|
||||
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())
|
||||
}
|
||||
@ -151,7 +164,7 @@ func (m *Monitor) complete() error {
|
||||
Modified: time.Now(),
|
||||
IsFolder: false,
|
||||
},
|
||||
ReadCloser: f,
|
||||
ReadCloser: struct{ io.ReadSeekCloser }{f},
|
||||
Mimetype: mimetype,
|
||||
}
|
||||
return op.Put(tsk.Ctx, storage, dstDir, stream, tsk.SetProgress)
|
||||
|
@ -122,6 +122,10 @@ func (tm *Manager[K]) ClearDone() {
|
||||
tm.RemoveByStates(SUCCEEDED, CANCELED, ERRORED)
|
||||
}
|
||||
|
||||
func (tm *Manager[K]) ClearSucceeded() {
|
||||
tm.RemoveByStates(SUCCEEDED)
|
||||
}
|
||||
|
||||
func (tm *Manager[K]) RawTasks() *generic_sync.MapOf[K, *Task[K]] {
|
||||
return &tm.tasks
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
@ -20,7 +22,7 @@ func IsApply(metaPath, reqPath string, applySub bool) bool {
|
||||
if utils.PathEqual(metaPath, reqPath) {
|
||||
return true
|
||||
}
|
||||
return utils.IsSubPath(reqPath, metaPath) && applySub
|
||||
return utils.IsSubPath(metaPath, reqPath) && applySub
|
||||
}
|
||||
|
||||
func CanAccess(user *model.User, meta *model.Meta, reqPath string, password string) bool {
|
||||
@ -49,3 +51,18 @@ func CanAccess(user *model.User, meta *model.Meta, reqPath string, password stri
|
||||
// validate password
|
||||
return meta.Password == password
|
||||
}
|
||||
|
||||
// ShouldProxy TODO need optimize
|
||||
// when should be proxy?
|
||||
// 1. config.MustProxy()
|
||||
// 2. storage.WebProxy
|
||||
// 3. proxy_types
|
||||
func ShouldProxy(storage driver.Driver, filename string) bool {
|
||||
if storage.Config().MustProxy() || storage.GetStorage().WebProxy {
|
||||
return true
|
||||
}
|
||||
if utils.SliceContains(conf.SlicesMap[conf.ProxyTypes], utils.Ext(filename)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
24
server/common/check_test.go
Normal file
24
server/common/check_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
package common
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIsApply(t *testing.T) {
|
||||
datas := []struct {
|
||||
metaPath string
|
||||
reqPath string
|
||||
applySub bool
|
||||
result bool
|
||||
}{
|
||||
{
|
||||
metaPath: "/",
|
||||
reqPath: "/test",
|
||||
applySub: true,
|
||||
result: true,
|
||||
},
|
||||
}
|
||||
for i, data := range datas {
|
||||
if IsApply(data.metaPath, data.reqPath, data.applySub) != data.result {
|
||||
t.Errorf("TestIsApply %d failed", i)
|
||||
}
|
||||
}
|
||||
}
|
@ -26,8 +26,9 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
||||
defer func() {
|
||||
_ = link.Data.Close()
|
||||
}()
|
||||
filename := file.GetName()
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename)))
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(file.GetSize(), 10))
|
||||
if link.Header != nil {
|
||||
// TODO clean header with blacklist or whitelist
|
||||
|
@ -2,6 +2,7 @@ package handles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
stdpath "path"
|
||||
"strings"
|
||||
|
||||
@ -14,6 +15,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Down(c *gin.Context) {
|
||||
@ -24,11 +26,10 @@ func Down(c *gin.Context) {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if shouldProxy(storage, filename) {
|
||||
if common.ShouldProxy(storage, filename) {
|
||||
Proxy(c)
|
||||
return
|
||||
} else {
|
||||
|
||||
link, _, err := fs.Link(c, rawPath, model.LinkArgs{
|
||||
IP: c.ClientIP(),
|
||||
Header: c.Request.Header,
|
||||
@ -38,6 +39,14 @@ func Down(c *gin.Context) {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if link.Data != nil {
|
||||
defer func(Data io.ReadCloser) {
|
||||
err := Data.Close()
|
||||
if err != nil {
|
||||
log.Errorf("close data error: %s", err)
|
||||
}
|
||||
}(link.Data)
|
||||
}
|
||||
c.Header("Referrer-Policy", "no-referrer")
|
||||
c.Header("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
|
||||
if setting.GetBool(conf.ForwardDirectLinkParams) {
|
||||
@ -102,21 +111,6 @@ func Proxy(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO need optimize
|
||||
// when should be proxy?
|
||||
// 1. config.MustProxy()
|
||||
// 2. storage.WebProxy
|
||||
// 3. proxy_types
|
||||
func shouldProxy(storage driver.Driver, filename string) bool {
|
||||
if storage.Config().MustProxy() || storage.GetStorage().WebProxy {
|
||||
return true
|
||||
}
|
||||
if utils.SliceContains(conf.SlicesMap[conf.ProxyTypes], utils.Ext(filename)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO need optimize
|
||||
// when can be proxy?
|
||||
// 1. text file
|
||||
|
@ -2,17 +2,21 @@ package handles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
stdpath "path"
|
||||
"regexp"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/fs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/internal/sign"
|
||||
"github.com/alist-org/alist/v3/pkg/generic"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type MkdirOrLinkReq struct {
|
||||
@ -92,6 +96,93 @@ func FsMove(c *gin.Context) {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
type RecursiveMoveReq struct {
|
||||
SrcDir string `json:"src_dir"`
|
||||
DstDir string `json:"dst_dir"`
|
||||
}
|
||||
|
||||
func FsRecursiveMove(c *gin.Context) {
|
||||
var req RecursiveMoveReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
|
||||
user := c.MustGet("user").(*model.User)
|
||||
if !user.CanMove() {
|
||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||
return
|
||||
}
|
||||
srcDir, err := user.JoinPath(req.SrcDir)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
dstDir, err := user.JoinPath(req.DstDir)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
|
||||
meta, err := op.GetNearestMeta(srcDir)
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Set("meta", meta)
|
||||
|
||||
rootFiles, err := fs.List(c, srcDir, false)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
|
||||
// record the file path
|
||||
filePathMap := make(map[model.Obj]string)
|
||||
movingFiles := generic.NewQueue[model.Obj]()
|
||||
for _, file := range rootFiles {
|
||||
movingFiles.Push(file)
|
||||
filePathMap[file] = srcDir
|
||||
}
|
||||
|
||||
for !movingFiles.IsEmpty() {
|
||||
|
||||
movingFile := movingFiles.Pop()
|
||||
movingFilePath := fmt.Sprintf("%s/%s", filePathMap[movingFile], movingFile.GetName())
|
||||
if movingFile.IsDir() {
|
||||
// directory, recursive move
|
||||
subFilePath := movingFilePath
|
||||
subFiles, err := fs.List(c, subFilePath, true)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
for _, subFile := range subFiles {
|
||||
movingFiles.Push(subFile)
|
||||
filePathMap[subFile] = subFilePath
|
||||
}
|
||||
} else {
|
||||
|
||||
if movingFilePath == dstDir {
|
||||
// same directory, don't move
|
||||
continue
|
||||
}
|
||||
|
||||
// move
|
||||
err := fs.Move(c, movingFilePath, dstDir, movingFiles.IsEmpty())
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
func FsCopy(c *gin.Context) {
|
||||
var req MoveCopyReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
@ -163,6 +254,67 @@ func FsRename(c *gin.Context) {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
type RegexRenameReq struct {
|
||||
SrcDir string `json:"src_dir"`
|
||||
SrcNameRegex string `json:"src_name_regex"`
|
||||
NewNameRegex string `json:"new_name_regex"`
|
||||
}
|
||||
|
||||
func FsRegexRename(c *gin.Context) {
|
||||
var req RegexRenameReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
user := c.MustGet("user").(*model.User)
|
||||
if !user.CanRename() {
|
||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||
return
|
||||
}
|
||||
|
||||
reqPath, err := user.JoinPath(req.SrcDir)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
|
||||
meta, err := op.GetNearestMeta(reqPath)
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Set("meta", meta)
|
||||
|
||||
srcRegexp, err := regexp.Compile(req.SrcNameRegex)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
|
||||
files, err := fs.List(c, reqPath, false)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
||||
if srcRegexp.MatchString(file.GetName()) {
|
||||
filePath := fmt.Sprintf("%s/%s", reqPath, file.GetName())
|
||||
newFileName := srcRegexp.ReplaceAllString(file.GetName(), req.NewNameRegex)
|
||||
if err := fs.Rename(c, filePath, newFileName); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
type RemoveReq struct {
|
||||
Dir string `json:"dir"`
|
||||
Names []string `json:"names"`
|
||||
@ -229,6 +381,14 @@ func Link(c *gin.Context) {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if link.Data != nil {
|
||||
defer func(Data io.ReadCloser) {
|
||||
err := Data.Close()
|
||||
if err != nil {
|
||||
log.Errorf("close link data error: %v", err)
|
||||
}
|
||||
}(link.Data)
|
||||
}
|
||||
common.SuccessResp(c, link)
|
||||
return
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
type SetQbittorrentReq struct {
|
||||
Url string `json:"url" form:"url"`
|
||||
Seedtime string `json:"seedtime" form:"seedtime"`
|
||||
}
|
||||
|
||||
func SetQbittorrent(c *gin.Context) {
|
||||
@ -19,14 +20,11 @@ func SetQbittorrent(c *gin.Context) {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
item := &model.SettingItem{
|
||||
Key: conf.QbittorrentUrl,
|
||||
Value: req.Url,
|
||||
Type: conf.TypeString,
|
||||
Group: model.SINGLE,
|
||||
Flag: model.PRIVATE,
|
||||
items := []model.SettingItem{
|
||||
{Key: conf.QbittorrentUrl, Value: req.Url, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||
{Key: conf.QbittorrentSeedtime, Value: req.Seedtime, Type: conf.TypeNumber, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||
}
|
||||
if err := op.SaveSettingItem(item); err != nil {
|
||||
if err := op.SaveSettingItems(items); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
|
@ -92,10 +92,27 @@ func taskRoute[K comparable](g *gin.RouterGroup, manager *task.Manager[K], k2Str
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
})
|
||||
g.POST("/retry", func(c *gin.Context) {
|
||||
tid := c.Query("tid")
|
||||
id, err := str2K(tid)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := manager.Retry(id); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
})
|
||||
g.POST("/clear_done", func(c *gin.Context) {
|
||||
manager.ClearDone()
|
||||
common.SuccessResp(c)
|
||||
})
|
||||
g.POST("/clear_succeeded", func(c *gin.Context) {
|
||||
manager.ClearSucceeded()
|
||||
common.SuccessResp(c)
|
||||
})
|
||||
}
|
||||
|
||||
func SetupTaskRoute(g *gin.RouterGroup) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
@ -14,7 +16,7 @@ import (
|
||||
// if token is empty, set user to guest
|
||||
func Auth(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == setting.GetStr(conf.Token) {
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(setting.GetStr(conf.Token))) == 1 {
|
||||
admin, err := op.GetAdmin()
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
|
@ -35,7 +35,7 @@ func FsUp(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !(common.CanAccess(user, meta, path, password) && (user.CanWrite() || common.CanWrite(meta, path))) {
|
||||
if !(common.CanAccess(user, meta, path, password) && (user.CanWrite() || common.CanWrite(meta, stdpath.Dir(path)))) {
|
||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||
c.Abort()
|
||||
return
|
||||
|
@ -21,6 +21,9 @@ func Init(e *gin.Engine) {
|
||||
}
|
||||
Cors(e)
|
||||
g := e.Group(conf.URL.Path)
|
||||
g.Any("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong")
|
||||
})
|
||||
common.SecretKey = []byte(conf.Conf.JwtSecret)
|
||||
g.Use(middlewares.StoragesLoaded)
|
||||
if conf.Conf.MaxConnections > 0 {
|
||||
@ -123,7 +126,9 @@ func _fs(g *gin.RouterGroup) {
|
||||
g.Any("/dirs", handles.FsDirs)
|
||||
g.POST("/mkdir", handles.FsMkdir)
|
||||
g.POST("/rename", handles.FsRename)
|
||||
g.POST("/regex_rename", handles.FsRegexRename)
|
||||
g.POST("/move", handles.FsMove)
|
||||
g.POST("/recursive_move", handles.FsRecursiveMove)
|
||||
g.POST("/copy", handles.FsCopy)
|
||||
g.POST("/remove", handles.FsRemove)
|
||||
g.PUT("/put", middlewares.FsUp, handles.FsStream)
|
||||
|
Reference in New Issue
Block a user