diff --git a/models/file.go b/models/file.go
index 7487325..a72bb53 100644
--- a/models/file.go
+++ b/models/file.go
@@ -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
+}
diff --git a/models/folder.go b/models/folder.go
index 2692f40..18aaf01 100644
--- a/models/folder.go
+++ b/models/folder.go
@@ -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
+}
diff --git a/pkg/filesystem/fsctx/context.go b/pkg/filesystem/fsctx/context.go
index d06235f..6718bd3 100644
--- a/pkg/filesystem/fsctx/context.go
+++ b/pkg/filesystem/fsctx/context.go
@@ -13,4 +13,6 @@ const (
PathCtx
// FileModelCtx 文件数据库模型
FileModelCtx
+ // HTTPCtx HTTP请求的上下文
+ HTTPCtx
)
diff --git a/pkg/filesystem/hooks.go b/pkg/filesystem/hooks.go
index c5c8e86..16c607b 100644
--- a/pkg/filesystem/hooks.go
+++ b/pkg/filesystem/hooks.go
@@ -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)
diff --git a/pkg/filesystem/upload.go b/pkg/filesystem/upload.go
index 39ac3c2..05793db 100644
--- a/pkg/filesystem/upload.go
+++ b/pkg/filesystem/upload.go
@@ -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():
// 客户端正常关闭,不执行操作
diff --git a/pkg/webdav/file.go b/pkg/webdav/file.go
index 3e4a018..1a74bf7 100644
--- a/pkg/webdav/file.go
+++ b/pkg/webdav/file.go
@@ -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
}
}
}
diff --git a/pkg/webdav/prop.go b/pkg/webdav/prop.go
index 2c48bc4..f73dc40 100644
--- a/pkg/webdav/prop.go
+++ b/pkg/webdav/prop.go
@@ -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 ``, 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 `` +
`` +
`` +
diff --git a/pkg/webdav/webdav.go b/pkg/webdav/webdav.go
index 92eb055..6984d61 100644
--- a/pkg/webdav/webdav.go
+++ b/pkg/webdav/webdav.go
@@ -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
}
diff --git a/routers/controllers/webdav.go b/routers/controllers/webdav.go
index e3cf0a2..cc8068a 100644
--- a/routers/controllers/webdav.go
+++ b/routers/controllers/webdav.go
@@ -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)
}
diff --git a/routers/router.go b/routers/router.go
index 7e769b7..7f83b54 100644
--- a/routers/router.go
+++ b/routers/router.go
@@ -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
}