mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-18 23:33:51 +01:00
This is a large-scale refactoring across the codebase that replaces the custom `gperr.Error` type with Go's standard `error` interface. The changes include: - Replacing `gperr.Error` return types with `error` in function signatures - Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()` - Using `%w` format verb for error wrapping instead of `.With()` method - Replacing `gperr.Subject()` calls with `gperr.PrependSubject()` - Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern - Update NewLogger to handle multiline error message - Updating `goutils` submodule to latest commit This refactoring aligns with Go idioms and removes the dependency on custom error handling abstractions in favor of standard library patterns.
170 lines
3.6 KiB
Go
170 lines
3.6 KiB
Go
package watcher
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/fsnotify/fsnotify"
|
|
"github.com/rs/zerolog"
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/yusing/godoxy/internal/watcher/events"
|
|
"github.com/yusing/goutils/task"
|
|
)
|
|
|
|
type DirWatcher struct {
|
|
zerolog.Logger
|
|
|
|
dir string
|
|
w *fsnotify.Watcher
|
|
|
|
fwMap map[string]*fileWatcher
|
|
mu sync.Mutex
|
|
|
|
eventCh chan Event
|
|
errCh chan error
|
|
|
|
task *task.Task
|
|
}
|
|
|
|
// NewDirectoryWatcher returns a DirWatcher instance.
|
|
//
|
|
// The DirWatcher watches the given directory for file system events.
|
|
// Currently, only events on files directly in the given directory are watched, not
|
|
// recursively.
|
|
//
|
|
// Note that the returned DirWatcher is not ready to use until the goroutine
|
|
// started by NewDirectoryWatcher has finished.
|
|
func NewDirectoryWatcher(parent task.Parent, dirPath string) *DirWatcher {
|
|
//! subdirectories are not watched
|
|
w, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
log.Panic().Err(err).Msg("unable to create fs watcher")
|
|
}
|
|
if err = w.Add(dirPath); err != nil {
|
|
log.Panic().Err(err).Msg("unable to create fs watcher")
|
|
}
|
|
helper := &DirWatcher{
|
|
Logger: log.With().
|
|
Str("type", "dir").
|
|
Str("path", dirPath).
|
|
Logger(),
|
|
dir: dirPath,
|
|
w: w,
|
|
fwMap: make(map[string]*fileWatcher),
|
|
eventCh: make(chan Event),
|
|
errCh: make(chan error),
|
|
task: parent.Subtask("dir_watcher("+dirPath+")", true),
|
|
}
|
|
go helper.start()
|
|
return helper
|
|
}
|
|
|
|
func (h *DirWatcher) Events(_ context.Context) (<-chan Event, <-chan error) {
|
|
return h.eventCh, h.errCh
|
|
}
|
|
|
|
func (h *DirWatcher) Add(relPath string) Watcher {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
|
|
// check if the watcher already exists
|
|
s, ok := h.fwMap[relPath]
|
|
if ok {
|
|
return s
|
|
}
|
|
s = &fileWatcher{
|
|
relPath: relPath,
|
|
eventCh: make(chan Event),
|
|
errCh: make(chan error),
|
|
}
|
|
h.fwMap[relPath] = s
|
|
return s
|
|
}
|
|
|
|
func (h *DirWatcher) cleanup() {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
|
|
h.w.Close()
|
|
close(h.eventCh)
|
|
close(h.errCh)
|
|
for _, fw := range h.fwMap {
|
|
close(fw.eventCh)
|
|
close(fw.errCh)
|
|
}
|
|
h.task.Finish(nil)
|
|
}
|
|
|
|
func (h *DirWatcher) start() {
|
|
defer h.cleanup()
|
|
|
|
for {
|
|
select {
|
|
case <-h.task.Context().Done():
|
|
return
|
|
case fsEvent, ok := <-h.w.Events:
|
|
if !ok {
|
|
return
|
|
}
|
|
// retrieve the watcher
|
|
relPath := strings.TrimPrefix(fsEvent.Name, h.dir)
|
|
relPath = strings.TrimPrefix(relPath, "/")
|
|
|
|
if len(relPath) > 0 && relPath[0] == '.' { // hideden file
|
|
continue
|
|
}
|
|
|
|
msg := Event{
|
|
Type: events.EventTypeFile,
|
|
ActorName: relPath,
|
|
}
|
|
switch {
|
|
case fsEvent.Has(fsnotify.Write):
|
|
msg.Action = events.ActionFileWritten
|
|
case fsEvent.Has(fsnotify.Create):
|
|
msg.Action = events.ActionFileCreated
|
|
case fsEvent.Has(fsnotify.Remove):
|
|
msg.Action = events.ActionFileDeleted
|
|
case fsEvent.Has(fsnotify.Rename):
|
|
msg.Action = events.ActionFileRenamed
|
|
default: // ignore other events
|
|
continue
|
|
}
|
|
|
|
// send event to directory watcher
|
|
select {
|
|
case h.eventCh <- msg:
|
|
h.Debug().Msg("sent event to directory watcher")
|
|
default:
|
|
h.Debug().Msg("failed to send event to directory watcher")
|
|
}
|
|
|
|
// send event to file watcher too
|
|
h.mu.Lock()
|
|
w, ok := h.fwMap[relPath]
|
|
h.mu.Unlock()
|
|
if ok {
|
|
select {
|
|
case w.eventCh <- msg:
|
|
h.Debug().Msg("sent event to file watcher " + relPath)
|
|
default:
|
|
h.Debug().Msg("failed to send event to file watcher " + relPath)
|
|
}
|
|
} else {
|
|
h.Debug().Msg("file watcher not found: " + relPath)
|
|
}
|
|
case err := <-h.w.Errors:
|
|
if errors.Is(err, fsnotify.ErrClosed) {
|
|
// closed manually?
|
|
return
|
|
}
|
|
select {
|
|
case h.errCh <- err:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
}
|