diff options
Diffstat (limited to 'internal/store/plugin.go')
| -rw-r--r-- | internal/store/plugin.go | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/internal/store/plugin.go b/internal/store/plugin.go new file mode 100644 index 0000000..545d9b4 --- /dev/null +++ b/internal/store/plugin.go @@ -0,0 +1,156 @@ +package store + +import ( + "fmt" + + "github.com/shiv/internal/events" + "github.com/shiv/internal/logger" +) + +const maxPluginLogLines = 500 + +// PluginEntry represents a loaded plugin and its persistent enabled state. +type PluginEntry struct { + Name string + Path string + Enabled bool +} + +// RegisterPlugin records a plugin in the database. If the plugin is new, +// enabled defaults to true. If it already exists, only the path is updated — +// the existing enabled state is preserved. Notifies the UI via PluginEntries. +func (s *Store) RegisterPlugin(name, path string) error { + if err := s.write(func() error { + _, err := s.db.Exec(` + INSERT INTO plugins (name, path, enabled) VALUES (?, ?, 1) + ON CONFLICT(name) DO UPDATE SET path = excluded.path`, + name, path, + ) + if err != nil { + return fmt.Errorf("store: register plugin %s: %w", name, err) + } + return nil + }); err != nil { + return err + } + + select { + case s.PluginEntries <- struct{}{}: + default: + } + return nil +} + +// Scans for plugins on the disk, removes them from DB if they are deleted from disk. +func (s *Store) ScanPlugins(active []string) error { + set := make(map[string]struct{}, len(active)) + for _, name := range active { + set[name] = struct{}{} + } + all, err := s.AllPlugins() + if err != nil { + return err + } + for _, e := range all { + if _, ok := set[e.Name]; ok { + continue + } + if err := s.write(func() error { + _, err := s.db.Exec(`DELETE FROM plugins WHERE name = ?`, e.Name) + return err + }); err != nil { + return fmt.Errorf("store: scan plugins remove %s: %w", e.Name, err) + } + } + select { + case s.PluginEntries <- struct{}{}: + default: + } + return nil +} + +// PluginEnabled returns the persisted enabled state for a plugin. +// Returns true if the plugin record is not found (safe default for new plugins). +func (s *Store) PluginEnabled(name string) (bool, error) { + var enabled int + err := s.db.QueryRow(`SELECT enabled FROM plugins WHERE name = ?`, name).Scan(&enabled) + if err != nil { + return true, err + } + return enabled == 1, nil +} + +// AllPlugins returns all known plugins ordered by name. +func (s *Store) AllPlugins() ([]PluginEntry, error) { + rows, err := s.db.Query(`SELECT name, path, enabled FROM plugins ORDER BY name ASC`) + if err != nil { + return nil, fmt.Errorf("store: query plugins: %w", err) + } + defer rows.Close() + + var entries []PluginEntry + for rows.Next() { + var e PluginEntry + var enabledInt int + if err := rows.Scan(&e.Name, &e.Path, &enabledInt); err != nil { + return nil, fmt.Errorf("store: scan plugin: %w", err) + } + e.Enabled = enabledInt == 1 + entries = append(entries, e) + } + return entries, rows.Err() +} + +// PluginLogs returns the in-memory log lines for a plugin, oldest first. +// Returns a copy to avoid races with the writer. +func (s *Store) PluginLogs(name string) []string { + s.pluginLogsMu.RLock() + defer s.pluginLogsMu.RUnlock() + lines := s.pluginLogs[name] + if len(lines) == 0 { + return nil + } + result := make([]string, len(lines)) + copy(result, lines) + return result +} + +// ObservePluginLog implements events.PluginLogObserver. +// Appends the log line to the in-memory ring buffer and notifies the UI. +func (s *Store) ObservePluginLog(e events.PluginLogEvent) { + s.pluginLogsMu.Lock() + lines := s.pluginLogs[e.Name] + lines = append(lines, e.Message) + if len(lines) > maxPluginLogLines { + lines = lines[len(lines)-maxPluginLogLines:] + } + s.pluginLogs[e.Name] = lines + s.pluginLogsMu.Unlock() + + select { + case s.PluginEntries <- struct{}{}: + default: + } +} + +// ObservePluginEnabled implements events.PluginEnabledObserver. +// Persists the new enabled state to the database and notifies the UI. +func (s *Store) ObservePluginEnabled(e events.SetPluginEnabledEvent) { + if err := s.write(func() error { + _, err := s.db.Exec(`UPDATE plugins SET enabled = ? WHERE name = ?`, + boolToInt(e.Enabled), e.Name, + ) + if err != nil { + return fmt.Errorf("store: set plugin enabled %s: %w", e.Name, err) + } + return nil + }); err != nil { + logger.Error("store: observe plugin enabled: %v", err) + return + } + + select { + case s.PluginEntries <- struct{}{}: + default: + } +} |
