diff --git a/html/src/Components/ShareComponent.tsx b/html/src/Components/ShareComponent.tsx
index 2b66e5a..69413de 100644
--- a/html/src/Components/ShareComponent.tsx
+++ b/html/src/Components/ShareComponent.tsx
@@ -18,7 +18,7 @@ export function ShareComponent(props: {share: Share}) {
const name = share.name
const count = share.count
const size = share.size
- const countString = count?(count + ' items' + (count > 1 ? 's' : '')):"empty"
+ const countString = count?(count + ' item' + (count > 1 ? 's' : '')):"empty"
// Function
const deleteShare = () => {
diff --git a/hupload/pkg/apiws/storageservice/storageservice.go b/hupload/pkg/apiws/storageservice/storageservice.go
index d0665c8..8845189 100644
--- a/hupload/pkg/apiws/storageservice/storageservice.go
+++ b/hupload/pkg/apiws/storageservice/storageservice.go
@@ -6,9 +6,11 @@ import (
)
type Share struct {
- Name string `json:"name"`
- Created time.Time `json:"created"`
- Owner string `json:"owner"`
+ Name string `json:"name"`
+ DateCreated time.Time `json:"created"`
+ Owner string `json:"owner"`
+ Size int64 `json:"size"`
+ Count int64 `json:"count"`
}
type Item struct {
@@ -16,7 +18,8 @@ type Item struct {
ItemInfo ItemInfo
}
type ItemInfo struct {
- Size int64
+ Size int64
+ DateModified time.Time
}
// BackendInterface must be implemented by any backend
@@ -27,6 +30,9 @@ type StorageServiceInterface interface {
// CreateItem creates a new item in a share
CreateItem(string, string, *bufio.Reader) (*Item, error)
+ // GetShare returns the share identified by share
+ GetShare(string) (*Share, error)
+
// ListShares returns the list of shares available
ListShares() ([]Share, error)
@@ -41,4 +47,7 @@ type StorageServiceInterface interface {
// GetItem returns the item identified by share and item
GetItemData(string, string) (*bufio.Reader, error)
+
+ // UpdateMetadata updates the metadata of share
+ UpdateMetadata(string) error
}
diff --git a/hupload/pkg/apiws/storageservice/storageservicefile.go b/hupload/pkg/apiws/storageservice/storageservicefile.go
index f7679d7..6e469cf 100644
--- a/hupload/pkg/apiws/storageservice/storageservicefile.go
+++ b/hupload/pkg/apiws/storageservice/storageservicefile.go
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"io"
+ "log/slog"
"os"
"path"
"sort"
@@ -32,7 +33,7 @@ func NewFileBackend(m map[string]any) *FileBackend {
// initialize creates the root directory for the backend
func (b *FileBackend) initialize() {
- path := b.Options["path"].(string)
+ path := b.stringOption("path")
if path == "" {
panic("path is required")
}
@@ -42,23 +43,39 @@ func (b *FileBackend) initialize() {
}
}
+func (b *FileBackend) stringOption(o string) string {
+ v, ok := b.Options[o].(string)
+ if !ok {
+ return ""
+ }
+ return v
+}
+
+func (b *FileBackend) int64Option(o string) int64 {
+ v, ok := b.Options[o].(int)
+ if !ok {
+ return 0
+ }
+ return int64(v)
+}
+
func (b *FileBackend) CreateShare(s string, o string) error {
- _, err := os.Stat(path.Join(b.Options["path"].(string), s))
+ _, err := os.Stat(path.Join(b.stringOption("path"), s))
if err == nil {
return errors.New("share already exists")
}
- err = os.Mkdir(path.Join(b.Options["path"].(string), s), 0755)
+ err = os.Mkdir(path.Join(b.stringOption("path"), s), 0755)
if err != nil {
return errors.New("cannot create share")
}
m := Share{
- Name: s,
- Owner: o,
- Created: time.Now(),
+ Name: s,
+ Owner: o,
+ DateCreated: time.Now(),
}
- f, err := os.Create(path.Join(b.Options["path"].(string), s, ".metadata"))
+ f, err := os.Create(path.Join(b.stringOption("path"), s, ".metadata"))
if err != nil {
return err
}
@@ -71,17 +88,42 @@ func (b *FileBackend) CreateShare(s string, o string) error {
}
func (b *FileBackend) CreateItem(s string, i string, r *bufio.Reader) (*Item, error) {
- p := path.Join(b.Options["path"].(string), s, i)
+ p := path.Join(b.stringOption("path"), s, i)
f, err := os.Create(p + suffix)
if err != nil {
return nil, errors.New("cannot create item")
}
defer f.Close()
- _, err = io.Copy(f, r)
- if err != nil {
- os.Remove(p + suffix)
- return nil, errors.New("cannot copy item content")
+ max := b.int64Option("max_bytes")
+ if max > 0 {
+ // Check if there is a max bytes associated to share
+ share, err := b.GetShare(s)
+ if err != nil {
+ return nil, errors.New("cannot get share")
+ }
+ maxWrite := max - share.Size
+ if maxWrite <= 0 {
+ return nil, errors.New("Max share capacity already reached")
+ }
+ reader := io.LimitReader(r, maxWrite)
+ written, err := io.Copy(f, reader)
+ if err != nil {
+ os.Remove(p + suffix)
+ return nil, errors.New("cannot copy item content")
+ }
+
+ if written == 0 || written == maxWrite {
+ os.Remove(p + suffix)
+ return nil, errors.New("Max share capacity reached")
+ }
+
+ } else {
+ _, err := io.Copy(f, r)
+ if err != nil {
+ os.Remove(p + suffix)
+ return nil, errors.New("cannot copy item content")
+ }
}
err = os.Rename(p+suffix, p)
if err != nil {
@@ -95,36 +137,47 @@ func (b *FileBackend) CreateItem(s string, i string, r *bufio.Reader) (*Item, er
return item, nil
}
+func (b *FileBackend) GetShare(s string) (*Share, error) {
+ fm, err := os.Open(path.Join(b.stringOption("path"), s, ".metadata"))
+ if err != nil {
+ return nil, err
+ }
+ defer fm.Close()
+
+ m := Share{}
+ err = json.NewDecoder(fm).Decode(&m)
+ if err != nil {
+ return nil, err
+ }
+ return &m, nil
+}
+
func (b *FileBackend) ListShares() ([]Share, error) {
- d, err := os.ReadDir(b.Options["path"].(string))
+ d, err := os.ReadDir(b.stringOption("path"))
if err != nil {
return nil, err
}
r := []Share{}
+
+ // Shares loop
for _, f := range d {
if f.IsDir() {
- fm, err := os.Open(path.Join(b.Options["path"].(string), f.Name(), ".metadata"))
+ m, err := b.GetShare(f.Name())
if err != nil {
continue
}
- defer fm.Close()
- m := Share{}
- err = json.NewDecoder(fm).Decode(&m)
- if err != nil {
- return nil, err
- }
- r = append(r, m)
+ r = append(r, *m)
}
}
sort.Slice(r, func(i, j int) bool {
- return r[i].Created.After(r[j].Created)
+ return r[i].DateCreated.After(r[j].DateCreated)
})
return r, nil
}
func (b *FileBackend) DeleteShare(s string) error {
- sharePath := path.Join(b.Options["path"].(string), s)
+ sharePath := path.Join(b.stringOption("path"), s)
err := os.RemoveAll(sharePath)
if err != nil {
return err
@@ -133,7 +186,7 @@ func (b *FileBackend) DeleteShare(s string) error {
}
func (b *FileBackend) ListShare(s string) ([]Item, error) {
- d, err := os.ReadDir(path.Join(b.Options["path"].(string), s))
+ d, err := os.ReadDir(path.Join(b.stringOption("path"), s))
if err != nil {
return nil, err
}
@@ -148,26 +201,79 @@ func (b *FileBackend) ListShare(s string) ([]Item, error) {
}
r = append(r, *i)
}
+
+ sort.Slice(r, func(i, j int) bool {
+ return r[i].ItemInfo.DateModified.After(r[j].ItemInfo.DateModified)
+ })
+
return r, nil
}
func (b *FileBackend) GetItem(s string, i string) (*Item, error) {
- p := path.Join(b.Options["path"].(string), s, i)
+ p := path.Join(b.stringOption("path"), s, i)
stat, err := os.Stat(p)
if err != nil {
return nil, err
}
return &Item{
Path: path.Join(s, i),
- ItemInfo: ItemInfo{Size: stat.Size()},
+ ItemInfo: ItemInfo{Size: stat.Size(), DateModified: stat.ModTime()},
}, nil
}
func (b *FileBackend) GetItemData(s string, i string) (*bufio.Reader, error) {
- p := path.Join(b.Options["path"].(string), s, i)
+ p := path.Join(b.stringOption("path"), s, i)
f, err := os.Open(p)
if err != nil {
return nil, err
}
return bufio.NewReader(f), nil
}
+
+func (b *FileBackend) UpdateMetadata(s string) error {
+ sd, err := os.ReadDir(path.Join(b.stringOption("path"), s))
+ if err != nil {
+ return err
+ }
+
+ fm, err := os.OpenFile(path.Join(b.stringOption("path"), s, ".metadata"), os.O_RDWR, 0644)
+ if err != nil {
+ return err
+ }
+ defer fm.Close()
+
+ m := Share{}
+ err = json.NewDecoder(fm).Decode(&m)
+ if err != nil {
+ return err
+ }
+
+ m.Size = 0
+ m.Count = 0
+
+ // Share content loop
+ for _, i := range sd {
+ if strings.HasPrefix(i.Name(), ".") {
+ continue
+ }
+ info, err := i.Info()
+ if err != nil {
+ slog.Error("cannot get file info", slog.String("error", err.Error()))
+ continue
+ }
+ m.Size += info.Size()
+ m.Count += 1
+ }
+
+ _, err = fm.Seek(0, 0)
+ if err != nil {
+ return err
+ }
+
+ err = json.NewEncoder(fm).Encode(m)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/hupload/routes.go b/hupload/routes.go
index b2af262..a37ab20 100644
--- a/hupload/routes.go
+++ b/hupload/routes.go
@@ -58,6 +58,7 @@ func postItem(w http.ResponseWriter, r *http.Request) {
b := bufio.NewReader(np)
item, err := api.StorageService.CreateItem(r.PathValue("share"), np.FileName(), b)
if err != nil {
+ w.WriteHeader(http.StatusNotAcceptable)
_, _ = w.Write([]byte(err.Error()))
return
}
@@ -68,6 +69,12 @@ func postItem(w http.ResponseWriter, r *http.Request) {
return
}
+ err = api.StorageService.UpdateMetadata(r.PathValue("share"))
+ if err != nil {
+ _, _ = w.Write([]byte(err.Error()))
+ return
+ }
+
_, _ = w.Write(c)
}