184 lines
5.0 KiB
Go
184 lines
5.0 KiB
Go
package storage
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
type PersonRecord struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
PhotoCount int `json:"photo_count"`
|
|
Photos []string `json:"photos,omitempty"`
|
|
PhotoIDs []int `json:"photo_ids,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
type FaceGalleryRepo struct {
|
|
dbPath string
|
|
}
|
|
|
|
func NewFaceGalleryRepo(dbPath string) *FaceGalleryRepo {
|
|
return &FaceGalleryRepo{dbPath: dbPath}
|
|
}
|
|
|
|
func (r *FaceGalleryRepo) open() (*sql.DB, error) {
|
|
db, err := sql.Open("sqlite", r.dbPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open app db: %w", err)
|
|
}
|
|
_, err = db.Exec(`
|
|
CREATE TABLE IF NOT EXISTS face_persons (
|
|
id INTEGER PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT '',
|
|
updated_at TEXT NOT NULL DEFAULT ''
|
|
);
|
|
CREATE TABLE IF NOT EXISTS face_photos (
|
|
id INTEGER PRIMARY KEY,
|
|
person_id INTEGER NOT NULL,
|
|
photo_path TEXT NOT NULL DEFAULT '',
|
|
created_at TEXT NOT NULL DEFAULT ''
|
|
);
|
|
`)
|
|
if err != nil {
|
|
db.Close()
|
|
return nil, fmt.Errorf("ensure face tables: %w", err)
|
|
}
|
|
return db, nil
|
|
}
|
|
|
|
func (r *FaceGalleryRepo) ListPersons() ([]PersonRecord, error) {
|
|
db, err := r.open()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer db.Close()
|
|
|
|
rows, err := db.Query(`SELECT id, name, created_at FROM face_persons ORDER BY created_at DESC`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []PersonRecord
|
|
for rows.Next() {
|
|
var p PersonRecord
|
|
if err := rows.Scan(&p.ID, &p.Name, &p.CreatedAt); err != nil {
|
|
continue
|
|
}
|
|
out = append(out, p)
|
|
}
|
|
// Load photos for each person
|
|
for i := range out {
|
|
photoRows, err := db.Query(`SELECT photo_path, id FROM face_photos WHERE person_id = ? ORDER BY id`, out[i].ID)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
for photoRows.Next() {
|
|
var path string
|
|
var pid int
|
|
if photoRows.Scan(&path, &pid) == nil {
|
|
out[i].Photos = append(out[i].Photos, path)
|
|
out[i].PhotoIDs = append(out[i].PhotoIDs, pid)
|
|
}
|
|
}
|
|
photoRows.Close()
|
|
out[i].PhotoCount = len(out[i].Photos)
|
|
}
|
|
if out == nil {
|
|
out = make([]PersonRecord, 0)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func (r *FaceGalleryRepo) AddPerson(name string) error {
|
|
db, err := r.open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
now := time.Now().Format(time.RFC3339)
|
|
_, err = db.Exec(`INSERT INTO face_persons(name, created_at, updated_at) VALUES(?, ?, ?)`, name, now, now)
|
|
return err
|
|
}
|
|
|
|
func (r *FaceGalleryRepo) AddPhoto(personID int, photoPath string) error {
|
|
db, err := r.open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
// Skip if already exists
|
|
var existing int
|
|
if db.QueryRow(`SELECT COUNT(*) FROM face_photos WHERE person_id = ? AND photo_path = ?`, personID, photoPath).Scan(&existing) == nil && existing > 0 {
|
|
return nil
|
|
}
|
|
now := time.Now().Format(time.RFC3339)
|
|
_, err = db.Exec(`INSERT INTO face_photos(person_id, photo_path, created_at) VALUES(?, ?, ?)`, personID, photoPath, now)
|
|
return err
|
|
}
|
|
|
|
func (r *FaceGalleryRepo) FindOrCreatePerson(name string) (int, error) {
|
|
db, err := r.open()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer db.Close()
|
|
var id int
|
|
err = db.QueryRow(`SELECT id FROM face_persons WHERE name = ?`, name).Scan(&id)
|
|
if err == nil {
|
|
return id, nil
|
|
}
|
|
now := time.Now().Format(time.RFC3339)
|
|
res, err := db.Exec(`INSERT INTO face_persons(name, created_at, updated_at) VALUES(?, ?, ?)`, name, now, now)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
lid, _ := res.LastInsertId()
|
|
return int(lid), nil
|
|
}
|
|
|
|
func (r *FaceGalleryRepo) DeletePerson(id int) error {
|
|
db, err := r.open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
_, err = db.Exec(`DELETE FROM face_photos WHERE person_id = ?`, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = db.Exec(`DELETE FROM face_persons WHERE id = ?`, id)
|
|
return err
|
|
}
|
|
|
|
func (r *FaceGalleryRepo) RenamePerson(id int, name string) error {
|
|
db, err := r.open()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
now := time.Now().Format(time.RFC3339)
|
|
_, err = db.Exec(`UPDATE face_persons SET name = ?, updated_at = ? WHERE id = ?`, name, now, id)
|
|
return err
|
|
}
|
|
|
|
func (r *FaceGalleryRepo) DeletePhoto(photoID int) error {
|
|
db, err := r.open()
|
|
if err != nil { return err }
|
|
defer db.Close()
|
|
// Get path before deleting
|
|
var path string
|
|
db.QueryRow(`SELECT photo_path FROM face_photos WHERE id = ?`, photoID).Scan(&path)
|
|
_, err = db.Exec(`DELETE FROM face_photos WHERE id = ?`, photoID)
|
|
if err != nil { return err }
|
|
// Remove file from disk
|
|
if path != "" { os.Remove(filepath.Join("dataset", path)) }
|
|
return nil
|
|
}
|
|
|