497 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			497 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2010 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 freebsd openbsd netbsd dragonfly darwin
 | 
						|
 | 
						|
package fsnotify
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"sync"
 | 
						|
	"syscall"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// Flags (from <sys/event.h>)
 | 
						|
	sys_NOTE_DELETE = 0x0001 /* vnode was removed */
 | 
						|
	sys_NOTE_WRITE  = 0x0002 /* data contents changed */
 | 
						|
	sys_NOTE_EXTEND = 0x0004 /* size increased */
 | 
						|
	sys_NOTE_ATTRIB = 0x0008 /* attributes changed */
 | 
						|
	sys_NOTE_LINK   = 0x0010 /* link count changed */
 | 
						|
	sys_NOTE_RENAME = 0x0020 /* vnode was renamed */
 | 
						|
	sys_NOTE_REVOKE = 0x0040 /* vnode access was revoked */
 | 
						|
 | 
						|
	// Watch all events
 | 
						|
	sys_NOTE_ALLEVENTS = sys_NOTE_DELETE | sys_NOTE_WRITE | sys_NOTE_ATTRIB | sys_NOTE_RENAME
 | 
						|
 | 
						|
	// Block for 100 ms on each call to kevent
 | 
						|
	keventWaitTime = 100e6
 | 
						|
)
 | 
						|
 | 
						|
type FileEvent struct {
 | 
						|
	mask   uint32 // Mask of events
 | 
						|
	Name   string // File name (optional)
 | 
						|
	create bool   // set by fsnotify package if found new file
 | 
						|
}
 | 
						|
 | 
						|
// IsCreate reports whether the FileEvent was triggered by a creation
 | 
						|
func (e *FileEvent) IsCreate() bool { return e.create }
 | 
						|
 | 
						|
// IsDelete reports whether the FileEvent was triggered by a delete
 | 
						|
func (e *FileEvent) IsDelete() bool { return (e.mask & sys_NOTE_DELETE) == sys_NOTE_DELETE }
 | 
						|
 | 
						|
// IsModify reports whether the FileEvent was triggered by a file modification
 | 
						|
