Interfaces for core functionality

This commit is contained in:
Abhishek Shroff
2024-03-04 21:55:18 +05:30
parent c8f138d6d1
commit 3acf6abb59
9 changed files with 242 additions and 199 deletions

View File

@@ -14,7 +14,7 @@ import (
)
var debugMode bool = false
var backend phylumfs.PhylumFs
var backend phylumfs.Backend
func Setup() {
viper.SetEnvPrefix("phylum")
@@ -51,9 +51,6 @@ func Setup() {
if err != nil {
log.Fatal(fmt.Sprintf("Unable to parse db connection String: %v\n", err))
}
if debugMode {
config.Tracer = phylumTracer{}
}
conn, err = pgx.ConnectConfig(context.Background(), config)
if err != nil {
log.Fatal(fmt.Sprintf("Unable to connect to database: %v\n", err))
@@ -71,14 +68,3 @@ func Setup() {
rootCmd.AddCommand([]*cobra.Command{setupServeCommand(), setupAdminCommand()}...)
rootCmd.Execute()
}
type phylumTracer struct {
}
func (p phylumTracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
log.Trace(fmt.Sprintf("%+v\n", data))
return ctx
}
func (p phylumTracer) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
}

View File

@@ -1,16 +1,13 @@
package cmds
import (
"context"
"fmt"
"strings"
"time"
webdav "github.com/emersion/go-webdav"
"github.com/fvbock/endless"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/shroff/phylum/server/internal/pgfs"
"github.com/shroff/phylum/server/internal/webdav"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -54,41 +51,18 @@ func setupServeCommand() *cobra.Command {
}
func setupWebdav(r *gin.RouterGroup) {
log.Info(fmt.Sprintf("Setting up WebDAV access at %s", r.BasePath()))
webdavHandler := webdav.Handler{
FileSystem: pgfs.New(backend, r.BasePath()),
}
handler := func(c *gin.Context) {
path := c.Params.ByName("path")
path = strings.TrimLeft(path, "/")
if path == "" {
// No path specified
c.Writer.WriteHeader(404)
return
}
index := strings.Index(path, "/")
if index != -1 {
path = path[0:index]
}
_, err := backend.FindRoot(context.Background(), path)
if err != nil {
c.Writer.WriteHeader(404)
return
}
webdavHandler.ServeHTTP(c.Writer, c.Request)
}
r.Handle("OPTIONS", "/*path", handler)
r.Handle("GET", "/*path", handler)
r.Handle("PUT", "/*path", handler)
r.Handle("HEAD", "/*path", handler)
r.Handle("POST", "/*path", handler)
r.Handle("DELETE", "/*path", handler)
r.Handle("MOVE", "/*path", handler)
r.Handle("COPY", "/*path", handler)
r.Handle("MKCOL", "/*path", handler)
r.Handle("PROPFIND", "/*path", handler)
r.Handle("PROPPATCH", "/*path", handler)
handler := webdav.NewHandler(backend, r.BasePath())
r.Handle("OPTIONS", "/*path", handler.HandleRequest)
r.Handle("GET", "/*path", handler.HandleRequest)
r.Handle("PUT", "/*path", handler.HandleRequest)
r.Handle("HEAD", "/*path", handler.HandleRequest)
r.Handle("POST", "/*path", handler.HandleRequest)
r.Handle("DELETE", "/*path", handler.HandleRequest)
r.Handle("MOVE", "/*path", handler.HandleRequest)
r.Handle("COPY", "/*path", handler.HandleRequest)
r.Handle("MKCOL", "/*path", handler.HandleRequest)
r.Handle("PROPFIND", "/*path", handler.HandleRequest)
r.Handle("PROPPATCH", "/*path", handler.HandleRequest)
}
func createEngine(corsEnabled bool, corsOrigins []string) *gin.Engine {

View File

@@ -0,0 +1,56 @@
package phylumfs
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/shroff/phylum/server/internal/sql"
)
type Backend interface {
FindRoot(ctx context.Context, name string) (Fs, error)
Mkroot(ctx context.Context, id uuid.UUID, name string) error
Rmroot(ctx context.Context, name string) error
}
type pgxBackend struct {
queries sql.Queries
cs contentStore
}
func New(conn *pgx.Conn) Backend {
queries := *sql.New(conn)
cs, err := newLocalContentStore("srv")
if err != nil {
panic(err)
}
return pgxBackend{queries: queries, cs: cs}
}
func (b pgxBackend) FindRoot(ctx context.Context, name string) (Fs, error) {
// TODO: Permissions checks
root, err := b.queries.FindRoot(ctx, name)
if err != nil {
return nil, err
}
return pgxFs{queries: b.queries, root: root.ID, cs: b.cs}, nil
}
func (b pgxBackend) Mkroot(ctx context.Context, id uuid.UUID, name string) error {
if _, err := b.queries.FindRoot(ctx, name); err == nil {
return fmt.Errorf("root directory already exists: %s", name)
} else {
return b.queries.CreateResource(ctx, sql.CreateResourceParams{ID: id, Parent: nil, Name: name, Dir: true})
}
}
func (b pgxBackend) Rmroot(ctx context.Context, name string) error {
if fs, err := b.FindRoot(ctx, name); err == nil {
return fs.DeleteRecursive(ctx, fs.rootId())
} else {
return fmt.Errorf("root directory does not exist: %s", name)
}
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/google/uuid"
)
type ContentStore interface {
type contentStore interface {
Open(id uuid.UUID) (io.ReadCloser, error)
Create(id uuid.UUID, callback func(hash.Hash, int, error)) (io.WriteCloser, error)
Delete(id uuid.UUID) error

88
internal/phylumfs/fs.go Normal file
View File

@@ -0,0 +1,88 @@
package phylumfs
import (
"context"
"encoding/base64"
"hash"
"io"
iofs "io/fs"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/shroff/phylum/server/internal/sql"
"github.com/sirupsen/logrus"
)
type Fs interface {
rootId() uuid.UUID
Open(id uuid.UUID) (io.ReadCloser, error)
ReadDir(ctx context.Context, id uuid.UUID, recursive bool) ([]sql.ReadDirRow, error)
DeleteRecursive(ctx context.Context, id uuid.UUID) error
CreateResource(ctx context.Context, id uuid.UUID, parent uuid.UUID, name string, dir bool) error
UpdateContents(ctx context.Context, id uuid.UUID) (io.WriteCloser, error)
ResourceByPath(ctx context.Context, path string) (*sql.ResourceByPathRow, error)
}
type pgxFs struct {
queries sql.Queries
root uuid.UUID
cs contentStore
}
func (fs pgxFs) rootId() uuid.UUID {
return fs.root
}
func (fs pgxFs) Open(id uuid.UUID) (io.ReadCloser, error) {
return fs.cs.Open(id)
}
func (fs pgxFs) ReadDir(ctx context.Context, id uuid.UUID, recursive bool) ([]sql.ReadDirRow, error) {
return fs.queries.ReadDir(ctx, sql.ReadDirParams{ID: id, Recursive: recursive})
}
func (fs pgxFs) DeleteRecursive(ctx context.Context, id uuid.UUID) error {
_, err := fs.queries.DeleteRecursive(ctx, id)
// TODO: Delete Contents
return err
}
func (fs pgxFs) CreateResource(ctx context.Context, id uuid.UUID, parent uuid.UUID, name string, dir bool) error {
return fs.queries.CreateResource(ctx, sql.CreateResourceParams{ID: id, Parent: &parent, Name: name, Dir: dir})
}
func (fs pgxFs) UpdateContents(ctx context.Context, id uuid.UUID) (io.WriteCloser, error) {
return fs.cs.Create(id, func(h hash.Hash, len int, err error) {
if err == nil {
etag := base64.StdEncoding.EncodeToString(h.Sum(nil))
err = fs.queries.UpdateResourceContents(ctx, sql.UpdateResourceContentsParams{
ID: id,
Size: pgtype.Int4{Int32: int32(len), Valid: true},
Etag: pgtype.Text{String: string(etag), Valid: true},
})
}
if err != nil {
logrus.Warn("Unable to update contents: " + err.Error())
fs.cs.Delete(id)
}
})
}
func (fs pgxFs) ResourceByPath(ctx context.Context, path string) (*sql.ResourceByPathRow, error) {
logrus.Trace("PhylumFs.ResourceByPath called with " + path)
segments := strings.Split(strings.Trim(path, "/"), "/")
if len(segments) == 0 {
return nil, iofs.ErrInvalid
}
res, err := fs.queries.ResourceByPath(ctx, sql.ResourceByPathParams{Search: segments, Root: fs.root})
if err != nil {
return nil, iofs.ErrNotExist
}
logrus.Trace(res)
//TODO: Permissions checks
return &res, nil
}

View File

@@ -10,7 +10,7 @@ import (
"github.com/google/uuid"
)
type LocalContentStore string
type localContentStore string
type contentWriter struct {
file *os.File
@@ -37,19 +37,19 @@ func (c contentWriter) Close() error {
return err
}
func NewLocalContentStore(root string) (ContentStore, error) {
func newLocalContentStore(root string) (contentStore, error) {
err := os.MkdirAll(root, 0750)
if err != nil {
return nil, err
}
return LocalContentStore(root), nil
return localContentStore(root), nil
}
func (l LocalContentStore) Open(id uuid.UUID) (io.ReadCloser, error) {
func (l localContentStore) Open(id uuid.UUID) (io.ReadCloser, error) {
return os.Open(l.path(id))
}
func (l LocalContentStore) Create(id uuid.UUID, callback func(hash.Hash, int, error)) (io.WriteCloser, error) {
func (l localContentStore) Create(id uuid.UUID, callback func(hash.Hash, int, error)) (io.WriteCloser, error) {
file, err := os.Create(l.path(id))
if err != nil {
return nil, err
@@ -59,10 +59,10 @@ func (l LocalContentStore) Create(id uuid.UUID, callback func(hash.Hash, int, er
return contentWriter{file, md5.New(), &addr, callback}, nil
}
func (l LocalContentStore) Delete(id uuid.UUID) error {
func (l localContentStore) Delete(id uuid.UUID) error {
return os.Remove(filepath.Join(string(l), id.String()))
}
func (l LocalContentStore) path(id uuid.UUID) string {
func (l localContentStore) path(id uuid.UUID) string {
return filepath.Join(string(l), id.String())
}

View File

@@ -1,108 +0,0 @@
package phylumfs
import (
"context"
"encoding/base64"
"fmt"
"hash"
"io"
"io/fs"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/shroff/phylum/server/internal/sql"
"github.com/sirupsen/logrus"
)
type PhylumFs struct {
queries sql.Queries
cs ContentStore
}
func New(conn *pgx.Conn) PhylumFs {
queries := *sql.New(conn)
cs, err := NewLocalContentStore("srv")
if err != nil {
panic(err)
}
return PhylumFs{queries: queries, cs: cs}
}
func (p PhylumFs) FindRoot(ctx context.Context, name string) (sql.Resource, error) {
// TODO: Permissions checks
return p.queries.FindRoot(ctx, name)
}
func (p PhylumFs) Mkroot(ctx context.Context, id uuid.UUID, name string) error {
if _, err := p.queries.FindRoot(ctx, name); err == nil {
return fmt.Errorf("root directory already exists: %s", name)
} else {
return p.queries.CreateResource(ctx, sql.CreateResourceParams{ID: id, Parent: nil, Name: name, Dir: true})
}
}
func (p PhylumFs) Rmroot(ctx context.Context, name string) error {
if root, err := p.queries.FindRoot(ctx, name); err == nil {
return p.DeleteRecursive(ctx, root.ID)
} else {
return fmt.Errorf("root directory does not exist: %s", name)
}
}
func (p PhylumFs) Open(id uuid.UUID) (io.ReadCloser, error) {
return p.cs.Open(id)
}
func (p PhylumFs) ReadDir(ctx context.Context, id uuid.UUID, recursive bool) ([]sql.ReadDirRow, error) {
return p.queries.ReadDir(ctx, sql.ReadDirParams{ID: id, Recursive: recursive})
}
func (p PhylumFs) DeleteRecursive(ctx context.Context, id uuid.UUID) error {
_, err := p.queries.DeleteRecursive(ctx, id)
// TODO: Delete Contents
return err
}
func (p PhylumFs) CreateResource(ctx context.Context, id uuid.UUID, parent uuid.UUID, name string, dir bool) error {
return p.queries.CreateResource(ctx, sql.CreateResourceParams{ID: id, Parent: &parent, Name: name, Dir: dir})
}
func (p PhylumFs) UpdateContents(ctx context.Context, id uuid.UUID) (io.WriteCloser, error) {
return p.cs.Create(id, func(h hash.Hash, len int, err error) {
if err == nil {
etag := base64.StdEncoding.EncodeToString(h.Sum(nil))
err = p.queries.UpdateResourceContents(ctx, sql.UpdateResourceContentsParams{
ID: id,
Size: pgtype.Int4{Int32: int32(len), Valid: true},
Etag: pgtype.Text{String: string(etag), Valid: true},
})
}
if err != nil {
logrus.Warn(fmt.Sprintf("Unable to update contents: %s", err))
p.cs.Delete(id)
}
})
}
func (p PhylumFs) ResourceByPath(ctx context.Context, path string) (*sql.ResourceByPathRow, error) {
segments := strings.Split(strings.TrimRight(path, "/"), "/")
if len(segments) == 0 {
return nil, fs.ErrInvalid
}
root, err := p.queries.FindRoot(ctx, segments[0])
if err != nil {
return nil, fs.ErrNotExist
}
res, err := p.queries.ResourceByPath(ctx, sql.ResourceByPathParams{Search: segments, Root: root.ID})
if err != nil {
return nil, fs.ErrNotExist
}
//TODO: Permissions checks
return &res, nil
}

View File

@@ -0,0 +1,51 @@
package webdav
import (
"context"
"strings"
webdav "github.com/emersion/go-webdav"
"github.com/gin-gonic/gin"
"github.com/shroff/phylum/server/internal/phylumfs"
"github.com/sirupsen/logrus"
)
type handler struct {
backend phylumfs.Backend
prefix string
}
func NewHandler(backend phylumfs.Backend, prefix string) *handler {
logrus.Info("Setting up WebDAV access at " + prefix)
return &handler{
backend: backend,
prefix: prefix,
}
}
func (h *handler) HandleRequest(c *gin.Context) {
path := c.Params.ByName("path")
path = strings.TrimLeft(path, "/")
if path == "" {
// No path specified
c.Writer.WriteHeader(404)
return
}
index := strings.Index(path, "/")
if index != -1 {
path = path[0:index]
}
root, err := h.backend.FindRoot(context.Background(), path)
if err != nil {
c.Writer.WriteHeader(404)
return
}
webdavHandler := webdav.Handler{
FileSystem: adapter{
fs: root,
prefix: h.prefix,
},
}
webdavHandler.ServeHTTP(c.Writer, c.Request)
}

View File

@@ -1,9 +1,9 @@
package pgfs
package webdav
import (
"context"
"io"
"io/fs"
iofs "io/fs"
"strings"
webdav "github.com/emersion/go-webdav"
@@ -12,24 +12,20 @@ import (
"github.com/shroff/phylum/server/internal/sql"
)
type Pgfs struct {
p phylumfs.PhylumFs
type adapter struct {
fs phylumfs.Fs
prefix string
}
func New(p phylumfs.PhylumFs, prefix string) Pgfs {
return Pgfs{p: p, prefix: prefix}
}
func (p Pgfs) Open(ctx context.Context, name string) (io.ReadCloser, error) {
func (p adapter) Open(ctx context.Context, name string) (io.ReadCloser, error) {
resource, err := p.resourceByPath(ctx, name)
if err != nil {
return nil, err
}
return p.p.Open(resource.ID)
return p.fs.Open(resource.ID)
}
func (p Pgfs) Stat(ctx context.Context, name string) (*webdav.FileInfo, error) {
func (p adapter) Stat(ctx context.Context, name string) (*webdav.FileInfo, error) {
resource, err := p.resourceByPath(ctx, name)
if err != nil {
return nil, err
@@ -44,15 +40,15 @@ func (p Pgfs) Stat(ctx context.Context, name string) (*webdav.FileInfo, error) {
}
return val, nil
}
func (p Pgfs) ReadDir(ctx context.Context, name string, recursive bool) ([]webdav.FileInfo, error) {
func (p adapter) ReadDir(ctx context.Context, name string, recursive bool) ([]webdav.FileInfo, error) {
dir, err := p.resourceByPath(ctx, name)
if err != nil {
return nil, err
}
if !dir.Dir {
return nil, fs.ErrInvalid
return nil, iofs.ErrInvalid
}
children, err := p.p.ReadDir(ctx, dir.ID, recursive)
children, err := p.fs.ReadDir(ctx, dir.ID, recursive)
if err != nil {
return nil, err
}
@@ -71,7 +67,7 @@ func (p Pgfs) ReadDir(ctx context.Context, name string, recursive bool) ([]webda
}
return result, nil
}
func (p Pgfs) Create(ctx context.Context, name string) (io.WriteCloser, error) {
func (p adapter) Create(ctx context.Context, name string) (io.WriteCloser, error) {
var id uuid.UUID
if resource, err := p.resourceByPath(ctx, name); err == nil {
id = resource.ID
@@ -81,52 +77,52 @@ func (p Pgfs) Create(ctx context.Context, name string) (io.WriteCloser, error) {
parentPath := name[0:index]
parent, err := p.resourceByPath(ctx, parentPath)
if err != nil {
return nil, fs.ErrNotExist
return nil, iofs.ErrNotExist
}
fileName := name[index+1:]
id = uuid.New()
if err = p.p.CreateResource(ctx, id, parent.ID, fileName, false); err != nil {
if err = p.fs.CreateResource(ctx, id, parent.ID, fileName, false); err != nil {
return nil, err
}
}
return p.p.UpdateContents(ctx, id)
return p.fs.UpdateContents(ctx, id)
}
func (p Pgfs) RemoveAll(ctx context.Context, name string) error {
func (p adapter) RemoveAll(ctx context.Context, name string) error {
resource, _ := p.resourceByPath(ctx, name)
if resource == nil {
return fs.ErrNotExist
return iofs.ErrNotExist
}
err := p.p.DeleteRecursive(ctx, resource.ID)
err := p.fs.DeleteRecursive(ctx, resource.ID)
return err
}
func (p Pgfs) Mkdir(ctx context.Context, name string) error {
func (p adapter) Mkdir(ctx context.Context, name string) error {
resource, _ := p.resourceByPath(ctx, name)
if resource != nil {
return fs.ErrExist
return iofs.ErrExist
}
name = strings.TrimRight(name, "/")
index := strings.LastIndex(name, "/")
parentPath := name[0:index]
parent, err := p.resourceByPath(ctx, parentPath)
if err != nil {
return fs.ErrNotExist
return iofs.ErrNotExist
}
dirName := name[index+1:]
err = p.p.CreateResource(ctx, uuid.New(), parent.ID, dirName, true)
err = p.fs.CreateResource(ctx, uuid.New(), parent.ID, dirName, true)
return err
}
func (p Pgfs) Copy(ctx context.Context, name, dest string, options *webdav.CopyOptions) (created bool, err error) {
func (p adapter) Copy(ctx context.Context, name, dest string, options *webdav.CopyOptions) (created bool, err error) {
// TODO: Implement
return false, nil
}
func (p Pgfs) Move(ctx context.Context, name, dest string, options *webdav.MoveOptions) (created bool, err error) {
func (p adapter) Move(ctx context.Context, name, dest string, options *webdav.MoveOptions) (created bool, err error) {
// TODO: Implement
return false, nil
}
func (p Pgfs) resourceByPath(ctx context.Context, name string) (res *sql.ResourceByPathRow, err error) {
return p.p.ResourceByPath(ctx, strings.TrimPrefix(name, p.prefix+"/"))
func (p adapter) resourceByPath(ctx context.Context, name string) (res *sql.ResourceByPathRow, err error) {
return p.fs.ResourceByPath(ctx, strings.TrimPrefix(name, p.prefix+"/"))
}