Merge remote-tracking branch 'origin/master'

pull/1710/head
Aaron Liu 2 years ago
commit c5ffdbfcfb

@ -44,6 +44,8 @@ const (
ThumbStatusMetadataKey = "thumb_status" ThumbStatusMetadataKey = "thumb_status"
ThumbSidecarMetadataKey = "thumb_sidecar" ThumbSidecarMetadataKey = "thumb_sidecar"
ChecksumMetadataKey = "webdav_checksum"
) )
func init() { func init() {

@ -10,7 +10,10 @@ import (
"github.com/cloudreve/Cloudreve/v3/pkg/serializer" "github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/pkg/util" "github.com/cloudreve/Cloudreve/v3/pkg/util"
"io/ioutil" "io/ioutil"
"net/http"
"strconv"
"strings" "strings"
"time"
) )
// Hook 钩子函数 // Hook 钩子函数
@ -268,3 +271,34 @@ func HookDeleteUploadSession(id string) Hook {
return nil return nil
} }
} }
// NewWebdavAfterUploadHook 每次创建一个新的钩子函数 rclone 在 PUT 请求里有 OC-Checksum 字符串
// 和 X-OC-Mtime
func NewWebdavAfterUploadHook(request *http.Request) func(ctx context.Context, fs *FileSystem, newFile fsctx.FileHeader) error {
var modtime time.Time
if timeVal := request.Header.Get("X-OC-Mtime"); timeVal != "" {
timeUnix, err := strconv.ParseInt(timeVal, 10, 64)
if err == nil {
modtime = time.Unix(timeUnix, 0)
}
}
checksum := request.Header.Get("OC-Checksum")
return func(ctx context.Context, fs *FileSystem, newFile fsctx.FileHeader) error {
file := newFile.Info().Model.(*model.File)
if !modtime.IsZero() {
err := model.DB.Model(file).UpdateColumn("updated_at", modtime).Error
if err != nil {
return err
}
}
if checksum != "" {
return file.UpdateMetadata(map[string]string{
model.ChecksumMetadataKey: checksum,
})
}
return nil
}
}

@ -38,26 +38,33 @@ func moveFiles(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst
fileIDs = []uint{src.(*model.File).ID} fileIDs = []uint{src.(*model.File).ID}
} }
// 判断是否需要移动 if overwrite {
if src.GetPosition() != path.Dir(dst) { if err := _checkOverwriteFile(ctx, fs, src, dst); err != nil {
err = fs.Move( return http.StatusInternalServerError, err
ctx, }
folderIDs, }
fileIDs,
src.GetPosition(),
path.Dir(dst), // 判断是否需要移动
) if src.GetPosition() != path.Dir(dst) {
} err = fs.Move(
ctx,
// 判断是否需要重命名 folderIDs,
if err == nil && src.GetName() != path.Base(dst) { fileIDs,
err = fs.Rename( src.GetPosition(),
ctx, path.Dir(dst),
folderIDs, )
fileIDs, }
path.Base(dst),
) // 判断是否需要重命名
} if err == nil && src.GetName() != path.Base(dst) {
err = fs.Rename(
ctx,
folderIDs,
fileIDs,
path.Base(dst),
)
}
if err != nil { if err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
@ -74,26 +81,54 @@ func copyFiles(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst
} }
recursion++ recursion++
if src.IsDir() {
err := fs.Copy( var (
ctx, fileIDs []uint
[]uint{src.(*model.Folder).ID}, folderIDs []uint
[]uint{}, src.(*model.Folder).Position, )
path.Dir(dst),
) if overwrite {
if err != nil { if err := _checkOverwriteFile(ctx, fs, src, dst); err != nil {
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
}
if src.IsDir() {
folderIDs = []uint{src.(*model.Folder).ID}
} else { } else {
err := fs.Copy(ctx, []uint{}, []uint{src.(*model.File).ID}, src.(*model.File).Position, path.Dir(dst)) fileIDs = []uint{src.(*model.File).ID}
if err != nil { }
return http.StatusInternalServerError, err
} err = fs.Copy(
ctx,
folderIDs,
fileIDs,
src.GetPosition(),
path.Dir(dst),
)
if err != nil {
return http.StatusInternalServerError, err
} }
return http.StatusNoContent, nil return http.StatusNoContent, nil
} }
// 判断目标 文件/夹 是否已经存在,存在则先删除目标文件/夹
func _checkOverwriteFile(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst string) error {
if src.IsDir() {
ok, folder := fs.IsPathExist(dst)
if ok {
return fs.Delete(ctx, []uint{folder.ID}, []uint{}, false, false)
}
} else {
ok, file := fs.IsFileExist(dst)
if ok {
return fs.Delete(ctx, []uint{}, []uint{file.ID}, false, false)
}
}
return nil
}
// walkFS traverses filesystem fs starting at name up to depth levels. // walkFS traverses filesystem fs starting at name up to depth levels.
// //
// Allowed values for depth are 0, 1 or infiniteDepth. For each visited node, // Allowed values for depth are 0, 1 or infiniteDepth. For each visited node,