func (e *FileEvent) IsModify() bool {
 | 
						|
	return ((e.mask&sys_NOTE_WRITE) == sys_NOTE_WRITE || (e.mask&sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB)
 | 
						|
}
 | 
						|
 | 
						|
// IsRename reports whether the FileEvent was triggered by a change name
 | 
						|
func (e *FileEvent) IsRename() bool { return (e.mask & sys_NOTE_RENAME) == sys_NOTE_RENAME }
 | 
						|
 | 
						|
// IsAttrib reports whether the FileEvent was triggered by a change in the file metadata.
 | 
						|
func (e *FileEvent) IsAttrib() bool {
 | 
						|
	return (e.mask & sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB
 | 
						|
}
 | 
						|
 | 
						|
type Watcher struct {
 | 
						|
	mu              sync.Mutex          // Mutex for the Watcher itself.
 | 
						|
	kq              int                 // File descriptor (as returned by the kqueue() syscall)
 | 
						|
	watches         map[string]int      // Map of watched file descriptors (key: path)
 | 
						|
	wmut            sync.Mutex          // Protects access to watches.
 | 
						|
	fsnFlags        map[string]uint32   // Map of watched files to flags used for filter
 | 
						|
	fsnmut          sync.Mutex          // Protects access to fsnFlags.
 | 
						|
	enFlags         map[string]uint32   // Map of watched files to evfilt note flags used in kqueue
 | 
						|
	enmut           sync.Mutex          // Protects access to enFlags.
 | 
						|
	paths           map[int]string      // Map of watched paths (key: watch descriptor)
 | 
						|
	finfo           map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor)
 | 
						|
	pmut            sync.Mutex          // Protects access to paths and finfo.
 | 
						|
	fileExists      map[string]bool     // Keep track of if we know this file exists (to stop duplicate create events)
 | 
						|
	femut           sync.Mutex          // Protects access to fileExists.
 | 
						|
	externalWatches map[string]bool     // Map of watches added by user of the library.
 | 
						|
	ewmut           sync.Mutex          // Protects access to externalWatches.
 | 
						|
	Error           chan error          // Errors are sent on this channel
 | 
						|
	internalEvent   chan *FileEvent     // Events are queued on this channel
 | 
						|
	Event           chan *FileEvent     // Events are returned on this channel
 | 
						|
	done            chan bool           // Channel for sending a "quit message" to the reader goroutine
 | 
						|
	isClosed        bool                // Set to true when Close() is first called
 | 
						|
}
 | 
						|
 | 
						|
// NewWatcher creates and returns a new kevent instance using kqueue(2)
 | 
						|
func NewWatcher() (*Watcher, error) {
 | 
						|
	fd, errno := syscall.Kqueue()
 | 
						|
	if fd == -1 {
 | 
						|
		return nil, os.NewSyscallError("kqueue", errno)
 | 
						|
	}
 | 
						|
	w := &Watcher{
 | 
						|
		kq:              fd,
 | 
						|
		watches:         make(map[string]int),
 | 
						|
		fsnFlags:        make(map[string]uint32),
 | 
						|
		enFlags:         make(map[string]uint32),
 | 
						|
		paths:           make(map[int]string),
 | 
						|
		finfo:           make(map[int]os.FileInfo),
 | 
						|
		fileExists:      make(map[string]bool),
 | 
						|
		externalWatches: make(map[string]bool),
 | 
						|
		internalEvent:   make(chan *FileEvent),
 | 
						|
		Event:           make(chan *FileEvent),
 | 
						|
		Error:           make(chan error),
 | 
						|
		done:            make(chan bool, 1),
 | 
						|
	}
 | 
						|
 | 
						|
	go w.readEvents()
 | 
						|
	go w.purgeEvents()
 | 
						|
	return w, nil
 | 
						|
}
 | 
						|
 | 
						|
// Close closes a kevent watcher instance
 | 
						|
// It sends a message to the reader goroutine to quit and removes all watches
 | 
						|
// associated with the kevent instance
 | 
						|
func (w *Watcher) Close() error {
 | 
						|
	w.mu.Lock()
 | 
						|
	if w.isClosed {
 | 
						|
		w.mu.Unlock()
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	w.isClosed = true
 | 
						|
	w.mu.Unlock()
 | 
						|
 | 
						|
	// Send "quit" message to the reader goroutine
 | 
						|
	w.done <- true
 | 
						|
	w.wmut.Lock()
 | 
						|
	ws := w.watches
 | 
						|
	w.wmut.Unlock()
 | 
						|
	for path := range ws {
 | 
						|
		w.removeWatch(path)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// AddWatch adds path to the watched file set.
 | 
						|
// The flags are interpreted as described in kevent(2).
 | 
						|
func (w *Watcher) addWatch(path string, flags uint32) error {
 | 
						|
	w.mu.Lock()
 | 
						|
	if w.isClosed {
 | 
						|
		w.mu.Unlock()
 | 
						|
		return errors.New("kevent instance already closed")
 | 
						|
	}
 | 
						|
	w.mu.Unlock()
 | 
						|
 | 
						|
	watchDir := false
 | 
						|
 | 
						|
	w.wmut.Lock()
 | 
						|
	watchfd, found := w.watches[path]
 | 
						|
	w.wmut.Unlock()
 | 
						|
	if !found {
 | 
						|
		fi, errstat := os.Lstat(path)
 | 
						|
		if errstat != nil {
 | 
						|
			return errstat
 | 
						|
		}
 | 
						|
 | 
						|
		// don't watch socket
 | 
						|
		if fi.Mode()&os.ModeSocket == os.ModeSocket {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		// Follow Symlinks
 | 
						|
		// Unfortunately, Linux can add bogus symlinks to watch list without
 | 
						|
		// issue, and Windows can't do symlinks period (AFAIK). To  maintain
 | 
						|
		// consistency, we will act like everything is fine. There will simply
 | 
						|
		// be no file events for broken symlinks.
 | 
						|
		// Hence the returns of nil on errors.
 | 
						|
		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
 | 
						|
			path, err := filepath.EvalSymlinks(path)
 | 
						|
			if err != nil {
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
 | 
						|
			fi, errstat = os.Lstat(path)
 | 
						|
			if errstat != nil {
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		fd, errno := syscall.Open(path, open_FLAGS, 0700)
 | 
						|
		if fd == -1 {
 | 
						|
			return errno
 | 
						|
		}
 | 
						|
		watchfd = fd
 | 
						|
 | 
						|
		w.wmut.Lock()
 | 
						|
		w.watches[path] = watchfd
 | 
						|
		w.wmut.Unlock()
 | 
						|
 | 
						|
		w.pmut.Lock()
 | 
						|
		w.paths[watchfd] = path
 | 
						|
		w.finfo[watchfd] = fi
 | 
						|
		w.pmut.Unlock()
 | 
						|
	}
 | 
						|
	// Watch the directory if it has not been watched before.
 | 
						|
	w.pmut.Lock()
 | 
						|
	w.enmut.Lock()
 | 
						|
	if w.finfo[watchfd].IsDir() &&
 | 
						|
		(flags&sys_NOTE_WRITE) == sys_NOTE_WRITE &&
 | 
						|
		(!found || (w.enFlags[path]&sys_NOTE_WRITE) != sys_NOTE_WRITE) {
 | 
						|
		watchDir = true
 | 
						|
	}
 | 
						|
	w.enmut.Unlock()
 | 
						|
	w.pmut.Unlock()
 | 
						|
 | 
						|
	w.enmut.Lock()
 | 
						|
	w.enFlags[path] = flags
 | 
						|
	w.enmut.Unlock()
 | 
						|
 | 
						|
	var kbuf [1]syscall.Kevent_t
 | 
						|
	watchEntry := &kbuf[0]
 | 
						|
	watchEntry.Fflags = flags
 | 
						|
	syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR)
 | 
						|
	entryFlags := watchEntry.Flags
 | 
						|
	success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil)
 | 
						|
	if success == -1 {
 | 
						|
		return errno
 | 
						|
	} else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
 | 
						|
		return errors.New("kevent add error")
 | 
						|
	}
 | 
						|
 | 
						|
	if watchDir {
 | 
						|
		errdir := w.watchDirectoryFiles(path)
 | 
						|
		if errdir != nil {
 | 
						|
			return errdir
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Watch adds path to the watched file set, watching all events.
 | 
						|
func (w *Watcher) watch(path string) error {
 | 
						|
	w.ewmut.Lock()
 | 
						|
	w.externalWatches[path] = true
 | 
						|
	w.ewmut.Unlock()
 | 
						|
	return w.addWatch(path, sys_NOTE_ALLEVENTS)
 | 
						|
}
 | 
						|
 | 
						|
// RemoveWatch removes path from the watched file set.
 | 
						|
func (w *Watcher) removeWatch(path string) error {
 | 
						|
	w.wmut.Lock()
 | 
						|
	watchfd, ok := w.watches[path]
 | 
						|
	w.wmut.Unlock()
 | 
						|
	if !ok {
 | 
						|
		return errors.New(fmt.Sprintf("can't remove non-existent kevent watch for: %s", path))
 | 
						|
	}
 | 
						|
	var kbuf [1]syscall.Kevent_t
 | 
						|
	watchEntry := &kbuf[0]
 | 
						|
	syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
 | 
						|
	entryFlags := watchEntry.Flags
 | 
						|
	success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil)
 | 
						|
	if success == -1 {
 | 
						|
		return os.NewSyscallError("kevent_rm_watch", errno)
 | 
						|
	} else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
 | 
						|
		return errors.New("kevent rm error")
 | 
						|
	}
 | 
						|
	syscall.Close(watchfd)
 | 
						|
	w.wmut.Lock()
 | 
						|
	delete(w.watches, path)
 | 
						|
	w.wmut.Unlock()
 | 
						|
	w.enmut.Lock()
 | 
						|
	delete(w.enFlags, path)
 | 
						|
	w.enmut.Unlock()
 | 
						|
	w.pmut.Lock()
 | 
						|
	delete(w.paths, watchfd)
 | 
						|
	fInfo := w.finfo[watchfd]
 | 
						|
	delete(w.finfo, watchfd)
 | 
						|
	w.pmut.Unlock()
 | 
						|
 | 
						|
	// Find all watched paths that are in this directory that are not external.
 | 
						|
	if fInfo.IsDir() {
 | 
						|
		var pathsToRemove []string
 | 
						|
		w.pmut.Lock()
 | 
						|
		for _, wpath := range w.paths {
 | 
						|
			wdir, _ := filepath.Split(wpath)
 | 
						|
			if filepath.Clean(wdir) == filepath.Clean(path) {
 | 
						|
				w.ewmut.Lock()
 | 
						|
				if !w.externalWatches[wpath] {
 | 
						|
					pathsToRemove = append(pathsToRemove, wpath)
 | 
						|
				}
 | 
						|
				w.ewmut.Unlock()
 | 
						|
			}
 | 
						|
		}
 | 
						|
		w.pmut.Unlock()
 | 
						|
		for _, p := range pathsToRemove {
 | 
						|
			// Since these are internal, not much sense in propagating error
 | 
						|
			// to the user, as that will just confuse them with an error about
 | 
						|
			// a path they did not explicitly watch themselves.
 | 
						|
			w.removeWatch(p)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// readEvents reads from the kqueue file descriptor, converts the
 | 
						|
// received events into Event objects and sends them via the Event channel
 | 
						|
func (w *Watcher) readEvents() {
 | 
						|
	var (
 | 
						|
		eventbuf [10]syscall.Kevent_t // Event buffer
 | 
						|
		events   []syscall.Kevent_t   // Received events
 | 
						|
		twait    *syscall.Timespec    // Time to block waiting for events
 | 
						|
		n        int                  // Number of events returned from kevent
 | 
						|
		errno    error                // Syscall errno
 | 
						|
	)
 | 
						|
	events = eventbuf[0:0]
 | 
						|
	twait = new(syscall.Timespec)
 | 
						|
	*twait = syscall.NsecToTimespec(keventWaitTime)
 | 
						|
 | 
						|
	for {
 | 
						|
		// See if there is a message on the "done" channel
 | 
						|
		var done bool
 | 
						|
		select {
 | 
						|
		case done = <-w.done:
 | 
						|
		default:
 | 
						|
		}
 | 
						|
 | 
						|
		// If "done" message is received
 | 
						|
		if done {
 | 
						|
			errno := syscall.Close(w.kq)
 | 
						|
			if errno != nil {
 | 
						|
				w.Error <- os.NewSyscallError("close", errno)
 | 
						|
			}
 | 
						|
			close(w.internalEvent)
 | 
						|
			close(w.Error)
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		// Get new events
 | 
						|
		if len(events) == 0 {
 | 
						|
			n, errno = syscall.Kevent(w.kq, nil, eventbuf[:], twait)
 | 
						|
 | 
						|
			// EINTR is okay, basically the syscall was interrupted before
 | 
						|
			// timeout expired.
 | 
						|
			if errno != nil && errno != syscall.EINTR {
 | 
						|
				w.Error <- os.NewSyscallError("kevent", errno)
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			// Received some events
 | 
						|
			if n > 0 {
 | 
						|
				events = eventbuf[0:n]
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Flush the events we received to the events channel
 | 
						|
		for len(events) > 0 {
 | 
						|
			fileEvent := new(FileEvent)
 | 
						|
			watchEvent := &events[0]
 | 
						|
			fileEvent.mask = uint32(watchEvent.Fflags)
 | 
						|
			w.pmut.Lock()
 | 
						|
			fileEvent.Name = w.paths[int(watchEvent.Ident)]
 | 
						|
			fileInfo := w.finfo[int(watchEvent.Ident)]
 | 
						|
			w.pmut.Unlock()
 | 
						|
			if fileInfo != nil && fileInfo.IsDir() && !fileEvent.IsDelete() {
 | 
						|
				// Double check to make sure the directory exist. This can happen when
 | 
						|
				// we do a rm -fr on a recursively watched folders and we receive a
 | 
						|
				// modification event first but the folder has been deleted and later
 | 
						|
				// receive the delete event
 | 
						|
				if _, err := os.Lstat(fileEvent.Name); os.IsNotExist(err) {
 | 
						|
					// mark is as delete event
 | 
						|
					fileEvent.mask |= sys_NOTE_DELETE
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if fileInfo != nil && fileInfo.IsDir() && fileEvent.IsModify() && !fileEvent.IsDelete() {
 | 
						|
				w.sendDirectoryChangeEvents(fileEvent.Name)
 | 
						|
			} else {
 | 
						|
				// Send the event on the events channel
 | 
						|
				w.internalEvent <- fileEvent
 | 
						|
			}
 | 
						|
 | 
						|
			// Move to next event
 | 
						|
			events = events[1:]
 | 
						|
 | 
						|
			if fileEvent.IsRename() {
 | 
						|
				w.removeWatch(fileEvent.Name)
 | 
						|
				w.femut.Lock()
 | 
						|
				delete(w.fileExists, fileEvent.Name)
 | 
						|
				w.femut.Unlock()
 | 
						|
			}
 | 
						|
			if fileEvent.IsDelete() {
 | 
						|
				w.removeWatch(fileEvent.Name)
 | 
						|
				w.femut.Lock()
 | 
						|
				delete(w.fileExists, fileEvent.Name)
 | 
						|
				w.femut.Unlock()
 | 
						|
 | 
						|
				// Look for a file that may have overwritten this
 | 
						|
				// (ie mv f1 f2 will delete f2 then create f2)
 | 
						|
				fileDir, _ := filepath.Split(fileEvent.Name)
 | 
						|
				fileDir = filepath.Clean(fileDir)
 | 
						|
				w.wmut.Lock()
 | 
						|
				_, found := w.watches[fileDir]
 | 
						|
				w.wmut.Unlock()
 | 
						|
				if found {
 | 
						|
					// make sure the directory exist before we watch for changes. When we
 | 
						|
					// do a recursive watch and perform rm -fr, the parent directory might
 | 
						|
					// have gone missing, ignore the missing directory and let the
 | 
						|
					// upcoming delete event remove the watch form the parent folder
 | 
						|
					if _, err := os.Lstat(fileDir); !os.IsNotExist(err) {
 | 
						|
						w.sendDirectoryChangeEvents(fileDir)
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
 | 
						|
	// Get all files
 | 
						|
	files, err := ioutil.ReadDir(dirPath)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// Search for new files
 | 
						|
	for _, fileInfo := range files {
 | 
						|
		filePath := filepath.Join(dirPath, fileInfo.Name())
 | 
						|
 | 
						|
		// Inherit fsnFlags from parent directory
 | 
						|
		w.fsnmut.Lock()
 | 
						|
		if flags, found := w.fsnFlags[dirPath]; found {
 | 
						|
			w.fsnFlags[filePath] = flags
 | 
						|
		} else {
 | 
						|
			w.fsnFlags[filePath] = FSN_ALL
 | 
						|
		}
 | 
						|
		w.fsnmut.Unlock()
 | 
						|
 | 
						|
		if fileInfo.IsDir() == false {
 | 
						|
			// Watch file to mimic linux fsnotify
 | 
						|
			e := w.addWatch(filePath, sys_NOTE_ALLEVENTS)
 | 
						|
			if e != nil {
 | 
						|
				return e
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			// If the user is currently watching directory
 | 
						|
			// we want to preserve the flags used
 | 
						|
			w.enmut.Lock()
 | 
						|
			currFlags, found := w.enFlags[filePath]
 | 
						|
			w.enmut.Unlock()
 | 
						|
			var newFlags uint32 = sys_NOTE_DELETE
 | 
						|
			if found {
 | 
						|
				newFlags |= currFlags
 | 
						|
			}
 | 
						|
 | 
						|
			// Linux gives deletes if not explicitly watching
 | 
						|
			e := w.addWatch(filePath, newFlags)
 | 
						|
			if e != nil {
 | 
						|
				return e
 | 
						|
			}
 | 
						|
		}
 | 
						|
		w.femut.Lock()
 | 
						|
		w.fileExists[filePath] = true
 | 
						|
		w.femut.Unlock()
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// sendDirectoryEvents searches the directory for newly created files
 | 
						|
// and sends them over the event channel. This functionality is to have
 | 
						|
// the BSD version of fsnotify match linux fsnotify which provides a
 | 
						|
// create event for files created in a watched directory.
 | 
						|
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
 | 
						|
	// Get all files
 | 
						|
	files, err := ioutil.ReadDir(dirPath)
 | 
						|
	if err != nil {
 | 
						|
		w.Error <- err
 | 
						|
	}
 | 
						|
 | 
						|
	// Search for new files
 | 
						|
	for _, fileInfo := range files {
 | 
						|
		filePath := filepath.Join(dirPath, fileInfo.Name())
 | 
						|
		w.femut.Lock()
 | 
						|
		_, doesExist := w.fileExists[filePath]
 | 
						|
		w.femut.Unlock()
 | 
						|
		if !doesExist {
 | 
						|
			// Inherit fsnFlags from parent directory
 | 
						|
			w.fsnmut.Lock()
 | 
						|
			if flags, found := w.fsnFlags[dirPath]; found {
 | 
						|
				w.fsnFlags[filePath] = flags
 | 
						|
			} else {
 | 
						|
				w.fsnFlags[filePath] = FSN_ALL
 | 
						|
			}
 | 
						|
			w.fsnmut.Unlock()
 | 
						|
 | 
						|
			// Send create event
 | 
						|
			fileEvent := new(FileEvent)
 | 
						|
			fileEvent.Name = filePath
 | 
						|
			fileEvent.create = true
 | 
						|
			w.internalEvent <- fileEvent
 | 
						|
		}
 | 
						|
		w.femut.Lock()
 | 
						|
		w.fileExists[filePath] = true
 | 
						|
		w.femut.Unlock()
 | 
						|
	}
 | 
						|
	w.watchDirectoryFiles(dirPath)
 | 
						|
}
 |