From b81a2b5ff4158a403e8d8afa5b0e8b90edb176bc Mon Sep 17 00:00:00 2001
From: Volker Schukai <volker.schukai@schukai.com>
Date: Sun, 10 Sep 2023 18:48:41 +0200
Subject: [PATCH] feat: new function StopWatching

---
 error.go           | 21 ++++++++++++++++++++
 lighthouse.go      | 34 ++++++++++++++++++++++++++++----
 lighthouse_test.go |  2 +-
 watching.go        | 49 ++++++++++++++++++++++++++++++++++++++++++++--
 watching_test.go   | 28 ++++++++++++++++++++++++++
 5 files changed, 127 insertions(+), 7 deletions(-)

diff --git a/error.go b/error.go
index 47ef9f1..c5cea17 100644
--- a/error.go
+++ b/error.go
@@ -29,9 +29,30 @@ func (e WatcherNotActiveError) Error() string {
 	return "Watcher is not active"
 }
 
+// WatcherNotActiveError represents an error when trying to add a path that is already being watched.
+type WatcherNotRunningError struct{}
+
+func (e WatcherNotRunningError) Error() string {
+	return "Watcher is not active"
+}
+
+// WatcherAlreadyRunningError represents an error when trying to add a path that is already being watched.
+type WatcherAlreadyRunningError struct{}
+
+func (e WatcherAlreadyRunningError) Error() string {
+	return "Watcher is already running"
+}
+
 // LighthouseNotActiveError represents an error when trying to add a path that is already being watched.
 type LighthouseNotActiveError struct{}
 
 func (e LighthouseNotActiveError) Error() string {
 	return "lighthouse is not active"
 }
+
+// WatcherNotActiveError represents an error when trying to add a path that is already being watched.
+type WatcherAlreadyActiveError struct{}
+
+func (e WatcherAlreadyActiveError) Error() string {
+	return "Watcher is already active"
+}
diff --git a/lighthouse.go b/lighthouse.go
index 3befc5d..bbbe346 100644
--- a/lighthouse.go
+++ b/lighthouse.go
@@ -23,8 +23,10 @@ type lighthouse struct {
 	fsnotify *fsnotify.Watcher
 	mutex    sync.Mutex
 	active   bool
+	running  bool
 	onError  EventErrorCallback
 	debounce time.Duration
+	quit     chan struct{}
 }
 
 // SetOnError Methode, um onError zu setzen
