Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
f1a9b68022 | |||
dda1da4576 | |||
5b7aa9c1cf | |||
a28aaceaad | |||
2bb200af87 | |||
97f1efbb72 | |||
bf8b6f4c2c | |||
bd33c200dc | |||
bc6baf1be0 | |||
dc8d5106f9 | |||
8c0dfe2f3d | |||
4e1be9bee6 | |||
4c5285e094 | |||
0838feeb82 | |||
ae791c8634 | |||
09f480318c | |||
4c5be5f07f | |||
9c1ffdbb82 | |||
18a63e34dd | |||
ff0bcfef8a | |||
4980b71ba3 | |||
b5bf5f4325 | |||
f9788ea7cf | |||
83644dab85 | |||
d94cf72da2 | |||
e98561ceb1 | |||
76f37373e0 | |||
61a06992c3 | |||
ddcba93eea | |||
bb969d8dc6 | |||
2383e851e2 | |||
330a767fd7 |
5
.github/workflows/auto_lang.yml
vendored
5
.github/workflows/auto_lang.yml
vendored
@ -7,6 +7,8 @@ on:
|
||||
paths:
|
||||
- 'drivers/**'
|
||||
- 'internal/bootstrap/data/setting.go'
|
||||
- 'internal/conf/const.go'
|
||||
- 'cmd/lang.go'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@ -42,6 +44,7 @@ jobs:
|
||||
cd alist
|
||||
go run ./main.go lang
|
||||
cd ..
|
||||
|
||||
- name: Copy lang file
|
||||
run: |
|
||||
cp -f ./alist/lang/*.json ./alist-web/src/lang/en/ 2>/dev/null || :
|
||||
@ -61,4 +64,4 @@ jobs:
|
||||
github_token: ${{ secrets.MY_TOKEN }}
|
||||
branch: main
|
||||
directory: alist-web
|
||||
repository: alist-org/alist-web
|
||||
repository: alist-org/alist-web
|
||||
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -2,9 +2,9 @@ name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '**' ]
|
||||
branches: [ 'main' ]
|
||||
pull_request:
|
||||
branches: [ '**' ]
|
||||
branches: [ 'main' ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -15,6 +15,7 @@ func Init() {
|
||||
bootstrap.InitConfig()
|
||||
bootstrap.Log()
|
||||
bootstrap.InitDB()
|
||||
bootstrap.InitIndex()
|
||||
data.InitData()
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ func writeFile(name string, data interface{}) {
|
||||
} else {
|
||||
log.Infof("%s.json changed, update file", name)
|
||||
//log.Infof("old: %+v\nnew:%+v", oldData, data)
|
||||
utils.WriteJsonToFile(fmt.Sprintf("lang/%s.json", name), data)
|
||||
utils.WriteJsonToFile(fmt.Sprintf("lang/%s.json", name), newData, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
//type BaseResp struct {
|
||||
@ -39,7 +40,7 @@ func (f File) GetSize() int64 {
|
||||
}
|
||||
|
||||
func (f File) GetName() string {
|
||||
return f.FileName
|
||||
return utils.MappingName(f.FileName)
|
||||
}
|
||||
|
||||
func (f File) ModTime() time.Time {
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
// 居然有四种返回方式
|
||||
@ -134,7 +136,7 @@ type Cloud189File struct {
|
||||
}
|
||||
|
||||
func (c *Cloud189File) GetSize() int64 { return c.Size }
|
||||
func (c *Cloud189File) GetName() string { return c.Name }
|
||||
func (c *Cloud189File) GetName() string { return utils.MappingName(c.Name) }
|
||||
func (c *Cloud189File) ModTime() time.Time {
|
||||
if c.parseTime == nil {
|
||||
c.parseTime = MustParseTime(c.LastOpTime)
|
||||
@ -166,7 +168,7 @@ type Cloud189Folder struct {
|
||||
}
|
||||
|
||||
func (c *Cloud189Folder) GetSize() int64 { return 0 }
|
||||
func (c *Cloud189Folder) GetName() string { return c.Name }
|
||||
func (c *Cloud189Folder) GetName() string { return utils.MappingName(c.Name) }
|
||||
func (c *Cloud189Folder) ModTime() time.Time {
|
||||
if c.parseTime == nil {
|
||||
c.parseTime = MustParseTime(c.LastOpTime)
|
||||
|
@ -30,6 +30,9 @@ func (d *AListV2) Init(ctx context.Context, storage model.Storage) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(d.Addition.Address) > 0 && string(d.Addition.Address[len(d.Addition.Address)-1]) == "/" {
|
||||
d.Addition.Address = d.Addition.Address[0 : len(d.Addition.Address)-1]
|
||||
}
|
||||
// TODO login / refresh token
|
||||
//op.MustSaveDriverStorage(d)
|
||||
return err
|
||||
|
@ -30,6 +30,9 @@ func (d *AListV3) Init(ctx context.Context, storage model.Storage) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(d.Addition.Address) > 0 && string(d.Addition.Address[len(d.Addition.Address)-1]) == "/" {
|
||||
d.Addition.Address = d.Addition.Address[0 : len(d.Addition.Address)-1]
|
||||
}
|
||||
// TODO login / refresh token
|
||||
//op.MustSaveDriverStorage(d)
|
||||
return err
|
||||
@ -46,7 +49,7 @@ func (d *AListV3) List(ctx context.Context, dir model.Obj, args model.ListArgs)
|
||||
SetResult(&resp).
|
||||
SetHeader("Authorization", d.AccessToken).
|
||||
SetBody(ListReq{
|
||||
PageReq: common.PageReq{
|
||||
PageReq: model.PageReq{
|
||||
Page: 1,
|
||||
PerPage: 0,
|
||||
},
|
||||
|
@ -3,11 +3,11 @@ package alist_v3
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type ListReq struct {
|
||||
common.PageReq
|
||||
model.PageReq
|
||||
Path string `json:"path" form:"path"`
|
||||
Password string `json:"password" form:"password"`
|
||||
Refresh bool `json:"refresh"`
|
||||
|
@ -62,6 +62,8 @@ func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp i
|
||||
return d.request(url, method, callback, resp)
|
||||
}
|
||||
return nil, errors.New(e.Message), e
|
||||
} else if res.IsError() {
|
||||
return nil, errors.New("bad status code " + res.Status()), e
|
||||
}
|
||||
return res.Body(), nil, e
|
||||
}
|
||||
|
@ -80,62 +80,40 @@ func (d *AliyundriveShare) List(ctx context.Context, dir model.Obj, args model.L
|
||||
|
||||
func (d *AliyundriveShare) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
data := base.Json{
|
||||
"drive_id": d.DriveId,
|
||||
"file_id": file.GetID(),
|
||||
"expire_sec": 14400,
|
||||
"drive_id": d.DriveId,
|
||||
"file_id": file.GetID(),
|
||||
// // Only ten minutes lifetime
|
||||
"expire_sec": 600,
|
||||
"share_id": d.ShareId,
|
||||
}
|
||||
var resp ShareLinkResp
|
||||
var e ErrorResp
|
||||
res, err := base.RestyClient.R().
|
||||
SetError(&e).SetBody(data).
|
||||
_, err := base.RestyClient.R().
|
||||
SetError(&e).SetBody(data).SetResult(&resp).
|
||||
SetHeader("content-type", "application/json").
|
||||
SetHeader("Authorization", "Bearer\t"+d.AccessToken).
|
||||
Post("https://api.aliyundrive.com/v2/file/get_download_url")
|
||||
SetHeader("x-share-token", d.ShareToken).
|
||||
Post("https://api.aliyundrive.com/v2/file/get_share_link_download_url")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var u string
|
||||
if e.Code != "" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = d.refreshToken()
|
||||
if e.Code == "AccessTokenInvalid" || e.Code == "ShareLinkTokenInvalid" {
|
||||
if e.Code == "AccessTokenInvalid" {
|
||||
err = d.refreshToken()
|
||||
} else {
|
||||
err = d.getShareToken()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Link(ctx, file, args)
|
||||
} else if e.Code == "ForbiddenNoPermission.File" {
|
||||
data = utils.MergeMap(data, base.Json{
|
||||
// Only ten minutes valid
|
||||
"expire_sec": 600,
|
||||
"share_id": d.ShareId,
|
||||
})
|
||||
var resp ShareLinkResp
|
||||
var e2 ErrorResp
|
||||
_, err = base.RestyClient.R().
|
||||
SetError(&e2).SetBody(data).SetResult(&resp).
|
||||
SetHeader("content-type", "application/json").
|
||||
SetHeader("Authorization", "Bearer\t"+d.AccessToken).
|
||||
SetHeader("x-share-token", d.ShareToken).
|
||||
Post("https://api.aliyundrive.com/v2/file/get_share_link_download_url")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e2.Code != "" {
|
||||
if e2.Code == "AccessTokenInvalid" || e2.Code == "ShareLinkTokenInvalid" {
|
||||
err = d.getShareToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Link(ctx, file, args)
|
||||
} else {
|
||||
return nil, errors.New(e2.Code + ":" + e2.Message)
|
||||
}
|
||||
} else {
|
||||
u = resp.DownloadUrl
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New(e.Code + ":" + e.Message)
|
||||
return nil, errors.New(e.Code + ": " + e.Message)
|
||||
}
|
||||
} else {
|
||||
u = utils.Json.Get(res.Body(), "url").ToString()
|
||||
u = resp.DownloadUrl
|
||||
}
|
||||
return &model.Link{
|
||||
Header: http.Header{
|
||||
|
@ -3,6 +3,8 @@ package baiduphoto
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
type TokenErrResp struct {
|
||||
@ -100,7 +102,7 @@ type (
|
||||
)
|
||||
|
||||
func (a *Album) GetSize() int64 { return 0 }
|
||||
func (a *Album) GetName() string { return fmt.Sprint(a.Title) }
|
||||
func (a *Album) GetName() string { return utils.MappingName(a.Title) }
|
||||
func (a *Album) ModTime() time.Time {
|
||||
if a.parseTime == nil {
|
||||
a.parseTime = toTime(a.Mtime)
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/disintegration/imaging"
|
||||
_ "golang.org/x/image/webp"
|
||||
)
|
||||
|
||||
type Local struct {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
@ -34,6 +35,9 @@ func (d *SMB) Init(ctx context.Context, storage model.Storage) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Index(d.Addition.Address, ":") < 0{
|
||||
d.Addition.Address = d.Addition.Address + ":445"
|
||||
}
|
||||
return d.initFS()
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
type ErrResp struct {
|
||||
@ -147,7 +149,7 @@ type Files struct {
|
||||
}
|
||||
|
||||
func (c *Files) GetSize() int64 { size, _ := strconv.ParseInt(c.Size, 10, 64); return size }
|
||||
func (c *Files) GetName() string { return c.Name }
|
||||
func (c *Files) GetName() string { return utils.MappingName(c.Name) }
|
||||
func (c *Files) ModTime() time.Time { return c.ModifiedTime }
|
||||
func (c *Files) IsDir() bool { return c.Kind == FOLDER }
|
||||
func (c *Files) GetID() string { return c.ID }
|
||||
|
@ -73,24 +73,14 @@ func (d *WebDav) List(ctx context.Context, dir model.Obj, args model.ListArgs) (
|
||||
//}
|
||||
|
||||
func (d *WebDav) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
callback := func(r *http.Request) {
|
||||
if args.Header.Get("Range") != "" {
|
||||
r.Header.Set("Range", args.Header.Get("Range"))
|
||||
}
|
||||
if args.Header.Get("If-Range") != "" {
|
||||
r.Header.Set("If-Range", args.Header.Get("If-Range"))
|
||||
}
|
||||
}
|
||||
reader, header, err := d.client.ReadStream(file.GetPath(), callback)
|
||||
url, header, err := d.client.Link(file.GetPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link := &model.Link{Data: reader}
|
||||
if header.Get("Content-Range") != "" {
|
||||
link.Status = 206
|
||||
link.Header = header
|
||||
}
|
||||
return link, nil
|
||||
return &model.Link{
|
||||
URL: url,
|
||||
Header: header,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *WebDav) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
|
@ -16,7 +16,7 @@ type Addition struct {
|
||||
var config = driver.Config{
|
||||
Name: "WebDav",
|
||||
LocalSort: true,
|
||||
OnlyLocal: true,
|
||||
OnlyProxy: true,
|
||||
DefaultRoot: "/",
|
||||
}
|
||||
|
||||
|
32
go.mod
32
go.mod
@ -3,15 +3,17 @@ module github.com/alist-org/alist/v3
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/SheltonZhu/115driver v1.0.12
|
||||
github.com/SheltonZhu/115driver v1.0.13
|
||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
||||
github.com/aws/aws-sdk-go v1.44.142
|
||||
github.com/aws/aws-sdk-go v1.44.152
|
||||
github.com/blevesearch/bleve/v2 v2.3.5
|
||||
github.com/caarlos0/env/v6 v6.10.1
|
||||
github.com/deckarep/golang-set/v2 v2.1.0
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gin-contrib/cors v1.4.0
|
||||
github.com/gin-gonic/gin v1.8.1
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/hirochachacha/go-smb2 v1.1.0
|
||||
@ -27,6 +29,7 @@ require (
|
||||
github.com/upyun/go-sdk/v3 v3.0.3
|
||||
github.com/winfsp/cgofuse v1.5.0
|
||||
golang.org/x/crypto v0.3.0
|
||||
golang.org/x/image v0.1.0
|
||||
golang.org/x/net v0.2.0
|
||||
gorm.io/driver/mysql v1.4.4
|
||||
gorm.io/driver/postgres v1.4.5
|
||||
@ -35,9 +38,26 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/RoaringBitmap/roaring v0.9.4 // 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
|
||||
github.com/bits-and-blooms/bitset v1.2.0 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.0.4 // indirect
|
||||
github.com/blevesearch/geo v0.1.15 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.0.4 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.3 // indirect
|
||||
github.com/blevesearch/segment v0.9.0 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.1 // indirect
|
||||
github.com/blevesearch/vellum v1.0.9 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.3.6 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.3.6 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.3.6 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.3.6 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.3.6 // indirect
|
||||
github.com/bluele/gcache v0.0.2 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/gaoyb7/115drive-webdav v0.1.8 // indirect
|
||||
@ -48,6 +68,9 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.11.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
github.com/goccy/go-json v0.9.7 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/protobuf v1.5.0 // indirect
|
||||
github.com/golang/snappy v0.0.1 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
@ -68,13 +91,14 @@ require (
|
||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/ugorji/go/codec v1.2.7 // indirect
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 // indirect
|
||||
go.etcd.io/bbolt v1.3.5 // indirect
|
||||
golang.org/x/sys v0.2.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
|
67
go.sum
67
go.sum
@ -2,8 +2,10 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/SheltonZhu/115driver v1.0.12 h1:+GlIM5h8tzuec6MzK0wFwb7bY77nav7JhY8lTljzls4=
|
||||
github.com/SheltonZhu/115driver v1.0.12/go.mod h1:00ixivHH5HqDj4S7kAWbkuUrjtsJTxc7cGv5RMw3RVs=
|
||||
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/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=
|
||||
@ -12,8 +14,43 @@ github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible h1:QoRMR0TCctLDqBCMyOu1e
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andreburgaud/crypt2go v1.1.0 h1:eitZxTPY1krUsxinsng3Qvt/Ud7q/aQmmYRh8p4hyPw=
|
||||
github.com/andreburgaud/crypt2go v1.1.0/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9yEt0zPe9Ayuhc=
|
||||
github.com/aws/aws-sdk-go v1.44.142 h1:KZ1/FDwCSft1DuNllFaBtWpcG0CW2NgQjvOrE1TdlXE=
|
||||
github.com/aws/aws-sdk-go v1.44.142/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go v1.44.152 h1:L9aaepO8wHB67gwuGD8VgIYH/cmQDxieCt7FeLa0+fI=
|
||||
github.com/aws/aws-sdk-go v1.44.152/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/blevesearch/bleve/v2 v2.3.5 h1:1wuR7eB8Fk9UaCaBUfnQt5V7zIpi4VDok9ExN7Rl+/8=
|
||||
github.com/blevesearch/bleve/v2 v2.3.5/go.mod h1:FneKGHMRrCLrp4X9+iy3wlBqgM2ALucg7bp8jUuAi/s=
|
||||
github.com/blevesearch/bleve_index_api v1.0.3/go.mod h1:fiwKS0xLEm+gBRgv5mumf0dhgFr2mDgZah1pqv1c1M4=
|
||||
github.com/blevesearch/bleve_index_api v1.0.4 h1:mtlzsyJjMIlDngqqB1mq8kPryUMIuEVVbRbJHOWEexU=
|
||||
github.com/blevesearch/bleve_index_api v1.0.4/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||
github.com/blevesearch/geo v0.1.15 h1:0NybEduqE5fduFRYiUKF0uqybAIFKXYjkBdXKYn7oA4=
|
||||
github.com/blevesearch/geo v0.1.15/go.mod h1:cRIvqCdk3cgMhGeHNNe6yPzb+w56otxbfo1FBJfR2Pc=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.3 h1:2UzpR2dR5DvSZk8tVJkcQ7D5xhoK/UBelYw8ttBHrRQ=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.3/go.mod h1:eZrfp1y+lUh+DzFjUcTBUSnKGuunyFIpBIvqYVzJfvc=
|
||||
github.com/blevesearch/segment v0.9.0 h1:5lG7yBCx98or7gK2cHMKPukPZ/31Kag7nONpoBt22Ac=
|
||||
github.com/blevesearch/segment v0.9.0/go.mod h1:9PfHYUdQCgHktBgvtUOF4x+pc4/l8rdH0u5spnW85UQ=
|
||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||
github.com/blevesearch/snowballstem v0.9.0/go.mod h1:PivSj3JMc8WuaFkTSRDW2SlrulNWPl4ABg1tC/hlgLs=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.1 h1:1SYRwyoFLwG3sj0ed89RLtM15amfX2pXlYbFOnF8zNU=
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.1/go.mod h1:MQDVGpHZrpe3Uy26zJBf/a8h0FZY6xJbthIMm8myH2Q=
|
||||
github.com/blevesearch/vellum v1.0.9 h1:PL+NWVk3dDGPCV0hoDu9XLLJgqU4E5s/dOeEJByQ2uQ=
|
||||
github.com/blevesearch/vellum v1.0.9/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||
github.com/blevesearch/zapx/v11 v11.3.6 h1:50jET4HUJ6eCqGxdhUt+mjybMvEX2MWyqLGtCx3yUgc=
|
||||
github.com/blevesearch/zapx/v11 v11.3.6/go.mod h1:B0CzJRj/pS7hJIroflRtFsa9mRHpMSucSgre0FVINns=
|
||||
github.com/blevesearch/zapx/v12 v12.3.6 h1:G304NHBLgQeZ+IHK/XRCM0nhHqAts8MEvHI6LhoDNM4=
|
||||
github.com/blevesearch/zapx/v12 v12.3.6/go.mod h1:iYi7tIKpauwU5os5wTxJITixr5Km21Hl365otMwdaP0=
|
||||
github.com/blevesearch/zapx/v13 v13.3.6 h1:vavltQHNdjQezhLZs5nIakf+w/uOa1oqZxB58Jy/3Ig=
|
||||
github.com/blevesearch/zapx/v13 v13.3.6/go.mod h1:X+FsTwCU8qOHtK0d/ArvbOH7qiIgViSQ1GQvcR6LSkI=
|
||||
github.com/blevesearch/zapx/v14 v14.3.6 h1:b9lub7TvcwUyJxK/cQtnN79abngKxsI7zMZnICU0WhE=
|
||||
github.com/blevesearch/zapx/v14 v14.3.6/go.mod h1:9X8W3XoikagU0rwcTqwZho7p9cC7m7zhPZO94S4wUvM=
|
||||
github.com/blevesearch/zapx/v15 v15.3.6 h1:VSswg/ysDxHgitcNkpUNtaTYS4j3uItpXWLAASphl6k=
|
||||
github.com/blevesearch/zapx/v15 v15.3.6/go.mod h1:5DbhhDTGtuQSns1tS2aJxJLPc91boXCvjOMeCLD1saM=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
@ -30,6 +67,8 @@ 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/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/gaoyb7/115drive-webdav v0.1.8 h1:EJt4PSmcbvBY4KUh2zSo5p6fN9LZFNkIzuKejipubVw=
|
||||
@ -62,12 +101,18 @@ github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@ -140,6 +185,7 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
@ -178,6 +224,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
||||
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 h1:dg/EaaJLPIg4xn2kaZil7Ax3wfoxcFXaBwyOTlcz5AI=
|
||||
@ -245,6 +293,8 @@ github.com/winfsp/cgofuse v1.5.0 h1:MsBP7Mi/LiJf/7/F3O/7HjjR009ds6KCdqXzKpZSWxI=
|
||||
github.com/winfsp/cgofuse v1.5.0/go.mod h1:h3awhoUOcn2VYVKCwDaYxSLlZwnyK+A8KaDoLUp2lbU=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
@ -275,8 +325,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
|
||||
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
@ -303,6 +353,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@ -171,7 +172,12 @@ func (m *Monitor) Complete() error {
|
||||
ReadCloser: f,
|
||||
Mimetype: mimetype,
|
||||
}
|
||||
return op.Put(tsk.Ctx, storage, dstDirActualPath, stream, tsk.SetProgress)
|
||||
relDir, err := filepath.Rel(m.tempDir, filepath.Dir(file.Path))
|
||||
if err != nil {
|
||||
log.Errorf("find relation directory error: %v", err)
|
||||
}
|
||||
newDistDir := filepath.Join(dstDirActualPath, relDir)
|
||||
return op.Put(tsk.Ctx, storage, newDistDir, stream, tsk.SetProgress)
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
@ -6,9 +6,11 @@ import (
|
||||
)
|
||||
|
||||
func InitAria2() {
|
||||
_, err := aria2.InitClient(2)
|
||||
if err != nil {
|
||||
//utils.Log.Errorf("failed to init aria2 client: %+v", err)
|
||||
utils.Log.Infof("Aria2 not ready.")
|
||||
}
|
||||
go func() {
|
||||
_, err := aria2.InitClient(2)
|
||||
if err != nil {
|
||||
//utils.Log.Errorf("failed to init aria2 client: %+v", err)
|
||||
utils.Log.Infof("Aria2 not ready.")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -13,12 +13,14 @@ import (
|
||||
|
||||
func InitConfig() {
|
||||
if flags.ForceBinDir {
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
utils.Log.Fatal(err)
|
||||
if !filepath.IsAbs(flags.DataDir) {
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
utils.Log.Fatal(err)
|
||||
}
|
||||
exPath := filepath.Dir(ex)
|
||||
flags.DataDir = filepath.Join(exPath, flags.DataDir)
|
||||
}
|
||||
exPath := filepath.Dir(ex)
|
||||
flags.DataDir = filepath.Join(exPath, "data")
|
||||
}
|
||||
configPath := filepath.Join(flags.DataDir, "config.json")
|
||||
log.Infof("reading config file: %s", configPath)
|
||||
|
@ -25,12 +25,13 @@ func initSettings() {
|
||||
settings[i].Flag = model.DEPRECATED
|
||||
}
|
||||
}
|
||||
if settings != nil && len(settings) > 0 {
|
||||
err = db.SaveSettingItems(settings)
|
||||
if err != nil {
|
||||
log.Fatalf("failed save settings: %+v", err)
|
||||
}
|
||||
}
|
||||
// what's going on here???
|
||||
//if settings != nil && len(settings) > 0 {
|
||||
// err = db.SaveSettingItems(settings)
|
||||
// if err != nil {
|
||||
// log.Fatalf("failed save settings: %+v", err)
|
||||
// }
|
||||
//}
|
||||
// insert new items
|
||||
for i := range initialSettingItems {
|
||||
v := initialSettingItems[i]
|
||||
@ -116,17 +117,23 @@ func InitialSettings() []model.SettingItem {
|
||||
{Key: conf.CustomizeHead, Value: `<script src="https://polyfill.io/v3/polyfill.min.js?features=String.prototype.replaceAll"></script>`, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: conf.CustomizeBody, Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: conf.LinkExpiration, Value: "0", Type: conf.TypeNumber, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: conf.SignAll, Value: "true", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: conf.PrivacyRegs, Value: `(?:(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])
|
||||
([[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}|::|:(?::[[:xdigit:]]{1,4}){1,6}|[[:xdigit:]]{1,4}:(?::[[:xdigit:]]{1,4}){1,5}|(?:[[:xdigit:]]{1,4}:){2}(?::[[:xdigit:]]{1,4}){1,4}|(?:[[:xdigit:]]{1,4}:){3}(?::[[:xdigit:]]{1,4}){1,3}|(?:[[:xdigit:]]{1,4}:){4}(?::[[:xdigit:]]{1,4}){1,2}|(?:[[:xdigit:]]{1,4}:){5}:[[:xdigit:]]{1,4}|(?:[[:xdigit:]]{1,4}:){1,6}:)
|
||||
(?U)access_token=(.*)&`,
|
||||
Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: conf.OcrApi, Value: "https://api.nn.ci/ocr/file/json", Type: conf.TypeString, Group: model.GLOBAL},
|
||||
{Key: conf.FilenameCharMapping, Value: `{"/": "|"}`, Type: conf.TypeText, Group: model.GLOBAL},
|
||||
|
||||
// aria2 settings
|
||||
{Key: conf.Aria2Uri, Value: "http://localhost:6800/jsonrpc", Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
|
||||
{Key: conf.Aria2Secret, Value: "", Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
|
||||
|
||||
// single settings
|
||||
{Key: conf.Token, Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||
{Key: conf.SearchIndex, Value: "none", Type: conf.TypeSelect, Options: "database,bleve,none", Group: model.INDEX},
|
||||
{Key: conf.IgnorePaths, Value: "", Type: conf.TypeText, Group: model.INDEX, Flag: model.PRIVATE, Help: `one path per line`},
|
||||
{Key: conf.IndexProgress, Value: "{}", Type: conf.TypeText, Group: model.SINGLE, Flag: model.PRIVATE},
|
||||
}
|
||||
if flags.Dev {
|
||||
initialSettingItems = append(initialSettingItems, []model.SettingItem{
|
||||
|
18
internal/bootstrap/index.go
Normal file
18
internal/bootstrap/index.go
Normal file
@ -0,0 +1,18 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/search"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func InitIndex() {
|
||||
progress, err := search.Progress()
|
||||
if err != nil {
|
||||
log.Errorf("init index error: %+v", err)
|
||||
return
|
||||
}
|
||||
if !progress.IsDone {
|
||||
progress.IsDone = true
|
||||
search.WriteProgress(progress)
|
||||
}
|
||||
}
|
@ -45,11 +45,13 @@ type Config struct {
|
||||
Database Database `json:"database"`
|
||||
Scheme Scheme `json:"scheme"`
|
||||
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
||||
BleveDir string `json:"bleve_dir" env:"BLEVE_DIR"`
|
||||
Log LogConfig `json:"log"`
|
||||
}
|
||||
|
||||
func DefaultConfig() *Config {
|
||||
tempDir := filepath.Join(flags.DataDir, "temp")
|
||||
indexDir := filepath.Join(flags.DataDir, "bleve")
|
||||
logPath := filepath.Join(flags.DataDir, "log/log.log")
|
||||
dbPath := filepath.Join(flags.DataDir, "data.db")
|
||||
return &Config{
|
||||
@ -64,6 +66,7 @@ func DefaultConfig() *Config {
|
||||
TablePrefix: "x_",
|
||||
DBFile: dbPath,
|
||||
},
|
||||
BleveDir: indexDir,
|
||||
Log: LogConfig{
|
||||
Enable: true,
|
||||
Name: logPath,
|
||||
|
@ -25,7 +25,7 @@ const (
|
||||
AudioTypes = "audio_types"
|
||||
VideoTypes = "video_types"
|
||||
ImageTypes = "image_types"
|
||||
//OfficeTypes = "office_types"
|
||||
// OfficeTypes = "office_types"
|
||||
ProxyTypes = "proxy_types"
|
||||
OfficeViewers = "office_viewers"
|
||||
PdfViewers = "pdf_viewers"
|
||||
@ -37,16 +37,22 @@ const (
|
||||
CustomizeHead = "customize_head"
|
||||
CustomizeBody = "customize_body"
|
||||
LinkExpiration = "link_expiration"
|
||||
SignAll = "sign_all"
|
||||
PrivacyRegs = "privacy_regs"
|
||||
OcrApi = "ocr_api"
|
||||
FilenameCharMapping = "filename_char_mapping"
|
||||
|
||||
// index
|
||||
SearchIndex = "search_index"
|
||||
IgnorePaths = "ignore_paths"
|
||||
|
||||
// aria2
|
||||
Aria2Uri = "aria2_uri"
|
||||
Aria2Secret = "aria2_secret"
|
||||
|
||||
// single
|
||||
Token = "token"
|
||||
Token = "token"
|
||||
IndexProgress = "index_progress"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -12,13 +12,18 @@ var db *gorm.DB
|
||||
|
||||
func Init(d *gorm.DB) {
|
||||
db = d
|
||||
var err error
|
||||
if conf.Conf.Database.Type == "mysql" {
|
||||
err = db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem))
|
||||
} else {
|
||||
err = db.AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem))
|
||||
}
|
||||
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode))
|
||||
if err != nil {
|
||||
log.Fatalf("failed migrate database: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func AutoMigrate(dst ...interface{}) error {
|
||||
var err error
|
||||
if conf.Conf.Database.Type == "mysql" {
|
||||
err = db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").AutoMigrate(dst...)
|
||||
} else {
|
||||
err = db.AutoMigrate(dst...)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
62
internal/db/searchnode.go
Normal file
62
internal/db/searchnode.go
Normal file
@ -0,0 +1,62 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func CreateSearchNode(node *model.SearchNode) error {
|
||||
return db.Create(node).Error
|
||||
}
|
||||
|
||||
func BatchCreateSearchNodes(nodes *[]model.SearchNode) error {
|
||||
return db.CreateInBatches(nodes, 1000).Error
|
||||
}
|
||||
|
||||
func DeleteSearchNodesByParent(prefix string) error {
|
||||
err := db.Where(fmt.Sprintf("%s LIKE ?",
|
||||
columnName("parent")), fmt.Sprintf("%s%%", prefix)).
|
||||
Delete(&model.SearchNode{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir, name := path.Split(prefix)
|
||||
return db.Where(fmt.Sprintf("%s = ? AND %s = ?",
|
||||
columnName("parent"), columnName("name")),
|
||||
utils.StandardizePath(dir), name).Delete(&model.SearchNode{}).Error
|
||||
}
|
||||
|
||||
func ClearSearchNodes() error {
|
||||
return db.Where("1 = 1").Delete(&model.SearchNode{}).Error
|
||||
}
|
||||
|
||||
func GetSearchNodesByParent(parent string) ([]model.SearchNode, error) {
|
||||
var nodes []model.SearchNode
|
||||
if err := db.Where(fmt.Sprintf("%s = ?",
|
||||
columnName("parent")), parent).Find(&nodes).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func SearchNode(req model.SearchReq) ([]model.SearchNode, int64, error) {
|
||||
searchDB := db.Model(&model.SearchNode{}).Where(
|
||||
fmt.Sprintf("%s LIKE ? AND %s LIKE ?",
|
||||
columnName("parent"),
|
||||
columnName("name")),
|
||||
fmt.Sprintf("%s%%", req.Parent),
|
||||
fmt.Sprintf("%%%s%%", req.Keywords))
|
||||
var count int64
|
||||
if err := searchDB.Count(&count).Error; err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed get users count")
|
||||
}
|
||||
var files []model.SearchNode
|
||||
if err := searchDB.Offset((req.Page - 1) * req.PerPage).Limit(req.PerPage).Find(&files).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return files, count, nil
|
||||
}
|
@ -11,81 +11,64 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type SettingItemHook struct {
|
||||
Hook func(item *model.SettingItem) error
|
||||
}
|
||||
type SettingItemHook func(item *model.SettingItem) error
|
||||
|
||||
var SettingItemHooks = map[string]SettingItemHook{
|
||||
conf.VideoTypes: {
|
||||
Hook: func(item *model.SettingItem) error {
|
||||
conf.TypesMap[conf.VideoTypes] = strings.Split(item.Value, ",")
|
||||
return nil
|
||||
},
|
||||
var settingItemHooks = map[string]SettingItemHook{
|
||||
conf.VideoTypes: func(item *model.SettingItem) error {
|
||||
conf.TypesMap[conf.VideoTypes] = strings.Split(item.Value, ",")
|
||||
return nil
|
||||
},
|
||||
conf.AudioTypes: {
|
||||
Hook: func(item *model.SettingItem) error {
|
||||
conf.TypesMap[conf.AudioTypes] = strings.Split(item.Value, ",")
|
||||
return nil
|
||||
},
|
||||
conf.AudioTypes: func(item *model.SettingItem) error {
|
||||
conf.TypesMap[conf.AudioTypes] = strings.Split(item.Value, ",")
|
||||
return nil
|
||||
},
|
||||
conf.ImageTypes: {
|
||||
Hook: func(item *model.SettingItem) error {
|
||||
conf.TypesMap[conf.ImageTypes] = strings.Split(item.Value, ",")
|
||||
return nil
|
||||
},
|
||||
conf.ImageTypes: func(item *model.SettingItem) error {
|
||||
conf.TypesMap[conf.ImageTypes] = strings.Split(item.Value, ",")
|
||||
return nil
|
||||
},
|
||||
conf.TextTypes: {
|
||||
Hook: func(item *model.SettingItem) error {
|
||||
conf.TypesMap[conf.TextTypes] = strings.Split(item.Value, ",")
|
||||
return nil
|
||||
},
|
||||
conf.TextTypes: func(item *model.SettingItem) error {
|
||||
conf.TypesMap[conf.TextTypes] = strings.Split(item.Value, ",")
|
||||
return nil
|
||||
},
|
||||
//conf.OfficeTypes: {
|
||||
// Hook: func(item *model.SettingItem) error {
|
||||
// conf.TypesMap[conf.OfficeTypes] = strings.Split(item.Value, ",")
|
||||
// return nil
|
||||
// },
|
||||
//},
|
||||
conf.ProxyTypes: {
|
||||
func(item *model.SettingItem) error {
|
||||
conf.TypesMap[conf.ProxyTypes] = strings.Split(item.Value, ",")
|
||||
return nil
|
||||
},
|
||||
conf.ProxyTypes: func(item *model.SettingItem) error {
|
||||
conf.TypesMap[conf.ProxyTypes] = strings.Split(item.Value, ",")
|
||||
return nil
|
||||
},
|
||||
conf.PrivacyRegs: {
|
||||
Hook: func(item *model.SettingItem) error {
|
||||
regStrs := strings.Split(item.Value, "\n")
|
||||
regs := make([]*regexp.Regexp, 0, len(regStrs))
|
||||
for _, regStr := range regStrs {
|
||||
reg, err := regexp.Compile(regStr)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
regs = append(regs, reg)
|
||||
}
|
||||
conf.PrivacyReg = regs
|
||||
return nil
|
||||
},
|
||||
},
|
||||
conf.FilenameCharMapping: {
|
||||
Hook: func(item *model.SettingItem) error {
|
||||
err := utils.Json.UnmarshalFromString(item.Value, &conf.FilenameCharMap)
|
||||
|
||||
conf.PrivacyRegs: func(item *model.SettingItem) error {
|
||||
regStrs := strings.Split(item.Value, "\n")
|
||||
regs := make([]*regexp.Regexp, 0, len(regStrs))
|
||||
for _, regStr := range regStrs {
|
||||
reg, err := regexp.Compile(regStr)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
log.Debugf("filename char mapping: %+v", conf.FilenameCharMap)
|
||||
return nil
|
||||
},
|
||||
regs = append(regs, reg)
|
||||
}
|
||||
conf.PrivacyReg = regs
|
||||
return nil
|
||||
},
|
||||
conf.FilenameCharMapping: func(item *model.SettingItem) error {
|
||||
err := utils.Json.UnmarshalFromString(item.Value, &conf.FilenameCharMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("filename char mapping: %+v", conf.FilenameCharMap)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func HandleSettingItem(item *model.SettingItem) (bool, error) {
|
||||
if hook, ok := SettingItemHooks[item.Key]; ok {
|
||||
return true, hook.Hook(item)
|
||||
if hook, ok := settingItemHooks[item.Key]; ok {
|
||||
return true, hook(item)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func RegisterSettingItemHook(key string, hook SettingItemHook) {
|
||||
settingItemHooks[key] = hook
|
||||
}
|
||||
|
||||
// func HandleSettingItems(items []model.SettingItem) error {
|
||||
// for i := range items {
|
||||
// if err := HandleSettingItem(&items[i]); err != nil {
|
||||
|
@ -4,44 +4,43 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/generic_sync"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var settingsMap map[string]string
|
||||
var publicSettingsMap map[string]string
|
||||
var settingsMap generic_sync.MapOf[string, string]
|
||||
var publicSettingsMap generic_sync.MapOf[string, string]
|
||||
|
||||
func settingsUpdate() {
|
||||
settingsMap = nil
|
||||
publicSettingsMap = nil
|
||||
settingsMap.Clear()
|
||||
publicSettingsMap.Clear()
|
||||
}
|
||||
|
||||
func GetPublicSettingsMap() map[string]string {
|
||||
if publicSettingsMap == nil {
|
||||
publicSettingsMap = make(map[string]string)
|
||||
if publicSettingsMap.Empty() {
|
||||
settingItems, err := GetPublicSettingItems()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get settingItems: %+v", err)
|
||||
}
|
||||
for _, settingItem := range settingItems {
|
||||
publicSettingsMap[settingItem.Key] = settingItem.Value
|
||||
publicSettingsMap.Store(settingItem.Key, settingItem.Value)
|
||||
}
|
||||
}
|
||||
return publicSettingsMap
|
||||
return publicSettingsMap.ToMap()
|
||||
}
|
||||
|
||||
func GetSettingsMap() map[string]string {
|
||||
if settingsMap == nil {
|
||||
settingsMap = make(map[string]string)
|
||||
func GetSettingsMap() *generic_sync.MapOf[string, string] {
|
||||
if settingsMap.Empty() {
|
||||
settingItems, err := GetSettingItems()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get settingItems: %+v", err)
|
||||
}
|
||||
for _, settingItem := range settingItems {
|
||||
settingsMap[settingItem.Key] = settingItem.Value
|
||||
settingsMap.Store(settingItem.Key, settingItem.Value)
|
||||
}
|
||||
}
|
||||
return settingsMap
|
||||
return &settingsMap
|
||||
}
|
||||
|
||||
func GetSettingItems() ([]model.SettingItem, error) {
|
||||
@ -108,11 +107,17 @@ func SaveSettingItems(items []model.SettingItem) error {
|
||||
others = append(others, items[i])
|
||||
}
|
||||
}
|
||||
err := db.Save(others).Error
|
||||
if err == nil {
|
||||
settingsUpdate()
|
||||
if len(others) > 0 {
|
||||
err := db.Save(others).Error
|
||||
if err != nil {
|
||||
if len(others) < len(items) {
|
||||
settingsUpdate()
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
settingsUpdate()
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveSettingItem(item model.SettingItem) error {
|
||||
|
7
internal/errs/search.go
Normal file
7
internal/errs/search.go
Normal file
@ -0,0 +1,7 @@
|
||||
package errs
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
SearchNotAvailable = fmt.Errorf("search not available")
|
||||
)
|
@ -84,7 +84,7 @@ func hide(objs []model.Obj, meta *model.Meta) []model.Obj {
|
||||
deleted := make([]bool, len(objs))
|
||||
rs := strings.Split(meta.Hide, "\n")
|
||||
for _, r := range rs {
|
||||
re, _ := regexp.Compile(r)
|
||||
re := regexp.MustCompile(r)
|
||||
for i, obj := range objs {
|
||||
if deleted[i] {
|
||||
continue
|
||||
|
45
internal/fs/walk.go
Normal file
45
internal/fs/walk.go
Normal file
@ -0,0 +1,45 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
// WalkFS traverses filesystem fs starting at name up to depth levels.
|
||||
//
|
||||
// WalkFS will stop when current depth > `depth`. For each visited node,
|
||||
// WalkFS calls walkFn. If a visited file system node is a directory and
|
||||
// walkFn returns path.SkipDir, walkFS will skip traversal of this node.
|
||||
func WalkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn func(reqPath string, info model.Obj) error) error {
|
||||
// This implementation is based on Walk's code in the standard path/path package.
|
||||
walkFnErr := walkFn(name, info)
|
||||
if walkFnErr != nil {
|
||||
if info.IsDir() && walkFnErr == filepath.SkipDir {
|
||||
return nil
|
||||
}
|
||||
return walkFnErr
|
||||
}
|
||||
if !info.IsDir() || depth == 0 {
|
||||
return nil
|
||||
}
|
||||
meta, _ := db.GetNearestMeta(name)
|
||||
// Read directory names.
|
||||
objs, err := List(context.WithValue(ctx, "meta", meta), name)
|
||||
if err != nil {
|
||||
return walkFnErr
|
||||
}
|
||||
for _, fileInfo := range objs {
|
||||
filename := path.Join(name, fileInfo.GetName())
|
||||
if err := WalkFS(ctx, depth-1, filename, fileInfo, walkFn); err != nil {
|
||||
if err == filepath.SkipDir {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
type Object struct {
|
||||
ID string
|
||||
@ -12,7 +16,7 @@ type Object struct {
|
||||
}
|
||||
|
||||
func (o *Object) GetName() string {
|
||||
return o.Name
|
||||
return utils.MappingName(o.Name)
|
||||
}
|
||||
|
||||
func (o *Object) GetSize() int64 {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package common
|
||||
package model
|
||||
|
||||
type PageReq struct {
|
||||
Page int `json:"page" form:"page"`
|
40
internal/model/search.go
Normal file
40
internal/model/search.go
Normal file
@ -0,0 +1,40 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type IndexProgress struct {
|
||||
ObjCount uint64 `json:"obj_count"`
|
||||
IsDone bool `json:"is_done"`
|
||||
LastDoneTime *time.Time `json:"last_done_time"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type SearchReq struct {
|
||||
Parent string `json:"parent"`
|
||||
Keywords string `json:"keywords"`
|
||||
PageReq
|
||||
}
|
||||
|
||||
type SearchNode struct {
|
||||
Parent string `json:"parent" gorm:"index"`
|
||||
Name string `json:"name" gorm:"index"`
|
||||
IsDir bool `json:"is_dir"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (p *SearchReq) Validate() error {
|
||||
if p.Page < 1 {
|
||||
return fmt.Errorf("page can't < 1")
|
||||
}
|
||||
if p.PerPage < 1 {
|
||||
return fmt.Errorf("per_page can't < 1")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SearchNode) Type() string {
|
||||
return "SearchNode"
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
package model
|
||||
|
||||
const (
|
||||
SITE = iota
|
||||
SINGLE = iota
|
||||
SITE
|
||||
STYLE
|
||||
PREVIEW
|
||||
GLOBAL
|
||||
SINGLE
|
||||
ARIA2
|
||||
INDEX
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -2,6 +2,7 @@ package model
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -89,3 +90,7 @@ func (u User) CanWebdavRead() bool {
|
||||
func (u User) CanWebdavManage() bool {
|
||||
return u.IsAdmin() || (u.Permission>>9)&1 == 1
|
||||
}
|
||||
|
||||
func (u User) JoinPath(reqPath string) (string, error) {
|
||||
return utils.JoinBasePath(u.BasePath, reqPath)
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Xhofe/go-cache"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
@ -59,6 +58,12 @@ func List(ctx context.Context, storage driver.Driver, path string, args model.Li
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to list objs")
|
||||
}
|
||||
// call hooks
|
||||
go func(reqPath string, files []model.Obj) {
|
||||
for _, hook := range objsUpdateHooks {
|
||||
hook(args.ReqPath, files)
|
||||
}
|
||||
}(args.ReqPath, files)
|
||||
if !storage.Config().NoCache {
|
||||
if len(files) > 0 {
|
||||
log.Debugf("set cache: %s => %+v", key, files)
|
||||
@ -123,7 +128,7 @@ func Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, er
|
||||
}
|
||||
for _, f := range files {
|
||||
// TODO maybe copy obj here
|
||||
if utils.MappingName(f.GetName(), conf.FilenameCharMap) == name {
|
||||
if f.GetName() == name {
|
||||
// use path as id, why don't set id in List function?
|
||||
// because files maybe cache, set id here can reduce memory usage
|
||||
if f.GetPath() == "" {
|
||||
|
13
internal/op/hook.go
Normal file
13
internal/op/hook.go
Normal file
@ -0,0 +1,13 @@
|
||||
package op
|
||||
|
||||
import "github.com/alist-org/alist/v3/internal/model"
|
||||
|
||||
type ObjsUpdateHook = func(parent string, objs []model.Obj)
|
||||
|
||||
var (
|
||||
objsUpdateHooks = make([]ObjsUpdateHook, 0)
|
||||
)
|
||||
|
||||
func RegisterObjsUpdateHook(hook ObjsUpdateHook) {
|
||||
objsUpdateHooks = append(objsUpdateHooks, hook)
|
||||
}
|
47
internal/search/bleve/init.go
Normal file
47
internal/search/bleve/init.go
Normal file
@ -0,0 +1,47 @@
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/search/searcher"
|
||||
"github.com/blevesearch/bleve/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var config = searcher.Config{
|
||||
Name: "bleve",
|
||||
}
|
||||
|
||||
func Init(indexPath *string) (bleve.Index, error) {
|
||||
log.Debugf("bleve path: %s", *indexPath)
|
||||
fileIndex, err := bleve.Open(*indexPath)
|
||||
if err == bleve.ErrorIndexPathDoesNotExist {
|
||||
log.Infof("Creating new index...")
|
||||
indexMapping := bleve.NewIndexMapping()
|
||||
searchNodeMapping := bleve.NewDocumentMapping()
|
||||
searchNodeMapping.AddFieldMappingsAt("is_dir", bleve.NewBooleanFieldMapping())
|
||||
// TODO: appoint analyzer
|
||||
parentFieldMapping := bleve.NewTextFieldMapping()
|
||||
searchNodeMapping.AddFieldMappingsAt("parent", parentFieldMapping)
|
||||
// TODO: appoint analyzer
|
||||
nameFieldMapping := bleve.NewKeywordFieldMapping()
|
||||
searchNodeMapping.AddFieldMappingsAt("name", nameFieldMapping)
|
||||
indexMapping.AddDocumentMapping("SearchNode", searchNodeMapping)
|
||||
fileIndex, err = bleve.New(*indexPath, indexMapping)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fileIndex, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
searcher.RegisterSearcher(config, func() (searcher.Searcher, error) {
|
||||
b, err := Init(&conf.Conf.BleveDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Bleve{BIndex: b}, nil
|
||||
})
|
||||
}
|
93
internal/search/bleve/search.go
Normal file
93
internal/search/bleve/search.go
Normal file
@ -0,0 +1,93 @@
|
||||
package bleve
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/search/searcher"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/blevesearch/bleve/v2"
|
||||
search2 "github.com/blevesearch/bleve/v2/search"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Bleve struct {
|
||||
BIndex bleve.Index
|
||||
}
|
||||
|
||||
func (b *Bleve) Config() searcher.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (b *Bleve) Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) {
|
||||
query := bleve.NewMatchQuery(req.Keywords)
|
||||
query.SetField("name")
|
||||
search := bleve.NewSearchRequest(query)
|
||||
search.Size = req.PerPage
|
||||
search.Fields = []string{"*"}
|
||||
searchResults, err := b.BIndex.Search(search)
|
||||
if err != nil {
|
||||
log.Errorf("search error: %+v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
res, err := utils.SliceConvert(searchResults.Hits, func(src *search2.DocumentMatch) (model.SearchNode, error) {
|
||||
return model.SearchNode{
|
||||
Parent: src.Fields["parent"].(string),
|
||||
Name: src.Fields["name"].(string),
|
||||
IsDir: src.Fields["is_dir"].(bool),
|
||||
Size: int64(src.Fields["size"].(float64)),
|
||||
}, nil
|
||||
})
|
||||
return res, int64(len(res)), nil
|
||||
}
|
||||
|
||||
func (b *Bleve) Index(ctx context.Context, node model.SearchNode) error {
|
||||
return b.BIndex.Index(uuid.NewString(), node)
|
||||
}
|
||||
|
||||
func (b *Bleve) BatchIndex(ctx context.Context, nodes []model.SearchNode) error {
|
||||
batch := b.BIndex.NewBatch()
|
||||
for _, node := range nodes {
|
||||
batch.Index(uuid.NewString(), node)
|
||||
}
|
||||
return b.BIndex.Batch(batch)
|
||||
}
|
||||
|
||||
func (b *Bleve) Get(ctx context.Context, parent string) ([]model.SearchNode, error) {
|
||||
return nil, errs.NotSupport
|
||||
}
|
||||
|
||||
func (b *Bleve) Del(ctx context.Context, prefix string) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (b *Bleve) Release(ctx context.Context) error {
|
||||
if b.BIndex != nil {
|
||||
return b.BIndex.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bleve) Clear(ctx context.Context) error {
|
||||
err := b.Release(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Removing old index...")
|
||||
err = os.RemoveAll(conf.Conf.BleveDir)
|
||||
if err != nil {
|
||||
log.Errorf("clear bleve error: %+v", err)
|
||||
}
|
||||
bIndex, err := Init(&conf.Conf.BleveDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.BIndex = bIndex
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ searcher.Searcher = (*Bleve)(nil)
|
220
internal/search/build.go
Normal file
220
internal/search/build.go
Normal file
@ -0,0 +1,220 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"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/pkg/mq"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
mapset "github.com/deckarep/golang-set/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
Running = atomic.Bool{}
|
||||
Quit chan struct{}
|
||||
)
|
||||
|
||||
func BuildIndex(ctx context.Context, indexPaths, ignorePaths []string, maxDepth int, count bool) error {
|
||||
var (
|
||||
err error
|
||||
objCount uint64 = 0
|
||||
fi model.Obj
|
||||
)
|
||||
Running.Store(true)
|
||||
Quit = make(chan struct{}, 1)
|
||||
indexMQ := mq.NewInMemoryMQ[ObjWithParent]()
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
log.Infof("index obj count: %d", objCount)
|
||||
indexMQ.ConsumeAll(func(messages []mq.Message[ObjWithParent]) {
|
||||
if len(messages) != 0 {
|
||||
log.Debugf("current index: %s", messages[len(messages)-1].Content.Parent)
|
||||
}
|
||||
if err = BatchIndex(ctx, utils.MustSliceConvert(messages,
|
||||
func(src mq.Message[ObjWithParent]) ObjWithParent {
|
||||
return src.Content
|
||||
})); err != nil {
|
||||
log.Errorf("build index in batch error: %+v", err)
|
||||
} else {
|
||||
objCount = objCount + uint64(len(messages))
|
||||
}
|
||||
if count {
|
||||
WriteProgress(&model.IndexProgress{
|
||||
ObjCount: objCount,
|
||||
IsDone: false,
|
||||
LastDoneTime: nil,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
case <-Quit:
|
||||
Running.Store(false)
|
||||
ticker.Stop()
|
||||
eMsg := ""
|
||||
now := time.Now()
|
||||
originErr := err
|
||||
indexMQ.ConsumeAll(func(messages []mq.Message[ObjWithParent]) {
|
||||
if err = BatchIndex(ctx, utils.MustSliceConvert(messages,
|
||||
func(src mq.Message[ObjWithParent]) ObjWithParent {
|
||||
return src.Content
|
||||
})); err != nil {
|
||||
log.Errorf("build index in batch error: %+v", err)
|
||||
} else {
|
||||
objCount = objCount + uint64(len(messages))
|
||||
}
|
||||
if originErr != nil {
|
||||
log.Errorf("build index error: %+v", originErr)
|
||||
eMsg = originErr.Error()
|
||||
} else {
|
||||
log.Infof("success build index, count: %d", objCount)
|
||||
}
|
||||
if count {
|
||||
WriteProgress(&model.IndexProgress{
|
||||
ObjCount: objCount,
|
||||
IsDone: true,
|
||||
LastDoneTime: &now,
|
||||
Error: eMsg,
|
||||
})
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
if Running.Load() {
|
||||
Quit <- struct{}{}
|
||||
}
|
||||
}()
|
||||
admin, err := db.GetAdmin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count {
|
||||
WriteProgress(&model.IndexProgress{
|
||||
ObjCount: 0,
|
||||
IsDone: false,
|
||||
})
|
||||
}
|
||||
for _, indexPath := range indexPaths {
|
||||
walkFn := func(indexPath string, info model.Obj) error {
|
||||
if !Running.Load() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
for _, avoidPath := range ignorePaths {
|
||||
if strings.HasPrefix(indexPath, avoidPath) {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
}
|
||||
// ignore root
|
||||
if indexPath == "/" {
|
||||
return nil
|
||||
}
|
||||
indexMQ.Publish(mq.Message[ObjWithParent]{
|
||||
Content: ObjWithParent{
|
||||
Obj: info,
|
||||
Parent: path.Dir(indexPath),
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
fi, err = fs.Get(ctx, indexPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: run walkFS concurrently
|
||||
err = fs.WalkFS(context.WithValue(ctx, "user", admin), maxDepth, indexPath, fi, walkFn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Clear(ctx context.Context) error {
|
||||
return instance.Clear(ctx)
|
||||
}
|
||||
|
||||
func Update(parent string, objs []model.Obj) {
|
||||
if instance == nil || !instance.Config().AutoUpdate || Running.Load() {
|
||||
return
|
||||
}
|
||||
ignorePaths, err := GetIgnorePaths()
|
||||
if err != nil {
|
||||
log.Errorf("update search index error while get ignore paths: %+v", err)
|
||||
return
|
||||
}
|
||||
if isIgnorePath(parent, ignorePaths) {
|
||||
return
|
||||
}
|
||||
ctx := context.Background()
|
||||
// only update when index have built
|
||||
progress, err := Progress()
|
||||
if err != nil {
|
||||
log.Errorf("update search index error while get progress: %+v", err)
|
||||
return
|
||||
}
|
||||
if !progress.IsDone {
|
||||
return
|
||||
}
|
||||
nodes, err := instance.Get(ctx, parent)
|
||||
if err != nil {
|
||||
log.Errorf("update search index error while get nodes: %+v", err)
|
||||
return
|
||||
}
|
||||
now := mapset.NewSet[string]()
|
||||
for i := range objs {
|
||||
now.Add(objs[i].GetName())
|
||||
}
|
||||
old := mapset.NewSet[string]()
|
||||
for i := range nodes {
|
||||
old.Add(nodes[i].Name)
|
||||
}
|
||||
// delete data that no longer exists
|
||||
toDelete := old.Difference(now)
|
||||
toAdd := now.Difference(old)
|
||||
for i := range nodes {
|
||||
if toDelete.Contains(nodes[i].Name) {
|
||||
log.Debugf("delete index: %s", path.Join(parent, nodes[i].Name))
|
||||
err = instance.Del(ctx, path.Join(parent, nodes[i].Name))
|
||||
if err != nil {
|
||||
log.Errorf("update search index error while del old node: %+v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := range objs {
|
||||
if toAdd.Contains(objs[i].GetName()) {
|
||||
log.Debugf("add index: %s", path.Join(parent, objs[i].GetName()))
|
||||
err = Index(ctx, parent, objs[i])
|
||||
if err != nil {
|
||||
log.Errorf("update search index error while index new node: %+v", err)
|
||||
return
|
||||
}
|
||||
// build index if it's a folder
|
||||
if objs[i].IsDir() {
|
||||
err = BuildIndex(ctx, []string{path.Join(parent, objs[i].GetName())}, ignorePaths, -1, false)
|
||||
if err != nil {
|
||||
log.Errorf("update search index error while build index: %+v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterObjsUpdateHook(Update)
|
||||
}
|
16
internal/search/db/init.go
Normal file
16
internal/search/db/init.go
Normal file
@ -0,0 +1,16 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/search/searcher"
|
||||
)
|
||||
|
||||
var config = searcher.Config{
|
||||
Name: "database",
|
||||
AutoUpdate: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
searcher.RegisterSearcher(config, func() (searcher.Searcher, error) {
|
||||
return &DB{}, nil
|
||||
})
|
||||
}
|
45
internal/search/db/search.go
Normal file
45
internal/search/db/search.go
Normal file
@ -0,0 +1,45 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/search/searcher"
|
||||
)
|
||||
|
||||
type DB struct{}
|
||||
|
||||
func (D DB) Config() searcher.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (D DB) Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) {
|
||||
return db.SearchNode(req)
|
||||
}
|
||||
|
||||
func (D DB) Index(ctx context.Context, node model.SearchNode) error {
|
||||
return db.CreateSearchNode(&node)
|
||||
}
|
||||
|
||||
func (D DB) BatchIndex(ctx context.Context, nodes []model.SearchNode) error {
|
||||
return db.BatchCreateSearchNodes(&nodes)
|
||||
}
|
||||
|
||||
func (D DB) Get(ctx context.Context, parent string) ([]model.SearchNode, error) {
|
||||
return db.GetSearchNodesByParent(parent)
|
||||
}
|
||||
|
||||
func (D DB) Del(ctx context.Context, prefix string) error {
|
||||
return db.DeleteSearchNodesByParent(prefix)
|
||||
}
|
||||
|
||||
func (D DB) Release(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (D DB) Clear(ctx context.Context) error {
|
||||
return db.ClearSearchNodes()
|
||||
}
|
||||
|
||||
var _ searcher.Searcher = (*DB)(nil)
|
6
internal/search/import.go
Normal file
6
internal/search/import.go
Normal file
@ -0,0 +1,6 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
_ "github.com/alist-org/alist/v3/internal/search/bleve"
|
||||
_ "github.com/alist-org/alist/v3/internal/search/db"
|
||||
)
|
95
internal/search/search.go
Normal file
95
internal/search/search.go
Normal file
@ -0,0 +1,95 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/search/searcher"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var instance searcher.Searcher = nil
|
||||
|
||||
// Init or reset index
|
||||
func Init(mode string) error {
|
||||
if instance != nil {
|
||||
// unchanged, do nothing
|
||||
if instance.Config().Name == mode {
|
||||
return nil
|
||||
}
|
||||
err := instance.Release(context.Background())
|
||||
if err != nil {
|
||||
log.Errorf("release instance err: %+v", err)
|
||||
}
|
||||
instance = nil
|
||||
}
|
||||
if Running.Load() {
|
||||
return fmt.Errorf("index is running")
|
||||
}
|
||||
if mode == "none" {
|
||||
log.Warnf("not enable search")
|
||||
return nil
|
||||
}
|
||||
s, ok := searcher.NewMap[mode]
|
||||
if !ok {
|
||||
return fmt.Errorf("not support index: %s", mode)
|
||||
}
|
||||
i, err := s()
|
||||
if err != nil {
|
||||
log.Errorf("init searcher error: %+v", err)
|
||||
} else {
|
||||
instance = i
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) {
|
||||
return instance.Search(ctx, req)
|
||||
}
|
||||
|
||||
func Index(ctx context.Context, parent string, obj model.Obj) error {
|
||||
if instance == nil {
|
||||
return errs.SearchNotAvailable
|
||||
}
|
||||
return instance.Index(ctx, model.SearchNode{
|
||||
Parent: parent,
|
||||
Name: obj.GetName(),
|
||||
IsDir: obj.IsDir(),
|
||||
Size: obj.GetSize(),
|
||||
})
|
||||
}
|
||||
|
||||
type ObjWithParent struct {
|
||||
Parent string
|
||||
model.Obj
|
||||
}
|
||||
|
||||
func BatchIndex(ctx context.Context, objs []ObjWithParent) error {
|
||||
if instance == nil {
|
||||
return errs.SearchNotAvailable
|
||||
}
|
||||
if len(objs) == 0 {
|
||||
return nil
|
||||
}
|
||||
var searchNodes []model.SearchNode
|
||||
for i := range objs {
|
||||
searchNodes = append(searchNodes, model.SearchNode{
|
||||
Parent: objs[i].Parent,
|
||||
Name: objs[i].GetName(),
|
||||
IsDir: objs[i].IsDir(),
|
||||
Size: objs[i].GetSize(),
|
||||
})
|
||||
}
|
||||
return instance.BatchIndex(ctx, searchNodes)
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterSettingItemHook(conf.SearchIndex, func(item *model.SettingItem) error {
|
||||
log.Debugf("searcher init, mode: %s", item.Value)
|
||||
return Init(item.Value)
|
||||
})
|
||||
}
|
9
internal/search/searcher/manage.go
Normal file
9
internal/search/searcher/manage.go
Normal file
@ -0,0 +1,9 @@
|
||||
package searcher
|
||||
|
||||
type New func() (Searcher, error)
|
||||
|
||||
var NewMap = map[string]New{}
|
||||
|
||||
func RegisterSearcher(config Config, searcher New) {
|
||||
NewMap[config.Name] = searcher
|
||||
}
|
31
internal/search/searcher/searcher.go
Normal file
31
internal/search/searcher/searcher.go
Normal file
@ -0,0 +1,31 @@
|
||||
package searcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Name string
|
||||
AutoUpdate bool
|
||||
}
|
||||
|
||||
type Searcher interface {
|
||||
// Config of the searcher
|
||||
Config() Config
|
||||
// Search specific keywords in specific path
|
||||
Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error)
|
||||
// Index obj with parent
|
||||
Index(ctx context.Context, node model.SearchNode) error
|
||||
// Index obj with parent in batches
|
||||
BatchIndex(ctx context.Context, nodes []model.SearchNode) error
|
||||
// Get by parent
|
||||
Get(ctx context.Context, parent string) ([]model.SearchNode, error)
|
||||
// Del with prefix
|
||||
Del(ctx context.Context, prefix string) error
|
||||
// Release resource
|
||||
Release(ctx context.Context) error
|
||||
// Clear all index
|
||||
Clear(ctx context.Context) error
|
||||
}
|
65
internal/search/util.go
Normal file
65
internal/search/util.go
Normal file
@ -0,0 +1,65 @@
|
||||
package search
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func Progress() (*model.IndexProgress, error) {
|
||||
p := setting.GetStr(conf.IndexProgress)
|
||||
var progress model.IndexProgress
|
||||
err := utils.Json.UnmarshalFromString(p, &progress)
|
||||
return &progress, err
|
||||
}
|
||||
|
||||
func WriteProgress(progress *model.IndexProgress) {
|
||||
p, err := utils.Json.MarshalToString(progress)
|
||||
if err != nil {
|
||||
log.Errorf("marshal progress error: %+v", err)
|
||||
}
|
||||
err = db.SaveSettingItem(model.SettingItem{
|
||||
Key: conf.IndexProgress,
|
||||
Value: p,
|
||||
Type: conf.TypeText,
|
||||
Group: model.SINGLE,
|
||||
Flag: model.PRIVATE,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("save progress error: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func GetIgnorePaths() ([]string, error) {
|
||||
storages, err := db.GetEnabledStorages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ignorePaths := make([]string, 0)
|
||||
var skipDrivers = []string{"AList V2", "AList V3"}
|
||||
for _, storage := range storages {
|
||||
if utils.SliceContains(skipDrivers, storage.Driver) {
|
||||
// TODO: request for indexing permission
|
||||
ignorePaths = append(ignorePaths, storage.MountPath)
|
||||
}
|
||||
}
|
||||
customIgnorePaths := setting.GetStr(conf.IgnorePaths)
|
||||
if customIgnorePaths != "" {
|
||||
ignorePaths = append(ignorePaths, strings.Split(customIgnorePaths, "\n")...)
|
||||
}
|
||||
return ignorePaths, nil
|
||||
}
|
||||
|
||||
func isIgnorePath(path string, ignorePaths []string) bool {
|
||||
for _, ignorePath := range ignorePaths {
|
||||
if strings.HasPrefix(path, ignorePath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func GetStr(key string, defaultValue ...string) string {
|
||||
val, ok := db.GetSettingsMap()[key]
|
||||
val, ok := db.GetSettingsMap().Load(key)
|
||||
if !ok {
|
||||
if len(defaultValue) > 0 {
|
||||
return defaultValue[0]
|
||||
|
75
pkg/generic/queue.go
Normal file
75
pkg/generic/queue.go
Normal file
@ -0,0 +1,75 @@
|
||||
package generic
|
||||
|
||||
type Queue[T any] struct {
|
||||
queue []T
|
||||
}
|
||||
|
||||
func NewQueue[T any]() *Queue[T] {
|
||||
return &Queue[T]{queue: make([]T, 0)}
|
||||
}
|
||||
|
||||
func (q *Queue[T]) Push(v T) {
|
||||
q.queue = append(q.queue, v)
|
||||
}
|
||||
|
||||
func (q *Queue[T]) Pop() T {
|
||||
v := q.queue[0]
|
||||
q.queue = q.queue[1:]
|
||||
return v
|
||||
}
|
||||
|
||||
func (q *Queue[T]) Len() int {
|
||||
return len(q.queue)
|
||||
}
|
||||
|
||||
func (q *Queue[T]) IsEmpty() bool {
|
||||
return len(q.queue) == 0
|
||||
}
|
||||
|
||||
func (q *Queue[T]) Clear() {
|
||||
q.queue = nil
|
||||
}
|
||||
|
||||
func (q *Queue[T]) Peek() T {
|
||||
return q.queue[0]
|
||||
}
|
||||
|
||||
func (q *Queue[T]) PeekN(n int) []T {
|
||||
return q.queue[:n]
|
||||
}
|
||||
|
||||
func (q *Queue[T]) PopN(n int) []T {
|
||||
v := q.queue[:n]
|
||||
q.queue = q.queue[n:]
|
||||
return v
|
||||
}
|
||||
|
||||
func (q *Queue[T]) PopAll() []T {
|
||||
v := q.queue
|
||||
q.queue = nil
|
||||
return v
|
||||
}
|
||||
|
||||
func (q *Queue[T]) PopWhile(f func(T) bool) []T {
|
||||
var i int
|
||||
for i = 0; i < len(q.queue); i++ {
|
||||
if !f(q.queue[i]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
v := q.queue[:i]
|
||||
q.queue = q.queue[i:]
|
||||
return v
|
||||
}
|
||||
|
||||
func (q *Queue[T]) PopUntil(f func(T) bool) []T {
|
||||
var i int
|
||||
for i = 0; i < len(q.queue); i++ {
|
||||
if f(q.queue[i]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
v := q.queue[:i]
|
||||
q.queue = q.queue[i:]
|
||||
return v
|
||||
}
|
@ -347,6 +347,23 @@ func (m *MapOf[K, V]) Values() []V {
|
||||
return values
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) Count() int {
|
||||
return len(m.dirty)
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) Empty() bool {
|
||||
return m.Count() == 0
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) ToMap() map[K]V {
|
||||
ans := make(map[K]V)
|
||||
m.Range(func(key K, value V) bool {
|
||||
ans[key] = value
|
||||
return true
|
||||
})
|
||||
return ans
|
||||
}
|
||||
|
||||
func (m *MapOf[K, V]) Clear() {
|
||||
m.Range(func(key K, value V) bool {
|
||||
m.Delete(key)
|
||||
|
@ -342,6 +342,58 @@ func (c *Client) Read(path string) ([]byte, error) {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *Client) Link(path string) (string, http.Header, error) {
|
||||
method := "HEAD"
|
||||
url := PathEscape(Join(c.root, path))
|
||||
r, err := http.NewRequest(method, url, nil)
|
||||
|
||||
if err != nil {
|
||||
return "", nil, newPathErrorErr("Link", path, err)
|
||||
}
|
||||
|
||||
for k, vals := range c.headers {
|
||||
for _, v := range vals {
|
||||
r.Header.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
c.authMutex.Lock()
|
||||
auth := c.auth
|
||||
c.authMutex.Unlock()
|
||||
|
||||
auth.Authorize(r, method, path)
|
||||
|
||||
if c.interceptor != nil {
|
||||
c.interceptor(method, r)
|
||||
}
|
||||
|
||||
rs, err := c.c.Do(r)
|
||||
if err != nil {
|
||||
return "", nil, newPathErrorErr("Link", path, err)
|
||||
}
|
||||
|
||||
if rs.StatusCode == 401 {
|
||||
wwwAuthenticateHeader := strings.ToLower(rs.Header.Get("Www-Authenticate"))
|
||||
if strings.Contains(wwwAuthenticateHeader, "digest") {
|
||||
c.authMutex.Lock()
|
||||
c.auth = &DigestAuth{auth.User(), auth.Pass(), digestParts(rs)}
|
||||
c.auth.Authorize(r, method, path)
|
||||
c.authMutex.Unlock()
|
||||
} else if strings.Contains(wwwAuthenticateHeader, "basic") {
|
||||
c.authMutex.Lock()
|
||||
c.auth = &BasicAuth{auth.User(), auth.Pass()}
|
||||
c.auth.Authorize(r, method, path)
|
||||
c.authMutex.Unlock()
|
||||
} else {
|
||||
return "", nil, newPathError("Authorize", c.root, rs.StatusCode)
|
||||
}
|
||||
} else if rs.StatusCode > 400 {
|
||||
return "", nil, newPathError("Authorize", path, rs.StatusCode)
|
||||
}
|
||||
|
||||
return r.URL.String(), r.Header, nil
|
||||
}
|
||||
|
||||
// ReadStream reads the stream for a given path
|
||||
func (c *Client) ReadStream(path string, callback func(rq *http.Request)) (io.ReadCloser, http.Header, error) {
|
||||
rs, err := c.req("GET", path, nil, callback)
|
||||
|
56
pkg/mq/mq.go
Normal file
56
pkg/mq/mq.go
Normal file
@ -0,0 +1,56 @@
|
||||
package mq
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/alist-org/alist/v3/pkg/generic"
|
||||
)
|
||||
|
||||
type Message[T any] struct {
|
||||
Content T
|
||||
}
|
||||
|
||||
type BasicConsumer[T any] func(Message[T])
|
||||
type AllConsumer[T any] func([]Message[T])
|
||||
|
||||
type MQ[T any] interface {
|
||||
Publish(Message[T])
|
||||
Consume(BasicConsumer[T])
|
||||
ConsumeAll(AllConsumer[T])
|
||||
Clear()
|
||||
}
|
||||
|
||||
type inMemoryMQ[T any] struct {
|
||||
queue generic.Queue[Message[T]]
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewInMemoryMQ[T any]() MQ[T] {
|
||||
return &inMemoryMQ[T]{queue: *generic.NewQueue[Message[T]]()}
|
||||
}
|
||||
|
||||
func (mq *inMemoryMQ[T]) Publish(msg Message[T]) {
|
||||
mq.Lock()
|
||||
defer mq.Unlock()
|
||||
mq.queue.Push(msg)
|
||||
}
|
||||
|
||||
func (mq *inMemoryMQ[T]) Consume(consumer BasicConsumer[T]) {
|
||||
mq.Lock()
|
||||
defer mq.Unlock()
|
||||
for !mq.queue.IsEmpty() {
|
||||
consumer(mq.queue.Pop())
|
||||
}
|
||||
}
|
||||
|
||||
func (mq *inMemoryMQ[T]) ConsumeAll(consumer AllConsumer[T]) {
|
||||
mq.Lock()
|
||||
defer mq.Unlock()
|
||||
consumer(mq.queue.PopAll())
|
||||
}
|
||||
|
||||
func (mq *inMemoryMQ[T]) Clear() {
|
||||
mq.Lock()
|
||||
defer mq.Unlock()
|
||||
mq.queue.Clear()
|
||||
}
|
@ -8,8 +8,8 @@ import (
|
||||
)
|
||||
|
||||
type Manager[K comparable] struct {
|
||||
workerC chan struct{}
|
||||
curID K
|
||||
workerC chan struct{}
|
||||
updateID func(*K)
|
||||
tasks generic_sync.MapOf[K, *Task[K]]
|
||||
}
|
||||
@ -94,7 +94,7 @@ func (tm *Manager[K]) RemoveByStates(states ...string) {
|
||||
tasks := tm.GetAll()
|
||||
for _, task := range tasks {
|
||||
if utils.SliceContains(states, task.GetState()) {
|
||||
tm.Remove(task.ID)
|
||||
_ = tm.Remove(task.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,6 +138,13 @@ func GetFileType(filename string) int {
|
||||
return conf.UNKNOWN
|
||||
}
|
||||
|
||||
func GetObjType(filename string, isDir bool) int {
|
||||
if isDir {
|
||||
return conf.FOLDER
|
||||
}
|
||||
return GetFileType(filename)
|
||||
}
|
||||
|
||||
func GetMimeType(name string) string {
|
||||
ext := path.Ext(name)
|
||||
m := mime.TypeByExtension(ext)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
stdjson "encoding/json"
|
||||
"os"
|
||||
|
||||
json "github.com/json-iterator/go"
|
||||
@ -10,8 +11,11 @@ import (
|
||||
var Json = json.ConfigCompatibleWithStandardLibrary
|
||||
|
||||
// WriteJsonToFile write struct to json file
|
||||
func WriteJsonToFile(dst string, data interface{}) bool {
|
||||
func WriteJsonToFile(dst string, data interface{}, std ...bool) bool {
|
||||
str, err := json.MarshalIndent(data, "", " ")
|
||||
if len(std) > 0 && std[0] {
|
||||
str, err = stdjson.MarshalIndent(data, "", " ")
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("failed convert Conf to []byte:%s", err.Error())
|
||||
return false
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
)
|
||||
|
||||
// StandardizePath convert path like '/' '/root' '/a/b'
|
||||
@ -60,3 +62,10 @@ func EncodePath(path string, all ...bool) string {
|
||||
}
|
||||
return strings.Join(seg, "/")
|
||||
}
|
||||
|
||||
func JoinBasePath(basePath, reqPath string) (string, error) {
|
||||
if strings.HasSuffix(reqPath, "..") || strings.Contains(reqPath, "../") {
|
||||
return "", errs.RelativePath
|
||||
}
|
||||
return stdpath.Join(basePath, reqPath), nil
|
||||
}
|
||||
|
@ -35,3 +35,12 @@ func SliceConvert[S any, D any](srcS []S, convert func(src S) (D, error)) ([]D,
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func MustSliceConvert[S any, D any](srcS []S, convert func(src S) D) []D {
|
||||
var res []D
|
||||
for i := range srcS {
|
||||
dst := convert(srcS[i])
|
||||
res = append(res, dst)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
func MappingName(name string, m map[string]string) string {
|
||||
for k, v := range m {
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
)
|
||||
|
||||
func MappingName(name string) string {
|
||||
for k, v := range conf.FilenameCharMap {
|
||||
name = strings.ReplaceAll(name, k, v)
|
||||
}
|
||||
return name
|
||||
|
@ -1,6 +1,9 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
@ -12,8 +15,17 @@ func CanWrite(meta *model.Meta, path string) bool {
|
||||
return meta.WSub || meta.Path == path
|
||||
}
|
||||
|
||||
func CanAccess(user *model.User, meta *model.Meta, path string, password string) bool {
|
||||
// if is not guest, can access
|
||||
func CanAccess(user *model.User, meta *model.Meta, reqPath string, password string) bool {
|
||||
// if the reqPath is in hide (only can check the nearest meta) and user can't see hides, can't access
|
||||
if meta != nil && !user.CanSeeHides() && meta.Hide != "" {
|
||||
for _, hide := range strings.Split(meta.Hide, "\n") {
|
||||
re := regexp.MustCompile(hide)
|
||||
if re.MatchString(reqPath[len(meta.Path):]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
// if is not guest and can access without password
|
||||
if user.CanAccessWithoutPassword() {
|
||||
return true
|
||||
}
|
||||
@ -22,7 +34,7 @@ func CanAccess(user *model.User, meta *model.Meta, path string, password string)
|
||||
return true
|
||||
}
|
||||
// if meta doesn't apply to sub_folder, can access
|
||||
if !utils.PathEqual(meta.Path, path) && !meta.PSub {
|
||||
if !utils.PathEqual(meta.Path, reqPath) && !meta.PSub {
|
||||
return true
|
||||
}
|
||||
// validate password
|
||||
|
@ -28,6 +28,8 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(file.GetSize(), 10))
|
||||
if link.Header != nil {
|
||||
// TODO clean header with blacklist or whitelist
|
||||
link.Header.Del("set-cookie")
|
||||
for h, val := range link.Header {
|
||||
w.Header()[h] = val
|
||||
}
|
||||
@ -81,6 +83,8 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
||||
_ = res.Body.Close()
|
||||
}()
|
||||
log.Debugf("proxy status: %d", res.StatusCode)
|
||||
// TODO clean header with blacklist or whitelist
|
||||
res.Header.Del("set-cookie")
|
||||
for h, v := range res.Header {
|
||||
w.Header()[h] = v
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/sign"
|
||||
stdpath "path"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/internal/sign"
|
||||
)
|
||||
|
||||
func Sign(obj model.Obj, parent string, encrypt bool) string {
|
||||
if obj.IsDir() || !encrypt {
|
||||
if obj.IsDir() || (!encrypt && !setting.GetBool(conf.SignAll)) {
|
||||
return ""
|
||||
}
|
||||
return sign.Sign(stdpath.Join(parent, obj.GetName()))
|
||||
|
@ -1,8 +1,6 @@
|
||||
package handles
|
||||
|
||||
import (
|
||||
stdpath "path"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/aria2"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
@ -58,9 +56,13 @@ func AddAria2(c *gin.Context) {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
||||
reqPath, err := user.JoinPath(req.Path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
for _, url := range req.Urls {
|
||||
err := aria2.AddURI(c, url, req.Path)
|
||||
err := aria2.AddURI(c, url, reqPath)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
|
@ -26,25 +26,29 @@ func FsMkdir(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
user := c.MustGet("user").(*model.User)
|
||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
||||
reqPath, err := user.JoinPath(req.Path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
if !user.CanWrite() {
|
||||
meta, err := db.GetNearestMeta(stdpath.Dir(req.Path))
|
||||
meta, err := db.GetNearestMeta(stdpath.Dir(reqPath))
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !common.CanWrite(meta, req.Path) {
|
||||
if !common.CanWrite(meta, reqPath) {
|
||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := fs.MakeDir(c, req.Path); err != nil {
|
||||
if err := fs.MakeDir(c, reqPath); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
fs.ClearCache(stdpath.Dir(req.Path))
|
||||
fs.ClearCache(stdpath.Dir(reqPath))
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
@ -69,17 +73,25 @@ func FsMove(c *gin.Context) {
|
||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||
return
|
||||
}
|
||||
req.SrcDir = stdpath.Join(user.BasePath, req.SrcDir)
|
||||
req.DstDir = stdpath.Join(user.BasePath, req.DstDir)
|
||||
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
|
||||
}
|
||||
for _, name := range req.Names {
|
||||
err := fs.Move(c, stdpath.Join(req.SrcDir, name), req.DstDir)
|
||||
err := fs.Move(c, stdpath.Join(srcDir, name), dstDir)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
fs.ClearCache(req.SrcDir)
|
||||
fs.ClearCache(req.DstDir)
|
||||
fs.ClearCache(srcDir)
|
||||
fs.ClearCache(dstDir)
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
@ -98,11 +110,19 @@ func FsCopy(c *gin.Context) {
|
||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||
return
|
||||
}
|
||||
req.SrcDir = stdpath.Join(user.BasePath, req.SrcDir)
|
||||
req.DstDir = stdpath.Join(user.BasePath, req.DstDir)
|
||||
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
|
||||
}
|
||||
var addedTask []string
|
||||
for _, name := range req.Names {
|
||||
ok, err := fs.Copy(c, stdpath.Join(req.SrcDir, name), req.DstDir)
|
||||
ok, err := fs.Copy(c, stdpath.Join(srcDir, name), dstDir)
|
||||
if ok {
|
||||
addedTask = append(addedTask, name)
|
||||
}
|
||||
@ -112,7 +132,7 @@ func FsCopy(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
if len(req.Names) != len(addedTask) {
|
||||
fs.ClearCache(req.DstDir)
|
||||
fs.ClearCache(dstDir)
|
||||
}
|
||||
if len(addedTask) > 0 {
|
||||
common.SuccessResp(c, fmt.Sprintf("Added %d tasks", len(addedTask)))
|
||||
@ -137,12 +157,16 @@ func FsRename(c *gin.Context) {
|
||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||
return
|
||||
}
|
||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
||||
if err := fs.Rename(c, req.Path, req.Name); err != nil {
|
||||
reqPath, err := user.JoinPath(req.Path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
if err := fs.Rename(c, reqPath, req.Name); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
fs.ClearCache(stdpath.Dir(req.Path))
|
||||
fs.ClearCache(stdpath.Dir(reqPath))
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
@ -166,9 +190,13 @@ func FsRemove(c *gin.Context) {
|
||||
common.ErrorResp(c, errs.PermissionDenied, 403)
|
||||
return
|
||||
}
|
||||
req.Dir = stdpath.Join(user.BasePath, req.Dir)
|
||||
reqDir, err := user.JoinPath(req.Dir)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
for _, name := range req.Names {
|
||||
err := fs.Remove(c, stdpath.Join(req.Dir, name))
|
||||
err := fs.Remove(c, stdpath.Join(reqDir, name))
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
@ -185,8 +213,10 @@ func Link(c *gin.Context) {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
user := c.MustGet("user").(*model.User)
|
||||
rawPath := stdpath.Join(user.BasePath, req.Path)
|
||||
//user := c.MustGet("user").(*model.User)
|
||||
//rawPath := stdpath.Join(user.BasePath, req.Path)
|
||||
// why need not join base_path? because it's always the full path
|
||||
rawPath := req.Path
|
||||
storage, err := fs.GetStorage(rawPath)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/fs"
|
||||
@ -19,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
type ListReq struct {
|
||||
common.PageReq
|
||||
model.PageReq
|
||||
Path string `json:"path" form:"path"`
|
||||
Password string `json:"password" form:"password"`
|
||||
Refresh bool `json:"refresh"`
|
||||
@ -57,8 +56,12 @@ func FsList(c *gin.Context) {
|
||||
}
|
||||
req.Validate()
|
||||
user := c.MustGet("user").(*model.User)
|
||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
||||
meta, err := db.GetNearestMeta(req.Path)
|
||||
reqPath, err := user.JoinPath(req.Path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
meta, err := db.GetNearestMeta(reqPath)
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
@ -66,30 +69,30 @@ func FsList(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
c.Set("meta", meta)
|
||||
if !common.CanAccess(user, meta, req.Path, req.Password) {
|
||||
common.ErrorStrResp(c, "password is incorrect", 403)
|
||||
if !common.CanAccess(user, meta, reqPath, req.Password) {
|
||||
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
||||
return
|
||||
}
|
||||
if !user.CanWrite() && !common.CanWrite(meta, req.Path) && req.Refresh {
|
||||
if !user.CanWrite() && !common.CanWrite(meta, reqPath) && req.Refresh {
|
||||
common.ErrorStrResp(c, "Refresh without permission", 403)
|
||||
return
|
||||
}
|
||||
objs, err := fs.List(c, req.Path, req.Refresh)
|
||||
objs, err := fs.List(c, reqPath, req.Refresh)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
total, objs := pagination(objs, &req.PageReq)
|
||||
provider := "unknown"
|
||||
storage, err := fs.GetStorage(req.Path)
|
||||
storage, err := fs.GetStorage(reqPath)
|
||||
if err == nil {
|
||||
provider = storage.GetStorage().Driver
|
||||
}
|
||||
common.SuccessResp(c, FsListResp{
|
||||
Content: toObjResp(objs, req.Path, isEncrypt(meta, req.Path)),
|
||||
Content: toObjsResp(objs, reqPath, isEncrypt(meta, reqPath)),
|
||||
Total: int64(total),
|
||||
Readme: getReadme(meta, req.Path),
|
||||
Write: user.CanWrite() || common.CanWrite(meta, req.Path),
|
||||
Readme: getReadme(meta, reqPath),
|
||||
Write: user.CanWrite() || common.CanWrite(meta, reqPath),
|
||||
Provider: provider,
|
||||
})
|
||||
}
|
||||
@ -101,15 +104,21 @@ func FsDirs(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
user := c.MustGet("user").(*model.User)
|
||||
reqPath := req.Path
|
||||
if req.ForceRoot {
|
||||
if !user.IsAdmin() {
|
||||
common.ErrorStrResp(c, "Permission denied", 403)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
||||
tmp, err := user.JoinPath(req.Path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
reqPath = tmp
|
||||
}
|
||||
meta, err := db.GetNearestMeta(req.Path)
|
||||
meta, err := db.GetNearestMeta(reqPath)
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
@ -117,11 +126,11 @@ func FsDirs(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
c.Set("meta", meta)
|
||||
if !common.CanAccess(user, meta, req.Path, req.Password) {
|
||||
common.ErrorStrResp(c, "password is incorrect", 403)
|
||||
if !common.CanAccess(user, meta, reqPath, req.Password) {
|
||||
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
||||
return
|
||||
}
|
||||
objs, err := fs.List(c, req.Path)
|
||||
objs, err := fs.List(c, reqPath)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
@ -140,7 +149,7 @@ func filterDirs(objs []model.Obj) []DirResp {
|
||||
for _, obj := range objs {
|
||||
if obj.IsDir() {
|
||||
dirs = append(dirs, DirResp{
|
||||
Name: utils.MappingName(obj.GetName(), conf.FilenameCharMap),
|
||||
Name: obj.GetName(),
|
||||
Modified: obj.ModTime(),
|
||||
})
|
||||
}
|
||||
@ -165,7 +174,7 @@ func isEncrypt(meta *model.Meta, path string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func pagination(objs []model.Obj, req *common.PageReq) (int, []model.Obj) {
|
||||
func pagination(objs []model.Obj, req *model.PageReq) (int, []model.Obj) {
|
||||
pageIndex, pageSize := req.Page, req.PerPage
|
||||
total := len(objs)
|
||||
start := (pageIndex - 1) * pageSize
|
||||
@ -179,25 +188,21 @@ func pagination(objs []model.Obj, req *common.PageReq) (int, []model.Obj) {
|
||||
return total, objs[start:end]
|
||||
}
|
||||
|
||||
func toObjResp(objs []model.Obj, parent string, encrypt bool) []ObjResp {
|
||||
func toObjsResp(objs []model.Obj, parent string, encrypt bool) []ObjResp {
|
||||
var resp []ObjResp
|
||||
for _, obj := range objs {
|
||||
thumb := ""
|
||||
if t, ok := obj.(model.Thumb); ok {
|
||||
thumb = t.Thumb()
|
||||
}
|
||||
tp := conf.FOLDER
|
||||
if !obj.IsDir() {
|
||||
tp = utils.GetFileType(obj.GetName())
|
||||
}
|
||||
resp = append(resp, ObjResp{
|
||||
Name: utils.MappingName(obj.GetName(), conf.FilenameCharMap),
|
||||
Name: obj.GetName(),
|
||||
Size: obj.GetSize(),
|
||||
IsDir: obj.IsDir(),
|
||||
Modified: obj.ModTime(),
|
||||
Sign: common.Sign(obj, parent, encrypt),
|
||||
Thumb: thumb,
|
||||
Type: tp,
|
||||
Type: utils.GetObjType(obj.GetName(), obj.IsDir()),
|
||||
})
|
||||
}
|
||||
return resp
|
||||
@ -223,8 +228,12 @@ func FsGet(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
user := c.MustGet("user").(*model.User)
|
||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
||||
meta, err := db.GetNearestMeta(req.Path)
|
||||
reqPath, err := user.JoinPath(req.Path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
meta, err := db.GetNearestMeta(reqPath)
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
common.ErrorResp(c, err, 500)
|
||||
@ -232,18 +241,18 @@ func FsGet(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
c.Set("meta", meta)
|
||||
if !common.CanAccess(user, meta, req.Path, req.Password) {
|
||||
common.ErrorStrResp(c, "password is incorrect", 403)
|
||||
if !common.CanAccess(user, meta, reqPath, req.Password) {
|
||||
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
||||
return
|
||||
}
|
||||
obj, err := fs.Get(c, req.Path)
|
||||
obj, err := fs.Get(c, reqPath)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
var rawURL string
|
||||
|
||||
storage, err := fs.GetStorage(req.Path)
|
||||
storage, err := fs.GetStorage(reqPath)
|
||||
provider := "unknown"
|
||||
if err == nil {
|
||||
provider = storage.Config().Name
|
||||
@ -257,13 +266,13 @@ func FsGet(c *gin.Context) {
|
||||
if storage.GetStorage().DownProxyUrl != "" {
|
||||
rawURL = fmt.Sprintf("%s%s?sign=%s",
|
||||
strings.Split(storage.GetStorage().DownProxyUrl, "\n")[0],
|
||||
utils.EncodePath(req.Path, true),
|
||||
sign.Sign(req.Path))
|
||||
utils.EncodePath(reqPath, true),
|
||||
sign.Sign(reqPath))
|
||||
} else {
|
||||
rawURL = fmt.Sprintf("%s/p%s?sign=%s",
|
||||
common.GetApiUrl(c.Request),
|
||||
utils.EncodePath(req.Path, true),
|
||||
sign.Sign(req.Path))
|
||||
utils.EncodePath(reqPath, true),
|
||||
sign.Sign(reqPath))
|
||||
}
|
||||
} else {
|
||||
// file have raw url
|
||||
@ -271,7 +280,7 @@ func FsGet(c *gin.Context) {
|
||||
rawURL = u.URL()
|
||||
} else {
|
||||
// if storage is not proxy, use raw url by fs.Link
|
||||
link, _, err := fs.Link(c, req.Path, model.LinkArgs{IP: c.ClientIP(), Header: c.Request.Header})
|
||||
link, _, err := fs.Link(c, reqPath, model.LinkArgs{IP: c.ClientIP(), Header: c.Request.Header})
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
@ -281,7 +290,7 @@ func FsGet(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
var related []model.Obj
|
||||
parentPath := stdpath.Dir(req.Path)
|
||||
parentPath := stdpath.Dir(reqPath)
|
||||
sameLevelFiles, err := fs.List(c, parentPath)
|
||||
if err == nil {
|
||||
related = filterRelated(sameLevelFiles, obj)
|
||||
@ -289,17 +298,17 @@ func FsGet(c *gin.Context) {
|
||||
parentMeta, _ := db.GetNearestMeta(parentPath)
|
||||
common.SuccessResp(c, FsGetResp{
|
||||
ObjResp: ObjResp{
|
||||
Name: utils.MappingName(obj.GetName(), conf.FilenameCharMap),
|
||||
Name: obj.GetName(),
|
||||
Size: obj.GetSize(),
|
||||
IsDir: obj.IsDir(),
|
||||
Modified: obj.ModTime(),
|
||||
Sign: common.Sign(obj, parentPath, isEncrypt(meta, req.Path)),
|
||||
Sign: common.Sign(obj, parentPath, isEncrypt(meta, reqPath)),
|
||||
Type: utils.GetFileType(obj.GetName()),
|
||||
},
|
||||
RawURL: rawURL,
|
||||
Readme: getReadme(meta, req.Path),
|
||||
Readme: getReadme(meta, reqPath),
|
||||
Provider: provider,
|
||||
Related: toObjResp(related, parentPath, isEncrypt(parentMeta, parentPath)),
|
||||
Related: toObjsResp(related, parentPath, isEncrypt(parentMeta, parentPath)),
|
||||
})
|
||||
}
|
||||
|
||||
@ -329,7 +338,12 @@ func FsOther(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
user := c.MustGet("user").(*model.User)
|
||||
req.Path = stdpath.Join(user.BasePath, req.Path)
|
||||
var err error
|
||||
req.Path, err = user.JoinPath(req.Path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
meta, err := db.GetNearestMeta(req.Path)
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
@ -339,7 +353,7 @@ func FsOther(c *gin.Context) {
|
||||
}
|
||||
c.Set("meta", meta)
|
||||
if !common.CanAccess(user, meta, req.Path, req.Password) {
|
||||
common.ErrorStrResp(c, "password is incorrect", 403)
|
||||
common.ErrorStrResp(c, "password is incorrect or you have no permission", 403)
|
||||
return
|
||||
}
|
||||
res, err := fs.Other(c, req.FsOtherArgs)
|
||||
|
@ -21,8 +21,11 @@ func FsStream(c *gin.Context) {
|
||||
}
|
||||
asTask := c.GetHeader("As-Task") == "true"
|
||||
user := c.MustGet("user").(*model.User)
|
||||
path = stdpath.Join(user.BasePath, path)
|
||||
|
||||
path, err = user.JoinPath(path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
dir, name := stdpath.Split(path)
|
||||
sizeStr := c.GetHeader("Content-Length")
|
||||
size, err := strconv.ParseInt(sizeStr, 10, 64)
|
||||
@ -61,8 +64,11 @@ func FsForm(c *gin.Context) {
|
||||
}
|
||||
asTask := c.GetHeader("As-Task") == "true"
|
||||
user := c.MustGet("user").(*model.User)
|
||||
path = stdpath.Join(user.BasePath, path)
|
||||
|
||||
path, err = user.JoinPath(path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
storage, err := fs.GetStorage(path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
|
65
server/handles/index.go
Normal file
65
server/handles/index.go
Normal file
@ -0,0 +1,65 @@
|
||||
package handles
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/search"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type BuildIndexReq struct {
|
||||
Paths []string `json:"paths"`
|
||||
MaxDepth int `json:"max_depth"`
|
||||
IgnorePaths []string `json:"ignore_paths"`
|
||||
}
|
||||
|
||||
func BuildIndex(c *gin.Context) {
|
||||
var req BuildIndexReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if search.Running.Load() {
|
||||
common.ErrorStrResp(c, "index is running", 400)
|
||||
return
|
||||
}
|
||||
ignorePaths, err := search.GetIgnorePaths()
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
ignorePaths = append(ignorePaths, req.IgnorePaths...)
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
err := search.Clear(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("clear index error: %+v", err)
|
||||
return
|
||||
}
|
||||
err = search.BuildIndex(context.Background(), req.Paths, ignorePaths, req.MaxDepth, true)
|
||||
if err != nil {
|
||||
log.Errorf("build index error: %+v", err)
|
||||
}
|
||||
}()
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
func StopIndex(c *gin.Context) {
|
||||
if !search.Running.Load() {
|
||||
common.ErrorStrResp(c, "index is not running", 400)
|
||||
return
|
||||
}
|
||||
search.Quit <- struct{}{}
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
func GetProgress(c *gin.Context) {
|
||||
progress, err := search.Progress()
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c, progress)
|
||||
}
|
@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
func ListMetas(c *gin.Context) {
|
||||
var req common.PageReq
|
||||
var req model.PageReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
|
64
server/handles/search.go
Normal file
64
server/handles/search.go
Normal file
@ -0,0 +1,64 @@
|
||||
package handles
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/search"
|
||||
"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"
|
||||
)
|
||||
|
||||
type SearchResp struct {
|
||||
model.SearchNode
|
||||
Type int `json:"type"`
|
||||
}
|
||||
|
||||
func Search(c *gin.Context) {
|
||||
var req model.SearchReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
nodes, total, err := search.Search(c, req)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
filteredNodes := []model.SearchNode{}
|
||||
user := c.MustGet("user").(*model.User)
|
||||
for _, node := range nodes {
|
||||
if !strings.HasPrefix(node.Parent, user.BasePath) {
|
||||
continue
|
||||
}
|
||||
meta, err := db.GetNearestMeta(node.Parent)
|
||||
if err != nil && !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
continue
|
||||
}
|
||||
if !common.CanAccess(user, meta, path.Join(node.Parent, node.Name), "") {
|
||||
continue
|
||||
}
|
||||
// node.Parent = "/" + strings.Replace(node.Parent, user.BasePath, "", 1)
|
||||
filteredNodes = append(filteredNodes, node)
|
||||
}
|
||||
common.SuccessResp(c, common.PageResp{
|
||||
Content: utils.MustSliceConvert(filteredNodes, nodeToSearchResp),
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
func nodeToSearchResp(node model.SearchNode) SearchResp {
|
||||
return SearchResp{
|
||||
SearchNode: node,
|
||||
Type: utils.GetObjType(node.Name, node.IsDir),
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func ListStorages(c *gin.Context) {
|
||||
var req common.PageReq
|
||||
var req model.PageReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func ListUsers(c *gin.Context) {
|
||||
var req common.PageReq
|
||||
var req model.PageReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
|
@ -3,9 +3,11 @@ package middlewares
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/internal/sign"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
@ -44,6 +46,9 @@ func parsePath(path string) string {
|
||||
}
|
||||
|
||||
func needSign(meta *model.Meta, path string) bool {
|
||||
if setting.GetBool(conf.SignAll) {
|
||||
return true
|
||||
}
|
||||
if meta == nil || meta.Password == "" {
|
||||
return false
|
||||
}
|
||||
|
@ -22,7 +22,11 @@ func FsUp(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
user := c.MustGet("user").(*model.User)
|
||||
path = stdpath.Join(user.BasePath, path)
|
||||
path, err = user.JoinPath(path)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 403)
|
||||
return
|
||||
}
|
||||
meta, err := db.GetNearestMeta(stdpath.Dir(path))
|
||||
if err != nil {
|
||||
if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
|
||||
|
19
server/middlewares/search.go
Normal file
19
server/middlewares/search.go
Normal file
@ -0,0 +1,19 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SearchIndex(c *gin.Context) {
|
||||
mode := setting.GetStr(conf.SearchIndex)
|
||||
if mode == "none" {
|
||||
common.ErrorResp(c, errs.SearchNotAvailable, 500)
|
||||
c.Abort()
|
||||
} else {
|
||||
c.Next()
|
||||
}
|
||||
}
|
@ -107,10 +107,16 @@ func admin(g *gin.RouterGroup) {
|
||||
ms := g.Group("/message")
|
||||
ms.POST("/get", message.HttpInstance.GetHandle)
|
||||
ms.POST("/send", message.HttpInstance.SendHandle)
|
||||
|
||||
index := g.Group("/index")
|
||||
index.POST("/build", middlewares.SearchIndex, handles.BuildIndex)
|
||||
index.POST("/stop", middlewares.SearchIndex, handles.StopIndex)
|
||||
index.GET("/progress", middlewares.SearchIndex, handles.GetProgress)
|
||||
}
|
||||
|
||||
func _fs(g *gin.RouterGroup) {
|
||||
g.Any("/list", handles.FsList)
|
||||
g.Any("/search", middlewares.SearchIndex, handles.Search)
|
||||
g.Any("/get", handles.FsGet)
|
||||
g.Any("/other", handles.FsOther)
|
||||
g.Any("/dirs", handles.FsDirs)
|
||||
@ -128,6 +134,7 @@ func _fs(g *gin.RouterGroup) {
|
||||
func Cors(r *gin.Engine) {
|
||||
config := cors.DefaultConfig()
|
||||
config.AllowAllOrigins = true
|
||||
config.AllowHeaders = append(config.AllowHeaders, "Authorization", "range", "File-Path", "As-Task", "Password")
|
||||
//config.AllowHeaders = append(config.AllowHeaders, "Authorization", "range", "File-Path", "As-Task", "Password")
|
||||
config.AllowHeaders = []string{"*"}
|
||||
r.Use(cors.New(config))
|
||||
}
|
||||
|
46
server/webdav/buffered_response_writer.go
Normal file
46
server/webdav/buffered_response_writer.go
Normal file
@ -0,0 +1,46 @@
|
||||
package webdav
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type bufferedResponseWriter struct {
|
||||
statusCode int
|
||||
data []byte
|
||||
header http.Header
|
||||
}
|
||||
|
||||
func (w *bufferedResponseWriter) Header() http.Header {
|
||||
if w.header == nil {
|
||||
w.header = make(http.Header)
|
||||
}
|
||||
return w.header
|
||||
}
|
||||
|
||||
func (w *bufferedResponseWriter) Write(bytes []byte) (int, error) {
|
||||
w.data = append(w.data, bytes...)
|
||||
return len(bytes), nil
|
||||
}
|
||||
|
||||
func (w *bufferedResponseWriter) WriteHeader(statusCode int) {
|
||||
if w.statusCode == 0 {
|
||||
w.statusCode = statusCode
|
||||
}
|
||||
}
|
||||
|
||||
func (w *bufferedResponseWriter) WriteToResponse(rw http.ResponseWriter) (int, error) {
|
||||
h := rw.Header()
|
||||
for k, vs := range w.header {
|
||||
for _, v := range vs {
|
||||
h.Add(k, v)
|
||||
}
|
||||
}
|
||||
rw.WriteHeader(w.statusCode)
|
||||
return rw.Write(w.data)
|
||||
}
|
||||
|
||||
func newBufferedResponseWriter() *bufferedResponseWriter {
|
||||
return &bufferedResponseWriter{
|
||||
statusCode: 0,
|
||||
}
|
||||
}
|
@ -10,11 +10,9 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/fs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
// slashClean is equivalent to but slightly more efficient than
|
||||
@ -101,7 +99,7 @@ func walkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn
|
||||
}
|
||||
|
||||
for _, fileInfo := range objs {
|
||||
filename := path.Join(name, utils.MappingName(fileInfo.GetName(), conf.FilenameCharMap))
|
||||
filename := path.Join(name, fileInfo.GetName())
|
||||
if err != nil {
|
||||
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||
return err
|
||||
|
@ -15,9 +15,7 @@ import (
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
)
|
||||
|
||||
// Proppatch describes a property update instruction as defined in RFC 4918.
|
||||
@ -199,7 +197,7 @@ func props(ctx context.Context, ls LockSystem, fi model.Obj, pnames []xml.Name)
|
||||
}
|
||||
// Otherwise, it must either be a live property or we don't know it.
|
||||
if prop := liveProps[pn]; prop.findFn != nil && (prop.dir || !isDir) {
|
||||
innerXML, err := prop.findFn(ctx, ls, utils.MappingName(fi.GetName(), conf.FilenameCharMap), fi)
|
||||
innerXML, err := prop.findFn(ctx, ls, fi.GetName(), fi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -375,7 +373,7 @@ func findDisplayName(ctx context.Context, ls LockSystem, name string, fi model.O
|
||||
// Hide the real name of a possibly prefixed root directory.
|
||||
return "", nil
|
||||
}
|
||||
return escapeXML(utils.MappingName(fi.GetName(), conf.FilenameCharMap)), nil
|
||||
return escapeXML(fi.GetName()), nil
|
||||
}
|
||||
|
||||
func findContentLength(ctx context.Context, ls LockSystem, name string, fi model.Obj) (string, error) {
|
||||
|
@ -45,30 +45,33 @@ func (h *Handler) stripPrefix(p string) (string, int, error) {
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := http.StatusBadRequest, errUnsupportedMethod
|
||||
brw := newBufferedResponseWriter()
|
||||
useBufferedWriter := true
|
||||
if h.LockSystem == nil {
|
||||
status, err = http.StatusInternalServerError, errNoLockSystem
|
||||
} else {
|
||||
switch r.Method {
|
||||
case "OPTIONS":
|
||||
status, err = h.handleOptions(w, r)
|
||||
status, err = h.handleOptions(brw, r)
|
||||
case "GET", "HEAD", "POST":
|
||||
useBufferedWriter = false
|
||||
status, err = h.handleGetHeadPost(w, r)
|
||||
case "DELETE":
|
||||
status, err = h.handleDelete(w, r)
|
||||
status, err = h.handleDelete(brw, r)
|
||||
case "PUT":
|
||||
status, err = h.handlePut(w, r)
|
||||
status, err = h.handlePut(brw, r)
|
||||
case "MKCOL":
|
||||
status, err = h.handleMkcol(w, r)
|
||||
status, err = h.handleMkcol(brw, r)
|
||||
case "COPY", "MOVE":
|
||||
status, err = h.handleCopyMove(w, r)
|
||||
status, err = h.handleCopyMove(brw, r)
|
||||
case "LOCK":
|
||||
status, err = h.handleLock(w, r)
|
||||
status, err = h.handleLock(brw, r)
|
||||
case "UNLOCK":
|
||||
status, err = h.handleUnlock(w, r)
|
||||
status, err = h.handleUnlock(brw, r)
|
||||
case "PROPFIND":
|
||||
status, err = h.handlePropfind(w, r)
|
||||
status, err = h.handlePropfind(brw, r)
|
||||
case "PROPPATCH":
|
||||
status, err = h.handleProppatch(w, r)
|
||||
status, err = h.handleProppatch(brw, r)
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,6 +80,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if status != http.StatusNoContent {
|
||||
w.Write([]byte(StatusText(status)))
|
||||
}
|
||||
} else if useBufferedWriter {
|
||||
brw.WriteToResponse(w)
|
||||
}
|
||||
if h.Logger != nil && err != nil {
|
||||
h.Logger(r, err)
|
||||
@ -178,7 +183,10 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status
|
||||
}
|
||||
ctx := r.Context()
|
||||
user := ctx.Value("user").(*model.User)
|
||||
reqPath = path.Join(user.BasePath, reqPath)
|
||||
reqPath, err = user.JoinPath(reqPath)
|
||||
if err != nil {
|
||||
return 403, err
|
||||
}
|
||||
allow := "OPTIONS, LOCK, PUT, MKCOL"
|
||||
if fi, err := fs.Get(ctx, reqPath); err == nil {
|
||||
if fi.IsDir() {
|
||||
@ -203,7 +211,10 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
|
||||
// TODO: check locks for read-only access??
|
||||
ctx := r.Context()
|
||||
user := ctx.Value("user").(*model.User)
|
||||
reqPath = path.Join(user.BasePath, reqPath)
|
||||
reqPath, err = user.JoinPath(reqPath)
|
||||
if err != nil {
|
||||
return 403, err
|
||||
}
|
||||
fi, err := fs.Get(ctx, reqPath)
|
||||
if err != nil {
|
||||
return http.StatusNotFound, err
|
||||
@ -218,7 +229,8 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
|
||||
w.Header().Set("ETag", etag)
|
||||
// Let ServeContent determine the Content-Type header.
|
||||
storage, _ := fs.GetStorage(reqPath)
|
||||
if storage.GetStorage().WebdavNative() {
|
||||
downProxyUrl := storage.GetStorage().DownProxyUrl
|
||||
if storage.GetStorage().WebdavNative() || (storage.GetStorage().WebdavProxy() && downProxyUrl == "") {
|
||||
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{Header: r.Header})
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
@ -227,19 +239,19 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
} else if storage.Config().MustProxy() || storage.GetStorage().WebdavProxy() {
|
||||
u := fmt.Sprintf("%s/p%s?sign=%s",
|
||||
common.GetApiUrl(r),
|
||||
} else if storage.GetStorage().WebdavProxy() && downProxyUrl != "" {
|
||||
u := fmt.Sprintf("%s%s?sign=%s",
|
||||
strings.Split(downProxyUrl, "\n")[0],
|
||||
utils.EncodePath(reqPath, true),
|
||||
sign.Sign(reqPath))
|
||||
w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
|
||||
http.Redirect(w, r, u, 302)
|
||||
http.Redirect(w, r, u, http.StatusFound)
|
||||
} else {
|
||||
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{IP: utils.ClientIP(r)})
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
http.Redirect(w, r, link.URL, 302)
|
||||
http.Redirect(w, r, link.URL, http.StatusFound)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
@ -257,7 +269,10 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request) (status i
|
||||
|
||||
ctx := r.Context()
|
||||
user := ctx.Value("user").(*model.User)
|
||||
reqPath = path.Join(user.BasePath, reqPath)
|
||||
reqPath, err = user.JoinPath(reqPath)
|
||||
if err != nil {
|
||||
return 403, err
|
||||
}
|
||||
// TODO: return MultiStatus where appropriate.
|
||||
|
||||
// "godoc os RemoveAll" says that "If the path does not exist, RemoveAll
|
||||
@ -290,7 +305,10 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
|
||||
// comments in http.checkEtag.
|
||||
ctx := r.Context()
|
||||
user := ctx.Value("user").(*model.User)
|
||||
reqPath = path.Join(user.BasePath, reqPath)
|
||||
reqPath, err = user.JoinPath(reqPath)
|
||||
if err != nil {
|
||||
return 403, err
|
||||
}
|
||||
obj := model.Object{
|
||||
Name: path.Base(reqPath),
|
||||
Size: r.ContentLength,
|
||||
@ -336,7 +354,10 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request) (status in
|
||||
|
||||
ctx := r.Context()
|
||||
user := ctx.Value("user").(*model.User)
|
||||
reqPath = path.Join(user.BasePath, reqPath)
|
||||
reqPath, err = user.JoinPath(reqPath)
|
||||
if err != nil {
|
||||
return 403, err
|
||||
}
|
||||
|
||||
if r.ContentLength > 0 {
|
||||
return http.StatusUnsupportedMediaType, nil
|
||||
@ -383,8 +404,14 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
|
||||
|
||||
ctx := r.Context()
|
||||
user := ctx.Value("user").(*model.User)
|
||||
src = path.Join(user.BasePath, src)
|
||||
dst = path.Join(user.BasePath, dst)
|
||||
src, err = user.JoinPath(src)
|
||||
if err != nil {
|
||||
return 403, err
|
||||
}
|
||||
dst, err = user.JoinPath(dst)
|
||||
if err != nil {
|
||||
return 403, err
|
||||
}
|
||||
|
||||
if r.Method == "COPY" {
|
||||
// Section 7.5.1 says that a COPY only needs to lock the destination,
|
||||
@ -475,7 +502,10 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
|
||||
}
|
||||
}
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path)
|
||||
reqPath = path.Join(user.BasePath, reqPath)
|
||||
reqPath, err = user.JoinPath(reqPath)
|
||||
if err != nil {
|
||||
return 403, err
|
||||
}
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
@ -556,7 +586,10 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
||||
}
|
||||
ctx := r.Context()
|
||||
user := ctx.Value("user").(*model.User)
|
||||
reqPath = path.Join(user.BasePath, reqPath)
|
||||
reqPath, err = user.JoinPath(reqPath)
|
||||
if err != nil {
|
||||
return 403, err
|
||||
}
|
||||
fi, err := fs.Get(ctx, reqPath)
|
||||
if err != nil {
|
||||
if errs.IsObjectNotFound(err) {
|
||||
@ -632,8 +665,10 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
|
||||
|
||||
ctx := r.Context()
|
||||
user := ctx.Value("user").(*model.User)
|
||||
reqPath = path.Join(user.BasePath, reqPath)
|
||||
|
||||
reqPath, err = user.JoinPath(reqPath)
|
||||
if err != nil {
|
||||
return 403, err
|
||||
}
|
||||
if _, err := fs.Get(ctx, reqPath); err != nil {
|
||||
if errs.IsObjectNotFound(err) {
|
||||
return http.StatusNotFound, err
|
||||
|
Reference in New Issue
Block a user