-
Notifications
You must be signed in to change notification settings - Fork 8
/
folder.go
266 lines (198 loc) · 6.96 KB
/
folder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// Copyright 2013 Andreas Koch. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fswatch
import (
"fmt"
"io/ioutil"
"path/filepath"
"time"
)
// numberOfFolderWatchers contains the current number of active folder watchers.
var numberOfFolderWatchers int
func init() {
numberOfFolderWatchers = 0
}
// NumberOfFolderWatchers returns the number of currently active folder watchers.
func NumberOfFolderWatchers() int {
return numberOfFolderWatchers
}
// A FolderWatcher can be used to watch a folder for modified or moved items.
type FolderWatcher struct {
changeDetails chan *FolderChange
modified chan bool
moved chan bool
stopped chan bool
recurse bool
skipFile func(path string) bool
folder string
running bool
wasStopped bool
checkInterval time.Duration
previousEntries []string
}
// NewFolderWatcher creates a new folder watcher for the given folder path.
// The recurse flag indicates whether the watcher shall include sub folders of the the given folder path.
// The skipFile expression can be used to exclude certains files or folders.
// The check interval in seconds defines how often the watcher shall check for changes (recommended: 1 - n seconds).
func NewFolderWatcher(folderPath string, recurse bool, skipFile func(path string) bool, checkIntervalInSeconds int) *FolderWatcher {
if checkIntervalInSeconds < 1 {
panic(fmt.Sprintf("Cannot create a folder watcher with a check interval of %v seconds.", checkIntervalInSeconds))
}
return &FolderWatcher{
modified: make(chan bool),
moved: make(chan bool),
stopped: make(chan bool),
changeDetails: make(chan *FolderChange),
recurse: recurse,
skipFile: skipFile,
folder: folderPath,
checkInterval: time.Duration(checkIntervalInSeconds),
}
}
func (folderWatcher *FolderWatcher) String() string {
return fmt.Sprintf("Folderwatcher %q", folderWatcher.folder)
}
// Modified returns a channel indicating if the current folder has been modified.
func (folderWatcher *FolderWatcher) Modified() chan bool {
return folderWatcher.modified
}
// Moved returns a channel indicating if the current folder has been moved.
func (folderWatcher *FolderWatcher) Moved() chan bool {
return folderWatcher.moved
}
// Stopped returns a channel indicating if current folder watcher stopped.
func (folderWatcher *FolderWatcher) Stopped() chan bool {
return folderWatcher.stopped
}
// ChangeDetails returns a model containing all changed during a given change interval.
func (folderWatcher *FolderWatcher) ChangeDetails() chan *FolderChange {
return folderWatcher.changeDetails
}
// Start starts the watch process.
func (folderWatcher *FolderWatcher) Start() {
folderWatcher.running = true
sleepInterval := time.Second * folderWatcher.checkInterval
go func() {
// get existing entries
var entryList []string
directory := folderWatcher.folder
previousEntryList := folderWatcher.getPreviousEntryList()
if previousEntryList != nil {
// use the entry list from a previous run
entryList = previousEntryList
} else {
// use a new entry list
newEntryList, _ := getFolderEntries(directory, folderWatcher.recurse, folderWatcher.skipFile)
entryList = newEntryList
}
// increment watcher count
numberOfFolderWatchers++
for folderWatcher.wasStopped == false {
// get new entries
updatedEntryList, _ := getFolderEntries(directory, folderWatcher.recurse, folderWatcher.skipFile)
// check for new items
newItems := make([]string, 0)
modifiedItems := make([]string, 0)
for _, entry := range updatedEntryList {
if isNewItem := !sliceContainsElement(entryList, entry); isNewItem {
// entry is new
newItems = append(newItems, entry)
continue
}
// check if the file changed
if newModTime, err := getLastModTimeFromFile(entry); err == nil {
// check if file has been modified
timeOfLastCheck := time.Now().Add(sleepInterval * -1)
if timeOfLastCheck.Before(newModTime) {
// existing entry has been modified
modifiedItems = append(modifiedItems, entry)
}
}
}
// check for moved items
movedItems := make([]string, 0)
for _, entry := range entryList {
isMoved := !sliceContainsElement(updatedEntryList, entry)
if isMoved {
movedItems = append(movedItems, entry)
}
}
// assign the new list
entryList = updatedEntryList
// sleep
time.Sleep(sleepInterval)
// check if something happened
if len(newItems) > 0 || len(movedItems) > 0 || len(modifiedItems) > 0 {
// send out change
go func() {
folderWatcher.modified <- true
}()
go func() {
log("Folder %q changed", directory)
folderWatcher.changeDetails <- newFolderChange(newItems, movedItems, modifiedItems)
}()
} else {
log("No change in folder %q", directory)
}
}
folderWatcher.running = false
// capture the entry list for a restart
folderWatcher.captureEntryList(entryList)
// inform channel-subscribers
go func() {
folderWatcher.stopped <- true
}()
// decrement the watch counter
numberOfFolderWatchers--
// final log message
log("Stopped folder watcher %q", folderWatcher.String())
}()
}
// Stop stops the watch process.
func (folderWatcher *FolderWatcher) Stop() {
log("Stopping folder watcher %q", folderWatcher.String())
folderWatcher.wasStopped = true
}
// IsRunning returns a flag indicating whether the watcher is currently running.
func (folderWatcher *FolderWatcher) IsRunning() bool {
return folderWatcher.running
}
// getPreviousEntryList returns the entry list of the last watcher-run.
func (folderWatcher *FolderWatcher) getPreviousEntryList() []string {
return folderWatcher.previousEntries
}
// Remember the entry list for a later restart
func (folderWatcher *FolderWatcher) captureEntryList(list []string) {
folderWatcher.previousEntries = list
}
// getFolderEntries returns a list of all entries for the given direcotry path.
// The recurse flag indicates whether the watcher shall include sub folders of the the given folder path.
// The skipFile expression can be used to exclude certains files or folders.
func getFolderEntries(directory string, recurse bool, skipFile func(path string) bool) ([]string, error) {
// the return array
entries := make([]string, 0)
// read the entries of the specified directory
directoryEntries, err := ioutil.ReadDir(directory)
if err != nil {
return entries, err
}
for _, entry := range directoryEntries {
// get the full path
subEntryPath := filepath.Join(directory, entry.Name())
// recurse or append
if recurse && entry.IsDir() {
// recurse (ignore errors, unreadable sub directories don't hurt much)
subFolderEntries, _ := getFolderEntries(subEntryPath, recurse, skipFile)
entries = append(entries, subFolderEntries...)
} else {
// check if the enty shall be ignored
if skipFile(subEntryPath) {
continue
}
// append entry
entries = append(entries, subEntryPath)
}
}
return entries, nil
}