filebrowser/backend/files/search.go

206 lines
5.2 KiB
Go
Raw Normal View History

package files
2023-06-15 01:08:09 +00:00
import (
2023-08-12 16:30:41 +00:00
"math/rand"
2023-06-15 01:08:09 +00:00
"path/filepath"
2023-06-18 15:04:31 +00:00
"sort"
2023-06-15 01:08:09 +00:00
"strings"
2023-06-16 17:29:43 +00:00
"sync"
2023-06-15 01:08:09 +00:00
"time"
)
2023-06-16 17:29:43 +00:00
var (
sessionInProgress sync.Map
maxSearchResults = 100
2023-06-16 17:29:43 +00:00
)
2023-06-15 01:08:09 +00:00
func (si *Index) Search(search string, scope string, sourceSession string) ([]string, map[string]map[string]bool) {
// Remove slashes
scope = strings.TrimLeft(scope, "/")
scope = strings.TrimRight(scope, "/")
runningHash := generateRandomHash(4)
sessionInProgress.Store(sourceSession, runningHash) // Store the value in the sync.Map
2023-06-18 15:04:31 +00:00
searchOptions := ParseSearch(search)
2023-07-13 02:23:29 +00:00
fileListTypes := make(map[string]map[string]bool)
matching := []string{}
count := 0
2023-06-18 15:04:31 +00:00
for _, searchTerm := range searchOptions.Terms {
2023-07-04 23:55:15 +00:00
if searchTerm == "" {
continue
}
si.mu.Lock()
for dirName, dir := range si.Directories {
isDir := true
2024-10-07 22:44:53 +00:00
files := []string{}
for _, item := range dir.Items {
if !item.IsDir {
files = append(files, item.Name)
}
}
value, found := sessionInProgress.Load(sourceSession)
if !found || value != runningHash {
2024-10-07 22:44:53 +00:00
si.mu.Unlock()
return []string{}, map[string]map[string]bool{}
}
if count > maxSearchResults {
break
}
pathName := scopedPathNameFilter(dirName, scope, isDir)
if pathName == "" {
continue // path not matched
}
fileTypes := map[string]bool{}
2024-10-07 22:44:53 +00:00
si.mu.Unlock()
matches, fileType := si.containsSearchTerm(dirName, searchTerm, *searchOptions, isDir, fileTypes)
si.mu.Lock()
if matches {
fileListTypes[pathName] = fileType
matching = append(matching, pathName)
count++
}
isDir = false
for _, file := range files {
if file == "" {
continue
}
2023-08-17 21:46:49 +00:00
value, found := sessionInProgress.Load(sourceSession)
if !found || value != runningHash {
return []string{}, map[string]map[string]bool{}
}
if count > maxSearchResults {
2023-08-17 21:46:49 +00:00
break
}
2024-08-25 00:12:47 +00:00
fullName := strings.TrimLeft(pathName+file, "/")
fileTypes := map[string]bool{}
2024-10-07 22:44:53 +00:00
si.mu.Unlock()
matches, fileType := si.containsSearchTerm(fullName, searchTerm, *searchOptions, isDir, fileTypes)
si.mu.Lock()
if !matches {
continue
}
fileListTypes[fullName] = fileType
matching = append(matching, fullName)
count++
2023-07-13 02:23:29 +00:00
}
2023-06-15 01:08:09 +00:00
}
2024-10-07 22:44:53 +00:00
si.mu.Unlock()
2023-06-15 01:08:09 +00:00
}
2023-06-16 17:29:43 +00:00
// Sort the strings based on the number of elements after splitting by "/"
sort.Slice(matching, func(i, j int) bool {
parts1 := strings.Split(matching[i], "/")
parts2 := strings.Split(matching[j], "/")
2023-06-16 17:29:43 +00:00
return len(parts1) < len(parts2)
})
return matching, fileListTypes
2023-06-15 01:08:09 +00:00
}
func scopedPathNameFilter(pathName string, scope string, isDir bool) string {
pathName = strings.TrimLeft(pathName, "/")
pathName = strings.TrimRight(pathName, "/")
if strings.HasPrefix(pathName, scope) || scope == "" {
pathName = strings.TrimPrefix(pathName, scope)
pathName = strings.TrimLeft(pathName, "/")
if isDir {
pathName = pathName + "/"
}
2023-08-17 21:46:49 +00:00
} else {
pathName = "" // return not matched
}
2023-08-17 21:46:49 +00:00
return pathName
}
2024-10-07 22:44:53 +00:00
func (si *Index) containsSearchTerm(pathName string, searchTerm string, options SearchOptions, isDir bool, fileTypes map[string]bool) (bool, map[string]bool) {
largerThan := int64(options.LargerThan) * 1024 * 1024
smallerThan := int64(options.SmallerThan) * 1024 * 1024
conditions := options.Conditions
2024-10-07 22:44:53 +00:00
fileName := filepath.Base(pathName)
adjustedPath := si.makeIndexPath(pathName, isDir)
// Convert to lowercase if not exact match
2023-08-12 16:30:41 +00:00
if !conditions["exact"] {
2024-10-07 22:44:53 +00:00
fileName = strings.ToLower(fileName)
searchTerm = strings.ToLower(searchTerm)
2023-08-12 16:30:41 +00:00
}
2024-10-07 22:44:53 +00:00
// Check if the file name contains the search term
if !strings.Contains(fileName, searchTerm) {
return false, map[string]bool{}
}
// Initialize file size and fileTypes map
var fileSize int64
extension := filepath.Ext(fileName)
// Collect file types
for _, k := range AllFiletypeOptions {
if IsMatchingType(extension, k) {
fileTypes[k] = true
}
2024-10-07 22:44:53 +00:00
}
fileTypes["dir"] = isDir
// Get file info if needed for size-related conditions
if largerThan > 0 || smallerThan > 0 {
fileInfo, exists := si.GetMetadataInfo(adjustedPath)
if !exists {
return false, fileTypes
} else if !isDir {
// Look for specific file in ReducedItems
for _, item := range fileInfo.ReducedItems {
lower := strings.ToLower(item.Name)
if strings.Contains(lower, searchTerm) {
if item.Size == 0 {
return false, fileTypes
}
fileSize = item.Size
break
}
2023-08-12 16:30:41 +00:00
}
2024-10-07 22:44:53 +00:00
} else {
fileSize = fileInfo.Size
}
if fileSize == 0 {
return false, fileTypes
}
}
// Evaluate all conditions
for t, v := range conditions {
if t == "exact" {
continue
}
switch t {
case "larger":
if largerThan > 0 {
if fileSize <= largerThan {
return false, fileTypes
}
2024-10-07 22:44:53 +00:00
}
case "smaller":
if smallerThan > 0 {
if fileSize >= smallerThan {
return false, fileTypes
}
}
2024-10-07 22:44:53 +00:00
default:
// Handle other file type conditions
notMatchType := v != fileTypes[t]
if notMatchType {
return false, fileTypes
2023-07-31 22:20:14 +00:00
}
}
}
2024-10-07 22:44:53 +00:00
return true, fileTypes
2023-06-18 15:04:31 +00:00
}
func generateRandomHash(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
2023-09-25 01:03:09 +00:00
rand.New(rand.NewSource(time.Now().UnixNano()))
result := make([]byte, length)
for i := range result {
result[i] = charset[rand.Intn(len(charset))]
}
return string(result)
}