Feat: adapt major methods of WebDAV for Cloudreve file system
This commit is contained in:
parent
1dac66e632
commit
9fdf2fe7ab
10 changed files with 327 additions and 258 deletions
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/HFO4/cloudreve/pkg/util"
|
||||
"github.com/jinzhu/gorm"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
// File 文件
|
||||
|
@ -166,3 +167,22 @@ func (file *File) UpdatePicInfo(value string) error {
|
|||
func (file *File) UpdateSize(value uint64) error {
|
||||
return DB.Model(&file).Update("size", value).Error
|
||||
}
|
||||
|
||||
/*
|
||||
实现 FileInfo.FileInfo 接口
|
||||
TODO 测试
|
||||
*/
|
||||
|
||||
func (file *File) GetName() string {
|
||||
return file.Name
|
||||
}
|
||||
|
||||
func (file *File) GetSize() uint64 {
|
||||
return file.Size
|
||||
}
|
||||
func (file *File) ModTime() time.Time {
|
||||
return file.UpdatedAt
|
||||
}
|
||||
func (file *File) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/HFO4/cloudreve/pkg/util"
|
||||
"github.com/jinzhu/gorm"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Folder 目录
|
||||
|
@ -260,3 +261,22 @@ func (folder *Folder) Rename(new string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
实现 FileInfo.FileInfo 接口
|
||||
TODO 测试
|
||||
*/
|
||||
|
||||
func (folder *Folder) GetName() string {
|
||||
return folder.Name
|
||||
}
|
||||
|
||||
func (folder *Folder) GetSize() uint64 {
|
||||
return 0
|
||||
}
|
||||
func (folder *Folder) ModTime() time.Time {
|
||||
return folder.UpdatedAt
|
||||
}
|
||||
func (folder *Folder) IsDir() bool {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -13,4 +13,6 @@ const (
|
|||
PathCtx
|
||||
// FileModelCtx 文件数据库模型
|
||||
FileModelCtx
|
||||
// HTTPCtx HTTP请求的上下文
|
||||
HTTPCtx
|
||||
)
|
||||
|
|
|
@ -161,6 +161,9 @@ func GenericAfterUpdate(ctx context.Context, fs *FileSystem) error {
|
|||
if !ok {
|
||||
return ErrObjectNotExist
|
||||
}
|
||||
|
||||
fs.SetTargetFile(&[]model.File{originFile})
|
||||
|
||||
newFile, ok := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
|
||||
if !ok {
|
||||
return ErrObjectNotExist
|
||||
|
@ -205,6 +208,7 @@ func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
|
|||
if err != nil {
|
||||
return ErrInsertFileRecord
|
||||
}
|
||||
fs.SetTargetFile(&[]model.File{*file})
|
||||
|
||||
// 异步尝试生成缩略图
|
||||
go fs.GenerateThumbnail(ctx, file)
|
||||
|
|
|
@ -78,9 +78,15 @@ func (fs *FileSystem) GenerateSavePath(ctx context.Context, file FileHeader) str
|
|||
|
||||
// CancelUpload 监测客户端取消上传
|
||||
func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHeader) {
|
||||
ginCtx := ctx.Value(fsctx.GinCtx).(*gin.Context)
|
||||
var reqContext context.Context
|
||||
if ginCtx, ok := ctx.Value(fsctx.GinCtx).(*gin.Context); ok {
|
||||
reqContext = ginCtx.Request.Context()
|
||||
} else {
|
||||
reqContext = ctx.Value(fsctx.HTTPCtx).(context.Context)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ginCtx.Request.Context().Done():
|
||||
case <-reqContext.Done():
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// 客户端正常关闭,不执行操作
|
||||
|
|
|
@ -6,6 +6,8 @@ package webdav
|
|||
|
||||
import (
|
||||
"context"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/filesystem"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -275,7 +277,13 @@ func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bo
|
|||
// Allowed values for depth are 0, 1 or infiniteDepth. For each visited node,
|
||||
// walkFS calls walkFn. If a visited file system node is a directory and
|
||||
// walkFn returns filepath.SkipDir, walkFS will skip traversal of this node.
|
||||
func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
||||
func walkFS(
|
||||
ctx context.Context,
|
||||
fs *filesystem.FileSystem,
|
||||
depth int,
|
||||
name string,
|
||||
info FileInfo,
|
||||
walkFn func(reqPath string, info FileInfo, err error) error) error {
|
||||
// This implementation is based on Walk's code in the standard path/filepath package.
|
||||
err := walkFn(name, info, nil)
|
||||
if err != nil {
|
||||
|
@ -291,30 +299,25 @@ func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info os.
|
|||
depth = 0
|
||||
}
|
||||
|
||||
// Read directory names.
|
||||
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return walkFn(name, info, err)
|
||||
}
|
||||
fileInfos, err := f.Readdir(0)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return walkFn(name, info, err)
|
||||
}
|
||||
dirs, _ := info.(*model.Folder).GetChildFolder()
|
||||
files, _ := info.(*model.Folder).GetChildFiles()
|
||||
|
||||
for _, fileInfo := range fileInfos {
|
||||
filename := path.Join(name, fileInfo.Name())
|
||||
fileInfo, err := fs.Stat(ctx, filename)
|
||||
for _, fileInfo := range files {
|
||||
filename := path.Join(name, fileInfo.Name)
|
||||
err = walkFS(ctx, fs, depth, filename, &fileInfo, walkFn)
|
||||
if err != nil {
|
||||
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = walkFS(ctx, fs, depth, filename, fileInfo, walkFn)
|
||||
if err != nil {
|
||||
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, fileInfo := range dirs {
|
||||
filename := path.Join(name, fileInfo.Name)
|
||||
err = walkFS(ctx, fs, depth, filename, &fileInfo, walkFn)
|
||||
if err != nil {
|
||||
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,19 @@ import (
|
|||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"github.com/HFO4/cloudreve/pkg/filesystem"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileInfo interface {
|
||||
GetSize() uint64
|
||||
GetName() string
|
||||
ModTime() time.Time
|
||||
IsDir() bool
|
||||
}
|
||||
|
||||
// Proppatch describes a property update instruction as defined in RFC 4918.
|
||||
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH
|
||||
type Proppatch struct {
|
||||
|
@ -103,7 +108,7 @@ type DeadPropsHolder interface {
|
|||
var liveProps = map[xml.Name]struct {
|
||||
// findFn implements the propfind function of this property. If nil,
|
||||
// it indicates a hidden property.
|
||||
findFn func(context.Context, FileSystem, LockSystem, string, os.FileInfo) (string, error)
|
||||
findFn func(context.Context, *filesystem.FileSystem, LockSystem, string, FileInfo) (string, error)
|
||||
// dir is true if the property applies to directories.
|
||||
dir bool
|
||||
}{
|
||||
|
@ -166,25 +171,10 @@ var liveProps = map[xml.Name]struct {
|
|||
//
|
||||
// Each Propstat has a unique status and each property name will only be part
|
||||
// of one Propstat element.
|
||||
func props(ctx context.Context, fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Propstat, error) {
|
||||
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func props(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, fi FileInfo, pnames []xml.Name) ([]Propstat, error) {
|
||||
isDir := fi.IsDir()
|
||||
|
||||
var deadProps map[xml.Name]Property
|
||||
if dph, ok := f.(DeadPropsHolder); ok {
|
||||
deadProps, err = dph.DeadProps()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
pstatOK := Propstat{Status: http.StatusOK}
|
||||
pstatNotFound := Propstat{Status: http.StatusNotFound}
|
||||
|
@ -196,7 +186,7 @@ func props(ctx context.Context, fs FileSystem, ls LockSystem, name string, pname
|
|||
}
|
||||
// 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, fs, ls, name, fi)
|
||||
innerXML, err := prop.findFn(ctx, fs, ls, "", fi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -214,25 +204,10 @@ func props(ctx context.Context, fs FileSystem, ls LockSystem, name string, pname
|
|||
}
|
||||
|
||||
// Propnames returns the property names defined for resource name.
|
||||
func propnames(ctx context.Context, fs FileSystem, ls LockSystem, name string) ([]xml.Name, error) {
|
||||
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func propnames(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, fi FileInfo) ([]xml.Name, error) {
|
||||
isDir := fi.IsDir()
|
||||
|
||||
var deadProps map[xml.Name]Property
|
||||
if dph, ok := f.(DeadPropsHolder); ok {
|
||||
deadProps, err = dph.DeadProps()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
pnames := make([]xml.Name, 0, len(liveProps)+len(deadProps))
|
||||
for pn, prop := range liveProps {
|
||||
|
@ -240,9 +215,6 @@ func propnames(ctx context.Context, fs FileSystem, ls LockSystem, name string) (
|
|||
pnames = append(pnames, pn)
|
||||
}
|
||||
}
|
||||
for pn := range deadProps {
|
||||
pnames = append(pnames, pn)
|
||||
}
|
||||
return pnames, nil
|
||||
}
|
||||
|
||||
|
@ -254,8 +226,8 @@ func propnames(ctx context.Context, fs FileSystem, ls LockSystem, name string) (
|
|||
// returned if they are named in 'include'.
|
||||
//
|
||||
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
|
||||
func allprop(ctx context.Context, fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]Propstat, error) {
|
||||
pnames, err := propnames(ctx, fs, ls, name)
|
||||
func allprop(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, info FileInfo, include []xml.Name) ([]Propstat, error) {
|
||||
pnames, err := propnames(ctx, fs, ls, info)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -269,12 +241,12 @@ func allprop(ctx context.Context, fs FileSystem, ls LockSystem, name string, inc
|
|||
pnames = append(pnames, pn)
|
||||
}
|
||||
}
|
||||
return props(ctx, fs, ls, name, pnames)
|
||||
return props(ctx, fs, ls, info, pnames)
|
||||
}
|
||||
|
||||
// Patch patches the properties of resource name. The return values are
|
||||
// constrained in the same manner as DeadPropsHolder.Patch.
|
||||
func patch(ctx context.Context, fs FileSystem, ls LockSystem, name string, patches []Proppatch) ([]Propstat, error) {
|
||||
func patch(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, patches []Proppatch) ([]Propstat, error) {
|
||||
conflict := false
|
||||
loop:
|
||||
for _, patch := range patches {
|
||||
|
@ -305,26 +277,6 @@ loop:
|
|||
return makePropstats(pstatForbidden, pstatFailedDep), nil
|
||||
}
|
||||
|
||||
f, err := fs.OpenFile(ctx, name, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
if dph, ok := f.(DeadPropsHolder); ok {
|
||||
ret, err := dph.Patch(patches)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat says that
|
||||
// "The contents of the prop XML element must only list the names of
|
||||
// properties to which the result in the status element applies."
|
||||
for _, pstat := range ret {
|
||||
for i, p := range pstat.Props {
|
||||
pstat.Props[i] = Property{XMLName: p.XMLName}
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
// The file doesn't implement the optional DeadPropsHolder interface, so
|
||||
// all patches are forbidden.
|
||||
pstat := Propstat{Status: http.StatusForbidden}
|
||||
|
@ -356,26 +308,26 @@ func escapeXML(s string) string {
|
|||
return s
|
||||
}
|
||||
|
||||
func findResourceType(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
||||
func findResourceType(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
|
||||
if fi.IsDir() {
|
||||
return `<D:collection xmlns:D="DAV:"/>`, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func findDisplayName(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
||||
func findDisplayName(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
|
||||
if slashClean(name) == "/" {
|
||||
// Hide the real name of a possibly prefixed root directory.
|
||||
return "", nil
|
||||
}
|
||||
return escapeXML(fi.Name()), nil
|
||||
return escapeXML(fi.GetName()), nil
|
||||
}
|
||||
|
||||
func findContentLength(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
||||
return strconv.FormatInt(fi.Size(), 10), nil
|
||||
func findContentLength(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
|
||||
return strconv.FormatUint(fi.GetSize(), 10), nil
|
||||
}
|
||||
|
||||
func findLastModified(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
||||
func findLastModified(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
|
||||
return fi.ModTime().UTC().Format(http.TimeFormat), nil
|
||||
}
|
||||
|
||||
|
@ -400,33 +352,34 @@ type ContentTyper interface {
|
|||
ContentType(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
func findContentType(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
||||
if do, ok := fi.(ContentTyper); ok {
|
||||
ctype, err := do.ContentType(ctx)
|
||||
if err != ErrNotImplemented {
|
||||
return ctype, err
|
||||
}
|
||||
}
|
||||
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
// This implementation is based on serveContent's code in the standard net/http package.
|
||||
ctype := mime.TypeByExtension(filepath.Ext(name))
|
||||
if ctype != "" {
|
||||
return ctype, nil
|
||||
}
|
||||
// Read a chunk to decide between utf-8 text and binary.
|
||||
var buf [512]byte
|
||||
n, err := io.ReadFull(f, buf[:])
|
||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
return "", err
|
||||
}
|
||||
ctype = http.DetectContentType(buf[:n])
|
||||
// Rewind file.
|
||||
_, err = f.Seek(0, os.SEEK_SET)
|
||||
return ctype, err
|
||||
func findContentType(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
|
||||
//if do, ok := fi.(ContentTyper); ok {
|
||||
// ctype, err := do.ContentType(ctx)
|
||||
// if err != ErrNotImplemented {
|
||||
// return ctype, err
|
||||
// }
|
||||
//}
|
||||
//f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
||||
//if err != nil {
|
||||
// return "", err
|
||||
//}
|
||||
//defer f.Close()
|
||||
//// This implementation is based on serveContent's code in the standard net/http package.
|
||||
//ctype := mime.TypeByExtension(filepath.Ext(name))
|
||||
//if ctype != "" {
|
||||
// return ctype, nil
|
||||
//}
|
||||
//// Read a chunk to decide between utf-8 text and binary.
|
||||
//var buf [512]byte
|
||||
//n, err := io.ReadFull(f, buf[:])
|
||||
//if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
// return "", err
|
||||
//}
|
||||
//ctype = http.DetectContentType(buf[:n])
|
||||
//// Rewind file.
|
||||
//_, err = f.Seek(0, os.SEEK_SET)
|
||||
//return ctype, err
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// ETager is an optional interface for the os.FileInfo objects
|
||||
|
@ -447,20 +400,11 @@ type ETager interface {
|
|||
ETag(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
func findETag(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
||||
if do, ok := fi.(ETager); ok {
|
||||
etag, err := do.ETag(ctx)
|
||||
if err != ErrNotImplemented {
|
||||
return etag, err
|
||||
}
|
||||
}
|
||||
// The Apache http 2.4 web server by default concatenates the
|
||||
// modification time and size of a file. We replicate the heuristic
|
||||
// with nanosecond granularity.
|
||||
return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size()), nil
|
||||
func findETag(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, reqPath string, fi FileInfo) (string, error) {
|
||||
return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.GetSize()), nil
|
||||
}
|
||||
|
||||
func findSupportedLock(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
||||
func findSupportedLock(ctx context.Context, fs *filesystem.FileSystem, ls LockSystem, name string, fi FileInfo) (string, error) {
|
||||
return `` +
|
||||
`<D:lockentry xmlns:D="DAV:">` +
|
||||
`<D:lockscope><D:exclusive/></D:lockscope>` +
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
package webdav // import "golang.org/x/net/webdav"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/HFO4/cloudreve/pkg/filesystem"
|
||||
"io"
|
||||
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
|
||||
"github.com/HFO4/cloudreve/pkg/filesystem/local"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -25,7 +26,7 @@ type Handler struct {
|
|||
// FileSystem is the virtual file system.
|
||||
FileSystem FileSystem
|
||||
// LockSystem is the lock management system.
|
||||
LockSystem LockSystem
|
||||
LockSystem map[uint]LockSystem
|
||||
// Logger is an optional error logger. If non-nil, it will be called
|
||||
// for all HTTP requests.
|
||||
Logger func(*http.Request, error)
|
||||
|
@ -35,13 +36,25 @@ func (h *Handler) stripPrefix(p string, uid uint) (string, int, error) {
|
|||
if h.Prefix == "" {
|
||||
return p, http.StatusOK, nil
|
||||
}
|
||||
prefix := h.Prefix + strconv.FormatUint(uint64(uid),10)
|
||||
prefix := h.Prefix + strconv.FormatUint(uint64(uid), 10)
|
||||
if r := strings.TrimPrefix(p, prefix); len(r) < len(p) {
|
||||
return r, http.StatusOK, nil
|
||||
}
|
||||
return p, http.StatusNotFound, errPrefixMismatch
|
||||
}
|
||||
|
||||
// isPathExist 路径是否存在
|
||||
func isPathExist(ctx context.Context, fs *filesystem.FileSystem, path string) (bool, FileInfo) {
|
||||
// 尝试目录
|
||||
if ok, folder := fs.IsPathExist(path); ok {
|
||||
return ok, folder
|
||||
}
|
||||
if ok, file := fs.IsFileExist(path); ok {
|
||||
return ok, file
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) {
|
||||
status, err := http.StatusBadRequest, errUnsupportedMethod
|
||||
if h.FileSystem == nil {
|
||||
|
@ -49,27 +62,31 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, fs *filesyst
|
|||
} else if h.LockSystem == nil {
|
||||
status, err = http.StatusInternalServerError, errNoLockSystem
|
||||
} else {
|
||||
// 检查并新建LockSystem
|
||||
if _, ok := h.LockSystem[fs.User.ID]; !ok {
|
||||
h.LockSystem[fs.User.ID] = NewMemLS()
|
||||
}
|
||||
switch r.Method {
|
||||
case "OPTIONS":
|
||||
status, err = h.handleOptions(w, r, fs)
|
||||
case "GET", "HEAD", "POST":
|
||||
status, err = h.handleGetHeadPost(w, r,fs)
|
||||
status, err = h.handleGetHeadPost(w, r, fs)
|
||||
case "DELETE":
|
||||
status, err = h.handleDelete(w, r,fs)
|
||||
status, err = h.handleDelete(w, r, fs)
|
||||
case "PUT":
|
||||
status, err = h.handlePut(w, r,fs)
|
||||
status, err = h.handlePut(w, r, fs)
|
||||
case "MKCOL":
|
||||
status, err = h.handleMkcol(w, r,fs)
|
||||
status, err = h.handleMkcol(w, r, fs)
|
||||
case "COPY", "MOVE":
|
||||
status, err = h.handleCopyMove(w, r,fs)
|
||||
status, err = h.handleCopyMove(w, r, fs)
|
||||
case "LOCK":
|
||||
status, err = h.handleLock(w, r,fs)
|
||||
status, err = h.handleLock(w, r, fs)
|
||||
case "UNLOCK":
|
||||
status, err = h.handleUnlock(w, r,fs)
|
||||
status, err = h.handleUnlock(w, r, fs)
|
||||
case "PROPFIND":
|
||||
status, err = h.handlePropfind(w, r,fs)
|
||||
status, err = h.handlePropfind(w, r, fs)
|
||||
case "PROPPATCH":
|
||||
status, err = h.handleProppatch(w, r,fs)
|
||||
status, err = h.handleProppatch(w, r, fs)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,8 +101,9 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, fs *filesyst
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) lock(now time.Time, root string) (token string, status int, err error) {
|
||||
token, err = h.LockSystem.Create(now, LockDetails{
|
||||
// OK
|
||||
func (h *Handler) lock(now time.Time, root string, fs *filesystem.FileSystem) (token string, status int, err error) {
|
||||
token, err = h.LockSystem[fs.User.ID].Create(now, LockDetails{
|
||||
Root: root,
|
||||
Duration: infiniteTimeout,
|
||||
ZeroDepth: true,
|
||||
|
@ -99,6 +117,7 @@ func (h *Handler) lock(now time.Time, root string) (token string, status int, er
|
|||
return token, 0, nil
|
||||
}
|
||||
|
||||
// ok
|
||||
func (h *Handler) confirmLocks(r *http.Request, src, dst string, fs *filesystem.FileSystem) (release func(), status int, err error) {
|
||||
hdr := r.Header.Get("If")
|
||||
if hdr == "" {
|
||||
|
@ -109,16 +128,16 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string, fs *filesystem.
|
|||
// locks are unlocked at the end of the HTTP request.
|
||||
now, srcToken, dstToken := time.Now(), "", ""
|
||||
if src != "" {
|
||||
srcToken, status, err = h.lock(now, src)
|
||||
srcToken, status, err = h.lock(now, src, fs)
|
||||
if err != nil {
|
||||
return nil, status, err
|
||||
}
|
||||
}
|
||||
if dst != "" {
|
||||
dstToken, status, err = h.lock(now, dst)
|
||||
dstToken, status, err = h.lock(now, dst, fs)
|
||||
if err != nil {
|
||||
if srcToken != "" {
|
||||
h.LockSystem.Unlock(now, srcToken)
|
||||
h.LockSystem[fs.User.ID].Unlock(now, srcToken)
|
||||
}
|
||||
return nil, status, err
|
||||
}
|
||||
|
@ -126,10 +145,10 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string, fs *filesystem.
|
|||
|
||||
return func() {
|
||||
if dstToken != "" {
|
||||
h.LockSystem.Unlock(now, dstToken)
|
||||
h.LockSystem[fs.User.ID].Unlock(now, dstToken)
|
||||
}
|
||||
if srcToken != "" {
|
||||
h.LockSystem.Unlock(now, srcToken)
|
||||
h.LockSystem[fs.User.ID].Unlock(now, srcToken)
|
||||
}
|
||||
}, 0, nil
|
||||
}
|
||||
|
@ -151,12 +170,17 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string, fs *filesystem.
|
|||
if u.Host != r.Host {
|
||||
continue
|
||||
}
|
||||
lsrc, status, err = h.stripPrefix(u.Path,fs.User.ID)
|
||||
lsrc, status, err = h.stripPrefix(u.Path, fs.User.ID)
|
||||
if err != nil {
|
||||
return nil, status, err
|
||||
}
|
||||
}
|
||||
release, err = h.LockSystem.Confirm(time.Now(), lsrc, dst, l.conditions...)
|
||||
release, err = h.LockSystem[fs.User.ID].Confirm(
|
||||
time.Now(),
|
||||
lsrc,
|
||||
dst,
|
||||
l.conditions...,
|
||||
)
|
||||
if err == ErrConfirmationFailed {
|
||||
continue
|
||||
}
|
||||
|
@ -172,8 +196,9 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string, fs *filesystem.
|
|||
return nil, http.StatusPreconditionFailed, ErrLocked
|
||||
}
|
||||
|
||||
//OK
|
||||
func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
|
@ -194,41 +219,41 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request, fs *file
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
// OK
|
||||
func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
// TODO: check locks for read-only access??
|
||||
|
||||
ctx := r.Context()
|
||||
f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDONLY, 0)
|
||||
rs, err := fs.GetContent(ctx, reqPath)
|
||||
if err != nil {
|
||||
return http.StatusNotFound, err
|
||||
if err == filesystem.ErrObjectNotExist {
|
||||
return http.StatusNotFound, err
|
||||
}
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
defer f.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return http.StatusNotFound, err
|
||||
}
|
||||
if fi.IsDir() {
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
|
||||
defer rs.Close()
|
||||
|
||||
etag, err := findETag(ctx, fs, h.LockSystem[fs.User.ID], reqPath, &fs.FileTarget[0])
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
w.Header().Set("ETag", etag)
|
||||
// Let ServeContent determine the Content-Type header.
|
||||
http.ServeContent(w, r, reqPath, fi.ModTime(), f)
|
||||
|
||||
// 获取文件内容
|
||||
http.ServeContent(w, r, reqPath, fs.FileTarget[0].UpdatedAt, rs)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// OK
|
||||
func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
release, status, err := h.confirmLocks(r, reqPath, "",fs)
|
||||
release, status, err := h.confirmLocks(r, reqPath, "", fs)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
|
@ -236,55 +261,89 @@ func (h *Handler) handleDelete(w http.ResponseWriter, r *http.Request, fs *files
|
|||
|
||||
ctx := r.Context()
|
||||
|
||||
// TODO: return MultiStatus where appropriate.
|
||||
|
||||
// "godoc os RemoveAll" says that "If the path does not exist, RemoveAll
|
||||
// returns nil (no error)." WebDAV semantics are that it should return a
|
||||
// "404 Not Found". We therefore have to Stat before we RemoveAll.
|
||||
if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return http.StatusNotFound, err
|
||||
// 尝试作为文件删除
|
||||
if ok, file := fs.IsFileExist(reqPath); ok {
|
||||
if err := fs.Delete(ctx, []uint{}, []uint{file.ID}); err != nil {
|
||||
return http.StatusMethodNotAllowed, err
|
||||
}
|
||||
return http.StatusMethodNotAllowed, err
|
||||
return http.StatusNoContent, nil
|
||||
}
|
||||
if err := h.FileSystem.RemoveAll(ctx, reqPath); err != nil {
|
||||
return http.StatusMethodNotAllowed, err
|
||||
|
||||
// 尝试作为目录删除
|
||||
if ok, folder := fs.IsPathExist(reqPath); ok {
|
||||
if err := fs.Delete(ctx, []uint{folder.ID}, []uint{}); err != nil {
|
||||
return http.StatusMethodNotAllowed, err
|
||||
}
|
||||
return http.StatusNoContent, nil
|
||||
}
|
||||
return http.StatusNoContent, nil
|
||||
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
// OK
|
||||
func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
release, status, err := h.confirmLocks(r, reqPath, "",fs)
|
||||
release, status, err := h.confirmLocks(r, reqPath, "", fs)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
defer release()
|
||||
// TODO(rost): Support the If-Match, If-None-Match headers? See bradfitz'
|
||||
// comments in http.checkEtag.
|
||||
ctx := r.Context()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
ctx = context.WithValue(ctx, fsctx.HTTPCtx, r.Context())
|
||||
|
||||
f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
fileSize, err := strconv.ParseUint(r.Header.Get("Content-Length"), 10, 64)
|
||||
if err != nil {
|
||||
return http.StatusNotFound, err
|
||||
return http.StatusMethodNotAllowed, err
|
||||
}
|
||||
_, copyErr := io.Copy(f, r.Body)
|
||||
fi, statErr := f.Stat()
|
||||
closeErr := f.Close()
|
||||
// TODO(rost): Returning 405 Method Not Allowed might not be appropriate.
|
||||
if copyErr != nil {
|
||||
return http.StatusMethodNotAllowed, copyErr
|
||||
fileName := path.Base(reqPath)
|
||||
filePath := path.Dir(reqPath)
|
||||
fileData := local.FileStream{
|
||||
MIMEType: r.Header.Get("Content-Type"),
|
||||
File: r.Body,
|
||||
Size: fileSize,
|
||||
Name: fileName,
|
||||
VirtualPath: filePath,
|
||||
}
|
||||
if statErr != nil {
|
||||
return http.StatusMethodNotAllowed, statErr
|
||||
|
||||
// 判断文件是否已存在
|
||||
exist, originFile := fs.IsFileExist(reqPath)
|
||||
if exist {
|
||||
// 已存在,为更新操作
|
||||
fs.Use("BeforeUpload", filesystem.HookValidateFile)
|
||||
fs.Use("BeforeUpload", filesystem.HookResetPolicy)
|
||||
fs.Use("BeforeUpload", filesystem.HookChangeCapacity)
|
||||
fs.Use("AfterUploadCanceled", filesystem.HookCleanFileContent)
|
||||
fs.Use("AfterUploadCanceled", filesystem.HookClearFileSize)
|
||||
fs.Use("AfterUploadCanceled", filesystem.HookGiveBackCapacity)
|
||||
fs.Use("AfterUpload", filesystem.GenericAfterUpdate)
|
||||
fs.Use("AfterValidateFailed", filesystem.HookCleanFileContent)
|
||||
fs.Use("AfterValidateFailed", filesystem.HookClearFileSize)
|
||||
fs.Use("AfterValidateFailed", filesystem.HookGiveBackCapacity)
|
||||
ctx = context.WithValue(ctx, fsctx.FileModelCtx, *originFile)
|
||||
} else {
|
||||
// 给文件系统分配钩子
|
||||
fs.Use("BeforeUpload", filesystem.HookValidateFile)
|
||||
fs.Use("BeforeUpload", filesystem.HookValidateCapacity)
|
||||
fs.Use("AfterUploadCanceled", filesystem.HookDeleteTempFile)
|
||||
fs.Use("AfterUploadCanceled", filesystem.HookGiveBackCapacity)
|
||||
fs.Use("AfterUpload", filesystem.GenericAfterUpload)
|
||||
fs.Use("AfterValidateFailed", filesystem.HookDeleteTempFile)
|
||||
fs.Use("AfterValidateFailed", filesystem.HookGiveBackCapacity)
|
||||
}
|
||||
if closeErr != nil {
|
||||
return http.StatusMethodNotAllowed, closeErr
|
||||
|
||||
// 执行上传
|
||||
err = fs.Upload(ctx, fileData)
|
||||
if err != nil {
|
||||
return http.StatusMethodNotAllowed, err
|
||||
}
|
||||
etag, err := findETag(ctx, h.FileSystem, h.LockSystem, reqPath, fi)
|
||||
|
||||
etag, err := findETag(ctx, fs, h.LockSystem[fs.User.ID], reqPath, &fs.FileTarget[0])
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
@ -292,6 +351,7 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *filesyst
|
|||
return http.StatusCreated, nil
|
||||
}
|
||||
|
||||
// OK
|
||||
func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
|
||||
if err != nil {
|
||||
|
@ -308,11 +368,8 @@ func (h *Handler) handleMkcol(w http.ResponseWriter, r *http.Request, fs *filesy
|
|||
if r.ContentLength > 0 {
|
||||
return http.StatusUnsupportedMediaType, nil
|
||||
}
|
||||
if err := h.FileSystem.Mkdir(ctx, reqPath, 0777); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return http.StatusConflict, err
|
||||
}
|
||||
return http.StatusMethodNotAllowed, err
|
||||
if err := fs.CreateDirectory(ctx, reqPath); err != nil {
|
||||
return http.StatusConflict, err
|
||||
}
|
||||
return http.StatusCreated, nil
|
||||
}
|
||||
|
@ -330,7 +387,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *fil
|
|||
return http.StatusBadGateway, errInvalidDestination
|
||||
}
|
||||
|
||||
src, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
|
||||
src, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
|
@ -375,7 +432,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *fil
|
|||
return copyFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") != "F", depth, 0)
|
||||
}
|
||||
|
||||
release, status, err := h.confirmLocks(r, src, dst,fs)
|
||||
release, status, err := h.confirmLocks(r, src, dst, fs)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
|
@ -392,6 +449,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request, fs *fil
|
|||
return moveFiles(ctx, h.FileSystem, src, dst, r.Header.Get("Overwrite") == "T")
|
||||
}
|
||||
|
||||
// OK
|
||||
func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (retStatus int, retErr error) {
|
||||
duration, err := parseTimeout(r.Header.Get("Timeout"))
|
||||
if err != nil {
|
||||
|
@ -402,7 +460,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *filesys
|
|||
return status, err
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
//ctx := r.Context()
|
||||
token, ld, now, created := "", LockDetails{}, time.Now(), false
|
||||
if li == (lockInfo{}) {
|
||||
// An empty lockInfo means to refresh the lock.
|
||||
|
@ -416,7 +474,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *filesys
|
|||
if token == "" {
|
||||
return http.StatusBadRequest, errInvalidLockToken
|
||||
}
|
||||
ld, err = h.LockSystem.Refresh(now, token, duration)
|
||||
ld, err = h.LockSystem[fs.User.ID].Refresh(now, token, duration)
|
||||
if err != nil {
|
||||
if err == ErrNoSuchLock {
|
||||
return http.StatusPreconditionFailed, err
|
||||
|
@ -436,7 +494,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *filesys
|
|||
return http.StatusBadRequest, errInvalidDepth
|
||||
}
|
||||
}
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
|
@ -446,7 +504,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *filesys
|
|||
OwnerXML: li.Owner.InnerXML,
|
||||
ZeroDepth: depth == 0,
|
||||
}
|
||||
token, err = h.LockSystem.Create(now, ld)
|
||||
token, err = h.LockSystem[fs.User.ID].Create(now, ld)
|
||||
if err != nil {
|
||||
if err == ErrLocked {
|
||||
return StatusLocked, err
|
||||
|
@ -455,20 +513,20 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *filesys
|
|||
}
|
||||
defer func() {
|
||||
if retErr != nil {
|
||||
h.LockSystem.Unlock(now, token)
|
||||
h.LockSystem[fs.User.ID].Unlock(now, token)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create the resource if it didn't previously exist.
|
||||
if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
|
||||
f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
// TODO: detect missing intermediate dirs and return http.StatusConflict?
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
f.Close()
|
||||
created = true
|
||||
}
|
||||
//if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
|
||||
// f, err := h.FileSystem.OpenFile(ctx, reqPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
// if err != nil {
|
||||
// // TODO: detect missing intermediate dirs and return http.StatusConflict?
|
||||
// return http.StatusInternalServerError, err
|
||||
// }
|
||||
// f.Close()
|
||||
// created = true
|
||||
//}
|
||||
|
||||
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
|
||||
// Lock-Token value is a Coded-URL. We add angle brackets.
|
||||
|
@ -486,6 +544,7 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request, fs *filesys
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
// OK
|
||||
func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
|
||||
// http://www.webdav.org/specs/rfc4918.html#HEADER_Lock-Token says that the
|
||||
// Lock-Token value is a Coded-URL. We strip its angle brackets.
|
||||
|
@ -495,7 +554,7 @@ func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request, fs *files
|
|||
}
|
||||
t = t[1 : len(t)-1]
|
||||
|
||||
switch err = h.LockSystem.Unlock(time.Now(), t); err {
|
||||
switch err = h.LockSystem[fs.User.ID].Unlock(time.Now(), t); err {
|
||||
case nil:
|
||||
return http.StatusNoContent, err
|
||||
case ErrForbidden:
|
||||
|
@ -509,19 +568,21 @@ func (h *Handler) handleUnlock(w http.ResponseWriter, r *http.Request, fs *files
|
|||
}
|
||||
}
|
||||
|
||||
// OK
|
||||
func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
|
||||
if reqPath == "" {
|
||||
reqPath = "/"
|
||||
}
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
ctx := r.Context()
|
||||
fi, err := h.FileSystem.Stat(ctx, reqPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return http.StatusNotFound, err
|
||||
}
|
||||
return http.StatusMethodNotAllowed, err
|
||||
ok, fi := isPathExist(ctx, fs, reqPath)
|
||||
if !ok {
|
||||
return http.StatusNotFound, err
|
||||
}
|
||||
|
||||
depth := infiniteDepth
|
||||
if hdr := r.Header.Get("Depth"); hdr != "" {
|
||||
depth = parseDepth(hdr)
|
||||
|
@ -536,13 +597,13 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *fil
|
|||
|
||||
mw := multistatusWriter{w: w}
|
||||
|
||||
walkFn := func(reqPath string, info os.FileInfo, err error) error {
|
||||
walkFn := func(reqPath string, info FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var pstats []Propstat
|
||||
if pf.Propname != nil {
|
||||
pnames, err := propnames(ctx, h.FileSystem, h.LockSystem, reqPath)
|
||||
pnames, err := propnames(ctx, fs, h.LockSystem[fs.User.ID], info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -552,21 +613,21 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *fil
|
|||
}
|
||||
pstats = append(pstats, pstat)
|
||||
} else if pf.Allprop != nil {
|
||||
pstats, err = allprop(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
|
||||
pstats, err = allprop(ctx, fs, h.LockSystem[fs.User.ID], info, pf.Prop)
|
||||
} else {
|
||||
pstats, err = props(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
|
||||
pstats, err = props(ctx, fs, h.LockSystem[fs.User.ID], info, pf.Prop)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
href := path.Join(h.Prefix, reqPath)
|
||||
href := path.Join(h.Prefix, strconv.FormatUint(uint64(fs.User.ID), 10), reqPath)
|
||||
if href != "/" && info.IsDir() {
|
||||
href += "/"
|
||||
}
|
||||
return mw.write(makePropstatResponse(href, pstats))
|
||||
}
|
||||
|
||||
walkErr := walkFS(ctx, h.FileSystem, depth, reqPath, fi, walkFn)
|
||||
walkErr := walkFS(ctx, fs, depth, reqPath, fi, walkFn)
|
||||
closeErr := mw.close()
|
||||
if walkErr != nil {
|
||||
return http.StatusInternalServerError, walkErr
|
||||
|
@ -578,11 +639,11 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request, fs *fil
|
|||
}
|
||||
|
||||
func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request, fs *filesystem.FileSystem) (status int, err error) {
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path,fs.User.ID)
|
||||
reqPath, status, err := h.stripPrefix(r.URL.Path, fs.User.ID)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
release, status, err := h.confirmLocks(r, reqPath, "",fs)
|
||||
release, status, err := h.confirmLocks(r, reqPath, "", fs)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
|
@ -590,17 +651,14 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request, fs *fi
|
|||
|
||||
ctx := r.Context()
|
||||
|
||||
if _, err := h.FileSystem.Stat(ctx, reqPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return http.StatusNotFound, err
|
||||
}
|
||||
return http.StatusMethodNotAllowed, err
|
||||
if exist, _ := isPathExist(ctx, fs, reqPath); !exist {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
patches, status, err := readProppatch(r.Body)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
pstats, err := patch(ctx, h.FileSystem, h.LockSystem, reqPath, patches)
|
||||
pstats, err := patch(ctx, fs, h.LockSystem[fs.User.ID], reqPath, patches)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
|
|
@ -10,22 +10,22 @@ import (
|
|||
|
||||
var handler *webdav.Handler
|
||||
|
||||
func init(){
|
||||
handler = &webdav.Handler{
|
||||
func init() {
|
||||
handler = &webdav.Handler{
|
||||
Prefix: "/dav/",
|
||||
FileSystem: webdav.AdapterFS(""),
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
LockSystem: make(map[uint]webdav.LockSystem),
|
||||
}
|
||||
}
|
||||
|
||||
func ServeWebDAV(c *gin.Context){
|
||||
func ServeWebDAV(c *gin.Context) {
|
||||
// 测试用user
|
||||
user,_ := model.GetUserByID(1)
|
||||
c.Set("user",&user)
|
||||
fs,err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil{
|
||||
util.Log().Panic("%s",err)
|
||||
user, _ := model.GetUserByID(1)
|
||||
c.Set("user", &user)
|
||||
fs, err := filesystem.NewFileSystemFromContext(c)
|
||||
if err != nil {
|
||||
util.Log().Panic("%s", err)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(c.Writer,c.Request,fs)
|
||||
handler.ServeHTTP(c.Writer, c.Request, fs)
|
||||
}
|
||||
|
|
|
@ -9,10 +9,22 @@ import (
|
|||
)
|
||||
|
||||
// initWebDAV 初始化WebDAV相关路由
|
||||
func initWebDAV(group *gin.RouterGroup){
|
||||
dav := group.Group(":uid")
|
||||
func initWebDAV(group *gin.RouterGroup) {
|
||||
{
|
||||
dav.Any("*path", controllers.ServeWebDAV)
|
||||
group.Any("", func(context *gin.Context) {
|
||||
context.Status(403)
|
||||
context.Abort()
|
||||
})
|
||||
|
||||
group.Any(":uid/*path", controllers.ServeWebDAV)
|
||||
group.Any(":uid", controllers.ServeWebDAV)
|
||||
group.Handle("PROPFIND", ":uid/*path", controllers.ServeWebDAV)
|
||||
group.Handle("PROPFIND", ":uid", controllers.ServeWebDAV)
|
||||
group.Handle("MKCOL", ":uid/*path", controllers.ServeWebDAV)
|
||||
group.Handle("LOCK", ":uid/*path", controllers.ServeWebDAV)
|
||||
group.Handle("UNLOCK", ":uid/*path", controllers.ServeWebDAV)
|
||||
group.Handle("PROPPATCH", ":uid/*path", controllers.ServeWebDAV)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,6 +164,6 @@ func InitRouter() *gin.Engine {
|
|||
}
|
||||
|
||||
// 初始化WebDAV相关路由
|
||||
initWebDAV(v3.Group("dav"))
|
||||
initWebDAV(r.Group("dav"))
|
||||
return r
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue