diff --git a/server/internal/api/serve/serve.go b/server/internal/api/serve/serve.go index ee8957aa..1f4ef3ea 100644 --- a/server/internal/api/serve/serve.go +++ b/server/internal/api/serve/serve.go @@ -25,7 +25,7 @@ var htmlReplacer = strings.NewReplacer( var ErrRangeNotSupported = errors.New("byte range not suppoered") -type Resource interface { +type ResourceInfo interface { FSName() string FSPath() string FSDir() bool @@ -34,7 +34,11 @@ type Resource interface { FSContentSize() int64 FSContentSHA256() string FSContentType() string - ReadDir(recursive bool) ([]Resource, error) +} + +type Resource interface { + ResourceInfo + ReadDir(recursive bool) ([]ResourceInfo, error) OpenRead(start, length int64) (io.ReadCloser, error) } diff --git a/server/internal/api/v1/fs/cat.go b/server/internal/api/v1/fs/cat.go index 2cbb2508..02160302 100644 --- a/server/internal/api/v1/fs/cat.go +++ b/server/internal/api/v1/fs/cat.go @@ -16,7 +16,7 @@ func handleCatRequest(c *gin.Context) { f := auth.GetFileSystem(c) r, err := f.ResourceByID(resourceID) - if r.Dir { + if r.FSDir() { err = fs.ErrResourceCollection } if err != nil { diff --git a/server/internal/api/v1/fs/download.go b/server/internal/api/v1/fs/download.go index ac8d97f4..c808d7bd 100644 --- a/server/internal/api/v1/fs/download.go +++ b/server/internal/api/v1/fs/download.go @@ -21,14 +21,14 @@ func handleDownloadRequest(c *gin.Context) { f := auth.GetFileSystem(c) r, err := f.ResourceByID(resourceID) - prefix := path.Dir(r.Path) + prefix := path.Dir(r.FSPath()) c.Writer.Header().Set("Content-Type", "application/zip") - c.Writer.Header().Set("Content-Disposition", "attachment; filename=\""+r.Name+".zip\"") + c.Writer.Header().Set("Content-Disposition", "attachment; filename=\""+r.FSName()+".zip\"") var zip = zip.NewWriter(c.Writer) close := c.Writer.CloseNotify() - err = r.Walk(2, func(r serve.Resource) error { + err = r.Walk(2, func(r serve.ResourceInfo) error { if r.FSDir() { return nil } @@ -47,7 +47,7 @@ func handleDownloadRequest(c *gin.Context) { if err != nil { return err } - in, err := r.OpenRead(0, -1) + in, err := r.(fs.Resource).OpenRead(0, -1) if err != nil { return err } diff --git a/server/internal/api/v1/fs/ls.go b/server/internal/api/v1/fs/ls.go index edff41f6..bea52513 100644 --- a/server/internal/api/v1/fs/ls.go +++ b/server/internal/api/v1/fs/ls.go @@ -32,14 +32,14 @@ func handleLsRequest(c *gin.Context) { func LsResponseFromResource(f fs.FileSystem, r fs.Resource) ResourceLsResponse { response := ResourceLsResponse{ Resource: r, - InheritedPermissions: r.InheritedPermissions, - } - if r.Dir { - children, err := r.ReadDir(false) - if err != nil { - panic(err) - } - response.Children = children + InheritedPermissions: r.FullPermissionsString(), } + // if r.FSDir() { + // children, err := r.ReadDir(false) + // if err != nil { + // panic(err) + // } + // response.Children = children + // } return response } diff --git a/server/internal/api/v1/my/routes.go b/server/internal/api/v1/my/routes.go index b7480a24..1e662e3d 100644 --- a/server/internal/api/v1/my/routes.go +++ b/server/internal/api/v1/my/routes.go @@ -20,11 +20,11 @@ type homeResponse struct { } type sharedResponse struct { - Shared []fs.Resource `json:"shared"` + Shared []fs.ResourceInfo `json:"shared"` } type bookmarksResponse struct { - Bookmarks []fs.Resource `json:"bookmarks"` + Bookmarks []fs.ResourceInfo `json:"bookmarks"` } type bookmarkIDParams struct { ID uuid.UUID `json:"id" binding:"required"` diff --git a/server/internal/api/webdav/impl/prop.go b/server/internal/api/webdav/impl/prop.go index dd55cb26..e106a0a8 100644 --- a/server/internal/api/webdav/impl/prop.go +++ b/server/internal/api/webdav/impl/prop.go @@ -69,7 +69,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(serve.Resource) string + findFn func(serve.ResourceInfo) string // dir is true if the property applies to directories. dir bool }{ @@ -132,7 +132,7 @@ 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(fs FileSystem, ls LockSystem, fi serve.Resource, pnames []xml.Name) ([]Propstat, error) { +func props(fs FileSystem, ls LockSystem, fi serve.ResourceInfo, pnames []xml.Name) ([]Propstat, error) { pstatOK := Propstat{Status: http.StatusOK} pstatNotFound := Propstat{Status: http.StatusNotFound} for _, pn := range pnames { @@ -153,7 +153,7 @@ func props(fs FileSystem, ls LockSystem, fi serve.Resource, pnames []xml.Name) ( } // propnames returns the property names defined for resource name. -func propnames(fs FileSystem, ls LockSystem, fi serve.Resource) ([]xml.Name, error) { +func propnames(fs FileSystem, ls LockSystem, fi serve.ResourceInfo) ([]xml.Name, error) { pnames := make([]xml.Name, 0, len(liveProps)) for pn, prop := range liveProps { if prop.findFn != nil && (prop.dir || !fi.FSDir()) { @@ -171,7 +171,7 @@ func propnames(fs FileSystem, ls LockSystem, fi serve.Resource) ([]xml.Name, err // returned if they are named in 'include'. // // See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND -func allprop(fs FileSystem, ls LockSystem, fi serve.Resource, include []xml.Name) ([]Propstat, error) { +func allprop(fs FileSystem, ls LockSystem, fi serve.ResourceInfo, include []xml.Name) ([]Propstat, error) { pnames, err := propnames(fs, ls, fi) if err != nil { return nil, err @@ -252,38 +252,38 @@ func escapeXML(s string) string { return s } -func findResourceType(fi serve.Resource) string { +func findResourceType(fi serve.ResourceInfo) string { if fi.FSDir() { return `` } return "" } -func findDisplayName(fi serve.Resource) string { +func findDisplayName(fi serve.ResourceInfo) string { return escapeXML(fi.FSName()) } -func findContentLength(fi serve.Resource) string { +func findContentLength(fi serve.ResourceInfo) string { return strconv.FormatInt(fi.FSContentSize(), 10) } -func findCreationDate(fi serve.Resource) string { +func findCreationDate(fi serve.ResourceInfo) string { return fi.FSCreated().UTC().Format(http.TimeFormat) } -func findLastModified(fi serve.Resource) string { +func findLastModified(fi serve.ResourceInfo) string { return fi.FSModified().UTC().Format(http.TimeFormat) } -func findContentType(fi serve.Resource) string { +func findContentType(fi serve.ResourceInfo) string { return fi.FSContentType() } -func findETag(fi serve.Resource) string { +func findETag(fi serve.ResourceInfo) string { return fi.FSContentSHA256() } -func findSupportedLock(fi serve.Resource) string { +func findSupportedLock(fi serve.ResourceInfo) string { return `` + `` + `` + diff --git a/server/internal/api/webdav/impl/webdav.go b/server/internal/api/webdav/impl/webdav.go index 68d437bf..f57affec 100644 --- a/server/internal/api/webdav/impl/webdav.go +++ b/server/internal/api/webdav/impl/webdav.go @@ -167,7 +167,7 @@ func (h *Handler) confirmLocks(r *http.Request, src, dst string) (release func() if res, err := h.FileSystem.ResourceByPath(path); err != nil { return false, err } else { - return res.ContentSHA256 == etag, nil + return res.FSContentSHA256() == etag, nil } }, l.conditions...) if err == ErrConfirmationFailed { @@ -192,7 +192,7 @@ func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) (status } allow := "OPTIONS, LOCK, PUT, MKCOL" if fi, err := h.FileSystem.ResourceByPath(reqPath); err == nil { - if fi.Dir { + if fi.FSDir() { allow = "OPTIONS, LOCK, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND" } else { allow = "OPTIONS, LOCK, GET, HEAD, POST, DELETE, PROPPATCH, COPY, MOVE, UNLOCK, PROPFIND, PUT" @@ -268,7 +268,7 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, } } else if err != nil { return http.StatusNotFound, err - } else if res.Dir { + } else if res.FSDir() { return http.StatusConflict, fs.ErrResourceCollection } @@ -286,7 +286,7 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int, if closeErr != nil { return http.StatusMethodNotAllowed, closeErr } - w.Header().Set("ETag", fi.ContentSHA256) + w.Header().Set("ETag", fi.FSContentSHA256()) return http.StatusCreated, nil } @@ -456,7 +456,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status mw := multistatusWriter{w: w} - writePropStat := func(r serve.Resource) error { + writePropStat := func(r serve.ResourceInfo) error { var pstats []Propstat if pf.Propname != nil { pnames, err := propnames(h.FileSystem, h.LockSystem, r) diff --git a/server/internal/command/fs/cat.go b/server/internal/command/fs/cat.go index 2887b992..afe91c73 100644 --- a/server/internal/command/fs/cat.go +++ b/server/internal/command/fs/cat.go @@ -23,7 +23,7 @@ func setupCatCommand() *cobra.Command { os.Exit(1) } - if r.Dir { + if r.FSDir() { fmt.Println("cannot read'" + pathOrUUID + "': is a directory") os.Exit(2) } diff --git a/server/internal/command/fs/cp.go b/server/internal/command/fs/cp.go index 22188b4a..89a53394 100644 --- a/server/internal/command/fs/cp.go +++ b/server/internal/command/fs/cp.go @@ -24,7 +24,7 @@ func setupCpCommand() *cobra.Command { } force, _ := cmd.Flags().GetBool("force") - if src.Dir { + if src.FSDir() { if recursive, err := cmd.Flags().GetBool("recursive"); err != nil || !recursive { fmt.Println("cannot copy'" + srcPathOrUUID + "' to '" + args[1] + "': must use -r to copy directories") os.Exit(1) diff --git a/server/internal/command/fs/import.go b/server/internal/command/fs/import.go index 5222da10..19daf56b 100644 --- a/server/internal/command/fs/import.go +++ b/server/internal/command/fs/import.go @@ -62,7 +62,7 @@ func setupImportCommand() *cobra.Command { ids["."], _ = uuid.NewUUID() create = append(create, db.CreateResourcesParams{ ID: ids["."], - Parent: destParent.ID, + Parent: destParent.ID(), Name: destName, Dir: stat.IsDir(), }) @@ -117,7 +117,7 @@ func setupImportCommand() *cobra.Command { return err } } else { - _, err := f.WithRoot(destParent.ID).ResourceByPath(destName) + _, err := f.WithRoot(destParent.ID()).ResourceByPath(destName) if err == nil { return errors.New("resource with name '" + destName + "' already exist. use -f to overwrite") } else if !errors.Is(err, fs.ErrResourceNotFound) { @@ -130,7 +130,7 @@ func setupImportCommand() *cobra.Command { } return err } - return dbh.UpdateResourceModified(ctx, destParent.ID) + return dbh.UpdateResourceModified(ctx, destParent.ID()) }) if err == nil { diff --git a/server/internal/command/fs/ls.go b/server/internal/command/fs/ls.go index a8fd4ac7..880e75df 100644 --- a/server/internal/command/fs/ls.go +++ b/server/internal/command/fs/ls.go @@ -25,10 +25,10 @@ func setupLsCommand() *cobra.Command { os.Exit(1) } - fmt.Println(r.Path + " " + r.InheritedPermissionsString()) - fmt.Println(formatRow(r.ID.String(), formatSize(r.ContentSize), r.ContentSHA256, ".", r.PermissionsString(), r.Publinks)) + fmt.Println(r.FSPath() + " " + r.FullPermissionsString()) + fmt.Println(formatRow(r.ID().String(), formatSize(r.FSContentSize()), r.FSContentSHA256(), ".", r.PermissionsString(), r.Publinks())) - if r.Dir { + if r.FSDir() { children, err := r.ReadDir(false) if err != nil { fmt.Println("cannot access '" + pathOrUUID + "': " + err.Error()) @@ -45,7 +45,7 @@ func setupLsCommand() *cobra.Command { return strings.Compare(strings.ToLower(a.FSName()), strings.ToLower((b.FSName()))) <= 0 }) for _, c := range children { - fmt.Println(formatResourceSummary(c.(fs.Resource))) + fmt.Println(formatResourceSummary(c.(fs.ResourceInfo))) } } }, @@ -68,10 +68,10 @@ func formatRow(id, size, sha256, name, permissions string, links int) string { return fmt.Sprintf("%s %2d %4s %s %-24s %s", id, links, size, sha256[0:8], name, permissions) } -func formatResourceSummary(r fs.Resource) string { - name := r.Name - if r.Dir { +func formatResourceSummary(r fs.ResourceInfo) string { + name := r.FSName() + if r.FSDir() { name += "/" } - return formatRow(r.ID.String(), formatSize(r.ContentSize), r.ContentSHA256, name, r.PermissionsString(), r.Publinks) + return formatRow(r.ID.String(), formatSize(r.FSContentSize()), r.FSContentSHA256(), name, r.PermissionsString(), r.Publinks) } diff --git a/server/internal/command/fs/rm.go b/server/internal/command/fs/rm.go index 578bc4af..1165ceef 100644 --- a/server/internal/command/fs/rm.go +++ b/server/internal/command/fs/rm.go @@ -22,7 +22,7 @@ func setupRmCommand() *cobra.Command { os.Exit(1) } - if r.Dir { + if r.FSDir() { if recursive, err := cmd.Flags().GetBool("recursive"); err != nil || !recursive { fmt.Println("cannot remove '" + pathOrUUID + "': Is a directory. Try again with '-r'") os.Exit(1) diff --git a/server/internal/command/user/add.go b/server/internal/command/user/add.go index 078e7879..fc1194c5 100644 --- a/server/internal/command/user/add.go +++ b/server/internal/command/user/add.go @@ -70,7 +70,7 @@ func setupUserAddCommand() *cobra.Command { fmt.Println("invalid value for flag 'chroot': " + err.Error()) os.Exit(1) } else { - root = r.ID + root = r.ID() } } @@ -110,7 +110,7 @@ func setupUserAddCommand() *cobra.Command { if _, err := home.UpdatePermissions(u.Username, fs.PermissionRead|fs.PermissionWrite|fs.PermissionShare); err != nil { return err } - return userManager.UpdateUserHome(u, home.ID) + return userManager.UpdateUserHome(u, home.ID()) } return nil diff --git a/server/internal/command/user/bookmarks.go b/server/internal/command/user/bookmarks.go index e00ff6cd..bf3e5baa 100644 --- a/server/internal/command/user/bookmarks.go +++ b/server/internal/command/user/bookmarks.go @@ -57,7 +57,7 @@ func setupBookmarksRemoveCommand() *cobra.Command { os.Exit(1) } - if err := user.CreateManager(context.Background()).RemoveBookmark(common.User(cmd), r.ID); err != nil { + if err := user.CreateManager(context.Background()).RemoveBookmark(common.User(cmd), r.ID()); err != nil { fmt.Println("unable to remove bookmark: " + err.Error()) os.Exit(1) } diff --git a/server/internal/command/user/cmd.go b/server/internal/command/user/cmd.go index 133afeb4..760342dd 100644 --- a/server/internal/command/user/cmd.go +++ b/server/internal/command/user/cmd.go @@ -44,7 +44,7 @@ func setupUserChrootCommand() *cobra.Command { if res, err := common.RootFileSystem().ResourceByPathOrUUID(idOrPath); err != nil { logrus.Fatal(err) } else { - userManager.UpdateUserRoot(user, res.ID) + userManager.UpdateUserRoot(user, res.ID()) } } }, diff --git a/server/internal/core/fs/copy_move.go b/server/internal/core/fs/copy_move.go index 2610c115..ad6884df 100644 --- a/server/internal/core/fs/copy_move.go +++ b/server/internal/core/fs/copy_move.go @@ -16,7 +16,7 @@ func (r Resource) Move(target string, overwrite bool) (Resource, bool, error) { } // Check source directory permissions - parent, err := r.f.ResourceByID(*r.ParentID) + parent, err := r.f.ResourceByID(*r.ParentID()) if err != nil { return Resource{}, false, err } @@ -35,8 +35,14 @@ func (r Resource) Move(target string, overwrite bool) (Resource, bool, error) { if !destParent.hasPermission(PermissionWrite) { return Resource{}, false, ErrInsufficientPermissions } - if res, err := r.f.db.ResourceByID(r.f.ctx, db.ResourceByIDParams{Root: r.ID, ResourceID: destParent.ID, Username: r.f.username}); err != nil || res.Found { - return Resource{}, false, ErrResourceMoveTargetSubdirectory + if res, err := r.f.db.ResourceByID(r.f.ctx, destParent.ID()); err != nil { + return Resource{}, false, err + } else { + for i := 1; i < len(res); i++ { + if res[i].ID == r.ID() { + return Resource{}, false, ErrResourceMoveTargetSubdirectory + } + } } var deleted = false @@ -49,14 +55,14 @@ func (r Resource) Move(target string, overwrite bool) (Resource, bool, error) { return err } } - if nameParent, err := f.db.UpdateResourceNameParent(f.ctx, db.UpdateResourceNameParentParams{ID: r.ID, Name: destName, Parent: destParent.ID}); err != nil { + if nameParent, err := f.db.UpdateResourceNameParent(f.ctx, db.UpdateResourceNameParentParams{ID: r.ID(), Name: destName, Parent: destParent.ID()}); err != nil { if strings.Contains(err.Error(), "unique_member_resource_name") { return ErrResourceNameConflict } return err } else { - r.Name = nameParent.Name - r.ParentID = nameParent.Parent + r.Ancestry[0].Name = nameParent.Name + r.Ancestry[0].ParentID = nameParent.Parent return nil } }) @@ -76,7 +82,7 @@ func (r Resource) Copy(target string, id uuid.UUID, recursive, overwrite bool) ( } return Resource{}, false, err } - if !destParent.Dir { + if !destParent.FSDir() { return Resource{}, false, ErrResourceNotCollection } @@ -84,35 +90,35 @@ func (r Resource) Copy(target string, id uuid.UUID, recursive, overwrite bool) ( return Resource{}, false, ErrInsufficientPermissions } - var tree []db.ResourceResult - if recursive && r.Dir { + var tree []db.PublinkedResource + if recursive && r.FSDir() { var err error - tree, err = r.f.db.ReadDir(r.f.ctx, db.ReadDirParams{ResourceID: r.ID, Path: r.Path, MinDepth: 1, MaxDepth: 1000}) + tree, err = r.f.db.ReadDir(r.f.ctx, db.ReadDirParams{ResourceID: r.ID(), MinDepth: 1, MaxDepth: 1000}) if err != nil { return Resource{}, false, err } } create := make([]db.CreateResourcesParams, 0, len(tree)+1) - copy := make(map[uuid.UUID]uuid.UUID) - ids := make(map[string]uuid.UUID) - if r.Dir { - ids[""] = id + contents := make(map[uuid.UUID]uuid.UUID) + ids := make(map[uuid.UUID]uuid.UUID) + if r.FSDir() { + ids[r.ID()] = id } else { - copy[r.ID] = id + contents[r.ID()] = id } create = append(create, db.CreateResourcesParams{ ID: id, - Parent: destParent.ID, + Parent: destParent.ID(), Name: destName, - Dir: r.Dir, - ContentSize: r.ContentSize, - ContentType: r.ContentType, - ContentSha256: r.ContentSHA256, + Dir: r.FSDir(), + ContentSize: r.FSContentSize(), + ContentType: r.FSContentType(), + ContentSha256: r.FSContentSHA256(), }) for _, src := range tree { id, _ := uuid.NewUUID() - parent := ids[src.Path[0:strings.LastIndex(src.Path, "/")]] + parent := ids[*src.Parent] create = append(create, db.CreateResourcesParams{ ID: id, @@ -125,9 +131,9 @@ func (r Resource) Copy(target string, id uuid.UUID, recursive, overwrite bool) ( }) if src.Dir { - ids[src.Path] = id + ids[src.ID] = id } else { - copy[src.ID] = id + contents[src.ID] = id } } @@ -147,12 +153,12 @@ func (r Resource) Copy(target string, id uuid.UUID, recursive, overwrite bool) ( } return err } - return dbh.UpdateResourceModified(f.ctx, destParent.ID) + return dbh.UpdateResourceModified(f.ctx, destParent.ID()) }) if err == nil { func() { - for k, v := range copy { + for k, v := range contents { if err := r.f.cs.CopyContents(k, v); err != nil { logrus.Warn("unable to copy " + k.String() + " to " + v.String() + ": " + err.Error()) } @@ -161,25 +167,36 @@ func (r Resource) Copy(target string, id uuid.UUID, recursive, overwrite bool) ( }() } - var dest = Resource{ - f: r.f, + parentID := destParent.ID() + var info = ResourceInfo{ ID: id, - ParentID: &destParent.ID, + ParentID: &parentID, Name: destName, - Dir: r.Dir, + Dir: r.FSDir(), Created: time.Now(), Modified: time.Now(), Deleted: nil, - ContentSize: r.ContentSize, - ContentType: r.ContentType, - ContentSHA256: r.ContentSHA256, + ContentSize: r.FSContentSize(), + ContentType: r.FSContentType(), + ContentSHA256: r.FSContentSHA256(), Permissions: "{}", - // Not Needed - // UserPermissions: ?? - // InheritedPermissions: ?? - // Path: destParent.Path + "/" + destName, + Publinks: 0, } - return dest, deleted, err + if destParent.FSPath() == "/" { + info.Path = "/" + info.Name + } else { + info.Path = destParent.FSPath() + "/" + info.Name + } + + ancestry := make([]ResourceInfo, len(destParent.Ancestry)+1) + copy(ancestry, destParent.Ancestry) + ancestry = append(ancestry, info) + created := Resource{ + f: r.f, + Ancestry: ancestry, + UserPermission: destParent.UserPermission, + } + return created, deleted, err } // targetByPathOrUUID splits a target path or uuid into its constituent components @@ -231,7 +248,7 @@ func (f filesystem) targetByPathOrUUID(src Resource, pathOrUUID string) (Resourc name := pathOrUUID[index+1:] if name == "" { - name = src.Name + name = src.FSName() } else if CheckNameInvalid(name) { return Resource{}, "", ErrResourceNameInvalid } diff --git a/server/internal/core/fs/create.go b/server/internal/core/fs/create.go index 7e112274..d057450f 100644 --- a/server/internal/core/fs/create.go +++ b/server/internal/core/fs/create.go @@ -32,7 +32,7 @@ func (f filesystem) CreateResourceByPath(path string, dir, recursive bool) (Reso } func (r Resource) CreateMemberResource(name string, id uuid.UUID, dir bool) (Resource, error) { - if !r.Dir { + if !r.FSDir() { return Resource{}, ErrResourceNotCollection } if !r.hasPermission(PermissionWrite) { @@ -47,7 +47,7 @@ func (r Resource) CreateMemberResource(name string, id uuid.UUID, dir bool) (Res var result db.Resource err := r.f.db.WithTx(r.f.ctx, func(d *db.DbHandler) error { var err error - parent := r.ID + parent := r.ID() if result, err = d.CreateResource(r.f.ctx, db.CreateResourceParams{ID: id, Parent: &parent, Name: name, Dir: dir}); err != nil { if strings.Contains(err.Error(), "unique_member_resource_name") { return ErrResourceNameConflict @@ -62,7 +62,7 @@ func (r Resource) CreateMemberResource(name string, id uuid.UUID, dir bool) (Res if err != nil { return Resource{}, err } - if !r.Dir { + if !r.FSDir() { out, err := r.f.cs.OpenWrite(id, nil, nil) if err == nil { err = out.Close() @@ -72,34 +72,54 @@ func (r Resource) CreateMemberResource(name string, id uuid.UUID, dir bool) (Res } } - p, err := MergePermissionStrings(r.InheritedPermissions, r.Permissions) - if err != nil { - return Resource{}, err - } - var deleted *time.Time - if result.Deleted.Valid { - deleted = &result.Deleted.Time - } - path := r.Path + "/" + result.Name - if r.Path == "/" { - path = result.Name + // return Resource{ + // f: r.f, + // ID: result.ID, + // ParentID: result.Parent, + // Name: result.Name, + // Created: result.Created.Time, + // Modified: result.Modified.Time, + // Deleted: deleted, + // Dir: result.Dir, + // ContentSize: result.ContentSize, + // ContentType: result.ContentType, + // ContentSHA256: result.ContentSha256, + // Permissions: string(result.Permissions), + // Path: path, + // UserPermission: r.UserPermission, + // }, nil + info := resourceInfoFromDBResource(result) + if r.FSPath() == "/" { + info.Path = "/" + info.Name + } else { + info.Path = r.FSPath() + "/" + info.Name } + ancestry := make([]ResourceInfo, len(r.Ancestry)+1) + copy(ancestry, r.Ancestry) + ancestry = append(ancestry, info) return Resource{ - f: r.f, - ID: result.ID, - ParentID: result.Parent, - Name: result.Name, - Created: result.Created.Time, - Modified: result.Modified.Time, - Deleted: deleted, - Dir: result.Dir, - ContentSize: result.ContentSize, - ContentType: result.ContentType, - ContentSHA256: result.ContentSha256, - Permissions: string(result.Permissions), - Path: path, - UserPermission: r.UserPermission, - InheritedPermissions: p, - Publinks: 0, + f: r.f, + Ancestry: ancestry, + UserPermission: r.UserPermission, }, nil } + +func resourceInfoFromDBResource(r db.Resource) ResourceInfo { + var delTime *time.Time + if r.Deleted.Valid { + delTime = &r.Deleted.Time + } + return ResourceInfo{ + ID: r.ID, + ParentID: r.Parent, + Name: r.Name, + Dir: r.Dir, + Created: r.Created.Time, + Modified: r.Modified.Time, + Deleted: delTime, + ContentSize: r.ContentSize, + ContentType: r.ContentType, + ContentSHA256: r.ContentSha256, + Permissions: string(r.Permissions), + } +} diff --git a/server/internal/core/fs/delete.go b/server/internal/core/fs/delete.go index fc99fcea..22294998 100644 --- a/server/internal/core/fs/delete.go +++ b/server/internal/core/fs/delete.go @@ -14,28 +14,28 @@ func (r Resource) DeleteChildRecursive(name string, hardDelete bool) error { return ErrInsufficientPermissions } - id, err := r.f.db.ChildResourceIDByName(r.f.ctx, db.ChildResourceIDByNameParams{Parent: r.ID, Name: name}) + id, err := r.f.db.ChildResourceIDByName(r.f.ctx, db.ChildResourceIDByNameParams{Parent: r.ID(), Name: name}) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return ErrResourceNotFound } return err } - return r.f.deleteRecursive(id, r.ID, hardDelete) + return r.f.deleteRecursive(id, r.ID(), hardDelete) } func (r Resource) DeleteRecursive(hardDelete bool) error { - if r.ParentID == nil { + if r.ParentID() == nil { return ErrInsufficientPermissions } - parent, err := r.f.ResourceByID(*r.ParentID) + parent, err := r.f.ResourceByID(*r.ParentID()) if err != nil { return err } if !parent.hasPermission(PermissionWrite) { return ErrInsufficientPermissions } - return r.f.deleteRecursive(r.ID, parent.ID, hardDelete) + return r.f.deleteRecursive(r.ID(), parent.ID(), hardDelete) } func (f filesystem) deleteRecursive(id, parent uuid.UUID, hardDelete bool) error { diff --git a/server/internal/core/fs/disk_usage.go b/server/internal/core/fs/disk_usage.go index e8bbd307..2f523a70 100644 --- a/server/internal/core/fs/disk_usage.go +++ b/server/internal/core/fs/disk_usage.go @@ -8,7 +8,7 @@ type DiskUsageInfo struct { } func (r Resource) DiskUsage() (DiskUsageInfo, error) { - if info, err := r.f.db.DiskUsage(r.f.ctx, r.ID); err != nil { + if info, err := r.f.db.DiskUsage(r.f.ctx, r.ID()); err != nil { return DiskUsageInfo{}, err } else { return DiskUsageInfo{ diff --git a/server/internal/core/fs/find.go b/server/internal/core/fs/find.go index 96f28ed2..a5d30c22 100644 --- a/server/internal/core/fs/find.go +++ b/server/internal/core/fs/find.go @@ -1,35 +1,86 @@ package fs import ( + "encoding/json" + "strings" "time" "github.com/google/uuid" - "github.com/jackc/pgx/v5" "github.com/shroff/phylum/server/internal/core/db" ) func (f filesystem) ResourceByPath(path string) (Resource, error) { - return f.resourceFromResult(f.db.ResourceByPath(f.ctx, db.ResourceByPathParams{Root: f.rootID, Path: path, Username: f.username})) + return f.resourceFromResult(f.db.ResourceByPath(f.ctx, db.ResourceByPathParams{Root: &f.rootID, Path: path})) } func (f filesystem) ResourceByID(id uuid.UUID) (Resource, error) { - return f.resourceFromResult(f.db.ResourceByID(f.ctx, db.ResourceByIDParams{Root: f.rootID, ResourceID: id, Username: f.username})) + return f.resourceFromResult(f.db.ResourceByID(f.ctx, id)) } -func (f filesystem) resourceFromResult(r db.ResourceResult, err error) (Resource, error) { - if err == pgx.ErrNoRows || !r.Found || (r.UserPermission&PermissionRead == 0) { +func (f filesystem) resourceFromResult(e []db.PublinkedResource, err error) (Resource, error) { + if len(e) == 0 { err = ErrResourceNotFound } if err != nil { return Resource{}, err } + var b strings.Builder + found := false + permission := Permission(0) + ancestry := make([]ResourceInfo, len(e)) + for i := len(e) - 1; i >= 0; i-- { + if i != len(e)-1 { + b.WriteByte('/') + b.WriteString(e[i].Name) + } + info := resourceInfoFromDBPublinkedResource(e[i]) + if b.Len() == 0 { + info.Path = "/" + } else { + info.Path = b.String() + } + ancestry[len(e)-i-1] = info + if e[i].ID == f.rootID { + found = true + } + if p, err := readPermissionFromJson(e[i].Permissions, f.username); err != nil { + return Resource{}, err + } else { + permission = permission | p + } + } + if !found || permission&PermissionRead == 0 { + return Resource{}, err + } + return Resource{ + f: f, + Ancestry: ancestry, + UserPermission: permission, + }, nil +} + +func readPermissionFromJson(j []byte, username string) (Permission, error) { + if len(j) == 2 { + return PermissionNone, nil + } + p := make(map[string]Permission) + err := json.Unmarshal(j, &p) + if err != nil { + return PermissionNone, err + } + if p, ok := p[username]; ok { + return p, nil + } + return PermissionNone, nil +} + +func resourceInfoFromDBPublinkedResource(r db.PublinkedResource) ResourceInfo { var delTime *time.Time if r.Deleted.Valid { delTime = &r.Deleted.Time } - return Resource{ - f: f, + return ResourceInfo{ ID: r.ID, ParentID: r.Parent, Name: r.Name, @@ -42,11 +93,7 @@ func (f filesystem) resourceFromResult(r db.ResourceResult, err error) (Resource ContentSHA256: r.ContentSha256, Permissions: string(r.Permissions), Publinks: int(r.Publinks), - // Definitely Needed - Path: r.Path, - UserPermission: r.UserPermission, - InheritedPermissions: string(r.InheritedPermissions), - }, nil + } } func (f filesystem) ResourceByPathOrUUID(pathOrUUID string) (Resource, error) { diff --git a/server/internal/core/fs/open.go b/server/internal/core/fs/open.go index b0448ffb..383d8e7b 100644 --- a/server/internal/core/fs/open.go +++ b/server/internal/core/fs/open.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "io" + "github.com/google/uuid" "github.com/shroff/phylum/server/internal/api/serve" "github.com/shroff/phylum/server/internal/core/db" ) @@ -12,16 +13,16 @@ func (r Resource) OpenRead(start, length int64) (io.ReadCloser, error) { if !r.hasPermission(PermissionRead) { return nil, ErrInsufficientPermissions } - return r.f.cs.OpenRead(r.ID, start, length) + return r.f.cs.OpenRead(r.ID(), start, length) } func (r Resource) OpenWrite() (io.WriteCloser, error) { if !r.hasPermission(PermissionWrite) { return nil, ErrInsufficientPermissions } - return r.f.cs.OpenWrite(r.ID, sha256.New, func(len int, sum, mime string) error { + return r.f.cs.OpenWrite(r.ID(), sha256.New, func(len int, sum, mime string) error { return r.f.db.UpdateResourceContents(r.f.ctx, db.UpdateResourceContentsParams{ - ID: r.ID, + ID: r.ID(), ContentType: mime, ContentSize: int64(len), ContentSha256: sum, @@ -29,11 +30,11 @@ func (r Resource) OpenWrite() (io.WriteCloser, error) { }) } -func (r Resource) ReadDir(recursive bool) ([]serve.Resource, error) { +func (r Resource) ReadDir(recursive bool) ([]serve.ResourceInfo, error) { if !r.hasPermission(PermissionRead) { return nil, ErrInsufficientPermissions } - if !r.Dir { + if !r.FSDir() { return nil, ErrResourceNotCollection } maxDepth := 1 @@ -41,43 +42,30 @@ func (r Resource) ReadDir(recursive bool) ([]serve.Resource, error) { maxDepth = 1000 } children, err := r.f.db.ReadDir(r.f.ctx, db.ReadDirParams{ - ResourceID: r.ID, - InheritedPermissions: []byte(r.InheritedPermissions), - Path: r.Path, - Username: r.f.username, - MinDepth: 1, - MaxDepth: int32(maxDepth), + ResourceID: r.ID(), + MinDepth: 1, + MaxDepth: int32(maxDepth), }) if err != nil { return nil, err } - result := make([]serve.Resource, len(children)) - + result := make([]serve.ResourceInfo, len(children)) + path := make(map[uuid.UUID]string) + path[r.ID()] = r.FSPath() + if r.FSPath() == "/" { + path[r.ID()] = "" + } for i, c := range children { - result[i] = Resource{ - f: r.f, - ID: c.ID, - ParentID: c.Parent, - Name: c.Name, - Created: c.Created.Time, - Modified: c.Modified.Time, - Deleted: nil, // Query will not return deleted results - Dir: c.Dir, - ContentSize: c.ContentSize, - ContentType: c.ContentType, - ContentSHA256: c.ContentSha256, - Permissions: string(c.Permissions), - Publinks: int(c.Publinks), - Path: c.Path, - UserPermission: c.UserPermission, - InheritedPermissions: string(c.InheritedPermissions), - } + info := resourceInfoFromDBPublinkedResource(c) + info.Path = path[*info.ParentID] + "/" + info.Name + path[info.ID] = info.Path + result[i] = info } return result, nil } -func (r Resource) Walk(depth int, fn func(serve.Resource) error) error { +func (r Resource) Walk(depth int, fn func(serve.ResourceInfo) error) error { err := fn(r) if err != nil { return err diff --git a/server/internal/core/fs/permission.go b/server/internal/core/fs/permission.go index 08f453b7..359edc9b 100644 --- a/server/internal/core/fs/permission.go +++ b/server/internal/core/fs/permission.go @@ -22,10 +22,27 @@ func (r Resource) hasPermission(p Permission) bool { } func (r Resource) PermissionsString() string { + return r.Ancestry[0].PermissionsString() +} + +func (r ResourceInfo) PermissionsString() string { return permissionJsonString(r.Permissions) } -func (r Resource) InheritedPermissionsString() string { - return permissionJsonString(r.InheritedPermissions) + +func (r Resource) FullPermissionsString() string { + result := make(map[string]Permission) + for _, i := range r.Ancestry { + p := make(map[string]Permission) + json.Unmarshal([]byte(i.Permissions), &p) + for k, v := range p { + result[k] = result[k] | v + } + } + res, err := json.Marshal(result) + if err != nil { + return err.Error() + } + return permissionJsonString(string(res)) } func permissionJsonString(j string) string { diff --git a/server/internal/core/fs/publink.go b/server/internal/core/fs/publink.go index 4e4647ed..893434d6 100644 --- a/server/internal/core/fs/publink.go +++ b/server/internal/core/fs/publink.go @@ -35,7 +35,7 @@ func (r Resource) CreatePublink(name, password string, duration time.Duration, a return r.f.db.CreatePublink(r.f.ctx, db.CreatePublinkParams{ Name: name, CreatedBy: r.f.username, - Root: r.ID, + Root: r.ID(), PasswordHash: passwordHash, Expires: expires, MaxAccesses: int32(accesses), @@ -43,7 +43,7 @@ func (r Resource) CreatePublink(name, password string, duration time.Duration, a } func (r Resource) ListPublinks() ([]publink.Info, error) { - res, err := r.f.db.PublinksByRoot(r.f.ctx, r.ID) + res, err := r.f.db.PublinksByRoot(r.f.ctx, r.ID()) if err != nil { return nil, err } diff --git a/server/internal/core/fs/resource.go b/server/internal/core/fs/resource.go index 11fc9ed0..d6f6b2ca 100644 --- a/server/internal/core/fs/resource.go +++ b/server/internal/core/fs/resource.go @@ -7,29 +7,46 @@ import ( ) type Resource struct { - f filesystem - ID uuid.UUID `json:"id"` - ParentID *uuid.UUID `json:"parent"` - Name string `json:"name"` - Dir bool `json:"dir"` - Created time.Time `json:"created"` - Modified time.Time `json:"modified"` - Deleted *time.Time `json:"deleted"` - ContentSize int64 `json:"csize"` - ContentType string `json:"ctype"` - ContentSHA256 string `json:"csha256"` - Permissions string `json:"permissions"` - Publinks int `json:"publinks"` - Path string `json:"path"` - UserPermission Permission `json:"perm"` - InheritedPermissions string `json:"inherited"` + f filesystem + Ancestry []ResourceInfo `json:"ancestry"` + UserPermission Permission `json:"perm"` } -func (r Resource) FSName() string { return r.Name } -func (r Resource) FSPath() string { return r.Path } -func (r Resource) FSDir() bool { return r.Dir } -func (r Resource) FSCreated() time.Time { return r.Created } -func (r Resource) FSModified() time.Time { return r.Modified } -func (r Resource) FSContentSize() int64 { return r.ContentSize } -func (r Resource) FSContentSHA256() string { return r.ContentSHA256 } -func (r Resource) FSContentType() string { return r.ContentType } +type ResourceInfo struct { + ID uuid.UUID `json:"id"` + ParentID *uuid.UUID `json:"parent"` + Name string `json:"name"` + Dir bool `json:"dir"` + Created time.Time `json:"created"` + Modified time.Time `json:"modified"` + Deleted *time.Time `json:"deleted"` + ContentSize int64 `json:"csize"` + ContentType string `json:"ctype"` + ContentSHA256 string `json:"csha256"` + Permissions string `json:"permissions"` + Publinks int `json:"publinks"` + Path string `json:"-"` +} + +func (r Resource) Info() ResourceInfo { return r.Ancestry[len(r.Ancestry)-1] } +func (r Resource) ID() uuid.UUID { return r.Info().ID } +func (r Resource) ParentID() *uuid.UUID { return r.Info().ParentID } +func (r Resource) FSName() string { return r.Info().Name } +func (r Resource) FSDir() bool { return r.Info().Dir } +func (r Resource) FSCreated() time.Time { return r.Info().Created } +func (r Resource) FSModified() time.Time { return r.Info().Modified } +func (r Resource) Deleted() *time.Time { return r.Info().Deleted } +func (r Resource) FSContentSize() int64 { return r.Info().ContentSize } +func (r Resource) FSContentSHA256() string { return r.Info().ContentSHA256 } +func (r Resource) FSContentType() string { return r.Info().ContentType } +func (r Resource) Publinks() int { return r.Info().Publinks } +func (r Resource) FSPath() string { return r.Info().Path } + +func (r ResourceInfo) FSName() string { return r.Name } +func (r ResourceInfo) FSDir() bool { return r.Dir } +func (r ResourceInfo) FSCreated() time.Time { return r.Created } +func (r ResourceInfo) FSModified() time.Time { return r.Modified } +func (r ResourceInfo) FSContentSize() int64 { return r.ContentSize } +func (r ResourceInfo) FSContentSHA256() string { return r.ContentSHA256 } +func (r ResourceInfo) FSContentType() string { return r.ContentType } +func (r ResourceInfo) FSPath() string { return r.Path } diff --git a/server/internal/core/fs/update.go b/server/internal/core/fs/update.go index 681335d4..862fa28c 100644 --- a/server/internal/core/fs/update.go +++ b/server/internal/core/fs/update.go @@ -15,12 +15,12 @@ func (r Resource) UpdatePermissions(username string, permission Permission) (Res var err error if permission == 0 { p, err = r.f.db.RemoveUserPermissionForResource(r.f.ctx, db.RemoveUserPermissionForResourceParams{ - ResourceID: r.ID, + ResourceID: r.ID(), Username: username, }) } else { p, err = r.f.db.UpdateUserPermissionsForResource(r.f.ctx, db.UpdateUserPermissionsForResourceParams{ - ResourceID: r.ID, + ResourceID: r.ID(), Username: username, Permission: permission, }) @@ -28,6 +28,6 @@ func (r Resource) UpdatePermissions(username string, permission Permission) (Res if err != nil { return Resource{}, err } - r.Permissions = string(p) + r.Ancestry[0].Permissions = string(p) return r, nil } diff --git a/server/internal/core/publink/filesystem.go b/server/internal/core/publink/filesystem.go index fdee997d..33cfafa7 100644 --- a/server/internal/core/publink/filesystem.go +++ b/server/internal/core/publink/filesystem.go @@ -17,7 +17,7 @@ type filesystem struct { } func (f filesystem) resourceByPath(search string) (Resource, error) { - e, err := f.db.ResourceByPath(f.ctx, db.ResourceByPathParams{Path: search}) + e, err := f.db.ResourceByPath(f.ctx, db.ResourceByPathParams{Path: search, Root: &f.root}) if len(e) == 0 { err = ErrNotFound } @@ -30,14 +30,18 @@ func (f filesystem) resourceByPath(search string) (Resource, error) { if e[i].ID == f.root { found = true } - if b.Len() != 0 { + if i != len(e)-i { b.WriteByte('/') + b.WriteString(e[i].Name) } - b.WriteString(e[i].Name) } if !found { return Resource{}, err } + path := b.String() + if b.Len() == 0 { + path = "/" + } r := e[0] return Resource{ @@ -45,7 +49,7 @@ func (f filesystem) resourceByPath(search string) (Resource, error) { id: r.ID, name: r.Name, dir: r.Dir, - path: b.String(), + path: path, created: r.Created.Time, modified: r.Modified.Time, contentSize: r.ContentSize, diff --git a/server/internal/core/publink/resource.go b/server/internal/core/publink/resource.go index b0739abc..7d2c38c8 100644 --- a/server/internal/core/publink/resource.go +++ b/server/internal/core/publink/resource.go @@ -37,7 +37,7 @@ func (r Resource) OpenRead(start, length int64) (io.ReadCloser, error) { return r.f.cs.OpenRead(r.id, start, length) } -func (r Resource) ReadDir(recursive bool) ([]serve.Resource, error) { +func (r Resource) ReadDir(recursive bool) ([]serve.ResourceInfo, error) { if !r.dir { return nil, errors.New("resource is not a collection") } @@ -53,7 +53,7 @@ func (r Resource) ReadDir(recursive bool) ([]serve.Resource, error) { return nil, err } - result := make([]serve.Resource, len(children)) + result := make([]serve.ResourceInfo, len(children)) parentPath := r.path if parentPath == "/" { diff --git a/server/internal/core/user/user.go b/server/internal/core/user/user.go index 493e059c..b9a5504b 100644 --- a/server/internal/core/user/user.go +++ b/server/internal/core/user/user.go @@ -41,8 +41,8 @@ type Manager interface { ReadAccessToken(accessToken string) (User, error) // user_lists.go - SharedResources(u User) (result []fs.Resource, err error) - Bookmarks(u User) ([]fs.Resource, error) + SharedResources(u User) (result []fs.ResourceInfo, err error) + Bookmarks(u User) ([]fs.ResourceInfo, error) AddBookmark(u User, resource fs.Resource) error RemoveBookmark(u User, id uuid.UUID) error } diff --git a/server/internal/core/user/user_lists.go b/server/internal/core/user/user_lists.go index 72b8686d..eb9c11e4 100644 --- a/server/internal/core/user/user_lists.go +++ b/server/internal/core/user/user_lists.go @@ -8,14 +8,14 @@ import ( "github.com/shroff/phylum/server/internal/core/fs" ) -func (m manager) SharedResources(u User) ([]fs.Resource, error) { +func (m manager) SharedResources(u User) ([]fs.ResourceInfo, error) { // TODO: #permissions This doesn't take permissions into account. is this okay? res, err := m.db.Queries.SharedResources(m.ctx, db.SharedResourcesParams{Username: u.Username, UserHome: u.Home}) if err != nil { return nil, err } - result := make([]fs.Resource, len(res)) + result := make([]fs.ResourceInfo, len(res)) for i, r := range res { result[i] = resourceFromDB(r) } @@ -23,33 +23,33 @@ func (m manager) SharedResources(u User) ([]fs.Resource, error) { } func (m manager) AddBookmark(u User, resource fs.Resource) error { - return m.db.Queries.AddBookmark(m.ctx, db.AddBookmarkParams{Username: u.Username, ID: resource.ID}) + return m.db.Queries.AddBookmark(m.ctx, db.AddBookmarkParams{Username: u.Username, ID: resource.ID()}) } func (m manager) RemoveBookmark(u User, id uuid.UUID) error { return m.db.Queries.RemoveBookmark(m.ctx, db.RemoveBookmarkParams{Username: u.Username, ID: id}) } -func (m manager) Bookmarks(u User) ([]fs.Resource, error) { +func (m manager) Bookmarks(u User) ([]fs.ResourceInfo, error) { // TODO: #permissions This doesn't take permissions into account. is this okay? res, err := m.db.Queries.ListBookmarks(m.ctx, u.Username) if err != nil { return nil, err } - result := make([]fs.Resource, len(res)) + result := make([]fs.ResourceInfo, len(res)) for i, r := range res { result[i] = resourceFromDB(r) } return result, err } -func resourceFromDB(r db.Resource) fs.Resource { +func resourceFromDB(r db.Resource) fs.ResourceInfo { var deleted *time.Time if r.Deleted.Valid { deleted = &r.Deleted.Time } - return fs.Resource{ + return fs.ResourceInfo{ ID: r.ID, ParentID: r.Parent, Name: r.Name,