mirror of https://github.com/cloudreve/Cloudreve
webdav兼容rclone的nextcloud选项(修改日期和checksum)
parent
c8b736bd8f
commit
1b4eff624d
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -74,22 +74,26 @@ func copyFiles(ctx context.Context, fs *filesystem.FileSystem, src FileInfo, dst
|
||||||
}
|
}
|
||||||
recursion++
|
recursion++
|
||||||
|
|
||||||
|
var (
|
||||||
|
fileIDs []uint
|
||||||
|
folderIDs []uint
|
||||||
|
)
|
||||||
if src.IsDir() {
|
if src.IsDir() {
|
||||||
err := fs.Copy(
|
folderIDs = []uint{src.(*model.Folder).ID}
|
||||||
|
} else {
|
||||||
|
fileIDs = []uint{src.(*model.File).ID}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fs.Copy(
|
||||||
ctx,
|
ctx,
|
||||||
[]uint{src.(*model.Folder).ID},
|
folderIDs,
|
||||||
[]uint{}, src.(*model.Folder).Position,
|
fileIDs,
|
||||||
|
src.GetPosition(),
|
||||||
path.Dir(dst),
|
path.Dir(dst),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
err := fs.Copy(ctx, []uint{}, []uint{src.(*model.File).ID}, src.(*model.File).Position, path.Dir(dst))
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.StatusNoContent, nil
|
return http.StatusNoContent, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -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…
Reference in New Issue