// 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" "os" "time" ) var numberOfFileWatchers int func init() { numberOfFolderWatchers = 0 } func NumberOfFileWatchers() int { return numberOfFileWatchers } type FileWatcher struct { modified chan bool moved chan bool stopped chan bool file string running bool wasStopped bool checkInterval time.Duration previousModTime time.Time } func NewFileWatcher(filePath string, checkIntervalInSeconds int) *FileWatcher { if checkIntervalInSeconds < 1 { panic(fmt.Sprintf("Cannot create a file watcher with a check interval of %v seconds.", checkIntervalInSeconds)) } return &FileWatcher{ modified: make(chan bool), moved: make(chan bool), stopped: make(chan bool), file: filePath, checkInterval: time.Duration(checkIntervalInSeconds), } } func (fileWatcher *FileWatcher) String() string { return fmt.Sprintf("Filewatcher %q", fileWatcher.file) } func (fileWatcher *FileWatcher) SetFile(filePath string) { fileWatcher.file = filePath } func (filewatcher *FileWatcher) Modified() chan bool { return filewatcher.modified } func (filewatcher *FileWatcher) Moved() chan bool { return filewatcher.moved } func (filewatcher *FileWatcher) Stopped() chan bool { return filewatcher.stopped } func (fileWatcher *FileWatcher) Start() { fileWatcher.running = true sleepInterval := time.Second * fileWatcher.checkInterval go func() { // increment watcher count numberOfFileWatchers++ var modTime time.Time previousModTime := fileWatcher.getPreviousModTime() if timeIsSet(previousModTime) { modTime = previousModTime } else { currentModTime, err := getLastModTimeFromFile(fileWatcher.file) if err != nil { // send out the notification log("File %q has been moved or is inaccessible.", fileWatcher.file) go func() { fileWatcher.moved <- true }() // stop this file watcher fileWatcher.Stop() } else { modTime = currentModTime } } for fileWatcher.wasStopped == false { newModTime, err := getLastModTimeFromFile(fileWatcher.file) if err != nil { // send out the notification log("File %q has been moved.", fileWatcher.file) go func() { fileWatcher.moved <- true }() // stop this file watcher fileWatcher.Stop() continue } // detect changes if modTime.Before(newModTime) { // send out the notification log("File %q has been modified.", fileWatcher.file) go func() { fileWatcher.modified <- true }() } else { log("File %q has not changed.", fileWatcher.file) } // assign the new modtime modTime = newModTime time.Sleep(sleepInterval) } fileWatcher.running = false // capture the entry list for a restart fileWatcher.captureModTime(modTime) // inform channel-subscribers go func() { fileWatcher.stopped <- true }() // decrement the watch counter numberOfFileWatchers-- // final log message log("Stopped file watcher %q", fileWatcher.String()) }() } func (fileWatcher *FileWatcher) Stop() { log("Stopping file watcher %q", fileWatcher.String()) fileWatcher.wasStopped = true } func (fileWatcher *FileWatcher) IsRunning() bool { return fileWatcher.running } func (fileWatcher *FileWatcher) getPreviousModTime() time.Time { return fileWatcher.previousModTime } // Remember the last mod time for a later restart func (fileWatcher *FileWatcher) captureModTime(modTime time.Time) { fileWatcher.previousModTime = modTime } func getLastModTimeFromFile(file string) (time.Time, error) { fileInfo, err := os.Stat(file) if err != nil { return time.Time{}, err } return fileInfo.ModTime(), nil } func timeIsSet(t time.Time) bool { return time.Time{} == t }