mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-23 08:48:32 +02:00
restructured the project to comply community guideline, for others check release note
This commit is contained in:
146
internal/watcher/directory_watcher.go
Normal file
146
internal/watcher/directory_watcher.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/sirupsen/logrus"
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||
"github.com/yusing/go-proxy/internal/watcher/events"
|
||||
)
|
||||
|
||||
type dirWatcher struct {
|
||||
dir string
|
||||
w *fsnotify.Watcher
|
||||
|
||||
fwMap F.Map[string, *fileWatcher]
|
||||
mu sync.Mutex
|
||||
|
||||
eventCh chan Event
|
||||
errCh chan E.NestedError
|
||||
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewDirectoryWatcher(ctx context.Context, dirPath string) *dirWatcher {
|
||||
//! subdirectories are not watched
|
||||
w, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
logrus.Panicf("unable to create fs watcher: %s", err)
|
||||
}
|
||||
if err = w.Add(dirPath); err != nil {
|
||||
logrus.Panicf("unable to create fs watcher: %s", err)
|
||||
}
|
||||
helper := &dirWatcher{
|
||||
dir: dirPath,
|
||||
w: w,
|
||||
fwMap: F.NewMapOf[string, *fileWatcher](),
|
||||
eventCh: make(chan Event),
|
||||
errCh: make(chan E.NestedError),
|
||||
ctx: ctx,
|
||||
}
|
||||
go helper.start()
|
||||
return helper
|
||||
}
|
||||
|
||||
func (h *dirWatcher) Events(_ context.Context) (<-chan Event, <-chan E.NestedError) {
|
||||
return h.eventCh, h.errCh
|
||||
}
|
||||
|
||||
func (h *dirWatcher) Add(relPath string) *fileWatcher {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
// check if the watcher already exists
|
||||
s, ok := h.fwMap.Load(relPath)
|
||||
if ok {
|
||||
return s
|
||||
}
|
||||
s = &fileWatcher{
|
||||
relPath: relPath,
|
||||
eventCh: make(chan Event),
|
||||
errCh: make(chan E.NestedError),
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
close(s.eventCh)
|
||||
close(s.errCh)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-h.ctx.Done():
|
||||
return
|
||||
case _, ok := <-h.eventCh:
|
||||
if !ok { // directory watcher closed
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
h.fwMap.Store(relPath, s)
|
||||
return s
|
||||
}
|
||||
|
||||
func (h *dirWatcher) start() {
|
||||
defer close(h.eventCh)
|
||||
defer h.w.Close()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-h.ctx.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, "/")
|
||||
|
||||
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:
|
||||
default:
|
||||
}
|
||||
|
||||
// send event to file watcher too
|
||||
w, ok := h.fwMap.Load(relPath)
|
||||
if ok {
|
||||
select {
|
||||
case w.eventCh <- msg:
|
||||
default:
|
||||
}
|
||||
}
|
||||
case err := <-h.w.Errors:
|
||||
if errors.Is(err, fsnotify.ErrClosed) {
|
||||
// closed manually?
|
||||
return
|
||||
}
|
||||
select {
|
||||
case h.errCh <- E.From(err):
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user