diff --git a/server/internal/app/core/file.go b/server/internal/app/core/file.go
deleted file mode 100644
index 2bd069bb..00000000
--- a/server/internal/app/core/file.go
+++ /dev/null
@@ -1,77 +0,0 @@
-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
- silo Silo
-}
-
-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.silo.OpenRead(ctx, f.id, start, length)
-}
-
-func (f file) ReadDir(ctx context.Context) ([]FileInfo, error) {
- return f.silo.ReadDir(ctx, f.id, false, false)
-}
diff --git a/server/internal/app/core/resource.go b/server/internal/app/core/resource.go
new file mode 100644
index 00000000..6490beda
--- /dev/null
+++ b/server/internal/app/core/resource.go
@@ -0,0 +1,76 @@
+package core
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "mime"
+ "path/filepath"
+ "time"
+
+ "github.com/google/uuid"
+)
+
+type ResourceInfo interface {
+ ID() uuid.UUID
+ Name() string
+ Size() int64
+ ModTime() time.Time
+ IsDir() bool
+ ETag() string
+ ContentType() string
+}
+
+type Resource interface {
+ ResourceInfo
+ OpenRead(ctx context.Context, start, length int64) (io.ReadCloser, error)
+ ReadDir(ctx context.Context) ([]Resource, error)
+}
+
+type resource struct {
+ silo Silo
+ id uuid.UUID
+ parentID *uuid.UUID
+ name string
+ size int64
+ collection bool
+ modTime time.Time
+ etag string
+}
+
+func (r resource) ID() uuid.UUID { return r.id }
+
+func (r resource) ParentID() *uuid.UUID { return r.parentID }
+
+func (r resource) Name() string { return r.name }
+
+func (r resource) Size() int64 { return r.size }
+
+func (r resource) ModTime() time.Time { return r.modTime }
+
+func (r resource) IsDir() bool { return r.collection }
+
+func (r resource) ETag() string {
+ if r.etag != "" {
+ return r.etag
+ }
+ // The Apache http 2.4 web server by default concatenates the
+ // modification time and size of a file.
+ return fmt.Sprintf(`"%x%x"`, r.modTime.UnixMilli(), r.size)
+}
+
+func (r resource) ContentType() string {
+ mimeType := mime.TypeByExtension(filepath.Ext(r.name))
+ if mimeType != "" {
+ return mimeType
+ }
+ return "application/octet-stream"
+}
+
+func (f resource) OpenRead(ctx context.Context, start, length int64) (io.ReadCloser, error) {
+ return f.silo.OpenRead(ctx, f.id, start, length)
+}
+
+func (f resource) ReadDir(ctx context.Context) ([]Resource, error) {
+ return f.silo.ReadDir(ctx, f.id, false, false)
+}
diff --git a/server/internal/app/core/silo.go b/server/internal/app/core/silo.go
index 20650d4e..054b624c 100644
--- a/server/internal/app/core/silo.go
+++ b/server/internal/app/core/silo.go
@@ -20,9 +20,9 @@ type Silo interface {
StorageName() string
OpenRead(ctx context.Context, id uuid.UUID, start, length int64) (io.ReadCloser, error)
OpenWrite(ctx context.Context, id uuid.UUID) (io.WriteCloser, error)
- ReadDir(ctx context.Context, id uuid.UUID, includeRoot bool, recursive bool) ([]FileInfo, error)
+ ReadDir(ctx context.Context, id uuid.UUID, includeRoot bool, recursive bool) ([]Resource, error)
DeleteRecursive(ctx context.Context, id uuid.UUID, hardDelete bool) error
- ResourceByPath(ctx context.Context, path string) (File, error)
+ ResourceByPath(ctx context.Context, path string) (Resource, error)
Move(ctx context.Context, id uuid.UUID, parent uuid.UUID, name string) error
CreateResource(ctx context.Context, id uuid.UUID, parent uuid.UUID, name string, dir bool) error
}
@@ -75,20 +75,22 @@ func (s *silo) OpenWrite(ctx context.Context, id uuid.UUID) (io.WriteCloser, err
})
}
-func (s *silo) ReadDir(ctx context.Context, id uuid.UUID, includeRoot bool, recursive bool) ([]FileInfo, error) {
+func (s *silo) ReadDir(ctx context.Context, id uuid.UUID, includeRoot bool, recursive bool) ([]Resource, error) {
children, err := s.db.Queries().ReadDir(ctx, sql.ReadDirParams{ID: id, IncludeRoot: includeRoot, Recursive: recursive})
if err != nil {
return nil, err
}
- result := make([]FileInfo, len(children))
+ result := make([]Resource, len(children))
for i, c := range children {
- result[i] = fileInfo{
+ result[i] = resource{
+ silo: s,
+ id: c.ID,
+ parentID: &id,
name: c.Name,
size: int64(c.Size.Int32),
modTime: c.Modified.Time,
collection: c.Dir,
- id: c.ID,
etag: c.Etag.String,
}
}
@@ -138,7 +140,7 @@ func (s *silo) Move(ctx context.Context, id uuid.UUID, parent uuid.UUID, name st
return s.db.Queries().Rename(ctx, sql.RenameParams{ID: id, Parent: parent, Name: name})
}
-func (s *silo) ResourceByPath(ctx context.Context, path string) (File, error) {
+func (s *silo) ResourceByPath(ctx context.Context, path string) (Resource, error) {
path = strings.Trim(path, "/")
segments := strings.Split(path, "/")
if path == "" {
@@ -153,15 +155,14 @@ func (s *silo) ResourceByPath(ctx context.Context, path string) (File, error) {
//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,
- },
- silo: s,
+ return resource{
+ silo: s,
+ id: res.ID,
+ parentID: res.Parent,
+ name: res.Name,
+ size: int64(res.Size.Int32),
+ collection: res.Dir,
+ modTime: res.Modified.Time,
+ etag: res.Etag.String,
}, nil
}
diff --git a/server/internal/handler_webdav/handler.go b/server/internal/handler_webdav/handler.go
index 6c1cd84c..3cc8548d 100644
--- a/server/internal/handler_webdav/handler.go
+++ b/server/internal/handler_webdav/handler.go
@@ -77,7 +77,7 @@ type adapter struct {
silo core.Silo
}
-func (a adapter) Stat(ctx context.Context, name string) (core.File, error) {
+func (a adapter) Stat(ctx context.Context, name string) (core.Resource, error) {
return a.silo.ResourceByPath(ctx, name)
}
diff --git a/server/internal/webdav/file.go b/server/internal/webdav/file.go
index 270acfef..18f03291 100644
--- a/server/internal/webdav/file.go
+++ b/server/internal/webdav/file.go
@@ -19,7 +19,7 @@ import (
// 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)
+ Stat(ctx context.Context, name string) (core.Resource, error)
Mkdir(ctx context.Context, name string) error
RemoveAll(ctx context.Context, name string) error
Rename(ctx context.Context, oldName, newName string) error
@@ -146,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 core.FileInfo, err error) error
+type WalkFunc func(path string, info core.Resource, 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 core.FileInfo, walkFn WalkFunc) error {
+func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info core.Resource, 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 {
diff --git a/server/internal/webdav/prop.go b/server/internal/webdav/prop.go
index 81210cb3..cde76e27 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.FileInfo) string
+ findFn func(core.ResourceInfo) 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.FileInfo) string {
+func findResourceType(fi core.ResourceInfo) string {
if fi.IsDir() {
return ``
}
return ""
}
-func findDisplayName(fi core.FileInfo) string {
+func findDisplayName(fi core.ResourceInfo) string {
return escapeXML(fi.Name())
}
-func findContentLength(fi core.FileInfo) string {
+func findContentLength(fi core.ResourceInfo) string {
return strconv.FormatInt(fi.Size(), 10)
}
-func findLastModified(fi core.FileInfo) string {
+func findLastModified(fi core.ResourceInfo) string {
return fi.ModTime().UTC().Format(http.TimeFormat)
}
-func findContentType(fi core.FileInfo) string {
+func findContentType(fi core.ResourceInfo) string {
return fi.ContentType()
}
-func findETag(fi core.FileInfo) string {
+func findETag(fi core.ResourceInfo) string {
return fi.ETag()
}
-func findSupportedLock(fi core.FileInfo) string {
+func findSupportedLock(fi core.ResourceInfo) string {
return `` +
`` +
`` +
diff --git a/server/internal/webdav/serve_resource.go b/server/internal/webdav/serve_resource.go
index 0a53316e..701eaa9f 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.File) {
+func serveCollection(w http.ResponseWriter, r *http.Request, file core.Resource) {
if !strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, r, r.URL.String()+"/", http.StatusMovedPermanently)
return
@@ -59,7 +59,7 @@ func serveCollection(w http.ResponseWriter, r *http.Request, file core.File) {
fmt.Fprintf(w, "\n")
}
-func serveResource(w http.ResponseWriter, r *http.Request, file core.File) {
+func serveResource(w http.ResponseWriter, r *http.Request, 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())
@@ -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.FileInfo) (done bool, rangeHeader string) {
+func checkPreconditions(w http.ResponseWriter, r *http.Request, ri core.ResourceInfo) (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.FileInfo) condResult {
+func checkIfMatch(r *http.Request, ri core.ResourceInfo) condResult {
im := r.Header.Get("If-Match")
if im == "" {
return condNone
@@ -319,7 +319,7 @@ func checkIfMatch(r *http.Request, ri core.FileInfo) condResult {
return condFalse
}
-func checkIfUnmodifiedSince(r *http.Request, ri core.FileInfo) condResult {
+func checkIfUnmodifiedSince(r *http.Request, ri core.ResourceInfo) 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.FileInfo) condResult {
return condFalse
}
-func checkIfNoneMatch(r *http.Request, ri core.FileInfo) condResult {
+func checkIfNoneMatch(r *http.Request, ri core.ResourceInfo) condResult {
inm := r.Header.Get("If-None-Match")
if inm == "" {
return condNone
@@ -368,7 +368,7 @@ func checkIfNoneMatch(r *http.Request, ri core.FileInfo) condResult {
return condTrue
}
-func checkIfModifiedSince(r *http.Request, ri core.FileInfo) condResult {
+func checkIfModifiedSince(r *http.Request, ri core.ResourceInfo) condResult {
if r.Method != "GET" && r.Method != "HEAD" {
return condNone
}
@@ -389,7 +389,7 @@ func checkIfModifiedSince(r *http.Request, ri core.FileInfo) condResult {
return condTrue
}
-func checkIfRange(r *http.Request, ri core.FileInfo) condResult {
+func checkIfRange(r *http.Request, ri core.ResourceInfo) 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 2725dcae..a863eee6 100644
--- a/server/internal/webdav/webdav.go
+++ b/server/internal/webdav/webdav.go
@@ -524,7 +524,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
mw := multistatusWriter{w: w}
- walkFn := func(reqPath string, info core.FileInfo, err error) error {
+ walkFn := func(reqPath string, info core.Resource, err error) error {
if err != nil {
return handlePropfindError(err, info)
}
@@ -625,7 +625,7 @@ func makePropstatResponse(href string, pstats []Propstat) *response {
return &resp
}
-func handlePropfindError(err error, info core.FileInfo) error {
+func handlePropfindError(err error, info core.ResourceInfo) error {
var skipResp error = nil
if info.IsDir() {
skipResp = filepath.SkipDir