2024-11-26 17:21:41 +00:00
|
|
|
package files
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gtsteffaniak/filebrowser/settings"
|
|
|
|
"github.com/gtsteffaniak/filebrowser/utils"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Index struct {
|
|
|
|
Root string
|
|
|
|
Directories map[string]*FileInfo
|
|
|
|
NumDirs uint64
|
|
|
|
NumFiles uint64
|
|
|
|
NumDeleted uint64
|
|
|
|
FilesChangedDuringIndexing bool
|
|
|
|
currentSchedule int
|
|
|
|
assessment string
|
|
|
|
indexingTime int
|
|
|
|
LastIndexed time.Time
|
|
|
|
SmartModifier time.Duration
|
|
|
|
mu sync.RWMutex
|
|
|
|
scannerMu sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
rootPath string = "/srv"
|
|
|
|
indexes []*Index
|
|
|
|
indexesMutex sync.RWMutex
|
|
|
|
)
|
|
|
|
|
|
|
|
func InitializeIndex(enabled bool) {
|
|
|
|
if enabled {
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
if settings.Config.Server.Root != "" {
|
|
|
|
rootPath = settings.Config.Server.Root
|
|
|
|
}
|
|
|
|
si := GetIndex(rootPath)
|
|
|
|
log.Println("Initializing index and assessing file system complexity")
|
|
|
|
si.RunIndexing("/", false)
|
|
|
|
go si.setupIndexingScanners()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Define a function to recursively index files and directories
|
|
|
|
func (si *Index) indexDirectory(adjustedPath string, quick, recursive bool) error {
|
|
|
|
realPath := strings.TrimRight(si.Root, "/") + adjustedPath
|
|
|
|
|
|
|
|
// Open the directory
|
|
|
|
dir, err := os.Open(realPath)
|
|
|
|
if err != nil {
|
|
|
|
si.RemoveDirectory(adjustedPath) // Remove, must have been deleted
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer dir.Close()
|
|
|
|
|
|
|
|
dirInfo, err := dir.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
combinedPath := adjustedPath + "/"
|
|
|
|
if adjustedPath == "/" {
|
|
|
|
combinedPath = "/"
|
|
|
|
}
|
|
|
|
// get whats currently in cache
|
|
|
|
si.mu.RLock()
|
|
|
|
cacheDirItems := []ItemInfo{}
|
|
|
|
modChange := true // default to true
|
|
|
|
cachedDir, exists := si.Directories[adjustedPath]
|
|
|
|
if exists && quick {
|
|
|
|
modChange = dirInfo.ModTime() != cachedDir.ModTime
|
|
|
|
cacheDirItems = cachedDir.Folders
|
|
|
|
}
|
|
|
|
si.mu.RUnlock()
|
|
|
|
|
|
|
|
// If the directory has not been modified since the last index, skip expensive readdir
|
|
|
|
// recursively check cached dirs for mod time changes as well
|
|
|
|
if !modChange && recursive {
|
|
|
|
for _, item := range cacheDirItems {
|
|
|
|
err = si.indexDirectory(combinedPath+item.Name, quick, true)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("error indexing directory %v : %v", combinedPath+item.Name, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if quick {
|
|
|
|
si.mu.Lock()
|
|
|
|
si.FilesChangedDuringIndexing = true
|
|
|
|
si.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read directory contents
|
|
|
|
files, err := dir.Readdir(-1)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var totalSize int64
|
|
|
|
fileInfos := []ItemInfo{}
|
|
|
|
dirInfos := []ItemInfo{}
|
|
|
|
|
|
|
|
// Process each file and directory in the current directory
|
|
|
|
for _, file := range files {
|
|
|
|
itemInfo := &ItemInfo{
|
|
|
|
Name: file.Name(),
|
|
|
|
ModTime: file.ModTime(),
|
|
|
|
}
|
|
|
|
if file.IsDir() {
|
|
|
|
dirPath := combinedPath + file.Name()
|
|
|
|
if recursive {
|
|
|
|
// Recursively index the subdirectory
|
|
|
|
err = si.indexDirectory(dirPath, quick, recursive)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Failed to index directory %s: %v", dirPath, err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
realDirInfo, exists := si.GetMetadataInfo(dirPath, true)
|
|
|
|
if exists {
|
|
|
|
itemInfo.Size = realDirInfo.Size
|
|
|
|
}
|
|
|
|
totalSize += itemInfo.Size
|
|
|
|
itemInfo.Type = "directory"
|
|
|
|
dirInfos = append(dirInfos, *itemInfo)
|
|
|
|
si.NumDirs++
|
|
|
|
} else {
|
|
|
|
_ = itemInfo.detectType(combinedPath+file.Name(), true, false, false)
|
|
|
|
itemInfo.Size = file.Size()
|
|
|
|
fileInfos = append(fileInfos, *itemInfo)
|
|
|
|
totalSize += itemInfo.Size
|
|
|
|
si.NumFiles++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Create FileInfo for the current directory
|
|
|
|
dirFileInfo := &FileInfo{
|
|
|
|
Path: adjustedPath,
|
|
|
|
Files: fileInfos,
|
|
|
|
Folders: dirInfos,
|
|
|
|
}
|
|
|
|
dirFileInfo.ItemInfo = ItemInfo{
|
|
|
|
Name: dirInfo.Name(),
|
|
|
|
Type: "directory",
|
|
|
|
Size: totalSize,
|
|
|
|
ModTime: dirInfo.ModTime(),
|
|
|
|
}
|
|
|
|
|
|
|
|
dirFileInfo.SortItems()
|
|
|
|
|
|
|
|
// Update the current directory metadata in the index
|
|
|
|
si.UpdateMetadata(dirFileInfo)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (si *Index) makeIndexPath(subPath string) string {
|
|
|
|
if strings.HasPrefix(subPath, "./") {
|
|
|
|
subPath = strings.TrimPrefix(subPath, ".")
|
|
|
|
}
|
2024-11-27 13:30:03 +00:00
|
|
|
if si.Root == subPath || subPath == "." {
|
2024-11-26 17:21:41 +00:00
|
|
|
return "/"
|
|
|
|
}
|
|
|
|
// clean path
|
|
|
|
subPath = strings.TrimSuffix(subPath, "/")
|
|
|
|
// remove index prefix
|
|
|
|
adjustedPath := strings.TrimPrefix(subPath, si.Root)
|
|
|
|
// remove trailing slash
|
|
|
|
adjustedPath = strings.TrimSuffix(adjustedPath, "/")
|
|
|
|
if !strings.HasPrefix(adjustedPath, "/") {
|
|
|
|
adjustedPath = "/" + adjustedPath
|
|
|
|
}
|
|
|
|
return adjustedPath
|
|
|
|
}
|
|
|
|
|
|
|
|
func (si *Index) recursiveUpdateDirSizes(childInfo *FileInfo, previousSize int64) {
|
|
|
|
parentDir := utils.GetParentDirectoryPath(childInfo.Path)
|
|
|
|
parentInfo, exists := si.GetMetadataInfo(parentDir, true)
|
|
|
|
if !exists || parentDir == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
newSize := parentInfo.Size - previousSize + childInfo.Size
|
|
|
|
parentInfo.Size += newSize
|
|
|
|
si.UpdateMetadata(parentInfo)
|
|
|
|
si.recursiveUpdateDirSizes(parentInfo, newSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (si *Index) RefreshFileInfo(opts FileOptions) error {
|
|
|
|
refreshOptions := FileOptions{
|
|
|
|
Path: opts.Path,
|
|
|
|
IsDir: opts.IsDir,
|
|
|
|
}
|
|
|
|
|
|
|
|
if !refreshOptions.IsDir {
|
|
|
|
refreshOptions.Path = si.makeIndexPath(filepath.Dir(refreshOptions.Path))
|
|
|
|
refreshOptions.IsDir = true
|
|
|
|
} else {
|
|
|
|
refreshOptions.Path = si.makeIndexPath(refreshOptions.Path)
|
|
|
|
}
|
|
|
|
err := si.indexDirectory(refreshOptions.Path, false, false)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("file/folder does not exist to refresh data: %s", refreshOptions.Path)
|
|
|
|
}
|
|
|
|
file, exists := si.GetMetadataInfo(refreshOptions.Path, true)
|
|
|
|
if !exists {
|
|
|
|
return fmt.Errorf("file/folder does not exist in metadata: %s", refreshOptions.Path)
|
|
|
|
}
|
|
|
|
|
|
|
|
current, firstExisted := si.GetMetadataInfo(refreshOptions.Path, true)
|
|
|
|
refreshParentInfo := firstExisted && current.Size != file.Size
|
|
|
|
//utils.PrintStructFields(*file)
|
|
|
|
result := si.UpdateMetadata(file)
|
|
|
|
if !result {
|
|
|
|
return fmt.Errorf("file/folder does not exist in metadata: %s", refreshOptions.Path)
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if refreshParentInfo {
|
|
|
|
si.recursiveUpdateDirSizes(file, current.Size)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|