package manifest import ( "database/sql" "fmt" "os" "path/filepath" _ "modernc.org/sqlite" ) type sqliteManifest struct { path string db *sql.DB open sqlOpenerFunc execFunc func(query string, args ...any) (sql.Result, error) } type sqlOpenerFunc func(driverName, dataSourceName string) (*sql.DB, error) var sqliteOpenFunc sqlOpenerFunc func defaultSQLOpener() sqlOpenerFunc { if sqliteOpenFunc != nil { return sqliteOpenFunc } return sql.Open } func SQLitePath(dir string) string { return filepath.Join(dir, "downloads.db") } func LoadSQLite(dir string) (*sqliteManifest, error) { m := &sqliteManifest{ path: SQLitePath(dir), open: defaultSQLOpener(), } return m, nil } func (m *sqliteManifest) OpenAppend() error { if m.db != nil { return nil } if err := os.MkdirAll(filepath.Dir(m.path), 0755); err != nil { return err } opener := m.open if opener == nil { opener = sql.Open } db, err := opener("sqlite", m.path) if err != nil { return fmt.Errorf("open sqlite: %w", err) } execFn := m.execFunc if execFn == nil { execFn = db.Exec } _, err = execFn(`CREATE TABLE IF NOT EXISTS downloads ( id TEXT PRIMARY KEY, filename TEXT NOT NULL DEFAULT '', size INTEGER NOT NULL DEFAULT 0, cloud TEXT NOT NULL DEFAULT '', exported INTEGER NOT NULL DEFAULT 0 )`) if err != nil { db.Close() return fmt.Errorf("create table: %w", err) } _, err = execFn(`CREATE INDEX IF NOT EXISTS idx_downloads_id ON downloads(id)`) if err != nil { db.Close() return fmt.Errorf("create index: %w", err) } m.db = db return nil } func (m *sqliteManifest) Has(id string) bool { if m.db == nil { return false } var count int err := m.db.QueryRow(`SELECT COUNT(*) FROM downloads WHERE id = ?`, id).Scan(&count) if err != nil { return false } return count > 0 } func (m *sqliteManifest) Add(id string, filename string, size int64, cloud string) { if m.db == nil { return } entry := newEntry(id, filename, size, cloud) m.db.Exec(`INSERT OR REPLACE INTO downloads (id, filename, size, cloud, exported) VALUES (?, ?, ?, ?, ?)`, id, entry.Filename, entry.Size, entry.Cloud, entry.Exported) } func (m *sqliteManifest) Save() error { return nil } func (m *sqliteManifest) Close() { if m.db != nil { m.db.Close() m.db = nil } } func (m *sqliteManifest) DB() *sql.DB { return m.db } func (m *sqliteManifest) Entries() map[string]Entry { if m.db == nil { return nil } out := make(map[string]Entry) rows, err := m.db.Query(`SELECT id, filename, size, cloud, exported FROM downloads`) if err != nil { return out } defer rows.Close() for rows.Next() { var e Entry if err := rows.Scan(&e.ID, &e.Filename, &e.Size, &e.Cloud, &e.Exported); err == nil { out[e.ID] = e } } return out }