summaryrefslogtreecommitdiff
path: root/internal/store/plugin.go
blob: 545d9b468612f9f27420b4a14166f0115bd01cd2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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:
	}
}