mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-01-06 03:31:02 -06:00
Merge fileInfo and file into resource
This commit is contained in:
@@ -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)
|
||||
}
|
||||
76
server/internal/app/core/resource.go
Normal file
76
server/internal/app/core/resource.go
Normal file
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 `<D:collection xmlns:D="DAV:"/>`
|
||||
}
|
||||
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 `` +
|
||||
`<D:lockentry xmlns:D="DAV:">` +
|
||||
`<D:lockscope><D:exclusive/></D:lockscope>` +
|
||||
|
||||
@@ -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, "</pre>\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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user