automated snapshot
This commit is contained in:
206
storage.go
Normal file
206
storage.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"github.com/d2fn/sumi/internal/ids"
|
||||
"github.com/go-git/go-git/v6"
|
||||
//"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v6/plumbing/object"
|
||||
"github.com/gen2brain/raylib-go/raylib"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
"database/sql"
|
||||
_ "modernc.org/sqlite" // pure Go, Nix-friendly
|
||||
)
|
||||
|
||||
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,
|
||||
}
|
||||
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(img *rl.Image) (string, error) {
|
||||
id, _ := s.gen.Next()
|
||||
kid := ids.Base62Encode(id)
|
||||
path := filepath.Join(s.snapshotsDir, kid)
|
||||
os.MkdirAll(path, 0755)
|
||||
|
||||
snapshotPng := filepath.Join(path, fmt.Sprintf("%s.png", kid))
|
||||
|
||||
rl.ExportImage(*img, snapshotPng)
|
||||
|
||||
hash, branch, committed, err := CommitAllIfDirty(s.repoRoot, "automated snapshot")
|
||||
|
||||
if err != nil {
|
||||
s.log.Printf("Error getting working tree in a known clean state: %v", err)
|
||||
} else {
|
||||
s.log.Printf("Created 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) (commitHash string, branch string, committed bool, err error) {
|
||||
r, err := git.PlainOpen(repoPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine branch (may be empty if detached)
|
||||
ref, err := r.Head()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ref.Name().IsBranch() {
|
||||
branch = ref.Name().Short()
|
||||
}
|
||||
|
||||
wt, err := r.Worktree()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
status, err := wt.Status()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if status.IsClean() {
|
||||
return "", branch, false, nil
|
||||
}
|
||||
|
||||
// Stage everything (git add -A)
|
||||
if err = wt.AddWithOptions(&git.AddOptions{All: true}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
hash, err := wt.Commit(message, &git.CommitOptions{
|
||||
Author: &object.Signature{
|
||||
Name: "sumi",
|
||||
Email: "sumi@local",
|
||||
When: time.Now(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return hash.String(), branch, true, nil
|
||||
}
|
||||
Reference in New Issue
Block a user