munin/internal/workspace/workspace.go
2026-10-19 10:13:56 +02:00

123 lines
3.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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
}