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