diff --git a/server/internal/api/routes/resources.go b/server/internal/api/routes/resources.go
index 9f707e32..447386d9 100644
--- a/server/internal/api/routes/resources.go
+++ b/server/internal/api/routes/resources.go
@@ -19,7 +19,7 @@ const errCodeResourceNotCollection = "resource_not_collection"
type resourceResponse struct {
ID uuid.UUID `json:"id"`
- Parent *uuid.UUID `json:"parent"`
+ Parent uuid.UUID `json:"parent"`
Name string `json:"name"`
Dir bool `json:"dir"`
Created time.Time `json:"created"`
diff --git a/server/internal/app/app.go b/server/internal/app/app.go
index 4bd064e6..ac4360bc 100644
--- a/server/internal/app/app.go
+++ b/server/internal/app/app.go
@@ -1,8 +1,12 @@
package app
import (
+ "context"
+
+ "github.com/google/uuid"
"github.com/shroff/phylum/server/internal/app/core"
"github.com/shroff/phylum/server/internal/db"
+ "github.com/shroff/phylum/server/internal/sql"
)
type App struct {
@@ -19,6 +23,15 @@ func Initialize(db *db.DbHandler, debug bool) error {
return err
}
+ if _, err := db.Queries().CreateResource(context.Background(), sql.CreateResourceParams{
+ ID: uuid.UUID{},
+ Parent: uuid.UUID{},
+ Name: "root",
+ Dir: true,
+ }); err != nil {
+ return err
+ }
+
Default = &App{
Debug: debug,
Db: db,
@@ -27,3 +40,7 @@ func Initialize(db *db.DbHandler, debug bool) error {
return nil
}
+
+func (a App) OpenFileSystem(ctx context.Context, user int32) (core.FileSystem, error) {
+ return core.OpenFileSystem(a.Db, ctx, nil, user)
+}
diff --git a/server/internal/app/core/filesystem.go b/server/internal/app/core/filesystem.go
new file mode 100644
index 00000000..293919c5
--- /dev/null
+++ b/server/internal/app/core/filesystem.go
@@ -0,0 +1,252 @@
+package core
+
+import (
+ "context"
+ "io"
+ "io/fs"
+ "strings"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/jackc/pgx/v5"
+ "github.com/jackc/pgx/v5/pgtype"
+ "github.com/shroff/phylum/server/internal/db"
+ "github.com/shroff/phylum/server/internal/sql"
+ "github.com/sirupsen/logrus"
+)
+
+type FileSystem interface {
+ OpenWithRoot(Resource) FileSystem
+ ResourceByPath(path string) (Resource, error)
+ ResourceByID(id uuid.UUID) (Resource, error)
+ OpenRead(r Resource, start, length int64) (io.ReadCloser, error)
+ OpenWrite(r Resource) (io.WriteCloser, error)
+ ReadDir(r Resource) ([]Resource, error)
+ CreateMemberResource(r Resource, id uuid.UUID, name string, dir bool) (Resource, error)
+ DeleteRecursive(r Resource, hardDelete bool) error
+ UpdateName(r Resource, name string) error
+ UpdateParent(r Resource, parent uuid.UUID) error
+ UpdatePermissions(r Resource, userID int32, permission int32) error
+}
+
+type filesystem struct {
+ db *db.DbHandler
+ ctx context.Context
+ root Resource
+ user int32
+}
+
+func OpenFileSystem(db *db.DbHandler, ctx context.Context, root Resource, user int32) (FileSystem, error) {
+ if root == nil {
+ root = resource{
+ id: uuid.UUID{},
+ permission: 0,
+ parentID: uuid.UUID{},
+ name: "root",
+ size: 0,
+ collection: true,
+ modTime: time.Time{},
+ etag: "",
+ }
+ }
+ return filesystem{
+ db: db,
+ ctx: ctx,
+ root: root,
+ user: user,
+ }, nil
+}
+
+func (f filesystem) OpenWithRoot(root Resource) FileSystem {
+ return filesystem{
+ db: f.db,
+ ctx: f.ctx,
+ root: root,
+ user: f.user,
+ }
+}
+
+func (f filesystem) ResourceByPath(path string) (Resource, 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(f.ctx, sql.ResourceByPathParams{Root: f.root.ID(), Search: segments, UserID: f.user})
+ if err != nil {
+ return nil, fs.ErrNotExist
+ }
+
+ if !res.Permission.Valid || res.Permission.Int32 == 0 {
+ return nil, ErrInsufficientPermissions
+ }
+
+ return resource{
+ id: res.ID,
+ permission: res.Permission.Int32,
+ parentID: res.Parent,
+ name: res.Name,
+ size: res.Size.Int64,
+ collection: res.Dir,
+ modTime: res.Modified.Time,
+ etag: res.Etag.String,
+ }, nil
+}
+
+func (f filesystem) ResourceByID(id uuid.UUID) (Resource, error) {
+ res, err := f.db.Queries().ResourceByIdWithPermissions(f.ctx, sql.ResourceByIdWithPermissionsParams{Root: f.root.ID(), ResourceID: id, UserID: f.user})
+ if err == pgx.ErrNoRows || res.Permission.Int32 == 0 {
+ err = fs.ErrNotExist
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ return resource{
+ id: res.ID,
+ permission: res.Permission.Int32,
+ parentID: res.Parent,
+ name: res.Name,
+ size: res.Size.Int64,
+ collection: res.Dir,
+ modTime: res.Modified.Time,
+ etag: res.Etag.String,
+ }, nil
+}
+func (f filesystem) OpenRead(r Resource, start, length int64) (io.ReadCloser, error) {
+ if r.Permission() < PermissionReadOnly {
+ return nil, ErrInsufficientPermissions
+ }
+ return r.storage.OpenRead(r.ID(), start, length)
+}
+
+func (f filesystem) OpenWrite(r Resource) (io.WriteCloser, error) {
+ if r.Permission() < PermissionReadWrite {
+ return nil, ErrInsufficientPermissions
+ }
+ return r.storage.OpenWrite(r.id, func(len int, etag string) error {
+ return r.db.Queries().UpdateResourceContents(f.ctx, sql.UpdateResourceContentsParams{
+ ID: r.id,
+ Size: pgtype.Int8{Int64: int64(len), Valid: true},
+ Etag: pgtype.Text{String: etag, Valid: true},
+ })
+ })
+}
+
+func (f filesystem) ReadDir(r Resource) ([]Resource, error) {
+ if r.Permission() < PermissionReadOnly {
+ return nil, ErrInsufficientPermissions
+ }
+ children, err := f.db.Queries().ReadDir(f.ctx, sql.ReadDirParams{ID: r.ID(), UserID: f.user, IncludeRoot: false, Recursive: false})
+ if err != nil {
+ return nil, err
+ }
+
+ result := make([]Resource, len(children))
+ for i, c := range children {
+ permission := r.Permission()
+ if c.Permission.Valid && c.Permission.Int32 > permission {
+ permission = c.Permission.Int32
+
+ }
+ result[i] = resource{
+ id: c.ID,
+ permission: permission,
+ parentID: r.ID(),
+ name: c.Name,
+ size: c.Size.Int64,
+ modTime: c.Modified.Time,
+ collection: c.Dir,
+ etag: c.Etag.String,
+ }
+ }
+ return result, nil
+}
+
+func (f filesystem) CreateMemberResource(r Resource, id uuid.UUID, name string, dir bool) (Resource, error) {
+ if r.Permission() < PermissionReadWrite {
+ return nil, ErrInsufficientPermissions
+ }
+ if !r.IsDir() {
+ return resource{}, ErrResourceNotCollection
+ }
+ var result sql.Resource
+ err := f.db.RunInTx(f.ctx, func(q *sql.Queries) error {
+ var err error
+ if result, err = q.CreateResource(f.ctx, sql.CreateResourceParams{ID: id, Parent: r.ID(), Name: name, Dir: dir}); err != nil {
+ return err
+ }
+ return q.UpdateResourceModified(f.ctx, r.ID())
+ })
+ if err != nil {
+ return nil, err
+ }
+ return resource{
+ id: id,
+ parentID: r.ID(),
+ permission: r.Permission(),
+ name: result.Name,
+ size: 0,
+ modTime: result.Modified.Time,
+ collection: dir,
+ etag: "",
+ }, nil
+}
+
+func (f filesystem) DeleteRecursive(r Resource, hardDelete bool) error {
+ if r.Permission() < PermissionReadWrite {
+ return ErrInsufficientPermissions
+ }
+ // TODO: versioning
+ return f.db.RunInTx(f.ctx, func(q *sql.Queries) error {
+ if hardDelete {
+ deleted, err := q.HardDeleteRecursive(f.ctx, r.ID())
+ if err != nil {
+ return err
+ }
+ errors := r.storage.Delete(deleted)
+ for err := range errors {
+ logrus.Warn(err)
+ }
+ } else {
+ if err := q.DeleteRecursive(f.ctx, r.ID()); err != nil {
+ return err
+ }
+ }
+
+ emptyUUID := uuid.UUID{}
+ if r.ParentID() != emptyUUID {
+ return q.UpdateResourceModified(f.ctx, r.ParentID())
+ }
+ return nil
+ })
+}
+
+func (f filesystem) UpdateName(r Resource, name string) error {
+ return f.db.Queries().UpdateResourceName(f.ctx, sql.UpdateResourceNameParams{ID: r.ID(), Name: name})
+}
+
+func (f filesystem) UpdateParent(r Resource, parent uuid.UUID) error {
+ emptyUUID := uuid.UUID{}
+ if r.ParentID() == emptyUUID {
+ return ErrCannotReparentRootResource
+ }
+ return f.db.Queries().UpdateResourceParent(f.ctx, sql.UpdateResourceParentParams{ID: r.ID(), Parent: parent})
+}
+
+func (f filesystem) UpdatePermissions(r Resource, userID int32, permission int32) error {
+ if r.Permission() < PermissionAdmin {
+ return ErrInsufficientPermissions
+ }
+ if permission > PermissionAdmin {
+ return ErrCannotGrantOwnerPermission
+ }
+
+ return f.db.Queries().UpdatePermissionsForResource(f.ctx, sql.UpdatePermissionsForResourceParams{
+ ResourceID: r.ID(),
+ UserID: userID,
+ Permission: permission,
+ })
+}
diff --git a/server/internal/app/core/resource.go b/server/internal/app/core/resource.go
index 3d56b9af..b7d15860 100644
--- a/server/internal/app/core/resource.go
+++ b/server/internal/app/core/resource.go
@@ -1,19 +1,13 @@
package core
import (
- "context"
"errors"
"fmt"
- "io"
"mime"
"path/filepath"
"time"
"github.com/google/uuid"
- "github.com/jackc/pgx/v5/pgtype"
- "github.com/shroff/phylum/server/internal/db"
- "github.com/shroff/phylum/server/internal/sql"
- "github.com/sirupsen/logrus"
)
const (
@@ -31,9 +25,9 @@ var (
ErrCannotReparentToRoot = errors.New("cannot reparent resource to root")
)
-type ResourceInfo interface {
+type Resource interface {
ID() uuid.UUID
- ParentID() *uuid.UUID
+ ParentID() uuid.UUID
Name() string
Size() int64
Permission() int32
@@ -43,27 +37,12 @@ type ResourceInfo interface {
ContentType() string
}
-type Resource interface {
- ResourceInfo
- OpenRead(start, length int64) (io.ReadCloser, error)
- OpenWrite(ctx context.Context) (io.WriteCloser, error)
- ReadDir(ctx context.Context) ([]Resource, error)
- CreateMemberResource(ctx context.Context, id uuid.UUID, name string, dir bool) (Resource, error)
- DeleteRecursive(ctx context.Context, hardDelete bool) error
- UpdateName(ctx context.Context, name string) error
- UpdateParent(ctx context.Context, parent uuid.UUID) error
- UpdatePermissions(ctx context.Context, userID int32, permission int32) error
-}
-
type resource struct {
- db *db.DbHandler
- storage Storage
id uuid.UUID
- parentID *uuid.UUID
+ permission int32
+ parentID uuid.UUID
name string
size int64
- userID int32
- permission int32
collection bool
modTime time.Time
etag string
@@ -73,7 +52,7 @@ func (r resource) ID() uuid.UUID { return r.id }
func (r resource) Permission() int32 { return r.permission }
-func (r resource) ParentID() *uuid.UUID { return r.parentID }
+func (r resource) ParentID() uuid.UUID { return r.parentID }
func (r resource) Name() string { return r.name }
@@ -99,143 +78,3 @@ func (r resource) ContentType() string {
}
return "application/octet-stream"
}
-
-func (r resource) OpenRead(start, length int64) (io.ReadCloser, error) {
- if r.permission < PermissionReadOnly {
- return nil, ErrInsufficientPermissions
- }
- return r.storage.OpenRead(r.id, start, length)
-}
-
-func (r resource) OpenWrite(ctx context.Context) (io.WriteCloser, error) {
- if r.permission < PermissionReadWrite {
- return nil, ErrInsufficientPermissions
- }
- return r.storage.OpenWrite(r.id, func(len int, etag string) error {
- return r.db.Queries().UpdateResourceContents(ctx, sql.UpdateResourceContentsParams{
- ID: r.id,
- Size: pgtype.Int8{Int64: int64(len), Valid: true},
- Etag: pgtype.Text{String: etag, Valid: true},
- })
- })
-}
-
-func (r resource) ReadDir(ctx context.Context) ([]Resource, error) {
- if r.permission < PermissionReadOnly {
- return nil, ErrInsufficientPermissions
- }
- children, err := r.db.Queries().ReadDir(ctx, sql.ReadDirParams{ID: r.id, UserID: r.userID, IncludeRoot: false, Recursive: false})
- if err != nil {
- return nil, err
- }
-
- result := make([]Resource, len(children))
- for i, c := range children {
- permission := r.Permission()
- if c.Permission.Valid && c.Permission.Int32 > permission {
- permission = c.Permission.Int32
-
- }
- result[i] = resource{
- db: r.db,
- storage: r.storage,
- id: c.ID,
- userID: r.userID,
- permission: permission,
- parentID: &r.id,
- name: c.Name,
- size: c.Size.Int64,
- modTime: c.Modified.Time,
- collection: c.Dir,
- etag: c.Etag.String,
- }
- }
- return result, nil
-}
-
-func (r resource) CreateMemberResource(ctx context.Context, id uuid.UUID, name string, dir bool) (Resource, error) {
- if r.permission < PermissionReadWrite {
- return nil, ErrInsufficientPermissions
- }
- if !r.collection {
- return resource{}, ErrResourceNotCollection
- }
- var result sql.Resource
- err := r.db.RunInTx(ctx, func(q *sql.Queries) error {
- var err error
- if result, err = q.CreateResource(ctx, sql.CreateResourceParams{ID: id, Parent: &r.id, Name: name, Dir: dir}); err != nil {
- return err
- }
- return q.UpdateResourceModified(ctx, r.id)
- })
- if err != nil {
- return nil, err
- }
- return resource{
- db: r.db,
- storage: r.storage,
- id: id,
- parentID: &r.id,
- userID: r.userID,
- permission: r.permission,
- name: result.Name,
- size: 0,
- modTime: result.Modified.Time,
- collection: dir,
- etag: "",
- }, nil
-}
-
-func (r resource) DeleteRecursive(ctx context.Context, hardDelete bool) error {
- if r.permission < PermissionReadWrite {
- return ErrInsufficientPermissions
- }
- // TODO: versioning
- return r.db.RunInTx(ctx, func(q *sql.Queries) error {
- if hardDelete {
- deleted, err := q.HardDeleteRecursive(ctx, r.id)
- if err != nil {
- return err
- }
- errors := r.storage.Delete(deleted)
- for err := range errors {
- logrus.Warn(err)
- }
- } else {
- if err := q.DeleteRecursive(ctx, r.id); err != nil {
- return err
- }
- }
-
- if r.parentID != nil {
- return q.UpdateResourceModified(ctx, *r.parentID)
- }
- return nil
- })
-}
-
-func (r resource) UpdateName(ctx context.Context, name string) error {
- return r.db.Queries().UpdateResourceName(ctx, sql.UpdateResourceNameParams{ID: r.id, Name: name})
-}
-
-func (r resource) UpdateParent(ctx context.Context, parent uuid.UUID) error {
- if r.parentID == nil {
- return ErrCannotReparentRootResource
- }
- return r.db.Queries().UpdateResourceParent(ctx, sql.UpdateResourceParentParams{ID: r.id, Parent: parent})
-}
-
-func (r resource) UpdatePermissions(ctx context.Context, userID int32, permission int32) error {
- if r.permission < PermissionAdmin {
- return ErrInsufficientPermissions
- }
- if permission > PermissionAdmin {
- return ErrCannotGrantOwnerPermission
- }
-
- return r.db.Queries().UpdatePermissionsForResource(ctx, sql.UpdatePermissionsForResourceParams{
- ResourceID: r.id,
- UserID: userID,
- Permission: permission,
- })
-}
diff --git a/server/internal/app/core/silo.go b/server/internal/app/core/silo.go
index 6e073198..9c9c0e02 100644
--- a/server/internal/app/core/silo.go
+++ b/server/internal/app/core/silo.go
@@ -1,13 +1,8 @@
package core
import (
- "context"
- "io/fs"
- "strings"
-
"github.com/google/uuid"
"github.com/shroff/phylum/server/internal/db"
- "github.com/shroff/phylum/server/internal/sql"
)
type Silo interface {
@@ -15,7 +10,6 @@ type Silo interface {
Name() string
Owner() int32
StorageName() string
- ResourceByPath(ctx context.Context, path string, userID int32) (Resource, error)
}
type silo struct {
@@ -51,35 +45,3 @@ func (s *silo) Owner() int32 {
func (s *silo) StorageName() string {
return s.storage.Name()
}
-
-func (s *silo) ResourceByPath(ctx context.Context, path string, userID int32) (Resource, 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 := s.db.Queries().ResourceByPath(ctx, sql.ResourceByPathParams{Root: s.root, Search: segments, UserID: userID})
- if err != nil {
- return nil, fs.ErrNotExist
- }
-
- if !res.Permission.Valid || res.Permission.Int32 == 0 {
- return nil, ErrInsufficientPermissions
- }
-
- return resource{
- db: s.db,
- storage: s.storage,
- id: res.ID,
- userID: userID,
- permission: res.Permission.Int32,
- parentID: res.Parent,
- name: res.Name,
- size: res.Size.Int64,
- collection: res.Dir,
- modTime: res.Modified.Time,
- etag: res.Etag.String,
- }, nil
-}
diff --git a/server/internal/app/resources.go b/server/internal/app/resources.go
index 9bf3e92d..86f067be 100644
--- a/server/internal/app/resources.go
+++ b/server/internal/app/resources.go
@@ -1,24 +1,7 @@
package app
import (
- "context"
"errors"
-
- "github.com/google/uuid"
- "github.com/jackc/pgx/v5"
- "github.com/shroff/phylum/server/internal/sql"
)
var ErrResourceNotFound = errors.New("resource not found")
-
-func (a *App) LocateResource(id uuid.UUID, userID int32) (uuid.UUID, int, error) {
- result, err := a.Db.Queries().PermissionsForResource(context.Background(), sql.PermissionsForResourceParams{ResourceID: id, UserID: userID})
- if err == pgx.ErrNoRows {
- err = ErrResourceNotFound
- }
- if err != nil {
- return uuid.UUID{}, 0, err
- }
-
- return result.ID, int(result.Permission.Int32), nil
-}
diff --git a/server/internal/app/silos.go b/server/internal/app/silos.go
index 79b66a86..655219b6 100644
--- a/server/internal/app/silos.go
+++ b/server/internal/app/silos.go
@@ -21,7 +21,7 @@ func (a App) CreateSilo(ctx context.Context, id uuid.UUID, owner int32, storage,
}
if _, err := q.CreateResource(ctx, sql.CreateResourceParams{
ID: id,
- Parent: nil,
+ Parent: uuid.UUID{},
Name: id.String(),
Dir: true,
}); err != nil {
@@ -86,25 +86,26 @@ func (a App) FindSilo(ctx context.Context, idOrName string) (core.Silo, error) {
}
func (a App) DeleteSilo(ctx context.Context, id uuid.UUID) error {
- return a.Db.RunInTx(ctx, func(q *sql.Queries) error {
- result, err := q.SiloById(ctx, id)
- if err != nil {
- return err
- }
- storage := a.FindStorageBackend(result.Storage)
- if storage == nil {
- return errors.New("storage backend not found for " + id.String())
- }
+ return nil
+ // return a.Db.RunInTx(ctx, func(q *sql.Queries) error {
+ // result, err := q.SiloById(ctx, id)
+ // if err != nil {
+ // return err
+ // }
+ // storage := a.FindStorageBackend(result.Storage)
+ // if storage == nil {
+ // return errors.New("storage backend not found for " + id.String())
+ // }
- silo := core.NewSilo(a.Db, result.Name, result.Owner, result.ID, storage)
- resource, err := silo.ResourceByPath(ctx, "/", -1)
- if err != nil {
- return err
- }
- if err := resource.DeleteRecursive(ctx, true); err != nil {
- return err
- }
+ // silo := core.NewSilo(a.Db, result.Name, result.Owner, result.ID, storage)
+ // resource, err := silo.ResourceByPath(ctx, "/", -1)
+ // if err != nil {
+ // return err
+ // }
+ // if err := resource.DeleteRecursive(ctx, true); err != nil {
+ // return err
+ // }
- return q.DeleteSilo(ctx, id)
- })
+ // return q.DeleteSilo(ctx, id)
+ // })
}
diff --git a/server/internal/handler_webdav/handler.go b/server/internal/handler_webdav/handler.go
index 5cf3ebeb..eba2ad82 100644
--- a/server/internal/handler_webdav/handler.go
+++ b/server/internal/handler_webdav/handler.go
@@ -42,46 +42,52 @@ func SetupHandler(r *gin.RouterGroup, app *app.App) {
}
func (h *handler) HandleRequest(c *gin.Context) {
- path := c.Params.ByName("path")
- identifier := strings.TrimLeft(path, "/")
- if identifier == "" {
- // No path specified
- c.JSON(404, gin.H{
- "ERR_CODE": "err_silo_path_not_specified",
- })
- return
- }
- index := strings.Index(identifier, "/")
- if index != -1 {
- identifier = identifier[0:index]
- }
- silo, err := h.app.FindSilo(context.Background(), identifier)
- if err != nil {
- c.Writer.WriteHeader(404)
- c.JSON(404, gin.H{
- "ERR_CODE": "err_silo_not_found",
- "ERR_DETAILS": err.Error(),
- })
- return
- }
+ // path := c.Params.ByName("path")
+ // identifier := strings.TrimLeft(path, "/")
+ // if identifier == "" {
+ // // No path specified
+ // c.JSON(404, gin.H{
+ // "ERR_CODE": "err_silo_path_not_specified",
+ // })
+ // return
+ // }
+ // index := strings.Index(identifier, "/")
+ // if index != -1 {
+ // identifier = identifier[0:index]
+ // }
- userID := auth.GetUserID(c)
+ fs, err := h.app.OpenFileSystem(c.Request.Context(), auth.GetUserID(c))
+
+ // silo, err := h.app.FindSilo(context.Background(), identifier)
+ if err != nil {
+ c.AbortWithStatusJSON(404, gin.H{
+ "ERR_CODE": "err_silo_not_found",
+ })
+ return
+ }
webdavHandler := webdav.Handler{
- Prefix: h.prefix + "/" + identifier,
- FileSystem: adapter{silo: silo, userID: userID},
+ Prefix: h.prefix,
+ FileSystem: adapter{fs: fs},
LockSystem: webdav.NewMemLS(),
}
webdavHandler.ServeHTTP(c.Writer, c.Request)
}
type adapter struct {
- silo core.Silo
- userID int32
+ fs core.FileSystem
}
func (a adapter) Stat(ctx context.Context, name string) (core.Resource, error) {
- return a.silo.ResourceByPath(ctx, name, a.userID)
+ return a.fs.ResourceByPath(name)
+}
+
+func (a adapter) OpenRead(r core.Resource, start, len int64) (io.ReadCloser, error) {
+ return a.fs.OpenRead(r, start, len)
+}
+
+func (a adapter) ReadDir(r core.Resource) ([]core.Resource, error) {
+ return a.fs.ReadDir(r)
}
func (a adapter) OpenWrite(ctx context.Context, name string) (io.WriteCloser, error) {
@@ -99,18 +105,18 @@ func (a adapter) OpenWrite(ctx context.Context, name string) (io.WriteCloser, er
}
resourceName := name[index+1:]
resourceId := uuid.New()
- resource, err = parent.CreateMemberResource(ctx, resourceId, resourceName, false)
+ resource, err = a.fs.CreateMemberResource(parent, resourceId, resourceName, false)
if err != nil {
return nil, err
}
- return resource.OpenWrite(ctx)
+ return a.fs.OpenWrite(resource)
}
return nil, err
}
if resource.IsDir() {
return nil, errors.New("cannot open collection for write")
}
- return resource.OpenWrite(ctx)
+ return a.fs.OpenWrite(resource)
}
func (a adapter) RemoveAll(ctx context.Context, name string) error {
@@ -118,7 +124,7 @@ func (a adapter) RemoveAll(ctx context.Context, name string) error {
if err != nil {
return fs.ErrNotExist
}
- return resource.DeleteRecursive(ctx, false)
+ return a.fs.DeleteRecursive(resource, false)
}
func (a adapter) Mkdir(ctx context.Context, name string) error {
@@ -133,7 +139,7 @@ func (a adapter) Mkdir(ctx context.Context, name string) error {
return fs.ErrNotExist
}
dirName := name[index+1:]
- _, err = parent.CreateMemberResource(ctx, uuid.New(), dirName, true)
+ _, err = a.fs.CreateMemberResource(parent, uuid.New(), dirName, true)
return err
}
@@ -158,13 +164,13 @@ func (a adapter) Rename(ctx context.Context, oldName, newName string) error {
return fs.ErrNotExist
}
- if src.ParentID() != nil && *src.ParentID() != parent.ID() {
- if err = src.UpdateParent(ctx, parent.ID()); err != nil {
+ if src.ParentID() != parent.ID() {
+ if err = a.fs.UpdateParent(src, parent.ID()); err != nil {
return err
}
}
if src.Name() != newName && newName != "" && newName != "/" {
- if err = src.UpdateName(ctx, newName); err != nil {
+ if err = a.fs.UpdateName(src, newName); err != nil {
return err
}
}
diff --git a/server/internal/migrations/data/001_resources.sql b/server/internal/migrations/data/001_resources.sql
index fbc858a6..8ebe147a 100644
--- a/server/internal/migrations/data/001_resources.sql
+++ b/server/internal/migrations/data/001_resources.sql
@@ -1,6 +1,6 @@
CREATE TABLE resources (
id uuid PRIMARY KEY,
- parent uuid REFERENCES resources(id) ON UPDATE CASCADE ON DELETE CASCADE,
+ parent uuid NOT NULL REFERENCES resources(id) ON UPDATE CASCADE ON DELETE CASCADE,
name TEXT NOT NULL,
dir BOOLEAN NOT NULL,
created TIMESTAMP NOT NULL,
diff --git a/server/internal/sql/models.go b/server/internal/sql/models.go
index 0722807b..13433c01 100644
--- a/server/internal/sql/models.go
+++ b/server/internal/sql/models.go
@@ -24,7 +24,7 @@ type Permission struct {
type Resource struct {
ID uuid.UUID
- Parent *uuid.UUID
+ Parent uuid.UUID
Name string
Dir bool
Created pgtype.Timestamp
diff --git a/server/internal/sql/permissions.sql.go b/server/internal/sql/permissions.sql.go
index b4a39d40..fe0628b2 100644
--- a/server/internal/sql/permissions.sql.go
+++ b/server/internal/sql/permissions.sql.go
@@ -12,32 +12,68 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
-const permissionsForResource = `-- name: PermissionsForResource :one
+const resourceByIdWithPermissions = `-- name: ResourceByIdWithPermissions :one
WITH RECURSIVE nodes(id, parent, permission) AS (
SELECT r.id, r.parent, p.permission
- FROM resources r LEFT JOIN permissions p on r.id = p.resource_id WHERE r.id = $1::uuid AND p.user_id = $2::int
+ FROM resources r
+ LEFT JOIN permissions p
+ on r.id = p.resource_id
+ AND p.user_id = $2::int
+ WHERE r.id = $3::uuid
UNION ALL
SELECT r.id, r.parent, CASE WHEN (n.permission IS NULL OR p.permission > n.permission) THEN p.permission ELSE n.permission END
- FROM resources r JOIN nodes n ON r.id = n.parent LEFT JOIN permissions p ON r.id = p.resource_id AND p.user_id = $2::int
+ FROM resources r
+ JOIN nodes n
+ ON r.id = n.parent
+ LEFT JOIN permissions p
+ ON r.id = p.resource_id AND p.user_id = $2::int
+ WHERE r.parent = $1::uuid
+ OR r.parent = '00000000-0000-0000-0000-000000000000'
)
-SELECT id, parent, permission FROM nodes WHERE parent IS NULL
+SELECT n.id, n.parent, permission, r.id, r.parent, name, dir, created, modified, deleted, size, etag FROM nodes n
+JOIN resources r
+ON n.id = r.id
+WHERE n.parent = $1::uuid
`
-type PermissionsForResourceParams struct {
- ResourceID uuid.UUID
+type ResourceByIdWithPermissionsParams struct {
+ Root uuid.UUID
UserID int32
+ ResourceID uuid.UUID
}
-type PermissionsForResourceRow struct {
+type ResourceByIdWithPermissionsRow struct {
ID uuid.UUID
- Parent *uuid.UUID
+ Parent uuid.UUID
Permission pgtype.Int4
+ ID_2 uuid.UUID
+ Parent_2 uuid.UUID
+ Name string
+ Dir bool
+ Created pgtype.Timestamp
+ Modified pgtype.Timestamp
+ Deleted pgtype.Timestamp
+ Size pgtype.Int8
+ Etag pgtype.Text
}
-func (q *Queries) PermissionsForResource(ctx context.Context, arg PermissionsForResourceParams) (PermissionsForResourceRow, error) {
- row := q.db.QueryRow(ctx, permissionsForResource, arg.ResourceID, arg.UserID)
- var i PermissionsForResourceRow
- err := row.Scan(&i.ID, &i.Parent, &i.Permission)
+func (q *Queries) ResourceByIdWithPermissions(ctx context.Context, arg ResourceByIdWithPermissionsParams) (ResourceByIdWithPermissionsRow, error) {
+ row := q.db.QueryRow(ctx, resourceByIdWithPermissions, arg.Root, arg.UserID, arg.ResourceID)
+ var i ResourceByIdWithPermissionsRow
+ err := row.Scan(
+ &i.ID,
+ &i.Parent,
+ &i.Permission,
+ &i.ID_2,
+ &i.Parent_2,
+ &i.Name,
+ &i.Dir,
+ &i.Created,
+ &i.Modified,
+ &i.Deleted,
+ &i.Size,
+ &i.Etag,
+ )
return i, err
}
diff --git a/server/internal/sql/resources.sql.go b/server/internal/sql/resources.sql.go
index 89b8fddc..d7ab461f 100644
--- a/server/internal/sql/resources.sql.go
+++ b/server/internal/sql/resources.sql.go
@@ -22,7 +22,7 @@ INSERT INTO resources(
type CreateResourceParams struct {
ID uuid.UUID
- Parent *uuid.UUID
+ Parent uuid.UUID
Name string
Dir bool
}
@@ -137,7 +137,7 @@ type ReadDirParams struct {
type ReadDirRow struct {
ID uuid.UUID
- Parent *uuid.UUID
+ Parent uuid.UUID
Name string
Dir bool
Created pgtype.Timestamp
@@ -236,7 +236,7 @@ type ResourceByPathParams struct {
type ResourceByPathRow struct {
ID uuid.UUID
- Parent *uuid.UUID
+ Parent uuid.UUID
Name string
Dir bool
Created pgtype.Timestamp
diff --git a/server/internal/webdav/file.go b/server/internal/webdav/file.go
index e08578a8..8cdb602e 100644
--- a/server/internal/webdav/file.go
+++ b/server/internal/webdav/file.go
@@ -23,6 +23,8 @@ type FileSystem interface {
Mkdir(ctx context.Context, name string) error
RemoveAll(ctx context.Context, name string) error
Rename(ctx context.Context, oldName, newName string) error
+ ReadDir(r core.Resource) ([]core.Resource, error)
+ OpenRead(r core.Resource, start, length int64) (io.ReadCloser, error)
OpenWrite(ctx context.Context, name string) (io.WriteCloser, error)
}
@@ -97,7 +99,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 := fs.ReadDir(srcStat)
if err != nil {
return http.StatusForbidden, err
}
@@ -114,7 +116,7 @@ func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bo
}
} else {
- srcFile, err := srcStat.OpenRead(0, -1)
+ srcFile, err := fs.OpenRead(srcStat, 0, -1)
if err != nil {
if os.IsNotExist(err) {
return http.StatusNotFound, err
@@ -174,7 +176,7 @@ func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info cor
if err != nil {
return walkFn(name, info, err)
}
- fileInfos, err := f.ReadDir(ctx)
+ fileInfos, err := fs.ReadDir(f)
if err != nil {
return walkFn(name, info, err)
}
diff --git a/server/internal/webdav/prop.go b/server/internal/webdav/prop.go
index cde76e27..38f4a569 100644
--- a/server/internal/webdav/prop.go
+++ b/server/internal/webdav/prop.go
@@ -70,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(core.ResourceInfo) string
+ findFn func(core.Resource) string
// dir is true if the property applies to directories.
dir bool
}{
@@ -265,34 +265,34 @@ func escapeXML(s string) string {
return s
}
-func findResourceType(fi core.ResourceInfo) string {
+func findResourceType(fi core.Resource) string {
if fi.IsDir() {
return ``
}
return ""
}
-func findDisplayName(fi core.ResourceInfo) string {
+func findDisplayName(fi core.Resource) string {
return escapeXML(fi.Name())
}
-func findContentLength(fi core.ResourceInfo) string {
+func findContentLength(fi core.Resource) string {
return strconv.FormatInt(fi.Size(), 10)
}
-func findLastModified(fi core.ResourceInfo) string {
+func findLastModified(fi core.Resource) string {
return fi.ModTime().UTC().Format(http.TimeFormat)
}
-func findContentType(fi core.ResourceInfo) string {
+func findContentType(fi core.Resource) string {
return fi.ContentType()
}
-func findETag(fi core.ResourceInfo) string {
+func findETag(fi core.Resource) string {
return fi.ETag()
}
-func findSupportedLock(fi core.ResourceInfo) string {
+func findSupportedLock(fi core.Resource) string {
return `` +
`` +
`` +
diff --git a/server/internal/webdav/serve_resource.go b/server/internal/webdav/serve_resource.go
index 0d358655..a4f3d518 100644
--- a/server/internal/webdav/serve_resource.go
+++ b/server/internal/webdav/serve_resource.go
@@ -25,7 +25,7 @@ var htmlReplacer = strings.NewReplacer(
"'", "'",
)
-func serveCollection(w http.ResponseWriter, r *http.Request, file core.Resource) {
+func serveCollection(w http.ResponseWriter, r *http.Request, fs FileSystem, file core.Resource) {
if !strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, r, r.URL.String()+"/", http.StatusMovedPermanently)
return
@@ -36,7 +36,7 @@ func serveCollection(w http.ResponseWriter, r *http.Request, file core.Resource)
}
w.Header().Set("Last-Modified", file.ModTime().Format(http.TimeFormat))
- files, err := file.ReadDir(r.Context())
+ files, err := fs.ReadDir(file)
if err != nil {
http.Error(w, "Error reading directory", http.StatusInternalServerError)
return
@@ -59,7 +59,7 @@ func serveCollection(w http.ResponseWriter, r *http.Request, file core.Resource)
fmt.Fprintf(w, "\n")
}
-func serveResource(w http.ResponseWriter, r *http.Request, file core.Resource) {
+func serveResource(w http.ResponseWriter, r *http.Request, fs FileSystem, file core.Resource) {
w.Header().Set("Etag", file.ETag())
w.Header().Set("Last-Modified", file.ModTime().Format(http.TimeFormat))
w.Header().Set("Content-Type", file.ContentType())
@@ -95,9 +95,9 @@ func serveResource(w http.ResponseWriter, r *http.Request, file core.Resource) {
sendSize = ra.length
code = http.StatusPartialContent
w.Header().Set("Content-Range", ra.contentRange(file.Size()))
- reader, err = file.OpenRead(ra.start, ra.length)
+ reader, err = fs.OpenRead(file, ra.start, ra.length)
} else {
- reader, err = file.OpenRead(0, -1)
+ reader, err = fs.OpenRead(file, 0, -1)
}
if err != nil {
@@ -206,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 core.ResourceInfo) (done bool, rangeHeader string) {
+func checkPreconditions(w http.ResponseWriter, r *http.Request, ri core.Resource) (done bool, rangeHeader string) {
// This function carefully follows RFC 7232 section 6.
ch := checkIfMatch(r, ri)
if ch == condNone {
@@ -289,7 +289,7 @@ const (
condFalse
)
-func checkIfMatch(r *http.Request, ri core.ResourceInfo) condResult {
+func checkIfMatch(r *http.Request, ri core.Resource) condResult {
im := r.Header.Get("If-Match")
if im == "" {
return condNone
@@ -319,7 +319,7 @@ func checkIfMatch(r *http.Request, ri core.ResourceInfo) condResult {
return condFalse
}
-func checkIfUnmodifiedSince(r *http.Request, ri core.ResourceInfo) condResult {
+func checkIfUnmodifiedSince(r *http.Request, ri core.Resource) condResult {
ius := r.Header.Get("If-Unmodified-Since")
if ius == "" || isZeroTime(ri.ModTime()) {
return condNone
@@ -338,7 +338,7 @@ func checkIfUnmodifiedSince(r *http.Request, ri core.ResourceInfo) condResult {
return condFalse
}
-func checkIfNoneMatch(r *http.Request, ri core.ResourceInfo) condResult {
+func checkIfNoneMatch(r *http.Request, ri core.Resource) condResult {
inm := r.Header.Get("If-None-Match")
if inm == "" {
return condNone
@@ -368,7 +368,7 @@ func checkIfNoneMatch(r *http.Request, ri core.ResourceInfo) condResult {
return condTrue
}
-func checkIfModifiedSince(r *http.Request, ri core.ResourceInfo) condResult {
+func checkIfModifiedSince(r *http.Request, ri core.Resource) condResult {
if r.Method != "GET" && r.Method != "HEAD" {
return condNone
}
@@ -389,7 +389,7 @@ func checkIfModifiedSince(r *http.Request, ri core.ResourceInfo) condResult {
return condTrue
}
-func checkIfRange(r *http.Request, ri core.ResourceInfo) condResult {
+func checkIfRange(r *http.Request, ri core.Resource) condResult {
if r.Method != "GET" && r.Method != "HEAD" {
return condNone
}
diff --git a/server/internal/webdav/webdav.go b/server/internal/webdav/webdav.go
index a863eee6..3c0bd292 100644
--- a/server/internal/webdav/webdav.go
+++ b/server/internal/webdav/webdav.go
@@ -206,9 +206,9 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
return http.StatusNotFound, err
}
if file.IsDir() {
- serveCollection(w, r, file)
+ serveCollection(w, r, h.FileSystem, file)
} else {
- serveResource(w, r, file)
+ serveResource(w, r, h.FileSystem, file)
}
return 0, nil
@@ -625,7 +625,7 @@ func makePropstatResponse(href string, pstats []Propstat) *response {
return &resp
}
-func handlePropfindError(err error, info core.ResourceInfo) error {
+func handlePropfindError(err error, info core.Resource) error {
var skipResp error = nil
if info.IsDir() {
skipResp = filepath.SkipDir
diff --git a/server/sql/queries/permissions.sql b/server/sql/queries/permissions.sql
index 0a3300e4..50e84c7b 100644
--- a/server/sql/queries/permissions.sql
+++ b/server/sql/queries/permissions.sql
@@ -1,13 +1,26 @@
--- name: PermissionsForResource :one
+-- name: ResourceByIdWithPermissions :one
WITH RECURSIVE nodes(id, parent, permission) AS (
SELECT r.id, r.parent, p.permission
- FROM resources r LEFT JOIN permissions p on r.id = p.resource_id WHERE r.id = @resource_id::uuid AND p.user_id = @user_id::int
+ FROM resources r
+ LEFT JOIN permissions p
+ on r.id = p.resource_id
+ AND p.user_id = @user_id::int
+ WHERE r.id = @resource_id::uuid
UNION ALL
SELECT r.id, r.parent, CASE WHEN (n.permission IS NULL OR p.permission > n.permission) THEN p.permission ELSE n.permission END
- FROM resources r JOIN nodes n ON r.id = n.parent LEFT JOIN permissions p ON r.id = p.resource_id AND p.user_id = @user_id::int
+ FROM resources r
+ JOIN nodes n
+ ON r.id = n.parent
+ LEFT JOIN permissions p
+ ON r.id = p.resource_id AND p.user_id = @user_id::int
+ WHERE r.parent = @root::uuid
+ OR r.parent = '00000000-0000-0000-0000-000000000000'
)
-SELECT * FROM nodes WHERE parent IS NULL;
+SELECT * FROM nodes n
+JOIN resources r
+ON n.id = r.id
+WHERE n.parent = @root::uuid;
-- name: UpdatePermissionsForResource :exec
INSERT INTO permissions(resource_id, user_id, permission)