├── .travis.yml ├── util.go ├── watcher.go ├── debug.go ├── LICENSE ├── folderchange.go ├── README.md ├── file.go └── folder.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.4 3 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Andreas Koch. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fswatch 6 | 7 | // sliceContainsElement returns true if the specified list of strings contains the given text element. 8 | func sliceContainsElement(listOfStrings []string, textElement string) bool { 9 | for _, t := range listOfStrings { 10 | if t == textElement { 11 | return true 12 | } 13 | } 14 | 15 | return false 16 | } 17 | -------------------------------------------------------------------------------- /watcher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Andreas Koch. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fswatch 6 | 7 | // Watcher defines the base set of of functions for a file and folder watcher. 8 | type Watcher interface { 9 | 10 | // Modified returns a boolean channel which sends a flag indicating 11 | // whether a filesystem item has been modified. 12 | Modified() chan bool 13 | 14 | // Moved returns a boolean channel which sends a flag indicating 15 | // whether a filesystem item has moved. 16 | Moved() chan bool 17 | 18 | // Stopped returns a boolean channel which sends a flag indicating 19 | // whether the filesystem watcher has stopped. 20 | Stopped() chan bool 21 | 22 | // Start starts the watch-process. 23 | Start() 24 | 25 | // Stop stops any the watch-process. 26 | Stop() 27 | 28 | // IsRunning returns a flag indicating whether this 29 | // filesystem watcher is running or not. 30 | IsRunning() bool 31 | } 32 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Andreas Koch. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fswatch 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | var ( 12 | debugIsEnabled = false 13 | debugMessages chan string 14 | ) 15 | 16 | // EnableDebug enables the debug mode for this package and returns a 17 | // debug message channel. 18 | func EnableDebug() chan string { 19 | debugIsEnabled = true 20 | debugMessages = make(chan string, 10) 21 | return debugMessages 22 | } 23 | 24 | // DisableDebug disables the debug mode for this package 25 | // and closes the debug message channel. 26 | func DisableDebug() { 27 | debugIsEnabled = false 28 | close(debugMessages) 29 | } 30 | 31 | // log sends a log message down the debug message channel if 32 | // debugging is enabled. 33 | func log(format string, v ...interface{}) { 34 | if !debugIsEnabled { 35 | return 36 | } 37 | 38 | debugMessages <- fmt.Sprint(fmt.Sprintf(format, v...)) 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | "New BSD License" 2 | 3 | Copyright (c) 2013, Andreas Koch "Andyk" 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | * Neither the name of the author nor the 15 | names of its contributors may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /folderchange.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Andreas Koch. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fswatch 6 | 7 | import ( 8 | "fmt" 9 | "time" 10 | ) 11 | 12 | // newFolderChange creates a new FolderChange instance from the given list if new, moved and modified items. 13 | func newFolderChange(newItems, movedItems, modifiedItems []string) *FolderChange { 14 | return &FolderChange{ 15 | timeStamp: time.Now(), 16 | newItems: newItems, 17 | movedItems: movedItems, 18 | modifiedItems: modifiedItems, 19 | } 20 | } 21 | 22 | // FolderChange represents changes (new, moved and modified items) of a folder at a given time. 23 | type FolderChange struct { 24 | timeStamp time.Time 25 | newItems []string 26 | movedItems []string 27 | modifiedItems []string 28 | } 29 | 30 | func (folderChange *FolderChange) String() string { 31 | return fmt.Sprintf("Folderchange (timestamp: %s, new: %d, moved: %d)", folderChange.timeStamp, len(folderChange.New()), len(folderChange.Moved())) 32 | } 33 | 34 | // TimeStamp retunrs the time stamp of the current folder change. 35 | func (folderChange *FolderChange) TimeStamp() time.Time { 36 | return folderChange.timeStamp 37 | } 38 | 39 | // New returns the new items of the current folder change. 40 | func (folderChange *FolderChange) New() []string { 41 | return folderChange.newItems 42 | } 43 | 44 | // Moved returns the moved items of the current folder change. 45 | func (folderChange *FolderChange) Moved() []string { 46 | return folderChange.movedItems 47 | } 48 | 49 | // Modified returns the modified items of the current folder change. 50 | func (folderChange *FolderChange) Modified() []string { 51 | return folderChange.modifiedItems 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fswatch 2 | 3 | fswatch is a go library for recursively watching file system changes to **does not** depend on inotify and therefore is not limit by the ulimit of your operating system. 4 | 5 | ## Motivation 6 | 7 | Why not use [inotify](http://en.wikipedia.org/wiki/Inotify)? Even though there are great libraries like [fsnotify](https://github.com/howeyc/fsnotify) that offer cross platform file system change notifications - the approach breaks when you want to watch a lot of files or folder. 8 | 9 | For example the default ulimit for Mac OS is set to 512. If you need to watch more files you have to increase the ulimit for open files per process. And this sucks. 10 | 11 | ## Usage 12 | 13 | ### Watching a single file 14 | 15 | If you want to watch a single file use the `NewFileWatcher` function to create a new file watcher: 16 | 17 | ```go 18 | go func() { 19 | checkIntervalInSeconds := 2 20 | fileWatcher := fswatch.NewFileWatcher("Some-file", checkIntervalInSeconds).Start() 21 | 22 | for fileWatcher.IsRunning() { 23 | 24 | select { 25 | case <-fileWatcher.Modified: 26 | 27 | go func() { 28 | // file changed. do something. 29 | }() 30 | 31 | case <-fileWatcher.Moved: 32 | 33 | go func() { 34 | // file moved. do something. 35 | }() 36 | } 37 | 38 | } 39 | }() 40 | ``` 41 | 42 | ### Watching a folder 43 | 44 | To watch a whole folder (and all of its child directories) for new, modified or deleted files you can use the `NewFolderWatcher` function. 45 | 46 | Parameters: 47 | 48 | 1. The path to the directory you want to monitor 49 | 2. A flag indicating whether the folder shall be watched recursively 50 | 3. An expression which decides which files are skipped 51 | 4. The check interval in seconds (1 - n seconds) 52 | 53 | 54 | ```go 55 | go func() { 56 | 57 | recurse := true // include all sub directories 58 | 59 | skipDotFilesAndFolders := func(path string) bool { 60 | return strings.HasPrefix(filepath.Base(path), ".") 61 | } 62 | 63 | checkIntervalInSeconds := 2 64 | 65 | folderWatcher := fswatch.NewFolderWatcher( 66 | "some-directory", 67 | recurse, 68 | skipDotFilesAndFolders, 69 | checkIntervalInSeconds 70 | ) 71 | 72 | folderWatcher.Start() 73 | 74 | for folderWatcher.IsRunning() { 75 | 76 | select { 77 | 78 | case <-folderWatcher.Modified(): 79 | fmt.Println("New or modified items detected") 80 | 81 | case <-folderWatcher.Moved(): 82 | fmt.Println("Items have been moved") 83 | 84 | case changes := <-folderWatcher.ChangeDetails(): 85 | 86 | fmt.Printf("%s\n", changes.String()) 87 | fmt.Printf("New: %#v\n", changes.New()) 88 | fmt.Printf("Modified: %#v\n", changes.Modified()) 89 | fmt.Printf("Moved: %#v\n", changes.Moved()) 90 | 91 | } 92 | } 93 | 94 | }() 95 | ``` 96 | ## go-fswatch in action 97 | 98 | You can see go-fswatch in action in the **live-reload** feature of my [markdown webserver "allmark"](https://allmark.io/). 99 | 100 | See: [github.com/andreaskoch/allmark/blob/master/src/allmark.io/modules/dataaccess/filesystem/watcher.go](https://github.com/andreaskoch/allmark/blob/master/src/allmark.io/modules/dataaccess/filesystem/watcher.go) 101 | 102 | I would still prefer using inotify, but go-fswatch has been doing it's job in allmark pretty well and works easily with relatively large folder structures. 103 | 104 | ## Build Status 105 | 106 | [![Build Status](https://travis-ci.org/andreaskoch/go-fswatch.png?branch=master)](https://travis-ci.org/andreaskoch/go-fswatch) 107 | 108 | ## Contribute 109 | 110 | All contributions are welcome. Especially if you have an idea 111 | 112 | - how to reliably increase the limit for the maximum number of open files from within an application so we can use inotify for large folder structures. 113 | - how to overcome the limitations of inotify without having to resort to checking the files for changes over and over again 114 | - or how to make the existing code more efficient 115 | 116 | please send me a message or a pull request. 117 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Andreas Koch. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fswatch 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "time" 11 | ) 12 | 13 | // numberOfFileWatchers contains the current number of active file watchers. 14 | var numberOfFileWatchers int 15 | 16 | func init() { 17 | numberOfFolderWatchers = 0 18 | } 19 | 20 | // NumberOfFileWatchers returns the number of currently active file watchers. 21 | func NumberOfFileWatchers() int { 22 | return numberOfFileWatchers 23 | } 24 | 25 | // A FileWatcher can be used to determine if a given file has been modified or moved. 26 | type FileWatcher struct { 27 | modified chan bool 28 | moved chan bool 29 | stopped chan bool 30 | 31 | file string 32 | running bool 33 | wasStopped bool 34 | checkInterval time.Duration 35 | 36 | previousModTime time.Time 37 | } 38 | 39 | // NewFileWatcher creates a new file watcher for a given file path. 40 | // The check interval in seconds defines how often the watcher shall check for changes (recommended: 1 - n seconds). 41 | func NewFileWatcher(filePath string, checkIntervalInSeconds int) *FileWatcher { 42 | 43 | if checkIntervalInSeconds < 1 { 44 | panic(fmt.Sprintf("Cannot create a file watcher with a check interval of %v seconds.", checkIntervalInSeconds)) 45 | } 46 | 47 | return &FileWatcher{ 48 | modified: make(chan bool), 49 | moved: make(chan bool), 50 | stopped: make(chan bool), 51 | 52 | file: filePath, 53 | checkInterval: time.Duration(checkIntervalInSeconds), 54 | } 55 | } 56 | 57 | func (fileWatcher *FileWatcher) String() string { 58 | return fmt.Sprintf("Filewatcher %q", fileWatcher.file) 59 | } 60 | 61 | // SetFile sets the file file for this file watcher. 62 | func (fileWatcher *FileWatcher) SetFile(filePath string) { 63 | fileWatcher.file = filePath 64 | } 65 | 66 | // Modified returns a channel indicating if the file has been modified. 67 | func (filewatcher *FileWatcher) Modified() chan bool { 68 | return filewatcher.modified 69 | } 70 | 71 | // Moved returns a channel indicating if the file has been moved. 72 | func (filewatcher *FileWatcher) Moved() chan bool { 73 | return filewatcher.moved 74 | } 75 | 76 | // Stopped returns a channel indicating if the file watcher stopped. 77 | func (filewatcher *FileWatcher) Stopped() chan bool { 78 | return filewatcher.stopped 79 | } 80 | 81 | // Start starts the watch process. 82 | func (fileWatcher *FileWatcher) Start() { 83 | fileWatcher.running = true 84 | sleepInterval := time.Second * fileWatcher.checkInterval 85 | 86 | go func() { 87 | 88 | // increment watcher count 89 | numberOfFileWatchers++ 90 | 91 | var modTime time.Time 92 | previousModTime := fileWatcher.getPreviousModTime() 93 | 94 | if timeIsSet(previousModTime) { 95 | modTime = previousModTime 96 | } else { 97 | currentModTime, err := getLastModTimeFromFile(fileWatcher.file) 98 | if err != nil { 99 | 100 | // send out the notification 101 | log("File %q has been moved or is inaccessible.", fileWatcher.file) 102 | go func() { 103 | fileWatcher.moved <- true 104 | }() 105 | 106 | // stop this file watcher 107 | fileWatcher.Stop() 108 | 109 | } else { 110 | 111 | modTime = currentModTime 112 | } 113 | 114 | } 115 | 116 | for fileWatcher.wasStopped == false { 117 | 118 | newModTime, err := getLastModTimeFromFile(fileWatcher.file) 119 | if err != nil { 120 | 121 | // send out the notification 122 | log("File %q has been moved.", fileWatcher.file) 123 | go func() { 124 | fileWatcher.moved <- true 125 | }() 126 | 127 | // stop this file watcher 128 | fileWatcher.Stop() 129 | 130 | continue 131 | } 132 | 133 | // detect changes 134 | if modTime.Before(newModTime) { 135 | 136 | // send out the notification 137 | log("File %q has been modified.", fileWatcher.file) 138 | go func() { 139 | fileWatcher.modified <- true 140 | }() 141 | 142 | } else { 143 | 144 | log("File %q has not changed.", fileWatcher.file) 145 | 146 | } 147 | 148 | // assign the new modtime 149 | modTime = newModTime 150 | 151 | time.Sleep(sleepInterval) 152 | 153 | } 154 | 155 | fileWatcher.running = false 156 | 157 | // capture the entry list for a restart 158 | fileWatcher.captureModTime(modTime) 159 | 160 | // inform channel-subscribers 161 | go func() { 162 | fileWatcher.stopped <- true 163 | }() 164 | 165 | // decrement the watch counter 166 | numberOfFileWatchers-- 167 | 168 | // final log message 169 | log("Stopped file watcher %q", fileWatcher.String()) 170 | }() 171 | } 172 | 173 | // Stop stops the watch process. 174 | func (fileWatcher *FileWatcher) Stop() { 175 | log("Stopping file watcher %q", fileWatcher.String()) 176 | fileWatcher.wasStopped = true 177 | } 178 | 179 | // IsRunning returns a flag indicating whether the watcher is currently running. 180 | func (fileWatcher *FileWatcher) IsRunning() bool { 181 | return fileWatcher.running 182 | } 183 | 184 | // getPreviousModTime returns the last known modification time of the file. 185 | func (fileWatcher *FileWatcher) getPreviousModTime() time.Time { 186 | return fileWatcher.previousModTime 187 | } 188 | 189 | // Remember the last mod time for a later restart 190 | func (fileWatcher *FileWatcher) captureModTime(modTime time.Time) { 191 | fileWatcher.previousModTime = modTime 192 | } 193 | 194 | // getLastModTimeFromFile returns the last modification time of the file with the given file path. 195 | // If modifiction time cannot be determined getLastModTimeFromFile will return an error. 196 | func getLastModTimeFromFile(file string) (time.Time, error) { 197 | fileInfo, err := os.Stat(file) 198 | if err != nil { 199 | return time.Time{}, err 200 | } 201 | 202 | return fileInfo.ModTime(), nil 203 | } 204 | 205 | // timeIsSet returns true if the supplied time is set / initialized. 206 | func timeIsSet(t time.Time) bool { 207 | return time.Time{} == t 208 | } 209 | -------------------------------------------------------------------------------- /folder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Andreas Koch. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fswatch 6 | 7 | import ( 8 | "fmt" 9 | "io/ioutil" 10 | "path/filepath" 11 | "time" 12 | ) 13 | 14 | // numberOfFolderWatchers contains the current number of active folder watchers. 15 | var numberOfFolderWatchers int 16 | 17 | func init() { 18 | numberOfFolderWatchers = 0 19 | } 20 | 21 | // NumberOfFolderWatchers returns the number of currently active folder watchers. 22 | func NumberOfFolderWatchers() int { 23 | return numberOfFolderWatchers 24 | } 25 | 26 | // A FolderWatcher can be used to watch a folder for modified or moved items. 27 | type FolderWatcher struct { 28 | changeDetails chan *FolderChange 29 | 30 | modified chan bool 31 | moved chan bool 32 | stopped chan bool 33 | 34 | recurse bool 35 | skipFile func(path string) bool 36 | 37 | folder string 38 | running bool 39 | wasStopped bool 40 | checkInterval time.Duration 41 | 42 | previousEntries []string 43 | } 44 | 45 | // NewFolderWatcher creates a new folder watcher for the given folder path. 46 | // The recurse flag indicates whether the watcher shall include sub folders of the the given folder path. 47 | // The skipFile expression can be used to exclude certains files or folders. 48 | // The check interval in seconds defines how often the watcher shall check for changes (recommended: 1 - n seconds). 49 | func NewFolderWatcher(folderPath string, recurse bool, skipFile func(path string) bool, checkIntervalInSeconds int) *FolderWatcher { 50 | 51 | if checkIntervalInSeconds < 1 { 52 | panic(fmt.Sprintf("Cannot create a folder watcher with a check interval of %v seconds.", checkIntervalInSeconds)) 53 | } 54 | 55 | return &FolderWatcher{ 56 | 57 | modified: make(chan bool), 58 | moved: make(chan bool), 59 | stopped: make(chan bool), 60 | 61 | changeDetails: make(chan *FolderChange), 62 | 63 | recurse: recurse, 64 | skipFile: skipFile, 65 | 66 | folder: folderPath, 67 | checkInterval: time.Duration(checkIntervalInSeconds), 68 | } 69 | } 70 | 71 | func (folderWatcher *FolderWatcher) String() string { 72 | return fmt.Sprintf("Folderwatcher %q", folderWatcher.folder) 73 | } 74 | 75 | // Modified returns a channel indicating if the current folder has been modified. 76 | func (folderWatcher *FolderWatcher) Modified() chan bool { 77 | return folderWatcher.modified 78 | } 79 | 80 | // Moved returns a channel indicating if the current folder has been moved. 81 | func (folderWatcher *FolderWatcher) Moved() chan bool { 82 | return folderWatcher.moved 83 | } 84 | 85 | // Stopped returns a channel indicating if current folder watcher stopped. 86 | func (folderWatcher *FolderWatcher) Stopped() chan bool { 87 | return folderWatcher.stopped 88 | } 89 | 90 | // ChangeDetails returns a model containing all changed during a given change interval. 91 | func (folderWatcher *FolderWatcher) ChangeDetails() chan *FolderChange { 92 | return folderWatcher.changeDetails 93 | } 94 | 95 | // Start starts the watch process. 96 | func (folderWatcher *FolderWatcher) Start() { 97 | folderWatcher.running = true 98 | sleepInterval := time.Second * folderWatcher.checkInterval 99 | 100 | go func() { 101 | 102 | // get existing entries 103 | var entryList []string 104 | directory := folderWatcher.folder 105 | 106 | previousEntryList := folderWatcher.getPreviousEntryList() 107 | 108 | if previousEntryList != nil { 109 | 110 | // use the entry list from a previous run 111 | entryList = previousEntryList 112 | 113 | } else { 114 | 115 | // use a new entry list 116 | newEntryList, _ := getFolderEntries(directory, folderWatcher.recurse, folderWatcher.skipFile) 117 | entryList = newEntryList 118 | } 119 | 120 | // increment watcher count 121 | numberOfFolderWatchers++ 122 | 123 | for folderWatcher.wasStopped == false { 124 | 125 | // get new entries 126 | updatedEntryList, _ := getFolderEntries(directory, folderWatcher.recurse, folderWatcher.skipFile) 127 | 128 | // check for new items 129 | newItems := make([]string, 0) 130 | modifiedItems := make([]string, 0) 131 | 132 | for _, entry := range updatedEntryList { 133 | 134 | if isNewItem := !sliceContainsElement(entryList, entry); isNewItem { 135 | // entry is new 136 | newItems = append(newItems, entry) 137 | continue 138 | } 139 | 140 | // check if the file changed 141 | if newModTime, err := getLastModTimeFromFile(entry); err == nil { 142 | 143 | // check if file has been modified 144 | timeOfLastCheck := time.Now().Add(sleepInterval * -1) 145 | 146 | if timeOfLastCheck.Before(newModTime) { 147 | 148 | // existing entry has been modified 149 | modifiedItems = append(modifiedItems, entry) 150 | } 151 | 152 | } 153 | } 154 | 155 | // check for moved items 156 | movedItems := make([]string, 0) 157 | for _, entry := range entryList { 158 | isMoved := !sliceContainsElement(updatedEntryList, entry) 159 | if isMoved { 160 | movedItems = append(movedItems, entry) 161 | } 162 | } 163 | 164 | // assign the new list 165 | entryList = updatedEntryList 166 | 167 | // sleep 168 | time.Sleep(sleepInterval) 169 | 170 | // check if something happened 171 | if len(newItems) > 0 || len(movedItems) > 0 || len(modifiedItems) > 0 { 172 | 173 | // send out change 174 | go func() { 175 | folderWatcher.modified <- true 176 | }() 177 | 178 | go func() { 179 | log("Folder %q changed", directory) 180 | folderWatcher.changeDetails <- newFolderChange(newItems, movedItems, modifiedItems) 181 | }() 182 | } else { 183 | log("No change in folder %q", directory) 184 | } 185 | } 186 | 187 | folderWatcher.running = false 188 | 189 | // capture the entry list for a restart 190 | folderWatcher.captureEntryList(entryList) 191 | 192 | // inform channel-subscribers 193 | go func() { 194 | folderWatcher.stopped <- true 195 | }() 196 | 197 | // decrement the watch counter 198 | numberOfFolderWatchers-- 199 | 200 | // final log message 201 | log("Stopped folder watcher %q", folderWatcher.String()) 202 | }() 203 | } 204 | 205 | // Stop stops the watch process. 206 | func (folderWatcher *FolderWatcher) Stop() { 207 | log("Stopping folder watcher %q", folderWatcher.String()) 208 | folderWatcher.wasStopped = true 209 | } 210 | 211 | // IsRunning returns a flag indicating whether the watcher is currently running. 212 | func (folderWatcher *FolderWatcher) IsRunning() bool { 213 | return folderWatcher.running 214 | } 215 | 216 | // getPreviousEntryList returns the entry list of the last watcher-run. 217 | func (folderWatcher *FolderWatcher) getPreviousEntryList() []string { 218 | return folderWatcher.previousEntries 219 | } 220 | 221 | // Remember the entry list for a later restart 222 | func (folderWatcher *FolderWatcher) captureEntryList(list []string) { 223 | folderWatcher.previousEntries = list 224 | } 225 | 226 | // getFolderEntries returns a list of all entries for the given direcotry path. 227 | // The recurse flag indicates whether the watcher shall include sub folders of the the given folder path. 228 | // The skipFile expression can be used to exclude certains files or folders. 229 | func getFolderEntries(directory string, recurse bool, skipFile func(path string) bool) ([]string, error) { 230 | 231 | // the return array 232 | entries := make([]string, 0) 233 | 234 | // read the entries of the specified directory 235 | directoryEntries, err := ioutil.ReadDir(directory) 236 | if err != nil { 237 | return entries, err 238 | } 239 | 240 | for _, entry := range directoryEntries { 241 | 242 | // get the full path 243 | subEntryPath := filepath.Join(directory, entry.Name()) 244 | 245 | // recurse or append 246 | if recurse && entry.IsDir() { 247 | 248 | // recurse (ignore errors, unreadable sub directories don't hurt much) 249 | subFolderEntries, _ := getFolderEntries(subEntryPath, recurse, skipFile) 250 | entries = append(entries, subFolderEntries...) 251 | 252 | } else { 253 | 254 | // check if the enty shall be ignored 255 | if skipFile(subEntryPath) { 256 | continue 257 | } 258 | 259 | // append entry 260 | entries = append(entries, subEntryPath) 261 | } 262 | 263 | } 264 | 265 | return entries, nil 266 | } 267 | --------------------------------------------------------------------------------