Задать вопрос
@accountnujen

Можно ли получить исходное имя файла до переименования из fsnotify?

пишу на пару с ИИ приложение на Go для windows, в котором есть функция отслеживания изменений в папке: создан, изменен, удален, переименован. Вот по поводу последнего есть проблема. Мне отдают только новое имя файла, старое мне недоступно. Это либо галлюцинации либо действительно только так: ии говорит, что невозможно понять, какое имя было до, потому что:

- renamedFrom (с маленькой буквы) — это неэкспортируемое (приватное) поле внутри структуры fsnotify.Event.
- Правила языка Go запрещают нам напрямую обращаться к приватным полям из другого пакета.
- Наш отладочный вывод log.Printf("%#v", event) мог показать это поле, потому что он использует специальные механизмы "рефлексии", но в обычном коде мы не можем написать event.renamedFrom.

Он предлагает решать проблему с помощью эвристики (хранить в буфере rename и ловить следующий create), что я считаю шизой на ранней стадии. При том что в логах мы видим вот такую картину
2025/10/05 22:48:31 [RENAME        "C:\CODE\temp\~WRD0000.tmp"]
2025/10/05 22:48:31 [CREATE        "C:\CODE\temp\afa.docx" ← "C:\CODE\temp\~WRD0000.tmp"]

Как видно, есть указатель при CREATE, если этот CREATE был на основе RENAME. Ну и как он сказал выше, использовать мы эти данные не можем.

Почему я считаю это галлюцинациями: когда я пытался понять, как внутри fsnotify работает - этот клоун сказал, что внутри ровно эта эвристика и используется. Что является гоневом, потому что в API Windows есть вот такое: FILE_ACTION_RENAMED_NEW_NAME (0x00000005).

вот код, который у меня сейчас

package main
import (
	"log"
	"path/filepath"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
)

var (
	debounceTimers = make(map[string]*time.Timer)
	debounceMu     sync.Mutex
)

var (
	// Карта для отслеживания файлов, которые мы меняем сами.
	internallyModifiedFiles = make(map[string]time.Time)
	internalModMu           sync.Mutex
)

func startFileWatcher(folderPath string) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Printf("ОШИБКА: не удалось создать наблюдателя: %v", err)
		return
	}
	defer watcher.Close()

	go func() {
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}

				// Пропускаем "внутренние" изменения
				internalModMu.Lock()
				ignoreUntil, isInternal := internallyModifiedFiles[event.Name]
				if isInternal && time.Now().Before(ignoreUntil) {
					internalModMu.Unlock()
					log.Printf("Watcher: Игнорируем внутреннее изменение для %s", filepath.Base(event.Name))
					continue
				}
				if isInternal {
					delete(internallyModifiedFiles, event.Name)
				}
				internalModMu.Unlock()

				// Пропускаем системные/временные файлы
				baseName := filepath.Base(event.Name)
				if shouldIgnoreFile(baseName) {
					continue
				}

				// --- ПРОСТАЯ И НАДЕЖНАЯ ЛОГИКА ---

				// ОБРАБОТКА УДАЛЕНИЯ И ПЕРЕИМЕНОВАНИЯ (как простое удаление)
				if event.Has(fsnotify.Remove) || event.Has(fsnotify.Rename) {
					log.Printf("Обнаружено удаление/переименование: %s. Добавляем в очередь.", baseName)
					relativePath, err := filepath.Rel(settings.LocalPath, event.Name)
					if err == nil {
						syncQueue <- SyncEvent{Path: filepath.ToSlash(relativePath), Type: "delete"}
					}
					continue
				}

				// ОБРАБОТКА СОЗДАНИЯ И ИЗМЕНЕНИЯ (через Debounce)
				if event.Has(fsnotify.Create) || event.Has(fsnotify.Write) {
					filePath := event.Name
					const debounceDuration = 4 * time.Second
					debounceMu.Lock()
					timer, exists := debounceTimers[filePath]
					if exists {
						timer.Reset(debounceDuration)
					} else {
						timer = time.AfterFunc(debounceDuration, func() {
							log.Printf("Debounce: Таймер для '%s' сработал. Добавляем в очередь.", baseName)
							syncQueue <- SyncEvent{Path: filePath, Type: "upsert"}
							debounceMu.Lock()
							delete(debounceTimers, filePath)
							debounceMu.Unlock()
						})
						debounceTimers[filePath] = timer
					}
					debounceMu.Unlock()
				}

			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}
				log.Println("Ошибка наблюдателя:", err)
			}
		}
	}()

	err = watcher.Add(folderPath)
	if err != nil {
		log.Printf("ОШИБКА: не удалось добавить папку '%s' для отслеживания: %v", folderPath, err)
		return
	}
	log.Printf("Начато отслеживание изменений в папке: %s", folderPath)
	<-make(chan bool)
}

func markFileAsInternallyModified(path string) {
	internalModMu.Lock()
	defer internalModMu.Unlock()
	internallyModifiedFiles[path] = time.Now().Add(10 * time.Second)
}

// shouldIgnoreFile - вспомогательная функция для проверки имени файла.
func shouldIgnoreFile(name string) bool {
	baseName := filepath.Base(name)
	if baseName == lockFileName || baseName == syncStateFileName || strings.HasPrefix(baseName, "~") {
		return true
	}
	return false
}

  • Вопрос задан
  • 208 просмотров
Подписаться 3 Простой 10 комментариев
Помогут разобраться в теме Все курсы
  • Нетология
    Go-разработчик с нуля
    9 месяцев
    Далее
  • Skillfactory
    Профессия Backend-разработчик на Go
    12 месяцев
    Далее
  • Яндекс Практикум
    Go-разработчик с нуля
    8 месяцев
    Далее
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы