Как правильно подойти к вопросу: можно ли получить исходное имя файла до переименования из 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 }
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос