|
@@ -296,34 +296,66 @@ func (s *Scanner) watchForChanges() {
|
|
|
|
|
|
// scanSingleFile scans a single file and executes all registered callbacks
|
|
|
func (s *Scanner) scanSingleFile(filePath string) error {
|
|
|
- // Set scanning state to true
|
|
|
- s.scanMutex.Lock()
|
|
|
- wasScanning := s.scanning
|
|
|
- s.scanning = true
|
|
|
- if !wasScanning {
|
|
|
- // Only broadcast if status changed from not scanning to scanning
|
|
|
- s.statusChan <- true
|
|
|
+ return s.scanSingleFileWithDepth(filePath, make(map[string]bool), 0)
|
|
|
+}
|
|
|
+
|
|
|
+// scanSingleFileWithDepth scans a single file with recursion protection
|
|
|
+func (s *Scanner) scanSingleFileWithDepth(filePath string, visited map[string]bool, depth int) error {
|
|
|
+ // Maximum recursion depth to prevent infinite recursion
|
|
|
+ const maxDepth = 10
|
|
|
+
|
|
|
+ if depth > maxDepth {
|
|
|
+ logger.Warn("Maximum recursion depth reached for file:", filePath)
|
|
|
+ return nil
|
|
|
}
|
|
|
- s.scanMutex.Unlock()
|
|
|
|
|
|
- // Ensure we reset scanning state when done
|
|
|
- defer func() {
|
|
|
+ // Resolve the absolute path to handle symlinks properly
|
|
|
+ absPath, err := filepath.Abs(filePath)
|
|
|
+ if err != nil {
|
|
|
+ logger.Error("Failed to resolve absolute path for:", filePath, err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check for circular includes
|
|
|
+ if visited[absPath] {
|
|
|
+ // Circular include detected, skip this file
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mark this file as visited
|
|
|
+ visited[absPath] = true
|
|
|
+
|
|
|
+ // Set scanning state to true only for the root call (depth 0)
|
|
|
+ var wasScanning bool
|
|
|
+ if depth == 0 {
|
|
|
s.scanMutex.Lock()
|
|
|
- s.scanning = false
|
|
|
- // Broadcast the completion
|
|
|
- s.statusChan <- false
|
|
|
+ wasScanning = s.scanning
|
|
|
+ s.scanning = true
|
|
|
+ if !wasScanning {
|
|
|
+ // Only broadcast if status changed from not scanning to scanning
|
|
|
+ s.statusChan <- true
|
|
|
+ }
|
|
|
s.scanMutex.Unlock()
|
|
|
- }()
|
|
|
+
|
|
|
+ // Ensure we reset scanning state when done (only for root call)
|
|
|
+ defer func() {
|
|
|
+ s.scanMutex.Lock()
|
|
|
+ s.scanning = false
|
|
|
+ // Broadcast the completion
|
|
|
+ s.statusChan <- false
|
|
|
+ s.scanMutex.Unlock()
|
|
|
+ }()
|
|
|
+ }
|
|
|
|
|
|
// Open the file
|
|
|
- file, err := os.Open(filePath)
|
|
|
+ file, err := os.Open(absPath)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
defer file.Close()
|
|
|
|
|
|
// Read the entire file content
|
|
|
- content, err := os.ReadFile(filePath)
|
|
|
+ content, err := os.ReadFile(absPath)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
@@ -331,9 +363,9 @@ func (s *Scanner) scanSingleFile(filePath string) error {
|
|
|
// Execute all registered callbacks
|
|
|
scanCallbacksMutex.RLock()
|
|
|
for _, callback := range scanCallbacks {
|
|
|
- err := callback(filePath, content)
|
|
|
+ err := callback(absPath, content)
|
|
|
if err != nil {
|
|
|
- logger.Error("Callback error for file", filePath, ":", err)
|
|
|
+ logger.Error("Callback error for file", absPath, ":", err)
|
|
|
}
|
|
|
}
|
|
|
scanCallbacksMutex.RUnlock()
|
|
@@ -363,7 +395,7 @@ func (s *Scanner) scanSingleFile(filePath string) error {
|
|
|
for _, matchedFile := range matchedFiles {
|
|
|
fileInfo, err := os.Stat(matchedFile)
|
|
|
if err == nil && !fileInfo.IsDir() {
|
|
|
- err = s.scanSingleFile(matchedFile)
|
|
|
+ err = s.scanSingleFileWithDepth(matchedFile, visited, depth+1)
|
|
|
if err != nil {
|
|
|
logger.Error("Failed to scan included file:", matchedFile, err)
|
|
|
}
|
|
@@ -379,7 +411,7 @@ func (s *Scanner) scanSingleFile(filePath string) error {
|
|
|
|
|
|
fileInfo, err := os.Stat(includePath)
|
|
|
if err == nil && !fileInfo.IsDir() {
|
|
|
- err = s.scanSingleFile(includePath)
|
|
|
+ err = s.scanSingleFileWithDepth(includePath, visited, depth+1)
|
|
|
if err != nil {
|
|
|
logger.Error("Failed to scan included file:", includePath, err)
|
|
|
}
|