From bcea7b74411f2dacc76f00014b3e1be765960a59 Mon Sep 17 00:00:00 2001 From: Abhishek Shroff Date: Thu, 18 Apr 2024 22:24:07 +0530 Subject: [PATCH] Tighten integration between to internal webdav library --- internal/command/command.go | 6 +- internal/command/library.go | 50 ++++---- internal/command/serve.go | 2 +- internal/core/file.go | 77 ++++++++++++ internal/core/filesystem.go | 139 ++++++++++++++++++++ internal/handler_go-webdav/adapter.go | 154 ----------------------- internal/handler_go-webdav/handler.go | 59 --------- internal/handler_webdav/adapter.go | 114 ----------------- internal/handler_webdav/handler.go | 145 ++++++++++++++++----- internal/handler_webdav/resource_info.go | 73 ----------- internal/library/library.go | 105 ---------------- internal/library/library_manager.go | 59 --------- internal/sql/resources.sql.go | 40 ++---- internal/storage/storage_manager.go | 19 +++ internal/webdav/file.go | 24 ++-- internal/webdav/prop.go | 18 +-- internal/webdav/serve_resource.go | 44 +++---- internal/webdav/webdav.go | 16 +-- sql/queries/resources.sql | 2 +- 19 files changed, 435 insertions(+), 711 deletions(-) create mode 100644 internal/core/file.go create mode 100644 internal/core/filesystem.go delete mode 100644 internal/handler_go-webdav/adapter.go delete mode 100644 internal/handler_go-webdav/handler.go delete mode 100644 internal/handler_webdav/adapter.go delete mode 100644 internal/handler_webdav/resource_info.go delete mode 100644 internal/library/library.go delete mode 100644 internal/library/library_manager.go diff --git a/internal/command/command.go b/internal/command/command.go index 4a3d3cb3..79ba4edb 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -5,7 +5,7 @@ import ( "os" "path" - "github.com/shroff/phylum/server/internal/library" + "github.com/shroff/phylum/server/internal/core" "github.com/shroff/phylum/server/internal/sql" "github.com/shroff/phylum/server/internal/storage" "github.com/shroff/phylum/server/internal/user" @@ -15,7 +15,7 @@ import ( ) var debug bool = false -var libraryManager *library.Manager +var fs *core.FileSystem var userManager *user.Manager var storageManager *storage.Manager @@ -68,7 +68,7 @@ func SetupCommand() { if err != nil { logrus.Fatal(err) } - libraryManager, err = library.NewManager(db, storageManager) + fs, err = core.OpenFileSystem(db, storageManager) if err != nil { logrus.Fatal(err) } diff --git a/internal/command/library.go b/internal/command/library.go index 301ca741..059f8a4c 100644 --- a/internal/command/library.go +++ b/internal/command/library.go @@ -1,10 +1,6 @@ package command import ( - "context" - - "github.com/google/uuid" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -26,24 +22,24 @@ func setupLibraryCreateCommand() *cobra.Command { Short: "Create Library", Args: cobra.ExactArgs(3), Run: func(cmd *cobra.Command, args []string) { - id := uuid.New() + // id := uuid.New() - storageName := args[0] - storage := storageManager.Find(storageName) - if storage == nil { - logrus.Fatal("Storage not found: " + storageName) - } + // storageName := args[0] + // storage := storageManager.Find(storageName) + // if storage == nil { + // logrus.Fatal("Storage not found: " + storageName) + // } - username := args[1] - user, err := userManager.FindUser(context.Background(), username) - if err != nil { - logrus.Fatal("User not found: " + username) - } - name := args[2] - if err := libraryManager.Create(context.Background(), id, storageName, user.ID, name); err != nil { - logrus.Fatal(err) - } - logrus.Info("Created " + id.String()) + // username := args[1] + // user, err := userManager.FindUser(context.Background(), username) + // if err != nil { + // logrus.Fatal("User not found: " + username) + // } + // name := args[2] + // if err := libraryManager.Create(context.Background(), id, storageName, user.ID, name); err != nil { + // logrus.Fatal(err) + // } + // logrus.Info("Created " + id.String()) }, } } @@ -54,13 +50,13 @@ func setupLibraryDeleteCommand() *cobra.Command { Short: "Delete Library", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - id, err := uuid.Parse(args[0]) - if err != nil { - logrus.Fatal("Not an ID: " + args[0]) - } - if err := libraryManager.Delete(context.Background(), id); err != nil { - logrus.Fatal(err) - } + // id, err := uuid.Parse(args[0]) + // if err != nil { + // logrus.Fatal("Not an ID: " + args[0]) + // } + // if err := libraryManager.Delete(context.Background(), id); err != nil { + // logrus.Fatal(err) + // } }, } } diff --git a/internal/command/serve.go b/internal/command/serve.go index add039bf..c11cbb4f 100644 --- a/internal/command/serve.go +++ b/internal/command/serve.go @@ -21,7 +21,7 @@ func setupServeCommand() *cobra.Command { config := viper.GetViper() engine := createEngine(config.GetBool("log_body"), config.GetBool("cors_enabled"), config.GetStringSlice("cors_origins")) - webdav.SetupHandler(engine.Group(config.GetString("webdav_prefix")), libraryManager, userManager) + webdav.SetupHandler(engine.Group(config.GetString("webdav_prefix")), fs, userManager) server := endless.NewServer(config.GetString("listen"), engine) server.BeforeBegin = func(addr string) { diff --git a/internal/core/file.go b/internal/core/file.go new file mode 100644 index 00000000..25cb37ac --- /dev/null +++ b/internal/core/file.go @@ -0,0 +1,77 @@ +package core + +import ( + "context" + "fmt" + "io" + "mime" + "path/filepath" + "time" + + "github.com/google/uuid" +) + +type FileInfo interface { + ID() uuid.UUID + Name() string + Size() int64 + ModTime() time.Time + IsDir() bool + ETag() string + ContentType() string +} + +type File interface { + FileInfo + OpenRead(ctx context.Context, start, length int64) (io.ReadCloser, error) + ReadDir(ctx context.Context) ([]FileInfo, error) +} + +type fileInfo struct { + id uuid.UUID + name string + size int64 + collection bool + modTime time.Time + etag string +} + +type file struct { + fileInfo + fs *FileSystem +} + +func (f fileInfo) ID() uuid.UUID { return f.id } + +func (f fileInfo) Name() string { return f.name } + +func (f fileInfo) Size() int64 { return f.size } + +func (f fileInfo) ModTime() time.Time { return f.modTime } + +func (f fileInfo) IsDir() bool { return f.collection } + +func (f fileInfo) ETag() string { + if f.etag != "" { + return f.etag + } + // The Apache http 2.4 web server by default concatenates the + // modification time and size of a file. + return fmt.Sprintf(`"%x%x"`, f.modTime.UnixMilli(), f.size) +} + +func (f fileInfo) ContentType() string { + mimeType := mime.TypeByExtension(filepath.Ext(f.name)) + if mimeType != "" { + return mimeType + } + return "application/octet-stream" +} + +func (f file) OpenRead(ctx context.Context, start, length int64) (io.ReadCloser, error) { + return f.fs.OpenRead(ctx, f.id, start, length) +} + +func (f file) ReadDir(ctx context.Context) ([]FileInfo, error) { + return f.fs.ReadDir(ctx, f.id, false, false) +} diff --git a/internal/core/filesystem.go b/internal/core/filesystem.go new file mode 100644 index 00000000..a5331f4c --- /dev/null +++ b/internal/core/filesystem.go @@ -0,0 +1,139 @@ +package core + +import ( + "context" + "io" + "io/fs" + "strings" + + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" + "github.com/shroff/phylum/server/internal/sql" + "github.com/shroff/phylum/server/internal/storage" + "github.com/sirupsen/logrus" +) + +type FileSystem struct { + db *sql.DbHandler + storageManager *storage.Manager +} + +func OpenFileSystem(db *sql.DbHandler, storageManager *storage.Manager) (*FileSystem, error) { + fs := &FileSystem{db: db, storageManager: storageManager} + if root, err := db.Queries().ResourceById(context.Background(), uuid.UUID{}); err != nil { + logrus.Info("Root directory not found. Creating") + if err := db.Queries().CreateResource(context.Background(), sql.CreateResourceParams{ID: uuid.UUID{}, Name: "root", Dir: true}); err != nil { + logrus.Fatal("Unable to create root directory: " + err.Error()) + } + } else if !root.Dir { + logrus.Fatal("Root is not a directory?!") + } + return fs, nil +} + +func (f *FileSystem) OpenRead(ctx context.Context, id uuid.UUID, start, length int64) (io.ReadCloser, error) { + return f.storageManager.OpenRead(ctx, id, start, length) +} + +func (f *FileSystem) OpenWrite(ctx context.Context, id uuid.UUID) (io.WriteCloser, error) { + return f.storageManager.OpenWrite(id, func(len int, etag string) error { + return f.db.Queries().UpdateResourceContents(ctx, sql.UpdateResourceContentsParams{ + ID: id, + Size: pgtype.Int4{Int32: int32(len), Valid: true}, + Etag: pgtype.Text{String: etag, Valid: true}, + }) + }) +} + +func (f *FileSystem) ReadDir(ctx context.Context, id uuid.UUID, includeRoot bool, recursive bool) ([]FileInfo, error) { + children, err := f.db.Queries().ReadDir(ctx, sql.ReadDirParams{ID: id, IncludeRoot: includeRoot, Recursive: recursive}) + if err != nil { + return nil, err + } + + result := make([]FileInfo, len(children)) + for i, c := range children { + result[i] = fileInfo{ + name: c.Name, + size: int64(c.Size.Int32), + modTime: c.Modified.Time, + collection: c.Dir, + id: c.ID, + etag: c.Etag.String, + } + } + return result, nil +} + +func (f *FileSystem) DeleteRecursive(ctx context.Context, id uuid.UUID) error { + // TODO: versioning + hardDelete := false + + return f.db.RunInTx(ctx, func(q *sql.Queries) error { + p, err := q.ResourceById(ctx, id) + if err != nil { + return err + } + + if hardDelete { + deleted, err := q.HardDeleteRecursive(ctx, id) + if err != nil { + return err + } + errors := f.storageManager.Delete(deleted) + for err := range errors { + logrus.Warn(err) + } + } else { + if err = q.DeleteRecursive(ctx, id); err != nil { + return err + } + } + + if p.Parent != nil { + return q.UpdateResourceModified(ctx, *p.Parent) + } + return nil + }) +} + +func (f *FileSystem) CreateResource(ctx context.Context, id uuid.UUID, parent uuid.UUID, name string, dir bool) error { + return f.db.RunInTx(ctx, func(q *sql.Queries) error { + if err := q.CreateResource(ctx, sql.CreateResourceParams{ID: id, Parent: &parent, Name: name, Dir: dir}); err != nil { + return err + } + return q.UpdateResourceModified(ctx, parent) + }) +} + +func (f *FileSystem) Move(ctx context.Context, id uuid.UUID, parent uuid.UUID, name string) error { + return f.db.Queries().Rename(ctx, sql.RenameParams{ID: id, Parent: parent, Name: name}) +} + +func (f *FileSystem) ResourceByPath(ctx context.Context, path string) (File, error) { + path = strings.Trim(path, "/") + segments := strings.Split(path, "/") + if path == "" { + // Calling strings.Split on an empty string returns a slice of length 1. That breaks the query + segments = []string{} + } + + res, err := f.db.Queries().ResourceByPath(ctx, segments) + if err != nil { + return nil, fs.ErrNotExist + } + + //TODO: Permissions checks + + return file{ + fileInfo: fileInfo{ + id: res.ID, + name: res.Name, + size: int64(res.Size.Int32), + collection: res.Dir, + modTime: res.Modified.Time, + etag: res.Etag.String, + }, + fs: f, + }, nil +} diff --git a/internal/handler_go-webdav/adapter.go b/internal/handler_go-webdav/adapter.go deleted file mode 100644 index 16a55a18..00000000 --- a/internal/handler_go-webdav/adapter.go +++ /dev/null @@ -1,154 +0,0 @@ -package webdav - -import ( - "context" - "fmt" - "io" - "io/fs" - "net/http" - "strings" - - webdav "github.com/emersion/go-webdav" - "github.com/google/uuid" - "github.com/shroff/phylum/server/internal/library" - "github.com/shroff/phylum/server/internal/sql" -) - -type adapter struct { - lib *library.Library - prefix string -} - -func (a adapter) Open(ctx context.Context, name string) (io.ReadCloser, error) { - resource, err := a.resourceByPath(ctx, name) - if err != nil { - return nil, err - } - return a.lib.OpenRead(ctx, resource.ID, 0, -1) -} - -func (a adapter) Stat(ctx context.Context, name string) (*webdav.FileInfo, error) { - resource, err := a.resourceByPath(ctx, name) - if err != nil { - return nil, err - } - val := &webdav.FileInfo{ - Path: string(a.prefix + resource.Path), - Size: int64(resource.Size.Int32), - ModTime: resource.Modified.Time, - IsDir: resource.Dir, - MIMEType: resource.Name, - ETag: resource.Etag.String, - } - return val, nil -} -func (a adapter) ReadDir(ctx context.Context, name string, recursive bool) ([]webdav.FileInfo, error) { - dir, err := a.resourceByPath(ctx, name) - if err != nil { - return nil, err - } - if !dir.Dir { - return nil, fs.ErrInvalid - } - children, err := a.lib.ReadDir(ctx, dir.ID, false, recursive) - if err != nil { - return nil, err - } - - result := make([]webdav.FileInfo, len(children)) - prefix := a.prefix + dir.Path - for i, c := range children { - result[i] = webdav.FileInfo{ - Path: string(prefix + c.Path), - Size: int64(c.Size.Int32), - ModTime: c.Modified.Time, - IsDir: c.Dir, - MIMEType: c.Name, - ETag: c.Etag.String, - } - } - return result, nil -} -func (a adapter) Create(ctx context.Context, name string) (io.WriteCloser, error) { - var id uuid.UUID - if resource, err := a.resourceByPath(ctx, name); err == nil { - id = resource.ID - } else { - name = strings.TrimRight(name, "/") - index := strings.LastIndex(name, "/") - parentPath := name[0:index] - parent, err := a.resourceByPath(ctx, parentPath) - if err != nil { - return nil, fs.ErrNotExist - } - fileName := name[index+1:] - id = uuid.New() - if err = a.lib.CreateResource(ctx, id, parent.ID, fileName, false); err != nil { - return nil, err - } - } - - return a.lib.OpenWrite(ctx, id) -} - -func (a adapter) RemoveAll(ctx context.Context, name string) error { - resource, err := a.resourceByPath(ctx, name) - if err != nil { - return fs.ErrNotExist - } - return a.lib.DeleteRecursive(ctx, resource.ID, false) -} - -func (a adapter) Mkdir(ctx context.Context, name string) error { - if _, err := a.resourceByPath(ctx, name); err == nil { - return fs.ErrExist - } - name = strings.TrimRight(name, "/") - index := strings.LastIndex(name, "/") - parentPath := name[0:index] - parent, err := a.resourceByPath(ctx, parentPath) - if err != nil { - return fs.ErrNotExist - } - dirName := name[index+1:] - err = a.lib.CreateResource(ctx, uuid.New(), parent.ID, dirName, true) - return err -} -func (a adapter) Copy(ctx context.Context, name, dest string, options *webdav.CopyOptions) (created bool, err error) { - // TODO: Implement - return false, webdav.NewHTTPError(http.StatusMethodNotAllowed, fmt.Errorf("not implemented")) -} -func (a adapter) Move(ctx context.Context, name, destName string, options *webdav.MoveOptions) (created bool, err error) { - src, err := a.resourceByPath(ctx, name) - if err != nil { - return false, fs.ErrNotExist - } - - existing, err := a.resourceByPath(ctx, destName) - if err == nil { - if options.NoOverwrite { - return false, fs.ErrExist - } else { - a.lib.DeleteRecursive(ctx, existing.ID, false) - } - } - - destName = strings.TrimRight(destName, "/") - index := strings.LastIndex(destName, "/") - parentPath := destName[0:index] - parent, err := a.resourceByPath(ctx, parentPath) - destName = destName[index+1:] - - if err != nil { - return false, webdav.NewHTTPError(http.StatusConflict, nil) - } - - if err = a.lib.Move(ctx, src.ID, parent.ID, destName); err != nil { - return false, err - } - return true, nil -} - -func (a adapter) resourceByPath(ctx context.Context, name string) (res sql.ResourceByPathRow, err error) { - return a.lib.ResourceByPath(ctx, strings.TrimPrefix(name, a.prefix)) -} diff --git a/internal/handler_go-webdav/handler.go b/internal/handler_go-webdav/handler.go deleted file mode 100644 index 812d53fa..00000000 --- a/internal/handler_go-webdav/handler.go +++ /dev/null @@ -1,59 +0,0 @@ -package webdav - -import ( - "context" - "strings" - - webdav "github.com/emersion/go-webdav" - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/shroff/phylum/server/internal/library" - "github.com/sirupsen/logrus" -) - -type handler struct { - libraryManager library.Manager - prefix string -} - -func NewHandler(libraryManager library.Manager, prefix string) *handler { - logrus.Info("Setting up WebDAV access at " + prefix) - return &handler{ - libraryManager: libraryManager, - prefix: prefix, - } -} - -func (h *handler) HandleRequest(c *gin.Context) { - path := c.Params.ByName("path") - idStr := strings.TrimLeft(path, "/") - if idStr == "" { - // No path specified - c.Writer.WriteHeader(404) - return - } - index := strings.Index(idStr, "/") - if index != -1 { - idStr = idStr[0:index] - } - - id, err := uuid.Parse(idStr) - if err != nil { - c.Writer.WriteHeader(404) - return - } - - library, err := h.libraryManager.Get(context.Background(), id) - if err != nil { - c.Writer.WriteHeader(404) - return - } - - webdavHandler := webdav.Handler{ - FileSystem: adapter{ - lib: library, - prefix: h.prefix + "/" + idStr, - }, - } - webdavHandler.ServeHTTP(c.Writer, c.Request) -} diff --git a/internal/handler_webdav/adapter.go b/internal/handler_webdav/adapter.go deleted file mode 100644 index 660745bd..00000000 --- a/internal/handler_webdav/adapter.go +++ /dev/null @@ -1,114 +0,0 @@ -package webdav - -import ( - "context" - "errors" - "io" - "io/fs" - "strings" - - "github.com/google/uuid" - "github.com/shroff/phylum/server/internal/library" - "github.com/shroff/phylum/server/internal/webdav" -) - -type adapter struct { - lib *library.Library -} - -func (a adapter) Stat(ctx context.Context, name string) (webdav.ResourceInfo, error) { - resource, err := a.lib.ResourceByPath(ctx, name) - if err != nil { - return nil, err - } - val := resourceInfo{ - lib: a.lib, - name: resource.Name, - size: int64(resource.Size.Int32), - modTime: resource.Modified.Time, - collection: resource.Dir, - resourceID: resource.ID, - etag: resource.Etag.String, - } - return val, nil -} - -func (a adapter) OpenWrite(ctx context.Context, name string) (io.WriteCloser, error) { - resource, err := a.lib.ResourceByPath(ctx, name) - resourceId := resource.ID - - if err != nil { - if err == fs.ErrNotExist { - // Try to create the resource if the parent collection exists - name = strings.TrimRight(name, "/") - index := strings.LastIndex(name, "/") - parentPath := name[0:index] - parent, err := a.lib.ResourceByPath(ctx, parentPath) - if err != nil { - return nil, fs.ErrNotExist - } - resourceName := name[index+1:] - resourceId = uuid.New() - err = a.lib.CreateResource(ctx, resourceId, parent.ID, resourceName, false) - if err != nil { - return nil, err - } - } else { - return nil, err - } - } else if resource.Dir { - return nil, errors.New("cannot open collection for write") - } - return a.lib.OpenWrite(ctx, resourceId) -} - -func (a adapter) RemoveAll(ctx context.Context, name string) error { - resource, err := a.lib.ResourceByPath(ctx, name) - if err != nil { - return fs.ErrNotExist - } - return a.lib.DeleteRecursive(ctx, resource.ID, false) -} - -func (a adapter) Mkdir(ctx context.Context, name string) error { - if _, err := a.lib.ResourceByPath(ctx, name); err == nil { - return fs.ErrExist - } - name = strings.TrimRight(name, "/") - index := strings.LastIndex(name, "/") - parentPath := name[0:index] - parent, err := a.lib.ResourceByPath(ctx, parentPath) - if err != nil { - return fs.ErrNotExist - } - dirName := name[index+1:] - err = a.lib.CreateResource(ctx, uuid.New(), parent.ID, dirName, true) - return err -} - -func (a adapter) Rename(ctx context.Context, oldName, newName string) error { - src, err := a.lib.ResourceByPath(ctx, oldName) - if err != nil { - return fs.ErrNotExist - } - - _, err = a.lib.ResourceByPath(ctx, newName) - if err == nil { - return fs.ErrExist - } - - newName = strings.TrimRight(newName, "/") - index := strings.LastIndex(newName, "/") - parentPath := newName[0:index] - parent, err := a.lib.ResourceByPath(ctx, parentPath) - newName = newName[index+1:] - - if err != nil { - return fs.ErrNotExist - } - - if err = a.lib.Move(ctx, src.ID, parent.ID, newName); err != nil { - return err - } - return nil -} diff --git a/internal/handler_webdav/handler.go b/internal/handler_webdav/handler.go index a2ba5ddf..e620596a 100644 --- a/internal/handler_webdav/handler.go +++ b/internal/handler_webdav/handler.go @@ -2,28 +2,31 @@ package webdav import ( "context" + "errors" + "io" + "io/fs" "net/http" "strings" "github.com/gin-gonic/gin" "github.com/google/uuid" + "github.com/shroff/phylum/server/internal/core" "github.com/shroff/phylum/server/internal/cryptutil" - "github.com/shroff/phylum/server/internal/library" "github.com/shroff/phylum/server/internal/user" "github.com/shroff/phylum/server/internal/webdav" "github.com/sirupsen/logrus" ) type handler struct { - libraryManager *library.Manager - prefix string + fs *core.FileSystem + prefix string } -func SetupHandler(r *gin.RouterGroup, libraryManager *library.Manager, userManager *user.Manager) { +func SetupHandler(r *gin.RouterGroup, fs *core.FileSystem, userManager *user.Manager) { logrus.Info("Setting up WebDAV access at " + r.BasePath()) handler := &handler{ - libraryManager: libraryManager, - prefix: r.BasePath(), + fs: fs, + prefix: r.BasePath(), } r.Use(func(c *gin.Context) { username, pass, ok := c.Request.BasicAuth() @@ -66,33 +69,117 @@ func SetupHandler(r *gin.RouterGroup, libraryManager *library.Manager, userManag } func (h *handler) HandleRequest(c *gin.Context) { - path := c.Params.ByName("path") - idStr := strings.TrimLeft(path, "/") - if idStr == "" { - // No path specified - c.Writer.WriteHeader(404) - return - } - index := strings.Index(idStr, "/") - if index != -1 { - idStr = idStr[0:index] - } - id, err := uuid.Parse(idStr) - if err != nil { - c.Writer.WriteHeader(404) - return - } + // path := c.Params.ByName("path") + // idStr := strings.TrimLeft(path, "/") + // if idStr == "" { + // // No path specified + // c.Writer.WriteHeader(404) + // return + // } + // index := strings.Index(idStr, "/") + // if index != -1 { + // idStr = idStr[0:index] + // } + // id, err := uuid.Parse(idStr) + // if err != nil { + // c.Writer.WriteHeader(404) + // return + // } - library, err := h.libraryManager.Get(context.Background(), id) - if err != nil { - c.Writer.WriteHeader(404) - return - } + // library, err := h.libraryManager.Get(context.Background(), id) + // if err != nil { + // c.Writer.WriteHeader(404) + // return + // } webdavHandler := webdav.Handler{ - Prefix: h.prefix + "/" + idStr, - FileSystem: adapter{lib: library}, + Prefix: h.prefix, + FileSystem: h, LockSystem: webdav.NewMemLS(), } webdavHandler.ServeHTTP(c.Writer, c.Request) } + +func (a handler) Stat(ctx context.Context, name string) (core.File, error) { + return a.fs.ResourceByPath(ctx, name) +} + +func (a handler) OpenWrite(ctx context.Context, name string) (io.WriteCloser, error) { + resource, err := a.fs.ResourceByPath(ctx, name) + resourceId := resource.ID() + + if err != nil { + if err == fs.ErrNotExist { + // Try to create the resource if the parent collection exists + name = strings.TrimRight(name, "/") + index := strings.LastIndex(name, "/") + parentPath := name[0:index] + parent, err := a.fs.ResourceByPath(ctx, parentPath) + if err != nil { + return nil, fs.ErrNotExist + } + resourceName := name[index+1:] + resourceId = uuid.New() + err = a.fs.CreateResource(ctx, resourceId, parent.ID(), resourceName, false) + if err != nil { + return nil, err + } + } else { + return nil, err + } + } else if resource.IsDir() { + return nil, errors.New("cannot open collection for write") + } + return a.fs.OpenWrite(ctx, resourceId) +} + +func (a handler) RemoveAll(ctx context.Context, name string) error { + resource, err := a.fs.ResourceByPath(ctx, name) + if err != nil { + return fs.ErrNotExist + } + return a.fs.DeleteRecursive(ctx, resource.ID()) +} + +func (a handler) Mkdir(ctx context.Context, name string) error { + if _, err := a.fs.ResourceByPath(ctx, name); err == nil { + return fs.ErrExist + } + name = strings.TrimRight(name, "/") + index := strings.LastIndex(name, "/") + parentPath := name[0:index] + parent, err := a.fs.ResourceByPath(ctx, parentPath) + if err != nil { + return fs.ErrNotExist + } + dirName := name[index+1:] + err = a.fs.CreateResource(ctx, uuid.New(), parent.ID(), dirName, true) + return err +} + +func (a handler) Rename(ctx context.Context, oldName, newName string) error { + src, err := a.fs.ResourceByPath(ctx, oldName) + if err != nil { + return fs.ErrNotExist + } + + _, err = a.fs.ResourceByPath(ctx, newName) + if err == nil { + return fs.ErrExist + } + + newName = strings.TrimRight(newName, "/") + index := strings.LastIndex(newName, "/") + parentPath := newName[0:index] + parent, err := a.fs.ResourceByPath(ctx, parentPath) + newName = newName[index+1:] + + if err != nil { + return fs.ErrNotExist + } + + if err = a.fs.Move(ctx, src.ID(), parent.ID(), newName); err != nil { + return err + } + return nil +} diff --git a/internal/handler_webdav/resource_info.go b/internal/handler_webdav/resource_info.go deleted file mode 100644 index ad985994..00000000 --- a/internal/handler_webdav/resource_info.go +++ /dev/null @@ -1,73 +0,0 @@ -package webdav - -import ( - "context" - "errors" - "fmt" - "io" - "mime" - "path/filepath" - "time" - - "github.com/google/uuid" - "github.com/shroff/phylum/server/internal/library" - "github.com/shroff/phylum/server/internal/webdav" -) - -type resourceInfo struct { - lib *library.Library - name string - size int64 - collection bool - modTime time.Time - resourceID uuid.UUID - etag string -} - -func (ri resourceInfo) Name() string { return ri.name } -func (ri resourceInfo) Size() int64 { return ri.size } -func (ri resourceInfo) ModTime() time.Time { return ri.modTime } -func (ri resourceInfo) IsDir() bool { return ri.collection } -func (ri resourceInfo) ETag() string { - if ri.etag != "" { - return ri.etag - } - // The Apache http 2.4 web server by default concatenates the - // modification time and size of a file. - return fmt.Sprintf(`"%x%x"`, ri.modTime.UnixMilli(), ri.size) -} -func (ri resourceInfo) ContentType() string { - mimeType := mime.TypeByExtension(filepath.Ext(ri.name)) - if mimeType != "" { - return mimeType - } - return "application/octet-stream" -} -func (ri resourceInfo) OpenRead(ctx context.Context, start, length int64) (io.ReadCloser, error) { - return ri.lib.OpenRead(ctx, ri.resourceID, start, length) -} -func (ri resourceInfo) Readdir(ctx context.Context) ([]webdav.ResourceInfo, error) { - if !ri.collection { - return nil, errors.New("readdir not supported for non-collection resources") - } - - children, err := ri.lib.ReadDir(context.Background(), ri.resourceID, false, false) - if err != nil { - return nil, err - } - - result := make([]webdav.ResourceInfo, len(children)) - for i, c := range children { - result[i] = resourceInfo{ - lib: ri.lib, - name: c.Name, - size: int64(c.Size.Int32), - modTime: c.Modified.Time, - collection: c.Dir, - resourceID: c.ID, - etag: c.Etag.String, - } - } - return result, nil - -} diff --git a/internal/library/library.go b/internal/library/library.go deleted file mode 100644 index f4e1cc26..00000000 --- a/internal/library/library.go +++ /dev/null @@ -1,105 +0,0 @@ -package library - -import ( - "context" - "io" - "io/fs" - "strings" - - "github.com/google/uuid" - "github.com/jackc/pgx/v5/pgtype" - "github.com/shroff/phylum/server/internal/sql" - "github.com/shroff/phylum/server/internal/storage" - "github.com/sirupsen/logrus" -) - -type Library struct { - db *sql.DbHandler - root uuid.UUID - cs storage.Storage -} - -func (l Library) OpenRead(ctx context.Context, id uuid.UUID, start, length int64) (io.ReadCloser, error) { - return l.cs.OpenRead(id, start, length) -} - -func (l Library) OpenWrite(ctx context.Context, id uuid.UUID) (io.WriteCloser, error) { - return l.cs.OpenWrite(id, func(len int, etag string) error { - return l.db.Queries().UpdateResourceContents(ctx, sql.UpdateResourceContentsParams{ - ID: id, - Size: pgtype.Int4{Int32: int32(len), Valid: true}, - Etag: pgtype.Text{String: etag, Valid: true}, - }) - }) -} - -func (l Library) ReadDir(ctx context.Context, id uuid.UUID, includeRoot bool, recursive bool) ([]sql.ReadDirRow, error) { - return l.db.Queries().ReadDir(ctx, sql.ReadDirParams{ID: id, IncludeRoot: includeRoot, Recursive: recursive}) -} - -func (l Library) DeleteRecursive(ctx context.Context, id uuid.UUID, hardDelete bool) error { - return l.db.RunInTx(ctx, func(q *sql.Queries) error { - p, err := q.ResourceById(ctx, id) - if err != nil { - return err - } - - if hardDelete { - - } else { - _, err = q.DeleteRecursive(ctx, id) - if err != nil { - return err - } - } - - if hardDelete { - deleted, err := q.HardDeleteRecursive(ctx, id) - if err != nil { - return err - } - if hardDelete { - errors := l.cs.Delete(deleted) - for err := range errors { - logrus.Warn(err) - } - } - } - - if p.Parent != nil { - return q.UpdateResourceModified(ctx, *p.Parent) - } - return nil - }) -} - -func (l Library) CreateResource(ctx context.Context, id uuid.UUID, parent uuid.UUID, name string, dir bool) error { - return l.db.RunInTx(ctx, func(q *sql.Queries) error { - if err := q.CreateResource(ctx, sql.CreateResourceParams{ID: id, Parent: &parent, Name: name, Dir: dir}); err != nil { - return err - } - return q.UpdateResourceModified(ctx, parent) - }) -} - -func (l Library) Move(ctx context.Context, id uuid.UUID, parent uuid.UUID, name string) error { - return l.db.Queries().Rename(ctx, sql.RenameParams{ID: id, Parent: parent, Name: name}) -} - -func (l Library) ResourceByPath(ctx context.Context, path string) (sql.ResourceByPathRow, error) { - path = strings.Trim(path, "/") - segments := strings.Split(path, "/") - if path == "" { - // Calling strings.Split on an empty string returns a slice of length 1. That breaks the query - segments = []string{} - } - - res, err := l.db.Queries().ResourceByPath(ctx, sql.ResourceByPathParams{Search: segments, Root: l.root}) - if err != nil { - return sql.ResourceByPathRow{}, fs.ErrNotExist - } - - //TODO: Permissions checks - - return res, nil -} diff --git a/internal/library/library_manager.go b/internal/library/library_manager.go deleted file mode 100644 index 9ad37ac2..00000000 --- a/internal/library/library_manager.go +++ /dev/null @@ -1,59 +0,0 @@ -package library - -import ( - "context" - "errors" - "fmt" - - "github.com/google/uuid" - "github.com/shroff/phylum/server/internal/sql" - "github.com/shroff/phylum/server/internal/storage" -) - -type Manager struct { - db *sql.DbHandler - storageManager *storage.Manager -} - -func NewManager(db *sql.DbHandler, storageManager *storage.Manager) (*Manager, error) { - return &Manager{db: db, storageManager: storageManager}, nil -} - -func (b Manager) Get(ctx context.Context, id uuid.UUID) (*Library, error) { - // TODO: Permissions checks - lib, err := b.db.Queries().LibraryById(ctx, id) - if err != nil { - return nil, err - } - cs := b.storageManager.Find(lib.StorageBackend) - if cs == nil { - return nil, errors.New("storage backend not found: " + lib.StorageBackend) - } - - return &Library{db: b.db, root: lib.ID, cs: cs}, nil -} - -func (b Manager) Create(ctx context.Context, id uuid.UUID, storageBackend string, owner int32, displayName string) error { - return b.db.RunInTx(ctx, func(q *sql.Queries) error { - if err := q.CreateLibrary(ctx, sql.CreateLibraryParams{ - ID: id, - StorageBackend: storageBackend, - Owner: owner, - DisplayName: displayName, - }); err != nil { - return err - } - if err := q.CreateResource(ctx, sql.CreateResourceParams{ID: id, Dir: true}); err != nil { - return err - } - return nil - }) -} - -func (b Manager) Delete(ctx context.Context, id uuid.UUID) error { - if lib, err := b.Get(ctx, id); err == nil { - return lib.DeleteRecursive(ctx, lib.root, true) - } else { - return fmt.Errorf("library does not exist: %s", id) - } -} diff --git a/internal/sql/resources.sql.go b/internal/sql/resources.sql.go index 582b0435..1ee4c749 100644 --- a/internal/sql/resources.sql.go +++ b/internal/sql/resources.sql.go @@ -37,7 +37,7 @@ func (q *Queries) CreateResource(ctx context.Context, arg CreateResourceParams) return err } -const deleteRecursive = `-- name: DeleteRecursive :many +const deleteRecursive = `-- name: DeleteRecursive :exec WITH RECURSIVE nodes(id, parent) AS ( SELECT r.id, r.parent FROM resources r WHERE r.id = $1::uuid @@ -49,32 +49,11 @@ WITH RECURSIVE nodes(id, parent) AS ( UPDATE resources SET modified = NOW(), deleted = NOW() WHERE id in (SELECT id FROM nodes) -RETURNING id, dir ` -type DeleteRecursiveRow struct { - ID uuid.UUID - Dir bool -} - -func (q *Queries) DeleteRecursive(ctx context.Context, id uuid.UUID) ([]DeleteRecursiveRow, error) { - rows, err := q.db.Query(ctx, deleteRecursive, id) - if err != nil { - return nil, err - } - defer rows.Close() - var items []DeleteRecursiveRow - for rows.Next() { - var i DeleteRecursiveRow - if err := rows.Scan(&i.ID, &i.Dir); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil +func (q *Queries) DeleteRecursive(ctx context.Context, id uuid.UUID) error { + _, err := q.db.Exec(ctx, deleteRecursive, id) + return err } const hardDeleteRecursive = `-- name: HardDeleteRecursive :many @@ -224,7 +203,7 @@ func (q *Queries) ResourceById(ctx context.Context, id uuid.UUID) (Resource, err const resourceByPath = `-- name: ResourceByPath :one WITH RECURSIVE nodes(id, parent, name, dir, created, modified, size, etag, depth, path, search) AS ( SELECT r.id, r.parent, r.name, r.dir, r.created, r.modified, r.size, r.etag, 0, ''::text, $1::text[] - FROM resources r WHERE r.id = $2::uuid + FROM resources r WHERE r.id = '00000000-0000-0000-0000-000000000000'::uuid UNION ALL SELECT r.id, r.parent, r.name, r.dir, r.created, r.modified, r.size, r.etag, n.depth + 1, concat(n.path, '/', r.name), n.search FROM resources r JOIN nodes n on r.parent = n.id @@ -234,11 +213,6 @@ WITH RECURSIVE nodes(id, parent, name, dir, created, modified, size, etag, depth SELECT id, parent, name, dir, created, modified, size, etag, depth, path, search FROM nodes WHERE cardinality(search) = depth ` -type ResourceByPathParams struct { - Search []string - Root uuid.UUID -} - type ResourceByPathRow struct { ID uuid.UUID Parent *uuid.UUID @@ -253,8 +227,8 @@ type ResourceByPathRow struct { Search []string } -func (q *Queries) ResourceByPath(ctx context.Context, arg ResourceByPathParams) (ResourceByPathRow, error) { - row := q.db.QueryRow(ctx, resourceByPath, arg.Search, arg.Root) +func (q *Queries) ResourceByPath(ctx context.Context, search []string) (ResourceByPathRow, error) { + row := q.db.QueryRow(ctx, resourceByPath, search) var i ResourceByPathRow err := row.Scan( &i.ID, diff --git a/internal/storage/storage_manager.go b/internal/storage/storage_manager.go index 6b8d1dd7..cb9ec344 100644 --- a/internal/storage/storage_manager.go +++ b/internal/storage/storage_manager.go @@ -3,7 +3,9 @@ package storage import ( "context" "errors" + "io" + "github.com/google/uuid" "github.com/shroff/phylum/server/internal/sql" ) @@ -48,6 +50,23 @@ func (m Manager) Create(name string, driver string, params map[string]string) er return nil } +func (m Manager) OpenRead(ctx context.Context, id uuid.UUID, start, length int64) (io.ReadCloser, error) { + return m.findStorage(id).OpenRead(id, start, length) +} + +func (m Manager) OpenWrite(id uuid.UUID, callback func(int, string) error) (io.WriteCloser, error) { + return m.findStorage(id).OpenWrite(id, callback) +} + +func (m Manager) Delete(rows []sql.HardDeleteRecursiveRow) []error { + // TODO: Not working + return nil +} + +func (m Manager) findStorage(id uuid.UUID) Storage { + return nil +} + func (m Manager) Find(name string) Storage { return m.backends[name] } diff --git a/internal/webdav/file.go b/internal/webdav/file.go index 09c9441d..2a42c372 100644 --- a/internal/webdav/file.go +++ b/internal/webdav/file.go @@ -11,31 +11,21 @@ import ( "os" "path" "path/filepath" - "time" + + "github.com/shroff/phylum/server/internal/core" ) // A FileSystem implements access to a collection of named files. The elements // in a file path are separated by slash ('/', U+002F) characters, regardless // of host operating system convention. type FileSystem interface { + Stat(ctx context.Context, name string) (core.File, error) Mkdir(ctx context.Context, name string) error RemoveAll(ctx context.Context, name string) error Rename(ctx context.Context, oldName, newName string) error - Stat(ctx context.Context, name string) (ResourceInfo, error) OpenWrite(ctx context.Context, name string) (io.WriteCloser, error) } -type ResourceInfo interface { - Name() string // base name of the file - Size() int64 // length in bytes for regular files; system-dependent for others - ModTime() time.Time // modification time - IsDir() bool // abbreviation for Mode().IsDir() - ETag() string // entity tag for efficient caching - ContentType() string // content type - OpenRead(ctx context.Context, start, length int64) (io.ReadCloser, error) - Readdir(ctx context.Context) ([]ResourceInfo, error) -} - // moveFiles moves files and/or directories from src to dst. // // See section 9.9.4 for when various HTTP status codes apply. @@ -107,7 +97,7 @@ func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bo return http.StatusForbidden, err } if depth == infiniteDepth { - children, err := srcStat.Readdir(ctx) + children, err := srcStat.ReadDir(ctx) if err != nil { return http.StatusForbidden, err } @@ -156,14 +146,14 @@ func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bo return http.StatusNoContent, nil } -type WalkFunc func(path string, info ResourceInfo, err error) error +type WalkFunc func(path string, info core.FileInfo, err error) error // walkFS traverses filesystem fs starting at name up to depth levels. // // 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 ResourceInfo, walkFn WalkFunc) error { +func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info core.FileInfo, walkFn WalkFunc) error { // This implementation is based on Walk's code in the standard path/filepath package. err := walkFn(name, info, nil) if err != nil { @@ -184,7 +174,7 @@ func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info Res if err != nil { return walkFn(name, info, err) } - fileInfos, err := f.Readdir(ctx) + fileInfos, err := f.ReadDir(ctx) if err != nil { return walkFn(name, info, err) } diff --git a/internal/webdav/prop.go b/internal/webdav/prop.go index 8c3a2f68..11bfbc3e 100644 --- a/internal/webdav/prop.go +++ b/internal/webdav/prop.go @@ -10,6 +10,8 @@ import ( "encoding/xml" "net/http" "strconv" + + "github.com/shroff/phylum/server/internal/core" ) // Proppatch describes a property update instruction as defined in RFC 4918. @@ -68,7 +70,7 @@ func makePropstats(x, y Propstat) []Propstat { var liveProps = map[xml.Name]struct { // findFn implements the propfind function of this property. If nil, // it indicates a hidden property. - findFn func(ResourceInfo) string + findFn func(core.FileInfo) string // dir is true if the property applies to directories. dir bool }{ @@ -263,34 +265,34 @@ func escapeXML(s string) string { return s } -func findResourceType(fi ResourceInfo) string { +func findResourceType(fi core.FileInfo) string { if fi.IsDir() { return `` } return "" } -func findDisplayName(fi ResourceInfo) string { +func findDisplayName(fi core.FileInfo) string { return escapeXML(fi.Name()) } -func findContentLength(fi ResourceInfo) string { +func findContentLength(fi core.FileInfo) string { return strconv.FormatInt(fi.Size(), 10) } -func findLastModified(fi ResourceInfo) string { +func findLastModified(fi core.FileInfo) string { return fi.ModTime().UTC().Format(http.TimeFormat) } -func findContentType(fi ResourceInfo) string { +func findContentType(fi core.FileInfo) string { return fi.ContentType() } -func findETag(fi ResourceInfo) string { +func findETag(fi core.FileInfo) string { return fi.ETag() } -func findSupportedLock(fi ResourceInfo) string { +func findSupportedLock(fi core.FileInfo) string { return `` + `` + `` + diff --git a/internal/webdav/serve_resource.go b/internal/webdav/serve_resource.go index af7fdc92..3f3a1996 100644 --- a/internal/webdav/serve_resource.go +++ b/internal/webdav/serve_resource.go @@ -11,6 +11,8 @@ import ( "strconv" "strings" "time" + + "github.com/shroff/phylum/server/internal/core" ) var htmlReplacer = strings.NewReplacer( @@ -23,18 +25,18 @@ var htmlReplacer = strings.NewReplacer( "'", "'", ) -func serveCollection(w http.ResponseWriter, r *http.Request, ri ResourceInfo) { +func serveCollection(w http.ResponseWriter, r *http.Request, file core.File) { if !strings.HasSuffix(r.URL.Path, "/") { http.Redirect(w, r, r.URL.String()+"/", http.StatusMovedPermanently) return } - if checkIfModifiedSince(r, ri) == condFalse { + if checkIfModifiedSince(r, file) == condFalse { writeNotModified(w) return } - w.Header().Set("Last-Modified", ri.ModTime().Format(http.TimeFormat)) + w.Header().Set("Last-Modified", file.ModTime().Format(http.TimeFormat)) - files, err := ri.Readdir(r.Context()) + files, err := file.ReadDir(r.Context()) if err != nil { http.Error(w, "Error reading directory", http.StatusInternalServerError) return @@ -57,21 +59,21 @@ func serveCollection(w http.ResponseWriter, r *http.Request, ri ResourceInfo) { fmt.Fprintf(w, "\n") } -func serveResource(w http.ResponseWriter, r *http.Request, ri ResourceInfo) { - w.Header().Set("Etag", ri.ETag()) - w.Header().Set("Last-Modified", ri.ModTime().Format(http.TimeFormat)) - w.Header().Set("Content-Type", ri.ContentType()) +func serveResource(w http.ResponseWriter, r *http.Request, file core.File) { + w.Header().Set("Etag", file.ETag()) + w.Header().Set("Last-Modified", file.ModTime().Format(http.TimeFormat)) + w.Header().Set("Content-Type", file.ContentType()) - done, rangeReq := checkPreconditions(w, r, ri) + done, rangeReq := checkPreconditions(w, r, file) if done { return } code := http.StatusOK - sendSize := ri.Size() - ranges, err := parseRange(rangeReq, ri.Size()) + sendSize := file.Size() + ranges, err := parseRange(rangeReq, file.Size()) if err != nil { - w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", ri.Size())) + w.Header().Set("Content-Range", fmt.Sprintf("bytes */%d", file.Size())) http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable) return } @@ -92,10 +94,10 @@ func serveResource(w http.ResponseWriter, r *http.Request, ri ResourceInfo) { ra := ranges[0] sendSize = ra.length code = http.StatusPartialContent - w.Header().Set("Content-Range", ra.contentRange(ri.Size())) - reader, err = ri.OpenRead(r.Context(), ra.start, ra.length) + w.Header().Set("Content-Range", ra.contentRange(file.Size())) + reader, err = file.OpenRead(r.Context(), ra.start, ra.length) } else { - reader, err = ri.OpenRead(r.Context(), 0, -1) + reader, err = file.OpenRead(r.Context(), 0, -1) } if err != nil { @@ -204,7 +206,7 @@ func parseRange(s string, size int64) ([]httpRange, error) { // checkPreconditions evaluates request preconditions and reports whether a precondition // resulted in sending StatusNotModified or StatusPreconditionFailed. -func checkPreconditions(w http.ResponseWriter, r *http.Request, ri ResourceInfo) (done bool, rangeHeader string) { +func checkPreconditions(w http.ResponseWriter, r *http.Request, ri core.FileInfo) (done bool, rangeHeader string) { // This function carefully follows RFC 7232 section 6. ch := checkIfMatch(r, ri) if ch == condNone { @@ -287,7 +289,7 @@ const ( condFalse ) -func checkIfMatch(r *http.Request, ri ResourceInfo) condResult { +func checkIfMatch(r *http.Request, ri core.FileInfo) condResult { im := r.Header.Get("If-Match") if im == "" { return condNone @@ -317,7 +319,7 @@ func checkIfMatch(r *http.Request, ri ResourceInfo) condResult { return condFalse } -func checkIfUnmodifiedSince(r *http.Request, ri ResourceInfo) condResult { +func checkIfUnmodifiedSince(r *http.Request, ri core.FileInfo) condResult { ius := r.Header.Get("If-Unmodified-Since") if ius == "" || isZeroTime(ri.ModTime()) { return condNone @@ -336,7 +338,7 @@ func checkIfUnmodifiedSince(r *http.Request, ri ResourceInfo) condResult { return condFalse } -func checkIfNoneMatch(r *http.Request, ri ResourceInfo) condResult { +func checkIfNoneMatch(r *http.Request, ri core.FileInfo) condResult { inm := r.Header.Get("If-None-Match") if inm == "" { return condNone @@ -366,7 +368,7 @@ func checkIfNoneMatch(r *http.Request, ri ResourceInfo) condResult { return condTrue } -func checkIfModifiedSince(r *http.Request, ri ResourceInfo) condResult { +func checkIfModifiedSince(r *http.Request, ri core.FileInfo) condResult { if r.Method != "GET" && r.Method != "HEAD" { return condNone } @@ -387,7 +389,7 @@ func checkIfModifiedSince(r *http.Request, ri ResourceInfo) condResult { return condTrue } -func checkIfRange(r *http.Request, ri ResourceInfo) condResult { +func checkIfRange(r *http.Request, ri core.FileInfo) condResult { if r.Method != "GET" && r.Method != "HEAD" { return condNone } diff --git a/internal/webdav/webdav.go b/internal/webdav/webdav.go index 6a63f745..eb5991b6 100644 --- a/internal/webdav/webdav.go +++ b/internal/webdav/webdav.go @@ -16,6 +16,8 @@ import ( "path/filepath" "strings" "time" + + "github.com/shroff/phylum/server/internal/core" ) type Handler struct { @@ -199,14 +201,14 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta } // TODO: check locks for read-only access?? ctx := r.Context() - fi, err := h.FileSystem.Stat(ctx, reqPath) + file, err := h.FileSystem.Stat(ctx, reqPath) if err != nil { return http.StatusNotFound, err } - if fi.IsDir() { - serveCollection(w, r, fi) + if file.IsDir() { + serveCollection(w, r, file) } else { - serveResource(w, r, fi) + serveResource(w, r, file) } return 0, nil @@ -522,7 +524,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status mw := multistatusWriter{w: w} - walkFn := func(reqPath string, info ResourceInfo, err error) error { + walkFn := func(reqPath string, info core.FileInfo, err error) error { if err != nil { return handlePropfindError(err, info) } @@ -623,9 +625,9 @@ func makePropstatResponse(href string, pstats []Propstat) *response { return &resp } -func handlePropfindError(err error, info ResourceInfo) error { +func handlePropfindError(err error, info core.FileInfo) error { var skipResp error = nil - if info != nil && info.IsDir() { + if info.IsDir() { skipResp = filepath.SkipDir } diff --git a/sql/queries/resources.sql b/sql/queries/resources.sql index 9157e3f3..037a477a 100644 --- a/sql/queries/resources.sql +++ b/sql/queries/resources.sql @@ -47,7 +47,7 @@ WHERE CASE WHEN @include_root::boolean THEN true ELSE depth > 0 END; -- name: ResourceByPath :one WITH RECURSIVE nodes(id, parent, name, dir, created, modified, size, etag, depth, path, search) AS ( SELECT r.id, r.parent, r.name, r.dir, r.created, r.modified, r.size, r.etag, 0, ''::text, @search::text[] - FROM resources r WHERE r.id = @root::uuid + FROM resources r WHERE r.id = '00000000-0000-0000-0000-000000000000'::uuid UNION ALL SELECT r.id, r.parent, r.name, r.dir, r.created, r.modified, r.size, r.etag, n.depth + 1, concat(n.path, '/', r.name), n.search FROM resources r JOIN nodes n on r.parent = n.id