Files
sumi/storage.go
2025-12-16 17:45:21 -06:00

226 lines
4.6 KiB
Go

package main
import (
"fmt"
"github.com/d2fn/sumi/internal/ids"
"github.com/go-git/go-git/v6"
"log"
//"github.com/go-git/go-git/v5/plumbing"
"database/sql"
"github.com/gen2brain/raylib-go/raylib"
"github.com/go-git/go-git/v6/plumbing/object"
_ "modernc.org/sqlite" // pure Go, Nix-friendly
"os"
"path/filepath"
"time"
)
type Storage struct {
repoRoot string
snapshotsDir string
gen *ids.Generator
db *sql.DB
log *log.Logger
}
func NewStorage(snapshotsDir string) (*Storage, error) {
log := log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
gen, _ := ids.NewGenerator(82)
db, err := OpenDB(filepath.Join(snapshotsDir, "snapshots.db"), log)
if err != nil {
return nil, err
}
s := Storage{
repoRoot: ".",
snapshotsDir: snapshotsDir,
gen: gen,
db: db,
log: log,
}
return &s, nil
}
func OpenDB(path string, log *log.Logger) (*sql.DB, error) {
log.Printf("Opening sqlite db")
first := false
if _, err := os.Stat(path); os.IsNotExist(err) {
log.Printf("Database not found, initializing")
first = true
}
db, err := sql.Open("sqlite", path)
if err != nil {
log.Printf("Error opening database at %s", path)
return nil, err
}
if first {
log.Printf("Initializing empty db with schema", path)
if err := initSchema(db); err != nil {
db.Close()
return nil, err
}
log.Printf("Error initializing schema: %v", err)
}
return db, nil
}
func initSchema(db *sql.DB) error {
schema, err := os.ReadFile("db/schema.sql")
if err != nil {
return err
}
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
if _, err := tx.Exec(string(schema)); err != nil {
return err
}
return tx.Commit()
}
func (s *Storage) Save() (string, error) {
id, _ := s.gen.Next()
kid := ids.Base62Encode(id)
path := filepath.Join(s.snapshotsDir, kid)
os.MkdirAll(path, 0755)
img := rl.LoadImageFromScreen()
defer rl.UnloadImage(img)
snapshotPng := filepath.Join(path, fmt.Sprintf("%s.png", kid))
rl.ExportImage(*img, snapshotPng)
s.log.Printf("Checking git status...\n")
hash, branch, committed, err := CommitAllIfDirty(s.repoRoot, "automated snapshot", s.log)
if err != nil {
s.log.Printf("Error getting working tree in a known clean state: %v", err)
} else {
if committed {
s.log.Printf("Created commit %s on %s for snapshot %s", hash, branch, kid)
} else {
s.log.Printf("Referencing commit %s on %s for snapshot %s", hash, branch, kid)
}
}
_, err = s.db.Exec(`
INSERT INTO snapshots (id, sid, created_at, branch, git_hash, committed, path)
VALUES (?, ?, ?, ?, ?, ?, ?)
`,
id,
kid,
time.Now().UnixMilli(),
branch,
hash,
committed,
path,
)
if err != nil {
s.log.Printf("Error inserting snapshot row into db: %v\n", err)
}
s.log.Printf("Saved snapshot to %s\n", path)
return path, nil
}
func HeadHash(repoPath string) (string, error) {
r, err := git.PlainOpen(repoPath)
if err != nil {
return "", err
}
ref, err := r.Head()
if err != nil {
return "", err
}
return ref.Hash().String(), nil
}
func IsDirty(repoPath string) (bool, error) {
r, err := git.PlainOpen(repoPath)
if err != nil {
return false, err
}
wt, err := r.Worktree()
if err != nil {
return false, err
}
status, err := wt.Status()
if err != nil {
return false, err
}
return !status.IsClean(), nil
}
func CommitAllIfDirty(repoPath, message string, log *log.Logger) (commitHash string, branch string, committed bool, err error) {
r, err := git.PlainOpen(repoPath)
if err != nil {
log.Printf("Error opening git repo")
return
}
// Determine branch (may be empty if detached)
ref, err := r.Head()
if err != nil {
log.Printf("Error determining head commit")
return
}
if ref.Name().IsBranch() {
branch = ref.Name().Short()
}
wt, err := r.Worktree()
if err != nil {
log.Printf("Error getting worktree state")
return
}
status, err := wt.Status()
if err != nil {
log.Printf("Error getting worktree status")
return
}
if status.IsClean() {
hash, err := HeadHash(repoPath)
if err != nil {
log.Printf("Repo was clean but there was an error checking the commit for HEAD: %v", err)
}
return hash, branch, false, nil
}
// Stage everything (git add -A)
if err = wt.AddWithOptions(&git.AddOptions{All: true}); err != nil {
log.Printf("Error adding git changes to index")
return
}
hash, err := wt.Commit(message, &git.CommitOptions{
Author: &object.Signature{
Name: "sumi",
Email: "sumi@local",
When: time.Now(),
},
})
if err != nil {
log.Printf("Error creating commit")
return
}
return hash.String(), branch, true, nil
}