Additions to domain/ and workspace/ chron-note packages
This commit is contained in:
69
chron-note/internal/workspace/io.go
Normal file
69
chron-note/internal/workspace/io.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package workspace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.michelsen.id/phill/chron/chron-note/internal/domain"
|
||||
)
|
||||
|
||||
func (ws *Workspace) Exists(id domain.ObjectID) (bool, error) {
|
||||
_, err := os.Stat(ws.ObjectPath(id))
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("stat object %s: %w", id.String(), err)
|
||||
}
|
||||
|
||||
func (ws *Workspace) Read(id domain.ObjectID) ([]byte, error) {
|
||||
b, err := os.ReadFile(ws.ObjectPath(id))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read object %s: %w", id.String(), err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (ws *Workspace) Write(id domain.ObjectID, b []byte) error {
|
||||
if err := os.MkdirAll(ws.Dir, 0o755); err != nil {
|
||||
return fmt.Errorf("mkdir workspace dir: %w", err)
|
||||
}
|
||||
|
||||
dst := ws.ObjectPath(id)
|
||||
|
||||
f, err := os.CreateTemp(ws.Dir, id.String()+".*.tmp")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create temp for object %s: %w", id.String(), err)
|
||||
}
|
||||
tmp := f.Name()
|
||||
|
||||
// Best-effort cleanup on failure.
|
||||
defer func() { _ = os.Remove(tmp) }()
|
||||
|
||||
if _, err := f.Write(b); err != nil {
|
||||
_ = f.Close()
|
||||
return fmt.Errorf("write temp for object %s: %w", id.String(), err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return fmt.Errorf("close temp for object %s: %w", id.String(), err)
|
||||
}
|
||||
|
||||
if err := os.Rename(tmp, dst); err != nil {
|
||||
return fmt.Errorf("rename temp for object %s: %w", id.String(), err)
|
||||
}
|
||||
|
||||
// Rename succeeded; prevent deferred cleanup.
|
||||
_ = os.Remove(tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ws *Workspace) Delete(id domain.ObjectID) error {
|
||||
p := ws.ObjectPath(id)
|
||||
err := os.Remove(p)
|
||||
if err == nil || os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("delete object %s: %w", id.String(), err)
|
||||
}
|
||||
46
chron-note/internal/workspace/list.go
Normal file
46
chron-note/internal/workspace/list.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package workspace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"git.michelsen.id/phill/chron/chron-note/internal/domain"
|
||||
)
|
||||
|
||||
func (ws *Workspace) ListObjectIDs() ([]domain.ObjectID, error) {
|
||||
entries, err := os.ReadDir(ws.Dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readdir workspace dir: %w", err)
|
||||
}
|
||||
|
||||
out := make([]domain.ObjectID, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
info, err := e.Info()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stat workspace entry: %w", err)
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
continue
|
||||
}
|
||||
|
||||
id, err := domain.ParseObjectID(e.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, id)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (ws *Workspace) Stat(id domain.ObjectID) (fs.FileInfo, error) {
|
||||
fi, err := os.Stat(ws.ObjectPath(id))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stat object %s: %w", id.String(), err)
|
||||
}
|
||||
return fi, nil
|
||||
}
|
||||
52
chron-note/internal/workspace/snapshot.go
Normal file
52
chron-note/internal/workspace/snapshot.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package workspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.michelsen.id/phill/chron/chron-note/internal/domain"
|
||||
)
|
||||
|
||||
type SnapshotObject struct {
|
||||
ID domain.ObjectID
|
||||
Size int64
|
||||
ModNS int64
|
||||
}
|
||||
|
||||
func (ws *Workspace) Snapshot(ctx context.Context) ([]SnapshotObject, error) {
|
||||
entries, err := os.ReadDir(ws.Dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readdir workspace dir: %w", err)
|
||||
}
|
||||
|
||||
out := make([]SnapshotObject, 0, len(entries))
|
||||
for _, e := range entries {
|
||||
if ctx.Err() != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
info, err := e.Info()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stat workspace entry: %w", err)
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
continue
|
||||
}
|
||||
|
||||
id, err := domain.ParseObjectID(e.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, SnapshotObject{
|
||||
ID: id,
|
||||
Size: info.Size(),
|
||||
ModNS: info.ModTime().UnixNano(),
|
||||
})
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
32
chron-note/internal/workspace/workspace.go
Normal file
32
chron-note/internal/workspace/workspace.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package workspace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.michelsen.id/phill/chron/chron-note/internal/domain"
|
||||
)
|
||||
|
||||
type Workspace struct {
|
||||
Root string
|
||||
Dir string
|
||||
}
|
||||
|
||||
func Open(root string) (*Workspace, error) {
|
||||
root = filepath.Clean(root)
|
||||
dir := filepath.Join(root, ".chron", "workspace")
|
||||
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return nil, fmt.Errorf("mkdir workspace dir: %w", err)
|
||||
}
|
||||
|
||||
return &Workspace{
|
||||
Root: root,
|
||||
Dir: dir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ws *Workspace) ObjectPath(id domain.ObjectID) string {
|
||||
return filepath.Join(ws.Dir, id.String())
|
||||
}
|
||||
Reference in New Issue
Block a user