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 }