@@ -47,7 +49,11 @@ type Lighthouse interface {
 	IsActiveWatched(path string) bool
 	IsWatched(path string) bool
 	IsRunning() bool
-	StartWatching()
+
+	IsActive() bool
+	StartWatching() error
+
+	StopWatching() error
 
 	SetOnError(callback EventErrorCallback)
 	SetDebounce(duration time.Duration)
@@ -56,6 +62,7 @@ type Lighthouse interface {
 // NewLighthouse creates a new lighthouse instance.
 func NewLighthouse() Lighthouse {
 	l := &lighthouse{}
+	l.debounce = 500 * time.Millisecond
 	l.checkAndInit()
 	return l
 
@@ -63,30 +70,49 @@ func NewLighthouse() Lighthouse {
 
 func (l *lighthouse) checkAndInit() {
 
+	var err error
+
 	if l.active {
 		return
 	}
 
-	l.debounce = 500 * time.Millisecond
-
 	if l.watchers == nil {
 		l.watchers = make(map[string]*Watch)
 	}
 
 	if l.fsnotify == nil {
-		var err error
 		l.fsnotify, err = fsnotify.NewWatcher()
 		if err != nil {
 			return
 		}
 	}
 
+	// test if fsnotify is running
+	err = l.fsnotify.Add("/undefined-path-123456789")
+	if err != nil {
+		if err.Error() == "inotify instance already closed" {
+			l.fsnotify, err = fsnotify.NewWatcher()
+			if err != nil {
+				return
+			}
+		}
+	}
+
 	l.active = true
 
 }
 
 // IsRunning returns true if the watcher is active, false otherwise.
 func (l *lighthouse) IsRunning() bool {
+	l.mutex.Lock()
+	defer l.mutex.Unlock()
+	return l.running
+}
+
+// IsActive returns true if the watcher is active, false otherwise.
+func (l *lighthouse) IsActive() bool {
+	l.mutex.Lock()
+	defer l.mutex.Unlock()
 	return l.active
 }
 
diff --git a/lighthouse_test.go b/lighthouse_test.go
index 6cd75cc..218ea09 100644
--- a/lighthouse_test.go
+++ b/lighthouse_test.go
@@ -48,7 +48,7 @@ func TestRemoveWatch(t *testing.T) {
 func TestNewLighthouse(t *testing.T) {
 	l := NewLighthouse()
 	assert.NotNil(t, l)
-	assert.True(t, l.IsRunning())
+	assert.True(t, l.IsActive())
 }
 
 func TestIsWatched(t *testing.T) {
diff --git a/watching.go b/watching.go
index 2c9f6cd..d896c54 100644
--- a/watching.go
+++ b/watching.go
@@ -7,7 +7,34 @@ import (
 	"time"
 )
 
-func (l *lighthouse) StartWatching() {
+// StopWatching stops watching the given file for changes
+// If the watcher is not running, an error WatcherNotRunningError is returned.
+func (l *lighthouse) StopWatching() error {
+
+	if !l.IsRunning() {
+		return WatcherNotRunningError{}
+	}
+
+	l.mutex.Lock()
+	defer l.mutex.Unlock()
+
+	close(l.quit)
+	l.running = false
+	return nil
+}
+
+// StartWatching starts watching the given file for changes
+func (l *lighthouse) StartWatching() error {
+	if l.IsRunning() {
+		return WatcherAlreadyRunningError{}
+	}
+
+	l.mutex.Lock()
+	defer l.mutex.Unlock()
+	l.quit = make(chan struct{})
+	l.running = true
+	l.checkAndInit()
+
 	go func() {
 		eventChannel := make(chan fsnotify.Event, 100)
 		errorChannel := make(chan error, 100)
@@ -17,7 +44,14 @@ func (l *lighthouse) StartWatching() {
 
 		go func() {
 			for {
+
+				l.mutex.Lock()
+				quit := l.quit
+				l.mutex.Unlock()
+
 				select {
+				case <-quit:
+					return
 				case event := <-l.fsnotify.Events:
 					eventChannel <- event
 				case err := <-l.fsnotify.Errors:
@@ -27,7 +61,14 @@ func (l *lighthouse) StartWatching() {
 		}()
 
 		for {
+
+			l.mutex.Lock()
+			quit := l.quit
+			l.mutex.Unlock()
+
 			select {
+			case <-quit:
+				return
 			case event := <-eventChannel:
 
 				debounceMutex.Lock()
@@ -123,8 +164,12 @@ func (l *lighthouse) StartWatching() {
 				debounceMutex.Unlock()
 
 			case err := <-errorChannel:
-				go l.onError(err)
+				if l.onError != nil {
+					go l.onError(err)
+				}
 			}
 		}
 	}()
+
+	return nil
 }
diff --git a/watching_test.go b/watching_test.go
index 3d8a33e..8e176ea 100644
--- a/watching_test.go
+++ b/watching_test.go
@@ -143,3 +143,31 @@ func TestRecreateEvent(t *testing.T) {
 		t.Fail()
 	}
 }
+
+func TestStopWatching(t *testing.T) {
+	l := NewLighthouse() // Erstellen Sie eine neue Instanz der `lighthouse`-Struktur.
+
+	// Start Watching
+	_ = l.StartWatching()
+
+	// Erlauben Sie dem System etwas Zeit, um den Watch-Prozess zu starten.
+	time.Sleep(time.Second * 1)
+	if !l.IsRunning() {
+		t.Fatal("l.active is false, but should be true after StartWatching")
+	}
+
+	// Stop Watching
+	err := l.StopWatching()
+	if err != nil {
+		t.Fatalf("StopWatching failed: %v", err)
+	}
+
+	// Überprüfen Sie, ob die Goroutine und der Watcher gestoppt wurden.
+	if l.IsRunning() {
+		t.Fatal("l.active is true, but should be false after StopWatching")
+	}
+
+	_ = l.StartWatching()
+	assert.True(t, l.IsRunning())
+
+}
-- 
GitLab