123 lines
3.9 KiB
Go
123 lines
3.9 KiB
Go
// Package workspace verwaltet die offenen Workspaces eines laufenden
|
||
// Prozesses.
|
||
//
|
||
// Jede Abteilung hat ihren eigenen, abgeschotteten Bereich (siehe
|
||
// AUSBAUSTUFEN.md 4.4): eine SQLite-Datei oder ein PostgreSQL-Schema — welches
|
||
// von beiden, entscheidet der store.Backend (AUSBAUSTUFEN.md 4.10). Ein
|
||
// Manager öffnet die Bereiche bei Bedarf und hält sie offen, statt bei jedem
|
||
// Request neu zu öffnen — das Öffnen (inklusive Migrationsprüfung) kostet
|
||
// spürbar mehr als ein Map-Zugriff.
|
||
package workspace
|
||
|
||
import (
|
||
"fmt"
|
||
"sort"
|
||
"sync"
|
||
|
||
"munin/internal/search"
|
||
"munin/internal/store"
|
||
)
|
||
|
||
// Workspace bündelt, was zu einer offenen Workspace-Datei gehört: die
|
||
// Datenbank und ihren Suchindex. Beide sind an dieselbe Datei gebunden und
|
||
// werden deshalb gemeinsam verwaltet — ein Registry, der aus Versehen an der
|
||
// falschen Datenbank hängt, wäre ein stiller Fehler.
|
||
type Workspace struct {
|
||
ID string
|
||
Store *store.Store
|
||
Search *search.Registry
|
||
}
|
||
|
||
// Manager öffnet Workspace-Dateien bei Bedarf und hält sie offen.
|
||
//
|
||
// Bei 10–40 Workspaces (siehe AUSBAUSTUFEN.md 4.4) ist das unproblematisch —
|
||
// Verdrängung selten gebrauchter Dateien würde erst bei einer deutlich
|
||
// höheren Zahl nötig, siehe AUSBAUSTUFEN.md 1.2.
|
||
type Manager struct {
|
||
backend store.Backend // SQLite-Dateien oder PostgreSQL-Schemas
|
||
|
||
mu sync.Mutex
|
||
open map[string]*Workspace
|
||
}
|
||
|
||
// NewManager legt einen Manager an, der seine Workspaces über backend öffnet.
|
||
func NewManager(backend store.Backend) *Manager {
|
||
return &Manager{backend: backend, open: make(map[string]*Workspace)}
|
||
}
|
||
|
||
// Get liefert den Workspace zu einer ID und öffnet ihn beim ersten Zugriff.
|
||
func (m *Manager) Get(workspaceID string) (*Workspace, error) {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
|
||
if w, ok := m.open[workspaceID]; ok {
|
||
return w, nil
|
||
}
|
||
|
||
s, err := m.backend.OpenWorkspace(workspaceID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("Workspace %s konnte nicht geöffnet werden: %w", workspaceID, err)
|
||
}
|
||
|
||
w := &Workspace{ID: workspaceID, Store: s, Search: search.NewRegistry(s)}
|
||
m.open[workspaceID] = w
|
||
return w, nil
|
||
}
|
||
|
||
// Delete schließt den Workspace, falls offen, und entfernt seinen Bereich
|
||
// unwiederbringlich — die Datei samt Nebendateien bzw. das Schema samt
|
||
// Tabellen (siehe store.Backend.DeleteWorkspace).
|
||
//
|
||
// Räumt nur den Bereich ab — die Kontroll-Datenbank (workspaces, key_index)
|
||
// muss getrennt bereinigt werden, siehe ControlStore.DeleteWorkspace.
|
||
// Reihenfolge ist wichtig: Erst dort löschen, dann hier — sonst verweist die
|
||
// Kontroll-Datenbank kurzzeitig auf einen Bereich, den es nicht mehr gibt.
|
||
func (m *Manager) Delete(workspaceID string) error {
|
||
m.mu.Lock()
|
||
if w, ok := m.open[workspaceID]; ok {
|
||
w.Store.Close()
|
||
delete(m.open, workspaceID)
|
||
}
|
||
m.mu.Unlock()
|
||
|
||
return m.backend.DeleteWorkspace(workspaceID)
|
||
}
|
||
|
||
// Backend gibt den Speicher zurück, über den der Manager öffnet — für die
|
||
// Systemseite (Beschreibung, Kapazität).
|
||
func (m *Manager) Backend() store.Backend { return m.backend }
|
||
|
||
// OpenWorkspaces liefert die gerade geöffneten Workspaces, nach ID sortiert. Eine
|
||
// Momentaufnahme: Währenddessen kann ein weiterer geöffnet werden.
|
||
func (m *Manager) OpenWorkspaces() []*Workspace {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
|
||
list := make([]*Workspace, 0, len(m.open))
|
||
for _, w := range m.open {
|
||
list = append(list, w)
|
||
}
|
||
sort.Slice(list, func(i, j int) bool { return list[i].ID < list[j].ID })
|
||
return list
|
||
}
|
||
|
||
// Location nennt, wo die Daten eines Workspace liegen — Schema oder Datei (siehe
|
||
// store.Backend.Ort). Öffnet dafür nichts.
|
||
func (m *Manager) Location(workspaceID string) string {
|
||
return m.backend.Location(workspaceID)
|
||
}
|
||
|
||
// Close schließt alle offenen Workspace-Dateien.
|
||
func (m *Manager) Close() error {
|
||
m.mu.Lock()
|
||
defer m.mu.Unlock()
|
||
|
||
var first error
|
||
for id, w := range m.open {
|
||
if err := w.Store.Close(); err != nil && first == nil {
|
||
first = err
|
||
}
|
||
delete(m.open, id)
|
||
}
|
||
return first
|
||
}
|