package db import ( "database/sql" "time" _ "modernc.org/sqlite" ) type DB struct { sql *sql.DB } type CopyRecord struct { DiskID string SourcePath string FileSize int64 CopiedAt time.Time } func Open(path string) (*DB, error) { conn, err := sql.Open("sqlite", path+"?_journal=WAL&_timeout=5000") if err != nil { return nil, err } conn.SetMaxOpenConns(1) d := &DB{sql: conn} if err := d.migrate(); err != nil { conn.Close() return nil, err } return d, nil } func (d *DB) Close() error { return d.sql.Close() } func (d *DB) migrate() error { _, err := d.sql.Exec(` CREATE TABLE IF NOT EXISTS copy_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, disk_id TEXT NOT NULL, source_path TEXT NOT NULL, file_size INTEGER NOT NULL DEFAULT 0, copied_at DATETIME NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ','now')) ); CREATE UNIQUE INDEX IF NOT EXISTS idx_copy_history_disk_path ON copy_history (disk_id, source_path); `) return err } func (d *DB) WasCopied(diskID, sourcePath string) (bool, error) { var n int err := d.sql.QueryRow( `SELECT COUNT(*) FROM copy_history WHERE disk_id=? AND source_path=?`, diskID, sourcePath, ).Scan(&n) return n > 0, err } func (d *DB) RecordCopy(rec CopyRecord) error { t := rec.CopiedAt if t.IsZero() { t = time.Now().UTC() } _, err := d.sql.Exec( `INSERT OR IGNORE INTO copy_history (disk_id, source_path, file_size, copied_at) VALUES (?,?,?,?)`, rec.DiskID, rec.SourcePath, rec.FileSize, t.Format(time.RFC3339), ) return err } func (d *DB) CopiedPaths(diskID string) (map[string]struct{}, error) { rows, err := d.sql.Query( `SELECT source_path FROM copy_history WHERE disk_id=?`, diskID, ) if err != nil { return nil, err } defer rows.Close() m := make(map[string]struct{}) for rows.Next() { var p string if err := rows.Scan(&p); err != nil { return nil, err } m[p] = struct{}{} } return m, rows.Err() }