@ -16,9 +16,30 @@ import (
"strconv" "strconv"
"time" "time"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
) )
type FileDeadProps struct {
*model.File
}
// 实现 webdav.DeadPropsHolder 接口不能在models.file里面定义
func (file *FileDeadProps) DeadProps() (map[xml.Name]Property, error) {
return map[xml.Name]Property{
xml.Name{Space: "http://owncloud.org/ns", Local: "checksums"}: {
XMLName: xml.Name{
Space: "http://owncloud.org/ns", Local: "checksums",
},
InnerXML: []byte("<checksum>" + file.MetadataSerialized[model.ChecksumMetadataKey] + "</checksum>"),
},
}, nil
}
func (file *FileDeadProps) Patch([]Proppatch) ([]Propstat, error) {
return nil, nil
}
type FileInfo interface { type FileInfo interface {
GetSize() uint64 GetSize() uint64
GetName() string GetName() string
@ -177,8 +198,18 @@ var liveProps = map[xml.Name]struct {
// of one Propstat element. // of one Propstat element.
func props(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, fi FileInfo, pnames []xml.Name) ([]Propstat, error) { func props(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, fi FileInfo, pnames []xml.Name) ([]Propstat, error) {
isDir := fi.IsDir() isDir := fi.IsDir()
if !isDir {
fi = &FileDeadProps{fi.(*model.File)}
}
var deadProps map[xml.Name]Property var deadProps map[xml.Name]Property
if dph, ok := fi.(DeadPropsHolder); ok {
var err error
deadProps, err = dph.DeadProps()
if err != nil {
return nil, err
}
}
pstatOK := Propstat{Status: http.StatusOK} pstatOK := Propstat{Status: http.StatusOK}
pstatNotFound := Propstat{Status: http.StatusNotFound} pstatNotFound := Propstat{Status: http.StatusNotFound}
@ -210,8 +241,18 @@ func props(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, fi Fil
// Propnames returns the property names defined for resource name. // Propnames returns the property names defined for resource name.
func propnames(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, fi FileInfo) ([]xml.Name, error) { func propnames(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, fi FileInfo) ([]xml.Name, error) {
isDir := fi.IsDir() isDir := fi.IsDir()
if !isDir {
fi = &FileDeadProps{fi.(*model.File)}
}
var deadProps map[xml.Name]Property var deadProps map[xml.Name]Property
if dph, ok := fi.(DeadPropsHolder); ok {
var err error
deadProps, err = dph.DeadProps()
if err != nil {
return nil, err
}
}
pnames := make([]xml.Name, 0, len(liveProps)+len(deadProps)) pnames := make([]xml.Name, 0, len(liveProps)+len(deadProps))
for pn, prop := range liveProps { for pn, prop := range liveProps {
@ -219,6 +260,9 @@ func propnames(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, fi
pnames = append(pnames, pn) pnames = append(pnames, pn)
} }
} }
for pn := range deadProps {
pnames = append(pnames, pn)
}
return pnames, nil return pnames, nil
} }

@ -389,6 +389,9 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *filesyst
fs.Use("AfterValidateFailed", filesystem.HookDeleteTempFile) fs.Use("AfterValidateFailed", filesystem.HookDeleteTempFile)
} }
// rclone 请求
fs.Use("AfterUpload", filesystem.NewWebdavAfterUploadHook(r))
// 执行上传 // 执行上传
err = fs.Upload(ctx, &fileData) err = fs.Upload(ctx, &fileData)
if err != nil { if err != nil {

Loading…
Cancel
Save