diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..13566b81b018ad684f3a35fee301741b2734c8f4 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/aws.xml b/.idea/aws.xml new file mode 100644 index 0000000000000000000000000000000000000000..ec328d0bbf68db9e7322932181cc811412e3ca87 --- /dev/null +++ b/.idea/aws.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="accountSettings"> + <option name="activeProfile" value="profile:default" /> + <option name="activeRegion" value="eu-west-1" /> + <option name="recentlyUsedProfiles"> + <list> + <option value="profile:default" /> + </list> + </option> + <option name="recentlyUsedRegions"> + <list> + <option value="eu-west-1" /> + </list> + </option> + </component> +</project> \ No newline at end of file diff --git a/.idea/libraries/fs.xml b/.idea/libraries/fs.xml new file mode 100644 index 0000000000000000000000000000000000000000..8b6d565e46c6323b2871bf88c06c0a867df3197a --- /dev/null +++ b/.idea/libraries/fs.xml @@ -0,0 +1,9 @@ +<component name="libraryTable"> + <library name="fs"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/html/template/testdata/fs.zip!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> +</component> \ No newline at end of file diff --git a/.idea/libraries/testdata.xml b/.idea/libraries/testdata.xml new file mode 100644 index 0000000000000000000000000000000000000000..35c65e895ec4bd07b38045f998746a3553fce0ff --- /dev/null +++ b/.idea/libraries/testdata.xml @@ -0,0 +1,36 @@ +<component name="libraryTable"> + <library name="testdata"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/time-22738.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/crc32-not-streamed.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/time-win7.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/test-trailing-junk.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/readme.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/zip64.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/utf8-winzip.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/time-7zip.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/winxp.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/utf8-infozip.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/time-osx.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/test.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/time-infozip.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/time-go.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/utf8-osx.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/zip64-2.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/test-prefix.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/dupdir.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/go-with-datadesc-sig.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/unix.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/utf8-winrar.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/utf8-7zip.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/dd.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/symlink.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/time-winzip.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/time-winrar.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/subdir.zip!/" /> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/src/archive/zip/testdata/test-baddirsz.zip!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> +</component> \ No newline at end of file diff --git a/.idea/libraries/zoneinfo.xml b/.idea/libraries/zoneinfo.xml new file mode 100644 index 0000000000000000000000000000000000000000..4d0d4c00e4b0718983f67870b84481ab5e7c1b49 --- /dev/null +++ b/.idea/libraries/zoneinfo.xml @@ -0,0 +1,9 @@ +<component name="libraryTable"> + <library name="zoneinfo"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/.devenv/profile/share/go/lib/time/zoneinfo.zip!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> +</component> \ No newline at end of file diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 0000000000000000000000000000000000000000..ec0b30fa7ea2824af6923493653e32595b0907a8 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="MarkdownSettings"> + <enabledExtensions> + <entry key="MermaidLanguageExtension" value="false" /> + <entry key="PlantUMLLanguageExtension" value="true" /> + </enabledExtensions> + </component> +</project> \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000000000000000000000000000000000..639900d13c6182e452e33a3bd638e70a0146c785 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectRootManager"> + <output url="file://$PROJECT_DIR$/out" /> + </component> +</project> \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..f355a0ce052680b7ef2d4e2c882eb345d2d510f9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea/watch.iml" filepath="$PROJECT_DIR$/.idea/watch.iml" /> + </modules> + </component> +</project> \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/.idea/watch.iml b/.idea/watch.iml new file mode 100644 index 0000000000000000000000000000000000000000..25ed3f6e7b6e344b6ca91ebcc5d005f35357f9cf --- /dev/null +++ b/.idea/watch.iml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="Go" enabled="true" /> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$" /> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/go.mod b/go.mod index d1381c6750bec50c89c83f5cf861edae94eb10ef..dae053abafe90339a41248df6f2f16931dd03e32 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,8 @@ module gitlab.schukai.com/oss/libraries/go/utilities/watch go 1.20 + +require ( + golang.org/x/sys v0.12.0 // indirect + gopkg.in/fsnotify.v1 v1.6.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000000000000000000000000000000000..81506839fd4b1123c098471778865379fc64d710 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= diff --git a/watch.go b/watch.go new file mode 100644 index 0000000000000000000000000000000000000000..b7c979116ca7639142fbb0ca5d1dbce61f18a8ba --- /dev/null +++ b/watch.go @@ -0,0 +1,85 @@ +package watch + +import ( + "gopkg.in/fsnotify.v1" + "sync" +) + +type WatchEvent int + +const ( + FileChanged WatchEvent = iota + FileAdded + FileDeleted + FileRemoved +) + +type EventHandler func(string, WatchEvent) + +type Watch struct { + watcher *fsnotify.Watcher + handlers map[string]EventHandler + queue chan string + mu sync.Mutex +} + +func NewWatch() (*Watch, error) { + w, err := fsnotify.NewWatcher() + if err != nil { + return nil, err + } + return &Watch{ + watcher: w, + handlers: make(map[string]EventHandler), + queue: make(chan string, 100), + }, nil +} + +func (w *Watch) Add(path string, handler EventHandler) error { + w.mu.Lock() + defer w.mu.Unlock() + w.handlers[path] = handler + return w.watcher.Add(path) +} + +func (w *Watch) Remove(path string) error { + w.mu.Lock() + defer w.mu.Unlock() + delete(w.handlers, path) + return w.watcher.Remove(path) +} + +func (w *Watch) startWorker(concurrency int) { + for i := 0; i < concurrency; i++ { + go func() { + for path := range w.queue { + if handler, exists := w.handlers[path]; exists { + handler(path, FileChanged) + } + } + }() + } +} + +func (w *Watch) Watch(concurrency int) { + w.startWorker(concurrency) + + for { + select { + case event := <-w.watcher.Events: + if handler, exists := w.handlers[event.Name]; exists { + we := FileChanged // Map fsnotify events to WatchEvent here + w.queue <- event.Name + handler(event.Name, we) + } + case err := <-w.watcher.Errors: + // Handle errors + println("Error:", err) + } + } +} + +func (w *Watch) Stop() { + close(w.queue) + w.watcher.Close() +} diff --git a/watch_test.go b/watch_test.go new file mode 100644 index 0000000000000000000000000000000000000000..9ac9a069fedd27512951e7dc353f0f21129c0f57 --- /dev/null +++ b/watch_test.go @@ -0,0 +1,47 @@ +package watch + +import ( + "io/ioutil" + "os" + "testing" + "time" +) + +func TestWatch(t *testing.T) { + w, err := NewWatch() + if err != nil { + t.Fatal(err) + } + + tmpFile, err := ioutil.TempFile("", "watch_test") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpFile.Name()) + + called := make(chan bool) + handler := func(path string, event WatchEvent) { + if path == tmpFile.Name() && event == FileChanged { + called <- true + } + } + + if err := w.Add(tmpFile.Name(), handler); err != nil { + t.Fatal(err) + } + + go w.Watch(1) + defer w.Stop() + + _, err = tmpFile.WriteString("test") + if err != nil { + t.Fatal(err) + } + + select { + case <-called: + // Success + case <-time.After(time.Second * 2): + t.Fatal("Event handler not called") + } +}