This repository has been archived on 2025-08-23. You can view files and clone it, but cannot push or open issues or pull requests.
Files
GreenCoast/internal/storage/fs.go
Dani d87e9322b5 Added example/dropin replacements for .env.example
Fixed the issue with PlainText (Complete Anon) posting
Need to fix device sign on issues.
Need to make it so that the non-signed in devices can only see their equalivant level of posts. (i.e. plaintext, public-encrypted, private-encrypted)
2025-08-22 22:59:05 -04:00

152 lines
3.2 KiB
Go

package api
import (
"crypto/sha256"
"encoding/hex"
"io"
"os"
"path/filepath"
"strings"
"time"
)
// SimpleFSStore is a minimal FS-backed implementation of BlobStore.
// Layout under root:
//
// <root>/<hash> - content
// <root>/<hash>.priv - presence means "private"
type SimpleFSStore struct {
root string
}
func NewSimpleFSStore(root string) *SimpleFSStore {
return &SimpleFSStore{root: root}
}
func (fs *SimpleFSStore) ensureRoot() error {
return os.MkdirAll(fs.root, 0o755)
}
func (fs *SimpleFSStore) pathFor(hash string) string {
return filepath.Join(fs.root, hash)
}
func (fs *SimpleFSStore) privPathFor(hash string) string {
return filepath.Join(fs.root, hash+".priv")
}
// Get implements BlobStore.Get
func (fs *SimpleFSStore) Get(hash string) (io.ReadCloser, int64, error) {
if err := fs.ensureRoot(); err != nil {
return nil, 0, err
}
f, err := os.Open(fs.pathFor(hash))
if err != nil {
return nil, 0, err
}
st, err := f.Stat()
if err != nil {
_ = f.Close()
return nil, 0, err
}
return f, st.Size(), nil
}
// Put implements BlobStore.Put
func (fs *SimpleFSStore) Put(r io.Reader, private bool) (string, int64, time.Time, error) {
if err := fs.ensureRoot(); err != nil {
return "", 0, time.Time{}, err
}
tmp, err := os.CreateTemp(fs.root, "put-*")
if err != nil {
return "", 0, time.Time{}, err
}
defer func() {
_ = tmp.Close()
_ = os.Remove(tmp.Name())
}()
h := sha256.New()
w := io.MultiWriter(tmp, h)
n, err := io.Copy(w, r)
if err != nil {
return "", 0, time.Time{}, err
}
hash := hex.EncodeToString(h.Sum(nil))
final := fs.pathFor(hash)
if err := os.Rename(tmp.Name(), final); err != nil {
return "", 0, time.Time{}, err
}
if private {
if err := os.WriteFile(fs.privPathFor(hash), nil, 0o600); err != nil {
_ = os.Remove(final)
return "", 0, time.Time{}, err
}
}
st, err := os.Stat(final)
if err != nil {
return "", 0, time.Time{}, err
}
return hash, n, st.ModTime().UTC(), nil
}
// Delete implements BlobStore.Delete
func (fs *SimpleFSStore) Delete(hash string) error {
if err := fs.ensureRoot(); err != nil {
return err
}
_ = os.Remove(fs.privPathFor(hash))
return os.Remove(fs.pathFor(hash))
}
// Walk implements BlobStore.Walk
func (fs *SimpleFSStore) Walk(fn func(hash string, bytes int64, private bool, storedAt time.Time) error) (int, error) {
if err := fs.ensureRoot(); err != nil {
return 0, err
}
ents, err := os.ReadDir(fs.root)
if err != nil {
return 0, err
}
count := 0
for _, e := range ents {
if e.IsDir() {
continue
}
name := e.Name()
// skip sidecars and non-64-hex filenames
if strings.HasSuffix(name, ".priv") || len(name) != 64 || !isHex(name) {
continue
}
full := fs.pathFor(name)
st, err := os.Stat(full)
if err != nil {
continue
}
private := false
if _, err := os.Stat(fs.privPathFor(name)); err == nil {
private = true
}
if err := fn(name, st.Size(), private, st.ModTime().UTC()); err != nil {
return count, err
}
count++
}
return count, nil
}
func isHex(s string) bool {
for i := 0; i < len(s); i++ {
c := s[i]
if !((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F')) {
return false
}
}
return true
}