blob: fa81783e572eedb57465fe5731ba3ca46233a3fc [file] [log] [blame]
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package fsnotify
import (
"errors"
"os"
"path/filepath"
"sync"
"syscall"
"time"
"unsafe"
)
const (
// Options for AddWatch
sys_FS_ONESHOT = 0x80000000
sys_FS_ONLYDIR = 0x1000000
// Events
sys_FS_ACCESS = 0x1
sys_FS_ALL_EVENTS = 0xfff
sys_FS_ATTRIB = 0x4
sys_FS_CLOSE = 0x18
sys_FS_CREATE = 0x100
sys_FS_DELETE = 0x200
sys_FS_DELETE_SELF = 0x400
sys_FS_MODIFY = 0x2
sys_FS_MOVE = 0xc0
sys_FS_MOVED_FROM = 0x40
sys_FS_MOVED_TO = 0x80
sys_FS_MOVE_SELF = 0x800
// Special events
sys_FS_IGNORED = 0x8000
sys_FS_Q_OVERFLOW = 0x4000
)
// Event is the type of the notification messages
// received on the watcher's Event channel.
type FileEvent struct {
mask uint32 // Mask of events
cookie uint32 // Unique cookie associating related events (for rename)
Name string // File name (optional)
}
// IsCreate reports whether the FileEvent was triggerd by a creation
func (e *FileEvent) IsCreate() bool { return (e.mask & sys_FS_CREATE) == sys_FS_CREATE }
// IsDelete reports whether the FileEvent was triggerd by a delete
func (e *FileEvent) IsDelete() bool {
return ((e.mask&sys_FS_DELETE) == sys_FS_DELETE || (e.mask&sys_FS_DELETE_SELF) == sys_FS_DELETE_SELF)
}
// IsModify reports whether the FileEvent was triggerd by a file modification or attribute change
func (e *FileEvent) IsModify() bool {
return ((e.mask&sys_FS_MODIFY) == sys_FS_MODIFY || (e.mask&sys_FS_ATTRIB) == sys_FS_ATTRIB)
}
// IsRename reports whether the FileEvent was triggerd by a change name
func (e *FileEvent) IsRename() bool {
return ((e.mask&sys_FS_MOVE) == sys_FS_MOVE || (e.mask&sys_FS_MOVE_SELF) == sys_FS_MOVE_SELF || (e.mask&sys_FS_MOVED_FROM) == sys_FS_MOVED_FROM || (e.mask&sys_FS_MOVED_TO) == sys_FS_MOVED_TO)
}
// A Watcher waits for and receives event notifications
// for a specific set of files and directories.
type Watcher struct {
mu sync.Mutex // Map access
dirWatches map[string]chan struct{} // Map of directories to channel that closes watch goroutine
fsnFlags map[string]uint32 // Map of watched files to flags used for filter
fsnmut sync.Mutex // Protects access to fsnFlags.
internalEvent chan *FileEvent // Events are queued on this channel
Event chan *FileEvent // Events are returned on this channel
Error chan error // Errors are sent on this channel
isClosed bool
}
// NewWatcher creates and returns a Watcher.
func NewWatcher() (*Watcher, error) {
w := &Watcher{
dirWatches: make(map[string]chan struct{}),
fsnFlags: make(map[string]uint32),
Event: make(chan *FileEvent),
internalEvent: make(chan *FileEvent),
Error: make(chan error),
}
go w.purgeEvents()
return w, nil
}
// Close closes a Watcher.
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the watcher.
func (w *Watcher) Close() error {
if w.isClosed {
return nil
}
w.isClosed = true
// Quit each directory watcher
for _, watchChan := range w.dirWatches {
close(watchChan)
}
close(w.Event)
close(w.Error)
return nil
}
// watch adds path to the watched file set.
func (w *Watcher) watch(path string) error {
if w.isClosed {
return errors.New("watcher is closed")
}
dir := filepath.Dir(path)
w.mu.Lock()
if _, watchExists := w.dirWatches[dir]; !watchExists {
if handle, err := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
syscall.FILE_LIST_DIRECTORY,
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
nil, syscall.OPEN_EXISTING,
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0); err != nil {
return os.NewSyscallError("CreateFile", err)
} else {
ch := make(chan struct{})
w.dirWatches[dir] = ch
w.watchDirectory(handle, path, ch)
time.Sleep(50 * time.Millisecond)
}
}
w.mu.Unlock()
return nil
}
// RemoveWatch removes path from the watched file set.
func (w *Watcher) removeWatch(path string) error {
w.mu.Lock()
if watchChan, watchExists := w.dirWatches[path]; watchExists {
close(watchChan)
delete(w.dirWatches, path)
} else if _, watchDir := w.dirWatches[filepath.Dir(path)]; !watchDir {
return errors.New("file was not being watched")
}
return nil
}
// Must run within the I/O thread.
func (w *Watcher) watchDirectory(handle syscall.Handle, path string, doneChan <-chan struct{}) {
fnEvents := make(chan *FileEvent)
quit := make(chan bool, 1)
dir := filepath.Base(path)
go func() {
var buf [4096]byte
var bytesReturned uint32
for {
syscall.ReadDirectoryChanges(handle, &buf[0],
uint32(unsafe.Sizeof(buf)), false, sys_FS_ALL_EVENTS, &bytesReturned, nil, 0)
if bytesReturned > 0 {
// Point "raw" to the event in the buffer
var offset uint32
for {
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&buf[offset]))
ebuf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
name := syscall.UTF16ToString(ebuf[:raw.FileNameLength/2])
fullname := dir + "\\" + name
var mask uint32
switch raw.Action {
case syscall.FILE_ACTION_ADDED:
mask = sys_FS_CREATE
case syscall.FILE_ACTION_REMOVED:
mask = sys_FS_DELETE_SELF
case syscall.FILE_ACTION_MODIFIED:
mask = sys_FS_MODIFY
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
mask = sys_FS_MOVE_SELF
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
mask = sys_FS_MOVE_SELF
}
fnEvents <- &FileEvent{Name: fullname, mask: mask}
// Move to the next event in the buffer
if raw.NextEntryOffset == 0 {
break
}
offset += raw.NextEntryOffset
}
}
select {
case <-quit:
return
default:
}
}
}()
go func() {
for {
select {
case ev := <-fnEvents:
w.fsnmut.Lock()
if fsnFlags, exists := w.fsnFlags[filepath.Dir(ev.Name)]; exists {
w.fsnFlags[ev.Name] = fsnFlags
} else {
w.fsnFlags[ev.Name] = FSN_ALL
}
w.fsnmut.Unlock()
w.internalEvent <- ev
case _, open := <-doneChan:
if !open {
quit <- true
syscall.CloseHandle(handle)
return
}
}
}
}()
}