163 lines
3.5 KiB
Go
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)
|
|
}
|