Fileserver/internal/filesystem/indexer_memory.go
2025-08-13 09:44:02 +02:00

163 lines
3.5 KiB
Go

package filesystem
import (
"fmt"
"os"
"path/filepath"
"sync"
)
var _ FileIndexer = (*MemoryIndexer)(nil)
// MemoryIndexer is an in-memory implementation of FileIndexer.
type MemoryIndexer struct {
root string
tree *FileNode
flat map[string]*FileNode
mu sync.RWMutex
}
// NewMemoryIndexer loads and returns an in-memory FileIndexer.
func NewMemoryIndexer(root string) (*MemoryIndexer, error) {
idx := &MemoryIndexer{
root: root,
flat: make(map[string]*FileNode),
}
flat := make(map[string]*FileNode)
tree, err := idx.buildTree(root, nil, flat)
if err != nil {
return nil, fmt.Errorf("load file index tree root: %v", err)
}
idx.tree = tree
idx.flat = flat
return idx, nil
}
// buildTree builds a FileNode tree in memory from a filesystem path.
func (m *MemoryIndexer) buildTree(path string, parent *FileNode, flatMap map[string]*FileNode) (*FileNode, error) {
info, err := os.Stat(path)
if err != nil {
return nil, fmt.Errorf("node stat: %v", err)
}
node := &FileNode{
Name: info.Name(),
IsDir: info.IsDir(),
Size: info.Size(),
ModTime: info.ModTime(),
Parent: parent,
}
flatMap[path] = node
if info.IsDir() {
children, err := os.ReadDir(path)
if err != nil {
return nil, fmt.Errorf("get children: %v", err)
}
for _, c := range children {
childPath := filepath.Join(path, c.Name())
childNode, err := m.buildTree(childPath, node, flatMap)
if err != nil {
fmt.Printf("Skipping child node due to failure: %v", err)
continue
}
node.Children = append(node.Children, childNode)
}
}
return node, nil
}
// GetNode retrieves a node from the indexer based on its path.
func (m *MemoryIndexer) GetNode(path string) (*FileNode, error) {
m.mu.RLock()
defer m.mu.RUnlock()
if node, ok := m.flat[path]; ok {
return node, nil
}
return nil, os.ErrNotExist
}
// Search retrieves all nodes that match the given SearchFilter.
func (m *MemoryIndexer) Search(filter SearchFilter) []*FileNode {
m.mu.RLock()
defer m.mu.RUnlock()
var matched []*FileNode
for _, node := range m.flat {
if filter(node) {
matched = append(matched, node)
}
}
return matched
}
// GetPath reconstructs the filesystem path of a node via reverse tree traversal.
func (m *MemoryIndexer) GetPath(node *FileNode) string {
var parts []string
for n := node; n != nil; n = n.Parent {
parts = append([]string{n.Name}, parts...)
}
return "/" + filepath.Join(parts...)
}
// Reload reloads the entire indexer tree into the memory from the filesystem.
func (m *MemoryIndexer) Reload() error {
m.mu.Lock()
defer m.mu.Unlock()
newFlat := make(map[string]*FileNode)
newTree, err := m.buildTree(m.root, nil, newFlat)
if err != nil {
return fmt.Errorf("load file index tree root: %v", err)
}
m.flat = newFlat
m.tree = newTree
return nil
}
// ReloadFrom reloads the given node and everything below it into memory from the filesystem.
func (m *MemoryIndexer) ReloadFrom(node *FileNode) error {
m.mu.Lock()
defer m.mu.Unlock()
path := m.GetPath(node)
newFlat := make(map[string]*FileNode)
newNode, err := m.buildTree(path, node.Parent, newFlat)
if err != nil {
return fmt.Errorf("load file index tree node: %v", err)
}
if node.Parent != nil {
for i, child := range node.Parent.Children {
if child == node {
node.Parent.Children[i] = newNode
break
}
}
}
for k, v := range newFlat {
m.flat[k] = v
}
return nil
}
// Count just returns the total amount of nodes in the internal indexer tree.
func (m *MemoryIndexer) Count() int {
return len(m.flat)
}