mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-11-25 11:29:29 +08:00
Compare commits
6 Commits
renovate/g
...
beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60a489eb68 | ||
|
|
b22e211044 | ||
|
|
ca401b9af9 | ||
|
|
addce8b691 | ||
|
|
42fc841dc1 | ||
|
|
4c0916b64b |
@@ -58,8 +58,13 @@ func (d *BaiduNetdisk) GetAddition() driver.Additional {
|
||||
}
|
||||
|
||||
func (d *BaiduNetdisk) Init(ctx context.Context) error {
|
||||
timeout := DEFAULT_UPLOAD_SLICE_TIMEOUT
|
||||
if d.UploadSliceTimeout > 0 {
|
||||
timeout = time.Second * time.Duration(d.UploadSliceTimeout)
|
||||
}
|
||||
|
||||
d.upClient = base.NewRestyClient().
|
||||
SetTimeout(UPLOAD_TIMEOUT).
|
||||
SetTimeout(timeout).
|
||||
SetRetryCount(UPLOAD_RETRY_COUNT).
|
||||
SetRetryWaitTime(UPLOAD_RETRY_WAIT_TIME).
|
||||
SetRetryMaxWaitTime(UPLOAD_RETRY_MAX_WAIT_TIME)
|
||||
|
||||
@@ -19,6 +19,7 @@ type Addition struct {
|
||||
AccessToken string
|
||||
RefreshToken string `json:"refresh_token" required:"true"`
|
||||
UploadThread string `json:"upload_thread" default:"3" help:"1<=thread<=32"`
|
||||
UploadSliceTimeout int `json:"upload_timeout" type:"number" default:"60" help:"per-slice upload timeout in seconds"`
|
||||
UploadAPI string `json:"upload_api" default:"https://d.pcs.baidu.com"`
|
||||
UseDynamicUploadAPI bool `json:"use_dynamic_upload_api" default:"true" help:"dynamically get upload api domain, when enabled, the 'Upload API' setting will be used as a fallback if failed to get"`
|
||||
CustomUploadPartSize int64 `json:"custom_upload_part_size" type:"number" default:"0" help:"0 for auto"`
|
||||
@@ -29,7 +30,7 @@ type Addition struct {
|
||||
const (
|
||||
UPLOAD_FALLBACK_API = "https://d.pcs.baidu.com" // 备用上传地址
|
||||
UPLOAD_URL_EXPIRE_TIME = time.Minute * 60 // 上传地址有效期(分钟)
|
||||
UPLOAD_TIMEOUT = time.Minute * 30 // 上传请求超时时间
|
||||
DEFAULT_UPLOAD_SLICE_TIMEOUT = time.Second * 60 // 上传分片请求默认超时时间
|
||||
UPLOAD_RETRY_COUNT = 3
|
||||
UPLOAD_RETRY_WAIT_TIME = time.Second * 1
|
||||
UPLOAD_RETRY_MAX_WAIT_TIME = time.Second * 5
|
||||
|
||||
16
drivers/local/copy_namedpipes.go
Normal file
16
drivers/local/copy_namedpipes.go
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build !windows && !plan9 && !netbsd && !aix && !illumos && !solaris && !js
|
||||
|
||||
package local
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func copyNamedPipe(dstPath string, mode os.FileMode, dirMode os.FileMode) error {
|
||||
if err := os.MkdirAll(filepath.Dir(dstPath), dirMode); err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.Mkfifo(dstPath, uint32(mode))
|
||||
}
|
||||
9
drivers/local/copy_namedpipes_x.go
Normal file
9
drivers/local/copy_namedpipes_x.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build windows || plan9 || netbsd || aix || illumos || solaris || js
|
||||
|
||||
package local
|
||||
|
||||
import "os"
|
||||
|
||||
func copyNamedPipe(_ string, _, _ os.FileMode) error {
|
||||
return nil
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||
"github.com/OpenListTeam/times"
|
||||
cp "github.com/otiai10/copy"
|
||||
log "github.com/sirupsen/logrus"
|
||||
_ "golang.org/x/image/webp"
|
||||
)
|
||||
@@ -297,16 +296,9 @@ func (d *Local) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return fmt.Errorf("the destination folder is a subfolder of the source folder")
|
||||
}
|
||||
err := os.Rename(srcPath, dstPath)
|
||||
if err != nil && strings.Contains(err.Error(), "invalid cross-device link") {
|
||||
// 跨设备移动,先复制再删除
|
||||
if err := d.Copy(ctx, srcObj, dstDir); err != nil {
|
||||
return err
|
||||
}
|
||||
// 复制成功后直接删除源文件/文件夹
|
||||
if srcObj.IsDir() {
|
||||
return os.RemoveAll(srcObj.GetPath())
|
||||
}
|
||||
return os.Remove(srcObj.GetPath())
|
||||
if isCrossDeviceError(err) {
|
||||
// 跨设备移动,变更为移动任务
|
||||
return errs.NotImplement
|
||||
}
|
||||
if err == nil {
|
||||
srcParent := filepath.Dir(srcPath)
|
||||
@@ -347,15 +339,14 @@ func (d *Local) Copy(_ context.Context, srcObj, dstDir model.Obj) error {
|
||||
if utils.IsSubPath(srcPath, dstPath) {
|
||||
return fmt.Errorf("the destination folder is a subfolder of the source folder")
|
||||
}
|
||||
// Copy using otiai10/copy to perform more secure & efficient copy
|
||||
err := cp.Copy(srcPath, dstPath, cp.Options{
|
||||
Sync: true, // Sync file to disk after copy, may have performance penalty in filesystem such as ZFS
|
||||
PreserveTimes: true,
|
||||
PreserveOwner: true,
|
||||
})
|
||||
info, err := os.Lstat(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 复制regular文件会返回errs.NotImplement, 转为复制任务
|
||||
if err = d.tryCopy(srcPath, dstPath, info); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.directoryMap.Has(filepath.Dir(dstPath)) {
|
||||
d.directoryMap.UpdateDirSize(filepath.Dir(dstPath))
|
||||
|
||||
@@ -3,6 +3,7 @@ package local
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
@@ -14,7 +15,9 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/KarpelesLab/reflink"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/disintegration/imaging"
|
||||
@@ -148,7 +151,7 @@ func (d *Local) getThumb(file model.Obj) (*bytes.Buffer, *string, error) {
|
||||
return nil, nil, err
|
||||
}
|
||||
if d.ThumbCacheFolder != "" {
|
||||
err = os.WriteFile(filepath.Join(d.ThumbCacheFolder, thumbName), buf.Bytes(), 0666)
|
||||
err = os.WriteFile(filepath.Join(d.ThumbCacheFolder, thumbName), buf.Bytes(), 0o666)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -405,3 +408,79 @@ func (m *DirectoryMap) DeleteDirNode(dirname string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Local) tryCopy(srcPath, dstPath string, info os.FileInfo) error {
|
||||
if info.Mode()&os.ModeDevice != 0 {
|
||||
return errors.New("cannot copy a device")
|
||||
} else if info.Mode()&os.ModeSymlink != 0 {
|
||||
return d.copySymlink(srcPath, dstPath)
|
||||
} else if info.Mode()&os.ModeNamedPipe != 0 {
|
||||
return copyNamedPipe(dstPath, info.Mode(), os.FileMode(d.mkdirPerm))
|
||||
} else if info.IsDir() {
|
||||
return d.recurAndTryCopy(srcPath, dstPath)
|
||||
} else {
|
||||
return tryReflinkCopy(srcPath, dstPath)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Local) copySymlink(srcPath, dstPath string) error {
|
||||
linkOrig, err := os.Readlink(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstDir := filepath.Dir(dstPath)
|
||||
if !filepath.IsAbs(linkOrig) {
|
||||
srcDir := filepath.Dir(srcPath)
|
||||
rel, err := filepath.Rel(dstDir, srcDir)
|
||||
if err != nil {
|
||||
rel, err = filepath.Abs(srcDir)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
linkOrig = filepath.Clean(filepath.Join(rel, linkOrig))
|
||||
}
|
||||
err = os.MkdirAll(dstDir, os.FileMode(d.mkdirPerm))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Symlink(linkOrig, dstPath)
|
||||
}
|
||||
|
||||
func (d *Local) recurAndTryCopy(srcPath, dstPath string) error {
|
||||
err := os.MkdirAll(dstPath, os.FileMode(d.mkdirPerm))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files, err := readDir(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
if !f.IsDir() {
|
||||
sp := filepath.Join(srcPath, f.Name())
|
||||
dp := filepath.Join(dstPath, f.Name())
|
||||
if err = d.tryCopy(sp, dp, f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
sp := filepath.Join(srcPath, f.Name())
|
||||
dp := filepath.Join(dstPath, f.Name())
|
||||
if err = d.recurAndTryCopy(sp, dp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tryReflinkCopy(srcPath, dstPath string) error {
|
||||
err := reflink.Always(srcPath, dstPath)
|
||||
if errors.Is(err, reflink.ErrReflinkUnsupported) || errors.Is(err, reflink.ErrReflinkFailed) || isCrossDeviceError(err) {
|
||||
return errs.NotImplement
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func isHidden(f fs.FileInfo, _ string) bool {
|
||||
@@ -27,3 +29,7 @@ func getDiskUsage(path string) (model.DiskUsage, error) {
|
||||
FreeSpace: free,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isCrossDeviceError(err error) bool {
|
||||
return errors.Is(err, unix.EXDEV)
|
||||
}
|
||||
|
||||
@@ -49,3 +49,7 @@ func getDiskUsage(path string) (model.DiskUsage, error) {
|
||||
FreeSpace: freeBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isCrossDeviceError(err error) bool {
|
||||
return errors.Is(err, windows.ERROR_NOT_SAME_DEVICE)
|
||||
}
|
||||
|
||||
@@ -360,6 +360,7 @@ func (d *OpenList) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.O
|
||||
Name: []string{name},
|
||||
PutIntoNewDir: args.PutIntoNewDir,
|
||||
SrcDir: dir,
|
||||
Overwrite: args.Overwrite,
|
||||
})
|
||||
})
|
||||
return err
|
||||
|
||||
@@ -167,4 +167,5 @@ type DecompressReq struct {
|
||||
Name []string `json:"name"`
|
||||
PutIntoNewDir bool `json:"put_into_new_dir"`
|
||||
SrcDir string `json:"src_dir"`
|
||||
Overwrite bool `json:"overwrite"`
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func (d *Strm) Init(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
if d.Version != 3 {
|
||||
if d.Version != 5 {
|
||||
types := strings.Split("mp4,mkv,flv,avi,wmv,ts,rmvb,webm,mp3,flac,aac,wav,ogg,m4a,wma,alac", ",")
|
||||
for _, ext := range types {
|
||||
if _, ok := d.supportSuffix[ext]; !ok {
|
||||
@@ -109,12 +109,13 @@ func (d *Strm) Init(ctx context.Context) error {
|
||||
types = strings.Split("ass,srt,vtt,sub,strm", ",")
|
||||
for _, ext := range types {
|
||||
if _, ok := d.downloadSuffix[ext]; !ok {
|
||||
d.supportSuffix[ext] = struct{}{}
|
||||
d.downloadSuffix[ext] = struct{}{}
|
||||
downloadTypes = append(downloadTypes, ext)
|
||||
}
|
||||
}
|
||||
d.DownloadFileTypes = strings.Join(downloadTypes, ",")
|
||||
d.Version = 3
|
||||
d.PathPrefix = "/d"
|
||||
d.Version = 5
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
type Addition struct {
|
||||
Paths string `json:"paths" required:"true" type:"text"`
|
||||
SiteUrl string `json:"siteUrl" type:"text" required:"false" help:"The prefix URL of the strm file"`
|
||||
PathPrefix string `json:"PathPrefix" type:"text" required:"false" default:"/d" help:"Path prefix"`
|
||||
DownloadFileTypes string `json:"downloadFileTypes" type:"text" default:"ass,srt,vtt,sub,strm" required:"false" help:"Files need to download with strm (usally subtitles)"`
|
||||
FilterFileTypes string `json:"filterFileTypes" type:"text" default:"mp4,mkv,flv,avi,wmv,ts,rmvb,webm,mp3,flac,aac,wav,ogg,m4a,wma,alac" required:"false" help:"Supports suffix name of strm file"`
|
||||
EncodePath bool `json:"encodePath" default:"true" required:"true" help:"encode the path in the strm file"`
|
||||
|
||||
@@ -3,7 +3,6 @@ package strm
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
stdpath "path"
|
||||
"strings"
|
||||
|
||||
@@ -69,11 +68,12 @@ func (d *Strm) convert2strmObjs(ctx context.Context, reqPath string, objs []mode
|
||||
if !obj.IsDir() {
|
||||
path = stdpath.Join(reqPath, obj.GetName())
|
||||
ext := strings.ToLower(utils.Ext(name))
|
||||
sourceExt := utils.SourceExt(name)
|
||||
if _, ok := d.downloadSuffix[ext]; ok {
|
||||
size = obj.GetSize()
|
||||
} else if _, ok := d.supportSuffix[ext]; ok {
|
||||
id = "strm"
|
||||
name = strings.TrimSuffix(name, ext) + "strm"
|
||||
name = strings.TrimSuffix(name, sourceExt) + "strm"
|
||||
size = int64(len(d.getLink(ctx, path)))
|
||||
} else {
|
||||
continue
|
||||
@@ -111,6 +111,13 @@ func (d *Strm) getLink(ctx context.Context, path string) string {
|
||||
signPath := sign.Sign(path)
|
||||
finalPath = fmt.Sprintf("%s?sign=%s", finalPath, signPath)
|
||||
}
|
||||
pathPrefix := d.PathPrefix
|
||||
if len(pathPrefix) > 0 {
|
||||
finalPath = stdpath.Join(pathPrefix, finalPath)
|
||||
}
|
||||
if !strings.HasPrefix(finalPath, "/") {
|
||||
finalPath = "/" + finalPath
|
||||
}
|
||||
if d.WithoutUrl {
|
||||
return finalPath
|
||||
}
|
||||
@@ -120,10 +127,7 @@ func (d *Strm) getLink(ctx context.Context, path string) string {
|
||||
} else {
|
||||
apiUrl = common.GetApiUrl(ctx)
|
||||
}
|
||||
if !strings.HasPrefix(finalPath, "/") {
|
||||
finalPath = "/" + finalPath
|
||||
}
|
||||
return fmt.Sprintf("%s/d%s",
|
||||
return fmt.Sprintf("%s%s",
|
||||
apiUrl,
|
||||
finalPath)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ var config = driver.Config{
|
||||
LocalSort: true,
|
||||
NoCache: true,
|
||||
CheckStatus: true,
|
||||
OnlyIndices: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
3
go.mod
3
go.mod
@@ -5,6 +5,7 @@ go 1.23.4
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2
|
||||
github.com/KarpelesLab/reflink v1.0.2
|
||||
github.com/KirCute/zip v1.0.1
|
||||
github.com/OpenListTeam/go-cache v0.1.0
|
||||
github.com/OpenListTeam/sftpd-openlist v1.0.1
|
||||
@@ -114,7 +115,6 @@ require (
|
||||
github.com/minio/minlz v1.0.0 // indirect
|
||||
github.com/minio/xxml v0.0.3 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/otiai10/mint v1.6.3 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/relvacode/iso8601 v1.6.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
@@ -256,7 +256,6 @@ require (
|
||||
github.com/multiformats/go-multihash v0.2.3 // indirect
|
||||
github.com/multiformats/go-multistream v0.4.1 // indirect
|
||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@@ -39,6 +39,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Da3zKi7/saferith v0.33.0-fixed h1:fnIWTk7EP9mZAICf7aQjeoAwpfrlCrkOvqmi6CbWdTk=
|
||||
github.com/Da3zKi7/saferith v0.33.0-fixed/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
|
||||
github.com/KarpelesLab/reflink v1.0.2 h1:hQ1aM3TmjU2kTNUx5p/HaobDoADYk+a6AuEinG4Cv88=
|
||||
github.com/KarpelesLab/reflink v1.0.2/go.mod h1:WGkTOKNjd1FsJKBw3mu4JvrPEDJyJJ+JPtxBkbPoCok=
|
||||
github.com/KirCute/zip v1.0.1 h1:L/tVZglOiDVKDi9Ud+fN49htgKdQ3Z0H80iX8OZk13c=
|
||||
github.com/KirCute/zip v1.0.1/go.mod h1:xhF7dCB+Bjvy+5a56lenYCKBsH+gxDNPZSy5Cp+nlXk=
|
||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||
@@ -585,10 +587,6 @@ github.com/ncw/swift/v2 v2.0.4 h1:hHWVFxn5/YaTWAASmn4qyq2p6OyP/Hm3vMLzkjEqR7w=
|
||||
github.com/ncw/swift/v2 v2.0.4/go.mod h1:cbAO76/ZwcFrFlHdXPjaqWZ9R7Hdar7HpjRXBfbjigk=
|
||||
github.com/nwaples/rardecode/v2 v2.1.1 h1:OJaYalXdliBUXPmC8CZGQ7oZDxzX1/5mQmgn0/GASew=
|
||||
github.com/nwaples/rardecode/v2 v2.1.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
|
||||
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
|
||||
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
||||
github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
|
||||
@@ -22,6 +22,8 @@ type Config struct {
|
||||
// - LinkCacheNone: no extra info added to cache key (default)
|
||||
// - flags (OR-able) can add more attributes to cache key (IP, UA, ...)
|
||||
LinkCacheMode `json:"-"`
|
||||
// if the driver only store indices of files (e.g. UrlTree)
|
||||
OnlyIndices bool `json:"only_indices"`
|
||||
}
|
||||
type LinkCacheMode int8
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ func (t *ArchiveDownloadTask) RunWithoutPushUploadTask() (*ArchiveContentUploadT
|
||||
DstActualPath: t.DstActualPath,
|
||||
dstStorage: t.DstStorage,
|
||||
DstStorageMp: t.DstStorageMp,
|
||||
overwrite: t.Overwrite,
|
||||
}
|
||||
return uploadTask, nil
|
||||
}
|
||||
@@ -142,6 +143,7 @@ type ArchiveContentUploadTask struct {
|
||||
DstStorageMp string
|
||||
finalized bool
|
||||
groupID string
|
||||
overwrite bool
|
||||
}
|
||||
|
||||
func (t *ArchiveContentUploadTask) GetName() string {
|
||||
@@ -232,6 +234,7 @@ func (t *ArchiveContentUploadTask) RunWithNextTaskCallback(f func(nextTask *Arch
|
||||
dstStorage: t.dstStorage,
|
||||
DstStorageMp: t.DstStorageMp,
|
||||
groupID: t.groupID,
|
||||
overwrite: t.overwrite,
|
||||
})
|
||||
if err != nil {
|
||||
es = stderrors.Join(es, err)
|
||||
@@ -241,6 +244,12 @@ func (t *ArchiveContentUploadTask) RunWithNextTaskCallback(f func(nextTask *Arch
|
||||
return es
|
||||
}
|
||||
} else {
|
||||
if !t.overwrite {
|
||||
dstPath := stdpath.Join(t.DstActualPath, t.ObjName)
|
||||
if res, _ := op.Get(t.Ctx(), t.dstStorage, dstPath); res != nil {
|
||||
return errs.ObjectAlreadyExists
|
||||
}
|
||||
}
|
||||
file, err := os.Open(t.FilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -24,14 +24,17 @@ type taskType uint8
|
||||
func (t taskType) String() string {
|
||||
if t == 0 {
|
||||
return "copy"
|
||||
} else {
|
||||
} else if t == 1 {
|
||||
return "move"
|
||||
} else {
|
||||
return "merge"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
copy taskType = iota
|
||||
move
|
||||
merge
|
||||
)
|
||||
|
||||
type FileTransferTask struct {
|
||||
@@ -67,7 +70,7 @@ func (t *FileTransferTask) Run() error {
|
||||
return t.RunWithNextTaskCallback(func(nextTask *FileTransferTask) error {
|
||||
nextTask.groupID = t.groupID
|
||||
task_group.TransferCoordinator.AddTask(t.groupID, nil)
|
||||
if t.TaskType == copy {
|
||||
if t.TaskType == copy || t.TaskType == merge {
|
||||
CopyTaskManager.Add(nextTask)
|
||||
} else {
|
||||
MoveTaskManager.Add(nextTask)
|
||||
@@ -109,7 +112,7 @@ func transfer(ctx context.Context, taskType taskType, srcObjPath, dstDirPath str
|
||||
}
|
||||
|
||||
if srcStorage.GetStorage() == dstStorage.GetStorage() {
|
||||
if taskType == copy {
|
||||
if taskType == copy || taskType == merge {
|
||||
err = op.Copy(ctx, srcStorage, srcObjActualPath, dstDirActualPath, lazyCache...)
|
||||
if !errors.Is(err, errs.NotImplement) && !errors.Is(err, errs.NotSupport) {
|
||||
return nil, err
|
||||
@@ -161,7 +164,7 @@ func transfer(ctx context.Context, taskType taskType, srcObjPath, dstDirPath str
|
||||
t.Creator, _ = ctx.Value(conf.UserKey).(*model.User)
|
||||
t.ApiUrl = common.GetApiUrl(ctx)
|
||||
t.groupID = dstDirPath
|
||||
if taskType == copy {
|
||||
if taskType == copy || taskType == merge {
|
||||
task_group.TransferCoordinator.AddTask(dstDirPath, nil)
|
||||
CopyTaskManager.Add(t)
|
||||
} else {
|
||||
@@ -177,6 +180,7 @@ func (t *FileTransferTask) RunWithNextTaskCallback(f func(nextTask *FileTransfer
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed get src [%s] file", t.SrcActualPath)
|
||||
}
|
||||
|
||||
if srcObj.IsDir() {
|
||||
t.Status = "src object is dir, listing objs"
|
||||
objs, err := op.List(t.Ctx(), t.SrcStorage, t.SrcActualPath, model.ListArgs{})
|
||||
@@ -184,17 +188,34 @@ func (t *FileTransferTask) RunWithNextTaskCallback(f func(nextTask *FileTransfer
|
||||
return errors.WithMessagef(err, "failed list src [%s] objs", t.SrcActualPath)
|
||||
}
|
||||
dstActualPath := stdpath.Join(t.DstActualPath, srcObj.GetName())
|
||||
if t.TaskType == copy {
|
||||
if t.TaskType == copy || t.TaskType == merge {
|
||||
if t.Ctx().Value(conf.NoTaskKey) != nil {
|
||||
defer op.Cache.DeleteDirectory(t.DstStorage, dstActualPath)
|
||||
} else {
|
||||
task_group.TransferCoordinator.AppendPayload(t.groupID, task_group.DstPathToRefresh(dstActualPath))
|
||||
}
|
||||
}
|
||||
|
||||
existedObjs := make(map[string]bool)
|
||||
if t.TaskType == merge {
|
||||
dstObjs, _ := op.List(t.Ctx(), t.DstStorage, dstActualPath, model.ListArgs{})
|
||||
for _, obj := range dstObjs {
|
||||
if !obj.IsDir() {
|
||||
existedObjs[obj.GetName()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, obj := range objs {
|
||||
if utils.IsCanceled(t.Ctx()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if t.TaskType == merge && !obj.IsDir() && existedObjs[obj.GetName()] {
|
||||
// skip existed file
|
||||
continue
|
||||
}
|
||||
|
||||
err = f(&FileTransferTask{
|
||||
TaskType: t.TaskType,
|
||||
TaskData: TaskData{
|
||||
|
||||
@@ -84,6 +84,14 @@ func Copy(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func Merge(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool) (task.TaskExtensionInfo, error) {
|
||||
res, err := transfer(ctx, merge, srcObjPath, dstDirPath, lazyCache...)
|
||||
if err != nil {
|
||||
log.Errorf("failed merge %s to %s: %+v", srcObjPath, dstDirPath, err)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func Rename(ctx context.Context, srcPath, dstName string, lazyCache ...bool) error {
|
||||
err := rename(ctx, srcPath, dstName, lazyCache...)
|
||||
if err != nil {
|
||||
|
||||
@@ -77,6 +77,7 @@ type ArchiveDecompressArgs struct {
|
||||
ArchiveInnerArgs
|
||||
CacheFull bool
|
||||
PutIntoNewDir bool
|
||||
Overwrite bool
|
||||
}
|
||||
|
||||
type SharingListArgs struct {
|
||||
|
||||
@@ -509,7 +509,7 @@ func Put(ctx context.Context, storage driver.Driver, dstDirPath string, file mod
|
||||
return errors.WithMessagef(errs.StorageNotInit, "storage status: %s", storage.GetStorage().Status)
|
||||
}
|
||||
// UrlTree PUT
|
||||
if storage.GetStorage().Driver == "UrlTree" {
|
||||
if storage.Config().OnlyIndices {
|
||||
var link string
|
||||
dstDirPath, link = urlTreeSplitLineFormPath(stdpath.Join(dstDirPath, file.GetName()))
|
||||
file = &stream.FileStream{Obj: &model.Object{Name: link}}
|
||||
|
||||
@@ -44,11 +44,15 @@ func IsSubPath(path string, subPath string) bool {
|
||||
}
|
||||
|
||||
func Ext(path string) string {
|
||||
return strings.ToLower(SourceExt(path))
|
||||
}
|
||||
|
||||
func SourceExt(path string) string {
|
||||
ext := stdpath.Ext(path)
|
||||
if len(ext) > 0 && ext[0] == '.' {
|
||||
ext = ext[1:]
|
||||
}
|
||||
return strings.ToLower(ext)
|
||||
return ext
|
||||
}
|
||||
|
||||
func EncodePath(path string, all ...bool) string {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package handles
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
stdpath "path"
|
||||
@@ -229,30 +228,15 @@ func FsArchiveList(c *gin.Context, req *ArchiveListReq, user *model.User) {
|
||||
})
|
||||
}
|
||||
|
||||
type StringOrArray []string
|
||||
|
||||
func (s *StringOrArray) UnmarshalJSON(data []byte) error {
|
||||
var value string
|
||||
if err := json.Unmarshal(data, &value); err == nil {
|
||||
*s = []string{value}
|
||||
return nil
|
||||
}
|
||||
var sliceValue []string
|
||||
if err := json.Unmarshal(data, &sliceValue); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = sliceValue
|
||||
return nil
|
||||
}
|
||||
|
||||
type ArchiveDecompressReq struct {
|
||||
SrcDir string `json:"src_dir" form:"src_dir"`
|
||||
DstDir string `json:"dst_dir" form:"dst_dir"`
|
||||
Name StringOrArray `json:"name" form:"name"`
|
||||
Name []string `json:"name" form:"name"`
|
||||
ArchivePass string `json:"archive_pass" form:"archive_pass"`
|
||||
InnerPath string `json:"inner_path" form:"inner_path"`
|
||||
CacheFull bool `json:"cache_full" form:"cache_full"`
|
||||
PutIntoNewDir bool `json:"put_into_new_dir" form:"put_into_new_dir"`
|
||||
Overwrite bool `json:"overwrite" form:"overwrite"`
|
||||
}
|
||||
|
||||
func FsArchiveDecompress(c *gin.Context) {
|
||||
@@ -295,6 +279,7 @@ func FsArchiveDecompress(c *gin.Context) {
|
||||
},
|
||||
CacheFull: req.CacheFull,
|
||||
PutIntoNewDir: req.PutIntoNewDir,
|
||||
Overwrite: req.Overwrite,
|
||||
})
|
||||
if e != nil {
|
||||
if errors.Is(e, errs.WrongArchivePassword) {
|
||||
|
||||
@@ -61,6 +61,8 @@ type MoveCopyReq struct {
|
||||
DstDir string `json:"dst_dir"`
|
||||
Names []string `json:"names"`
|
||||
Overwrite bool `json:"overwrite"`
|
||||
SkipExisting bool `json:"skip_existing"`
|
||||
Merge bool `json:"merge"`
|
||||
}
|
||||
|
||||
func FsMove(c *gin.Context) {
|
||||
@@ -89,20 +91,25 @@ func FsMove(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var validNames []string
|
||||
if !req.Overwrite {
|
||||
for _, name := range req.Names {
|
||||
if res, _ := fs.Get(c.Request.Context(), stdpath.Join(dstDir, name), &fs.GetArgs{NoLog: true}); res != nil {
|
||||
if res, _ := fs.Get(c.Request.Context(), stdpath.Join(dstDir, name), &fs.GetArgs{NoLog: true}); res != nil && !req.SkipExisting {
|
||||
common.ErrorStrResp(c, fmt.Sprintf("file [%s] exists", name), 403)
|
||||
return
|
||||
} else if res == nil {
|
||||
validNames = append(validNames, name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
validNames = req.Names
|
||||
}
|
||||
|
||||
// Create all tasks immediately without any synchronous validation
|
||||
// All validation will be done asynchronously in the background
|
||||
var addedTasks []task.TaskExtensionInfo
|
||||
for i, name := range req.Names {
|
||||
t, err := fs.Move(c.Request.Context(), stdpath.Join(srcDir, name), dstDir, len(req.Names) > i+1)
|
||||
for i, name := range validNames {
|
||||
t, err := fs.Move(c.Request.Context(), stdpath.Join(srcDir, name), dstDir, len(validNames) > i+1)
|
||||
if t != nil {
|
||||
addedTasks = append(addedTasks, t)
|
||||
}
|
||||
@@ -151,20 +158,34 @@ func FsCopy(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var validNames []string
|
||||
if !req.Overwrite {
|
||||
for _, name := range req.Names {
|
||||
if res, _ := fs.Get(c.Request.Context(), stdpath.Join(dstDir, name), &fs.GetArgs{NoLog: true}); res != nil {
|
||||
if !req.SkipExisting && !req.Merge {
|
||||
common.ErrorStrResp(c, fmt.Sprintf("file [%s] exists", name), 403)
|
||||
return
|
||||
} else if req.Merge && res.IsDir() {
|
||||
validNames = append(validNames, name)
|
||||
}
|
||||
} else {
|
||||
validNames = append(validNames, name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
validNames = req.Names
|
||||
}
|
||||
|
||||
// Create all tasks immediately without any synchronous validation
|
||||
// All validation will be done asynchronously in the background
|
||||
var addedTasks []task.TaskExtensionInfo
|
||||
for i, name := range req.Names {
|
||||
t, err := fs.Copy(c.Request.Context(), stdpath.Join(srcDir, name), dstDir, len(req.Names) > i+1)
|
||||
for i, name := range validNames {
|
||||
var t task.TaskExtensionInfo
|
||||
if req.Merge {
|
||||
t, err = fs.Merge(c.Request.Context(), stdpath.Join(srcDir, name), dstDir, len(validNames) > i+1)
|
||||
} else {
|
||||
t, err = fs.Copy(c.Request.Context(), stdpath.Join(srcDir, name), dstDir, len(validNames) > i+1)
|
||||
}
|
||||
if t != nil {
|
||||
addedTasks = append(addedTasks, t)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user