package database import ( "fmt" "log" "os" "path/filepath" "sync" "github.com/glebarez/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" "AI-Expert-Sidebar/internal/models" ) var ( mu sync.RWMutex settingsDB *gorm.DB activeLib *gorm.DB activeLibNm string DataDir string ) // Init opens/creates the settings database ($HOME/Library/Application Support/AI-Expert-Sidebar/settings.db). func Init() error { dir, err := appDataDir() if err != nil { return err } DataDir = dir if err := os.MkdirAll(dir, 0o750); err != nil { return fmt.Errorf("create data dir: %w", err) } db, err := openSQLite(filepath.Join(dir, "settings.db")) if err != nil { return fmt.Errorf("open settings.db: %w", err) } if err := db.AutoMigrate(&models.AppSetting{}, &models.Library{}); err != nil { return fmt.Errorf("migrate settings schema: %w", err) } mu.Lock() settingsDB = db mu.Unlock() log.Printf("[DB] Settings DB ready at %s/settings.db", dir) return nil } // OpenLibrary switches the active knowledge library. func OpenLibrary(lib models.Library) error { db, err := openSQLite(lib.FilePath) if err != nil { return fmt.Errorf("open library %q: %w", lib.Name, err) } if err := db.AutoMigrate(&models.Entry{}); err != nil { return fmt.Errorf("migrate library %q: %w", lib.Name, err) } mu.Lock() activeLib = db activeLibNm = lib.Name mu.Unlock() // Persist preference settingsDB.Exec( "INSERT INTO app_settings(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", "active_library", lib.Name, ) log.Printf("[DB] Active library: %s", lib.Name) return nil } // NewLibraryDB creates a fresh SQLite DB at path and migrates the Entry schema. func NewLibraryDB(path string) error { db, err := openSQLite(path) if err != nil { return err } return db.AutoMigrate(&models.Entry{}) } // NewLibraryDBReadOnly opens an existing SQLite DB read-only (for counting etc). func NewLibraryDBReadOnly(path string) (*gorm.DB, error) { return openSQLite(path + "?mode=ro") } // GetSettings returns the global settings DB (AppSetting + Library tables). func GetSettings() *gorm.DB { mu.RLock() defer mu.RUnlock() return settingsDB } // Get returns the active knowledge library DB. func Get() *gorm.DB { mu.RLock() defer mu.RUnlock() return activeLib } // GetActiveLibName returns the display name of the currently open library. func GetActiveLibName() string { mu.RLock() defer mu.RUnlock() return activeLibNm } // IsReady reports whether both the settings DB and an active library are open. func IsReady() bool { mu.RLock() defer mu.RUnlock() return settingsDB != nil && activeLib != nil } // ── helpers ─────────────────────────────────────────────────────────────────── func openSQLite(path string) (*gorm.DB, error) { return gorm.Open(sqlite.Open(path), &gorm.Config{ Logger: logger.Default.LogMode(logger.Warn), }) } func appDataDir() (string, error) { dir, err := os.UserConfigDir() if err != nil { return "", fmt.Errorf("user config dir: %w", err) } return filepath.Join(dir, "AI-Expert-Sidebar"), nil }