package index import ( "fmt" "os" "path/filepath" "sync" ) // TenantIndexManager manages a pool of Xapian indexes, one per tenant. // Tenant 0 / nil maps to the global index used by superadmin and as a fallback. type TenantIndexManager struct { basePath string batchSize int backend string mu sync.RWMutex pool map[int64]Indexer // tenant_id -> Indexer global Indexer // tenant_id == 0 / nil (superadmin, fallback) } // NewTenantIndexManager creates a new TenantIndexManager. // The global index lives at basePath directly; per-tenant indexes at basePath/tenant-/. func NewTenantIndexManager(basePath string, batchSize int, backend string) (*TenantIndexManager, error) { // Ensure the base path directory exists. if err := os.MkdirAll(basePath, 0o750); err != nil { return nil, fmt.Errorf("tenant index manager: mkdir base: %w", err) } global, err := New(basePath, batchSize, backend) if err != nil { return nil, fmt.Errorf("tenant index manager: open global: %w", err) } return &TenantIndexManager{ basePath: basePath, batchSize: batchSize, backend: backend, pool: make(map[int64]Indexer), global: global, }, nil } // ForTenant returns the Indexer for the given tenant. // nil or pointer to 0 returns the global index. // Other tenant IDs lazily open a per-tenant index at basePath/tenant-/. func (m *TenantIndexManager) ForTenant(tenantID *int64) Indexer { if tenantID == nil || *tenantID == 0 { return m.global } id := *tenantID // Fast path: read lock. m.mu.RLock() if idx, ok := m.pool[id]; ok { m.mu.RUnlock() return idx } m.mu.RUnlock() // Slow path: write lock, create if not exists. m.mu.Lock() defer m.mu.Unlock() // Double check after acquiring write lock. if idx, ok := m.pool[id]; ok { return idx } dir := filepath.Join(m.basePath, fmt.Sprintf("tenant-%d", id)) if err := os.MkdirAll(dir, 0o750); err != nil { // Return global as fallback on error. return m.global } idx, err := New(dir, m.batchSize, m.backend) if err != nil { // Return global as fallback on error. return m.global } m.pool[id] = idx return idx } // Global returns the global (non-tenant) index. func (m *TenantIndexManager) Global() Indexer { return m.global } // Close closes all indexes (global + per-tenant). func (m *TenantIndexManager) Close() error { m.mu.Lock() defer m.mu.Unlock() var firstErr error for id, idx := range m.pool { if err := idx.Close(); err != nil && firstErr == nil { firstErr = fmt.Errorf("close tenant-%d index: %w", id, err) } delete(m.pool, id) } if m.global != nil { if err := m.global.Close(); err != nil && firstErr == nil { firstErr = fmt.Errorf("close global index: %w", err) } } return firstErr }