package store import ( "context" "database/sql" "sync" _ "modernc.org/sqlite" ) type SQLite struct { db *sql.DB mu sync.Mutex } func NewSQLite(path string) (*SQLite, error) { db, err := sql.Open("sqlite", path) if err != nil { return nil, err } s := &SQLite{db: db} if err = s.init(); err != nil { _ = db.Close() return nil, err } return s, nil } func (s *SQLite) init() error { queries := []string{ `CREATE TABLE IF NOT EXISTS downloads (id TEXT PRIMARY KEY)`, `CREATE TABLE IF NOT EXISTS failed_downloads ( source TEXT NOT NULL, media_type TEXT NOT NULL, id TEXT NOT NULL, PRIMARY KEY (source, media_type, id) )`, } for _, q := range queries { if _, err := s.db.Exec(q); err != nil { return err } } return nil } func (s *SQLite) IsDownloaded(ctx context.Context, id string) (bool, error) { s.mu.Lock() defer s.mu.Unlock() var count int err := s.db.QueryRowContext(ctx, `SELECT COUNT(1) FROM downloads WHERE id = ?`, id).Scan(&count) if err != nil { return false, err } return count > 0, nil } func (s *SQLite) MarkDownloaded(ctx context.Context, id string) error { s.mu.Lock() defer s.mu.Unlock() _, err := s.db.ExecContext(ctx, `INSERT OR IGNORE INTO downloads(id) VALUES (?)`, id) return err } func (s *SQLite) MarkFailed(ctx context.Context, source, mediaType, id string) error { s.mu.Lock() defer s.mu.Unlock() _, err := s.db.ExecContext( ctx, `INSERT OR IGNORE INTO failed_downloads(source, media_type, id) VALUES (?, ?, ?)`, source, mediaType, id, ) return err } func (s *SQLite) Close() error { if s.db == nil { return nil } return s.db.Close() }