Add support for copying files and folders.
This commit is contained in:
4
vendor/github.com/radovskyb/watcher/.travis.yml
generated
vendored
Normal file
4
vendor/github.com/radovskyb/watcher/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.7
|
||||
- tip
|
27
vendor/github.com/radovskyb/watcher/LICENSE
generated
vendored
Normal file
27
vendor/github.com/radovskyb/watcher/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2016, Benjamin Radovsky.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of watcher nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
181
vendor/github.com/radovskyb/watcher/README.md
generated
vendored
Normal file
181
vendor/github.com/radovskyb/watcher/README.md
generated
vendored
Normal file
@ -0,0 +1,181 @@
|
||||
# watcher
|
||||
|
||||
[](https://travis-ci.org/radovskyb/watcher)
|
||||
|
||||
`watcher` is a Go package for watching for files or directory changes (recursively or non recursively) without using filesystem events, which allows it to work cross platform consistently.
|
||||
|
||||
`watcher` watches for changes and notifies over channels either anytime an event or an error has occurred.
|
||||
|
||||
Events contain the `os.FileInfo` of the file or directory that the event is based on and the type of event and file or directory path.
|
||||
|
||||
[Installation](#installation)
|
||||
[Features](#features)
|
||||
[Example](#example)
|
||||
[Contributing](#contributing)
|
||||
[Watcher Command](#command)
|
||||
|
||||
# Update
|
||||
- Event.OldPath has been added [Aug 17, 2019]
|
||||
- Added new file filter hooks (Including a built in regexp filtering hook) [Dec 12, 2018]
|
||||
- Event.Path for Rename and Move events is now returned in the format of `fromPath -> toPath`
|
||||
|
||||
#### Chmod event is not supported under windows.
|
||||
|
||||
# Installation
|
||||
|
||||
```shell
|
||||
go get -u github.com/radovskyb/watcher/...
|
||||
```
|
||||
|
||||
# Features
|
||||
|
||||
- Customizable polling interval.
|
||||
- Filter Events.
|
||||
- Watch folders recursively or non-recursively.
|
||||
- Choose to ignore hidden files.
|
||||
- Choose to ignore specified files and folders.
|
||||
- Notifies the `os.FileInfo` of the file that the event is based on. e.g `Name`, `ModTime`, `IsDir`, etc.
|
||||
- Notifies the full path of the file that the event is based on or the old and new paths if the event was a `Rename` or `Move` event.
|
||||
- Limit amount of events that can be received per watching cycle.
|
||||
- List the files being watched.
|
||||
- Trigger custom events.
|
||||
|
||||
# Todo
|
||||
|
||||
- Write more tests.
|
||||
- Write benchmarks.
|
||||
|
||||
# Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/radovskyb/watcher"
|
||||
)
|
||||
|
||||
func main() {
|
||||
w := watcher.New()
|
||||
|
||||
// SetMaxEvents to 1 to allow at most 1 event's to be received
|
||||
// on the Event channel per watching cycle.
|
||||
//
|
||||
// If SetMaxEvents is not set, the default is to send all events.
|
||||
w.SetMaxEvents(1)
|
||||
|
||||
// Only notify rename and move events.
|
||||
w.FilterOps(watcher.Rename, watcher.Move)
|
||||
|
||||
// Only files that match the regular expression during file listings
|
||||
// will be watched.
|
||||
r := regexp.MustCompile("^abc$")
|
||||
w.AddFilterHook(watcher.RegexFilterHook(r, false))
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event := <-w.Event:
|
||||
fmt.Println(event) // Print the event's info.
|
||||
case err := <-w.Error:
|
||||
log.Fatalln(err)
|
||||
case <-w.Closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Watch this folder for changes.
|
||||
if err := w.Add("."); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Watch test_folder recursively for changes.
|
||||
if err := w.AddRecursive("../test_folder"); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Print a list of all of the files and folders currently
|
||||
// being watched and their paths.
|
||||
for path, f := range w.WatchedFiles() {
|
||||
fmt.Printf("%s: %s\n", path, f.Name())
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
|
||||
// Trigger 2 events after watcher started.
|
||||
go func() {
|
||||
w.Wait()
|
||||
w.TriggerEvent(watcher.Create, nil)
|
||||
w.TriggerEvent(watcher.Remove, nil)
|
||||
}()
|
||||
|
||||
// Start the watching process - it'll check for changes every 100ms.
|
||||
if err := w.Start(time.Millisecond * 100); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Contributing
|
||||
If you would ike to contribute, simply submit a pull request.
|
||||
|
||||
# Command
|
||||
|
||||
`watcher` comes with a simple command which is installed when using the `go get` command from above.
|
||||
|
||||
# Usage
|
||||
|
||||
```
|
||||
Usage of watcher:
|
||||
-cmd string
|
||||
command to run when an event occurs
|
||||
-dotfiles
|
||||
watch dot files (default true)
|
||||
-ignore string
|
||||
comma separated list of paths to ignore
|
||||
-interval string
|
||||
watcher poll interval (default "100ms")
|
||||
-keepalive
|
||||
keep alive when a cmd returns code != 0
|
||||
-list
|
||||
list watched files on start
|
||||
-pipe
|
||||
pipe event's info to command's stdin
|
||||
-recursive
|
||||
watch folders recursively (default true)
|
||||
-startcmd
|
||||
run the command when watcher starts
|
||||
```
|
||||
|
||||
All of the flags are optional and watcher can also be called by itself:
|
||||
```shell
|
||||
watcher
|
||||
```
|
||||
(watches the current directory recursively for changes and notifies any events that occur.)
|
||||
|
||||
A more elaborate example using the `watcher` command:
|
||||
```shell
|
||||
watcher -dotfiles=false -recursive=false -cmd="./myscript" main.go ../
|
||||
```
|
||||
In this example, `watcher` will ignore dot files and folders and won't watch any of the specified folders recursively. It will also run the script `./myscript` anytime an event occurs while watching `main.go` or any files or folders in the previous directory (`../`).
|
||||
|
||||
Using the `pipe` and `cmd` flags together will send the event's info to the command's stdin when changes are detected.
|
||||
|
||||
First create a file called `script.py` with the following contents:
|
||||
```python
|
||||
import sys
|
||||
|
||||
for line in sys.stdin:
|
||||
print (line + " - python")
|
||||
```
|
||||
|
||||
Next, start watcher with the `pipe` and `cmd` flags enabled:
|
||||
```shell
|
||||
watcher -cmd="python script.py" -pipe=true
|
||||
```
|
||||
|
||||
Now when changes are detected, the event's info will be output from the running python script.
|
12
vendor/github.com/radovskyb/watcher/ishidden.go
generated
vendored
Normal file
12
vendor/github.com/radovskyb/watcher/ishidden.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// +build !windows
|
||||
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func isHiddenFile(path string) (bool, error) {
|
||||
return strings.HasPrefix(filepath.Base(path), "."), nil
|
||||
}
|
21
vendor/github.com/radovskyb/watcher/ishidden_windows.go
generated
vendored
Normal file
21
vendor/github.com/radovskyb/watcher/ishidden_windows.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
// +build windows
|
||||
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func isHiddenFile(path string) (bool, error) {
|
||||
pointer, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
attributes, err := syscall.GetFileAttributes(pointer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return attributes&syscall.FILE_ATTRIBUTE_HIDDEN != 0, nil
|
||||
}
|
9
vendor/github.com/radovskyb/watcher/samefile.go
generated
vendored
Normal file
9
vendor/github.com/radovskyb/watcher/samefile.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
// +build !windows
|
||||
|
||||
package watcher
|
||||
|
||||
import "os"
|
||||
|
||||
func sameFile(fi1, fi2 os.FileInfo) bool {
|
||||
return os.SameFile(fi1, fi2)
|
||||
}
|
12
vendor/github.com/radovskyb/watcher/samefile_windows.go
generated
vendored
Normal file
12
vendor/github.com/radovskyb/watcher/samefile_windows.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// +build windows
|
||||
|
||||
package watcher
|
||||
|
||||
import "os"
|
||||
|
||||
func sameFile(fi1, fi2 os.FileInfo) bool {
|
||||
return fi1.ModTime() == fi2.ModTime() &&
|
||||
fi1.Size() == fi2.Size() &&
|
||||
fi1.Mode() == fi2.Mode() &&
|
||||
fi1.IsDir() == fi2.IsDir()
|
||||
}
|
715
vendor/github.com/radovskyb/watcher/watcher.go
generated
vendored
Normal file
715
vendor/github.com/radovskyb/watcher/watcher.go
generated
vendored
Normal file
@ -0,0 +1,715 @@
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrDurationTooShort occurs when calling the watcher's Start
|
||||
// method with a duration that's less than 1 nanosecond.
|
||||
ErrDurationTooShort = errors.New("error: duration is less than 1ns")
|
||||
|
||||
// ErrWatcherRunning occurs when trying to call the watcher's
|
||||
// Start method and the polling cycle is still already running
|
||||
// from previously calling Start and not yet calling Close.
|
||||
ErrWatcherRunning = errors.New("error: watcher is already running")
|
||||
|
||||
// ErrWatchedFileDeleted is an error that occurs when a file or folder that was
|
||||
// being watched has been deleted.
|
||||
ErrWatchedFileDeleted = errors.New("error: watched file or folder deleted")
|
||||
|
||||
// ErrSkip is less of an error, but more of a way for path hooks to skip a file or
|
||||
// directory.
|
||||
ErrSkip = errors.New("error: skipping file")
|
||||
)
|
||||
|
||||
// An Op is a type that is used to describe what type
|
||||
// of event has occurred during the watching process.
|
||||
type Op uint32
|
||||
|
||||
// Ops
|
||||
const (
|
||||
Create Op = iota
|
||||
Write
|
||||
Remove
|
||||
Rename
|
||||
Chmod
|
||||
Move
|
||||
)
|
||||
|
||||
var ops = map[Op]string{
|
||||
Create: "CREATE",
|
||||
Write: "WRITE",
|
||||
Remove: "REMOVE",
|
||||
Rename: "RENAME",
|
||||
Chmod: "CHMOD",
|
||||
Move: "MOVE",
|
||||
}
|
||||
|
||||
// String prints the string version of the Op consts
|
||||
func (e Op) String() string {
|
||||
if op, found := ops[e]; found {
|
||||
return op
|
||||
}
|
||||
return "???"
|
||||
}
|
||||
|
||||
// An Event describes an event that is received when files or directory
|
||||
// changes occur. It includes the os.FileInfo of the changed file or
|
||||
// directory and the type of event that's occurred and the full path of the file.
|
||||
type Event struct {
|
||||
Op
|
||||
Path string
|
||||
OldPath string
|
||||
os.FileInfo
|
||||
}
|
||||
|
||||
// String returns a string depending on what type of event occurred and the
|
||||
// file name associated with the event.
|
||||
func (e Event) String() string {
|
||||
if e.FileInfo == nil {
|
||||
return "???"
|
||||
}
|
||||
|
||||
pathType := "FILE"
|
||||
if e.IsDir() {
|
||||
pathType = "DIRECTORY"
|
||||
}
|
||||
return fmt.Sprintf("%s %q %s [%s]", pathType, e.Name(), e.Op, e.Path)
|
||||
}
|
||||
|
||||
// FilterFileHookFunc is a function that is called to filter files during listings.
|
||||
// If a file is ok to be listed, nil is returned otherwise ErrSkip is returned.
|
||||
type FilterFileHookFunc func(info os.FileInfo, fullPath string) error
|
||||
|
||||
// RegexFilterHook is a function that accepts or rejects a file
|
||||
// for listing based on whether it's filename or full path matches
|
||||
// a regular expression.
|
||||
func RegexFilterHook(r *regexp.Regexp, useFullPath bool) FilterFileHookFunc {
|
||||
return func(info os.FileInfo, fullPath string) error {
|
||||
str := info.Name()
|
||||
|
||||
if useFullPath {
|
||||
str = fullPath
|
||||
}
|
||||
|
||||
// Match
|
||||
if r.MatchString(str) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// No match.
|
||||
return ErrSkip
|
||||
}
|
||||
}
|
||||
|
||||
// Watcher describes a process that watches files for changes.
|
||||
type Watcher struct {
|
||||
Event chan Event
|
||||
Error chan error
|
||||
Closed chan struct{}
|
||||
close chan struct{}
|
||||
wg *sync.WaitGroup
|
||||
|
||||
// mu protects the following.
|
||||
mu *sync.Mutex
|
||||
ffh []FilterFileHookFunc
|
||||
running bool
|
||||
names map[string]bool // bool for recursive or not.
|
||||
files map[string]os.FileInfo // map of files.
|
||||
ignored map[string]struct{} // ignored files or directories.
|
||||
ops map[Op]struct{} // Op filtering.
|
||||
ignoreHidden bool // ignore hidden files or not.
|
||||
maxEvents int // max sent events per cycle
|
||||
}
|
||||
|
||||
// New creates a new Watcher.
|
||||
func New() *Watcher {
|
||||
// Set up the WaitGroup for w.Wait().
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
return &Watcher{
|
||||
Event: make(chan Event),
|
||||
Error: make(chan error),
|
||||
Closed: make(chan struct{}),
|
||||
close: make(chan struct{}),
|
||||
mu: new(sync.Mutex),
|
||||
wg: &wg,
|
||||
files: make(map[string]os.FileInfo),
|
||||
ignored: make(map[string]struct{}),
|
||||
names: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
// SetMaxEvents controls the maximum amount of events that are sent on
|
||||
// the Event channel per watching cycle. If max events is less than 1, there is
|
||||
// no limit, which is the default.
|
||||
func (w *Watcher) SetMaxEvents(delta int) {
|
||||
w.mu.Lock()
|
||||
w.maxEvents = delta
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
// AddFilterHook
|
||||
func (w *Watcher) AddFilterHook(f FilterFileHookFunc) {
|
||||
w.mu.Lock()
|
||||
w.ffh = append(w.ffh, f)
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
// IgnoreHiddenFiles sets the watcher to ignore any file or directory
|
||||
// that starts with a dot.
|
||||
func (w *Watcher) IgnoreHiddenFiles(ignore bool) {
|
||||
w.mu.Lock()
|
||||
w.ignoreHidden = ignore
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
// FilterOps filters which event op types should be returned
|
||||
// when an event occurs.
|
||||
func (w *Watcher) FilterOps(ops ...Op) {
|
||||
w.mu.Lock()
|
||||
w.ops = make(map[Op]struct{})
|
||||
for _, op := range ops {
|
||||
w.ops[op] = struct{}{}
|
||||
}
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
// Add adds either a single file or directory to the file list.
|
||||
func (w *Watcher) Add(name string) (err error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
name, err = filepath.Abs(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If name is on the ignored list or if hidden files are
|
||||
// ignored and name is a hidden file or directory, simply return.
|
||||
_, ignored := w.ignored[name]
|
||||
|
||||
isHidden, err := isHiddenFile(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ignored || (w.ignoreHidden && isHidden) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add the directory's contents to the files list.
|
||||
fileList, err := w.list(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range fileList {
|
||||
w.files[k] = v
|
||||
}
|
||||
|
||||
// Add the name to the names list.
|
||||
w.names[name] = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) list(name string) (map[string]os.FileInfo, error) {
|
||||
fileList := make(map[string]os.FileInfo)
|
||||
|
||||
// Make sure name exists.
|
||||
stat, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileList[name] = stat
|
||||
|
||||
// If it's not a directory, just return.
|
||||
if !stat.IsDir() {
|
||||
return fileList, nil
|
||||
}
|
||||
|
||||
// It's a directory.
|
||||
fInfoList, err := ioutil.ReadDir(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Add all of the files in the directory to the file list as long
|
||||
// as they aren't on the ignored list or are hidden files if ignoreHidden
|
||||
// is set to true.
|
||||
outer:
|
||||
for _, fInfo := range fInfoList {
|
||||
path := filepath.Join(name, fInfo.Name())
|
||||
_, ignored := w.ignored[path]
|
||||
|
||||
isHidden, err := isHiddenFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ignored || (w.ignoreHidden && isHidden) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, f := range w.ffh {
|
||||
err := f(fInfo, path)
|
||||
if err == ErrSkip {
|
||||
continue outer
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
fileList[path] = fInfo
|
||||
}
|
||||
return fileList, nil
|
||||
}
|
||||
|
||||
// AddRecursive adds either a single file or directory recursively to the file list.
|
||||
func (w *Watcher) AddRecursive(name string) (err error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
name, err = filepath.Abs(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileList, err := w.listRecursive(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range fileList {
|
||||
w.files[k] = v
|
||||
}
|
||||
|
||||
// Add the name to the names list.
|
||||
w.names[name] = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Watcher) listRecursive(name string) (map[string]os.FileInfo, error) {
|
||||
fileList := make(map[string]os.FileInfo)
|
||||
|
||||
return fileList, filepath.Walk(name, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range w.ffh {
|
||||
err := f(info, path)
|
||||
if err == ErrSkip {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If path is ignored and it's a directory, skip the directory. If it's
|
||||
// ignored and it's a single file, skip the file.
|
||||
_, ignored := w.ignored[path]
|
||||
|
||||
isHidden, err := isHiddenFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ignored || (w.ignoreHidden && isHidden) {
|
||||
if info.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Add the path and it's info to the file list.
|
||||
fileList[path] = info
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Remove removes either a single file or directory from the file's list.
|
||||
func (w *Watcher) Remove(name string) (err error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
name, err = filepath.Abs(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the name from w's names list.
|
||||
delete(w.names, name)
|
||||
|
||||
// If name is a single file, remove it and return.
|
||||
info, found := w.files[name]
|
||||
if !found {
|
||||
return nil // Doesn't exist, just return.
|
||||
}
|
||||
if !info.IsDir() {
|
||||
delete(w.files, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete the actual directory from w.files
|
||||
delete(w.files, name)
|
||||
|
||||
// If it's a directory, delete all of it's contents from w.files.
|
||||
for path := range w.files {
|
||||
if filepath.Dir(path) == name {
|
||||
delete(w.files, path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveRecursive removes either a single file or a directory recursively from
|
||||
// the file's list.
|
||||
func (w *Watcher) RemoveRecursive(name string) (err error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
name, err = filepath.Abs(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the name from w's names list.
|
||||
delete(w.names, name)
|
||||
|
||||
// If name is a single file, remove it and return.
|
||||
info, found := w.files[name]
|
||||
if !found {
|
||||
return nil // Doesn't exist, just return.
|
||||
}
|
||||
if !info.IsDir() {
|
||||
delete(w.files, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If it's a directory, delete all of it's contents recursively
|
||||
// from w.files.
|
||||
for path := range w.files {
|
||||
if strings.HasPrefix(path, name) {
|
||||
delete(w.files, path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ignore adds paths that should be ignored.
|
||||
//
|
||||
// For files that are already added, Ignore removes them.
|
||||
func (w *Watcher) Ignore(paths ...string) (err error) {
|
||||
for _, path := range paths {
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Remove any of the paths that were already added.
|
||||
if err := w.RemoveRecursive(path); err != nil {
|
||||
return err
|
||||
}
|
||||
w.mu.Lock()
|
||||
w.ignored[path] = struct{}{}
|
||||
w.mu.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WatchedFiles returns a map of files added to a Watcher.
|
||||
func (w *Watcher) WatchedFiles() map[string]os.FileInfo {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
files := make(map[string]os.FileInfo)
|
||||
for k, v := range w.files {
|
||||
files[k] = v
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
// fileInfo is an implementation of os.FileInfo that can be used
|
||||
// as a mocked os.FileInfo when triggering an event when the specified
|
||||
// os.FileInfo is nil.
|
||||
type fileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
modTime time.Time
|
||||
sys interface{}
|
||||
dir bool
|
||||
}
|
||||
|
||||
func (fs *fileInfo) IsDir() bool {
|
||||
return fs.dir
|
||||
}
|
||||
func (fs *fileInfo) ModTime() time.Time {
|
||||
return fs.modTime
|
||||
}
|
||||
func (fs *fileInfo) Mode() os.FileMode {
|
||||
return fs.mode
|
||||
}
|
||||
func (fs *fileInfo) Name() string {
|
||||
return fs.name
|
||||
}
|
||||
func (fs *fileInfo) Size() int64 {
|
||||
return fs.size
|
||||
}
|
||||
func (fs *fileInfo) Sys() interface{} {
|
||||
return fs.sys
|
||||
}
|
||||
|
||||
// TriggerEvent is a method that can be used to trigger an event, separate to
|
||||
// the file watching process.
|
||||
func (w *Watcher) TriggerEvent(eventType Op, file os.FileInfo) {
|
||||
w.Wait()
|
||||
if file == nil {
|
||||
file = &fileInfo{name: "triggered event", modTime: time.Now()}
|
||||
}
|
||||
w.Event <- Event{Op: eventType, Path: "-", FileInfo: file}
|
||||
}
|
||||
|
||||
func (w *Watcher) retrieveFileList() map[string]os.FileInfo {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
fileList := make(map[string]os.FileInfo)
|
||||
|
||||
var list map[string]os.FileInfo
|
||||
var err error
|
||||
|
||||
for name, recursive := range w.names {
|
||||
if recursive {
|
||||
list, err = w.listRecursive(name)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
w.mu.Unlock()
|
||||
if name == err.(*os.PathError).Path {
|
||||
w.Error <- ErrWatchedFileDeleted
|
||||
w.RemoveRecursive(name)
|
||||
}
|
||||
w.mu.Lock()
|
||||
} else {
|
||||
w.Error <- err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
list, err = w.list(name)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
w.mu.Unlock()
|
||||
if name == err.(*os.PathError).Path {
|
||||
w.Error <- ErrWatchedFileDeleted
|
||||
w.Remove(name)
|
||||
}
|
||||
w.mu.Lock()
|
||||
} else {
|
||||
w.Error <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add the file's to the file list.
|
||||
for k, v := range list {
|
||||
fileList[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return fileList
|
||||
}
|
||||
|
||||
// Start begins the polling cycle which repeats every specified
|
||||
// duration until Close is called.
|
||||
func (w *Watcher) Start(d time.Duration) error {
|
||||
// Return an error if d is less than 1 nanosecond.
|
||||
if d < time.Nanosecond {
|
||||
return ErrDurationTooShort
|
||||
}
|
||||
|
||||
// Make sure the Watcher is not already running.
|
||||
w.mu.Lock()
|
||||
if w.running {
|
||||
w.mu.Unlock()
|
||||
return ErrWatcherRunning
|
||||
}
|
||||
w.running = true
|
||||
w.mu.Unlock()
|
||||
|
||||
// Unblock w.Wait().
|
||||
w.wg.Done()
|
||||
|
||||
for {
|
||||
// done lets the inner polling cycle loop know when the
|
||||
// current cycle's method has finished executing.
|
||||
done := make(chan struct{})
|
||||
|
||||
// Any events that are found are first piped to evt before
|
||||
// being sent to the main Event channel.
|
||||
evt := make(chan Event)
|
||||
|
||||
// Retrieve the file list for all watched file's and dirs.
|
||||
fileList := w.retrieveFileList()
|
||||
|
||||
// cancel can be used to cancel the current event polling function.
|
||||
cancel := make(chan struct{})
|
||||
|
||||
// Look for events.
|
||||
go func() {
|
||||
w.pollEvents(fileList, evt, cancel)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
// numEvents holds the number of events for the current cycle.
|
||||
numEvents := 0
|
||||
|
||||
inner:
|
||||
for {
|
||||
select {
|
||||
case <-w.close:
|
||||
close(cancel)
|
||||
close(w.Closed)
|
||||
return nil
|
||||
case event := <-evt:
|
||||
if len(w.ops) > 0 { // Filter Ops.
|
||||
_, found := w.ops[event.Op]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
numEvents++
|
||||
if w.maxEvents > 0 && numEvents > w.maxEvents {
|
||||
close(cancel)
|
||||
break inner
|
||||
}
|
||||
w.Event <- event
|
||||
case <-done: // Current cycle is finished.
|
||||
break inner
|
||||
}
|
||||
}
|
||||
|
||||
// Update the file's list.
|
||||
w.mu.Lock()
|
||||
w.files = fileList
|
||||
w.mu.Unlock()
|
||||
|
||||
// Sleep and then continue to the next loop iteration.
|
||||
time.Sleep(d)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Watcher) pollEvents(files map[string]os.FileInfo, evt chan Event,
|
||||
cancel chan struct{}) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
// Store create and remove events for use to check for rename events.
|
||||
creates := make(map[string]os.FileInfo)
|
||||
removes := make(map[string]os.FileInfo)
|
||||
|
||||
// Check for removed files.
|
||||
for path, info := range w.files {
|
||||
if _, found := files[path]; !found {
|
||||
removes[path] = info
|
||||
}
|
||||
}
|
||||
|
||||
// Check for created files, writes and chmods.
|
||||
for path, info := range files {
|
||||
oldInfo, found := w.files[path]
|
||||
if !found {
|
||||
// A file was created.
|
||||
creates[path] = info
|
||||
continue
|
||||
}
|
||||
if oldInfo.ModTime() != info.ModTime() {
|
||||
select {
|
||||
case <-cancel:
|
||||
return
|
||||
case evt <- Event{Write, path, path, info}:
|
||||
}
|
||||
}
|
||||
if oldInfo.Mode() != info.Mode() {
|
||||
select {
|
||||
case <-cancel:
|
||||
return
|
||||
case evt <- Event{Chmod, path, path, info}:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for renames and moves.
|
||||
for path1, info1 := range removes {
|
||||
for path2, info2 := range creates {
|
||||
if sameFile(info1, info2) {
|
||||
e := Event{
|
||||
Op: Move,
|
||||
Path: path2,
|
||||
OldPath: path1,
|
||||
FileInfo: info1,
|
||||
}
|
||||
// If they are from the same directory, it's a rename
|
||||
// instead of a move event.
|
||||
if filepath.Dir(path1) == filepath.Dir(path2) {
|
||||
e.Op = Rename
|
||||
}
|
||||
|
||||
delete(removes, path1)
|
||||
delete(creates, path2)
|
||||
|
||||
select {
|
||||
case <-cancel:
|
||||
return
|
||||
case evt <- e:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send all the remaining create and remove events.
|
||||
for path, info := range creates {
|
||||
select {
|
||||
case <-cancel:
|
||||
return
|
||||
case evt <- Event{Create, path, "", info}:
|
||||
}
|
||||
}
|
||||
for path, info := range removes {
|
||||
select {
|
||||
case <-cancel:
|
||||
return
|
||||
case evt <- Event{Remove, path, path, info}:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait blocks until the watcher is started.
|
||||
func (w *Watcher) Wait() {
|
||||
w.wg.Wait()
|
||||
}
|
||||
|
||||
// Close stops a Watcher and unlocks its mutex, then sends a close signal.
|
||||
func (w *Watcher) Close() {
|
||||
w.mu.Lock()
|
||||
if !w.running {
|
||||
w.mu.Unlock()
|
||||
return
|
||||
}
|
||||
w.running = false
|
||||
w.files = make(map[string]os.FileInfo)
|
||||
w.names = make(map[string]bool)
|
||||
w.mu.Unlock()
|
||||
// Send a close signal to the Start method.
|
||||
w.close <- struct{}{}
|
||||
}
|
Reference in New Issue
Block a user