Example #1
0
// Watch is the primary listener for this resource and it outputs events.
func (obj *RecWatcher) Watch() error {
	if obj.watcher == nil {
		return fmt.Errorf("Watcher is not initialized!")
	}
	obj.wg.Add(1)
	defer obj.wg.Done()

	patharray := util.PathSplit(obj.safename) // tokenize the path
	var index = len(patharray)                // starting index
	var current string                        // current "watcher" location
	var deltaDepth int                        // depth delta between watcher and event
	var send = false                          // send event?

	for {
		current = strings.Join(patharray[0:index], "/")
		if current == "" { // the empty string top is the root dir ("/")
			current = "/"
		}
		if obj.Flags.Debug {
			log.Printf("Watching: %s", current) // attempting to watch...
		}
		// initialize in the loop so that we can reset on rm-ed handles
		if err := obj.watcher.Add(current); err != nil {
			if obj.Flags.Debug {
				log.Printf("watcher.Add(%s): Error: %v", current, err)
			}

			if err == syscall.ENOENT {
				index-- // usually not found, move up one dir
				index = int(math.Max(1, float64(index)))
				continue
			}

			if err == syscall.ENOSPC {
				// no space left on device, out of inotify watches
				// TODO: consider letting the user fall back to
				// polling if they hit this error very often...
				return fmt.Errorf("Out of inotify watches: %v", err)
			} else if os.IsPermission(err) {
				return fmt.Errorf("Permission denied adding a watch: %v", err)
			}
			return fmt.Errorf("Unknown error: %v", err)
		}

		select {
		case event := <-obj.watcher.Events:
			if obj.Flags.Debug {
				log.Printf("Watch(%s), Event(%s): %v", current, event.Name, event.Op)
			}
			// the deeper you go, the bigger the deltaDepth is...
			// this is the difference between what we're watching,
			// and the event... doesn't mean we can't watch deeper
			if current == event.Name {
				deltaDepth = 0 // i was watching what i was looking for

			} else if util.HasPathPrefix(event.Name, current) {
				deltaDepth = len(util.PathSplit(current)) - len(util.PathSplit(event.Name)) // -1 or less

			} else if util.HasPathPrefix(current, event.Name) {
				deltaDepth = len(util.PathSplit(event.Name)) - len(util.PathSplit(current)) // +1 or more
				// if below me...
				if _, exists := obj.watches[event.Name]; exists {
					send = true
					if event.Op&fsnotify.Remove == fsnotify.Remove {
						obj.watcher.Remove(event.Name)
						delete(obj.watches, event.Name)
					}
					if (event.Op&fsnotify.Create == fsnotify.Create) && isDir(event.Name) {
						obj.watcher.Add(event.Name)
						obj.watches[event.Name] = struct{}{}
						if err := obj.addSubFolders(event.Name); err != nil {
							return err
						}
					}
				}

			} else {
				// TODO: different watchers get each others events!
				// https://github.com/go-fsnotify/fsnotify/issues/95
				// this happened with two values such as:
				// event.Name: /tmp/mgmt/f3 and current: /tmp/mgmt/f2
				continue
			}
			//log.Printf("The delta depth is: %v", deltaDepth)

			// if we have what we wanted, awesome, send an event...
			if event.Name == obj.safename {
				//log.Println("Event!")
				// FIXME: should all these below cases trigger?
				send = true

				if obj.isDir {
					if err := obj.addSubFolders(obj.safename); err != nil {
						return err
					}
				}

				// file removed, move the watch upwards
				if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) {
					//log.Println("Removal!")
					obj.watcher.Remove(current)
					index--
				}

				// we must be a parent watcher, so descend in
				if deltaDepth < 0 {
					// XXX: we can block here due to: https://github.com/fsnotify/fsnotify/issues/123
					obj.watcher.Remove(current)
					index++
				}

				// if safename starts with event.Name, we're above, and no event should be sent
			} else if util.HasPathPrefix(obj.safename, event.Name) {
				//log.Println("Above!")

				if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) {
					log.Println("Removal!")
					obj.watcher.Remove(current)
					index--
				}

				if deltaDepth < 0 {
					log.Println("Parent!")
					if util.PathPrefixDelta(obj.safename, event.Name) == 1 { // we're the parent dir
						send = true
					}
					obj.watcher.Remove(current)
					index++
				}

				// if event.Name startswith safename, send event, we're already deeper
			} else if util.HasPathPrefix(event.Name, obj.safename) {
				//log.Println("Event2!")
				send = true
			}

			// do all our event sending all together to avoid duplicate msgs
			if send {
				send = false
				// only invalid state on certain types of events
				obj.events <- Event{Error: nil, Body: &event}
			}

		case err := <-obj.watcher.Errors:
			return fmt.Errorf("Unknown watcher error: %v", err)

		case <-obj.exit:
			return nil
		}
	}
}
Example #2
0
File: file.go Project: ffrank/mgmt
// Watch is the primary listener for this resource and it outputs events.
// This one is a file watcher for files and directories.
// Modify with caution, it is probably important to write some test cases first!
// If the Watch returns an error, it means that something has gone wrong, and it
// must be restarted. On a clean exit it returns nil.
// FIXME: Also watch the source directory when using obj.Source !!!
func (obj *FileRes) Watch(processChan chan event.Event) error {
	if obj.IsWatching() {
		return nil // TODO: should this be an error?
	}
	obj.SetWatching(true)
	defer obj.SetWatching(false)
	cuuid := obj.converger.Register()
	defer cuuid.Unregister()

	var startup bool
	Startup := func(block bool) <-chan time.Time {
		if block {
			return nil // blocks forever
			//return make(chan time.Time) // blocks forever
		}
		return time.After(time.Duration(500) * time.Millisecond) // 1/2 the resolution of converged timeout
	}

	var safename = path.Clean(obj.path) // no trailing slash

	var err error
	obj.watcher, err = fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	defer obj.watcher.Close()

	patharray := util.PathSplit(safename) // tokenize the path
	var index = len(patharray)            // starting index
	var current string                    // current "watcher" location
	var deltaDepth int                    // depth delta between watcher and event
	var send = false                      // send event?
	var exit = false
	var dirty = false

	isDir := func(p string) bool {
		finfo, err := os.Stat(p)
		if err != nil {
			return false
		}
		return finfo.IsDir()
	}

	if obj.isDir {
		if err := obj.addSubFolders(safename); err != nil {
			return err
		}
	}
	for {
		current = strings.Join(patharray[0:index], "/")
		if current == "" { // the empty string top is the root dir ("/")
			current = "/"
		}
		if global.DEBUG {
			log.Printf("%s[%s]: Watching: %v", obj.Kind(), obj.GetName(), current) // attempting to watch...
		}
		// initialize in the loop so that we can reset on rm-ed handles
		err = obj.watcher.Add(current)
		if err != nil {
			if global.DEBUG {
				log.Printf("%s[%s]: watcher.Add(%v): Error: %v", obj.Kind(), obj.GetName(), current, err)
			}
			if err == syscall.ENOENT {
				index-- // usually not found, move up one dir
			} else if err == syscall.ENOSPC {
				// no space left on device, out of inotify watches
				// TODO: consider letting the user fall back to
				// polling if they hit this error very often...
				return fmt.Errorf("%s[%s]: Out of inotify watches: %v", obj.Kind(), obj.GetName(), err)
			} else if os.IsPermission(err) {
				return fmt.Errorf("%s[%s]: Permission denied to add a watch: %v", obj.Kind(), obj.GetName(), err)
			} else {
				return fmt.Errorf("Unknown %s[%s] error: %v", obj.Kind(), obj.GetName(), err)
			}
			index = int(math.Max(1, float64(index)))
			continue
		}

		obj.SetState(ResStateWatching) // reset
		select {
		case event := <-obj.watcher.Events:
			if global.DEBUG {
				log.Printf("%s[%s]: Watch(%s), Event(%s): %v", obj.Kind(), obj.GetName(), current, event.Name, event.Op)
			}
			cuuid.SetConverged(false) // XXX: technically i can detect if the event is erroneous or not first
			// the deeper you go, the bigger the deltaDepth is...
			// this is the difference between what we're watching,
			// and the event... doesn't mean we can't watch deeper
			if current == event.Name {
				deltaDepth = 0 // i was watching what i was looking for

			} else if util.HasPathPrefix(event.Name, current) {
				deltaDepth = len(util.PathSplit(current)) - len(util.PathSplit(event.Name)) // -1 or less

			} else if util.HasPathPrefix(current, event.Name) {
				deltaDepth = len(util.PathSplit(event.Name)) - len(util.PathSplit(current)) // +1 or more
				// if below me...
				if _, exists := obj.watches[event.Name]; exists {
					send = true
					dirty = true
					if event.Op&fsnotify.Remove == fsnotify.Remove {
						obj.watcher.Remove(event.Name)
						delete(obj.watches, event.Name)
					}
					if (event.Op&fsnotify.Create == fsnotify.Create) && isDir(event.Name) {
						obj.watcher.Add(event.Name)
						obj.watches[event.Name] = struct{}{}
						if err := obj.addSubFolders(event.Name); err != nil {
							return err
						}
					}
				}

			} else {
				// TODO different watchers get each others events!
				// https://github.com/go-fsnotify/fsnotify/issues/95
				// this happened with two values such as:
				// event.Name: /tmp/mgmt/f3 and current: /tmp/mgmt/f2
				continue
			}
			//log.Printf("The delta depth is: %v", deltaDepth)

			// if we have what we wanted, awesome, send an event...
			if event.Name == safename {
				//log.Println("Event!")
				// FIXME: should all these below cases trigger?
				send = true
				dirty = true

				if obj.isDir {
					if err := obj.addSubFolders(safename); err != nil {
						return err
					}
				}

				// file removed, move the watch upwards
				if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) {
					//log.Println("Removal!")
					obj.watcher.Remove(current)
					index--
				}

				// we must be a parent watcher, so descend in
				if deltaDepth < 0 {
					obj.watcher.Remove(current)
					index++
				}

				// if safename starts with event.Name, we're above, and no event should be sent
			} else if util.HasPathPrefix(safename, event.Name) {
				//log.Println("Above!")

				if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) {
					log.Println("Removal!")
					obj.watcher.Remove(current)
					index--
				}

				if deltaDepth < 0 {
					log.Println("Parent!")
					if util.PathPrefixDelta(safename, event.Name) == 1 { // we're the parent dir
						send = true
						dirty = true
					}
					obj.watcher.Remove(current)
					index++
				}

				// if event.Name startswith safename, send event, we're already deeper
			} else if util.HasPathPrefix(event.Name, safename) {
				//log.Println("Event2!")
				send = true
				dirty = true
			}

		case err := <-obj.watcher.Errors:
			cuuid.SetConverged(false)
			return fmt.Errorf("Unknown %s[%s] watcher error: %v", obj.Kind(), obj.GetName(), err)

		case event := <-obj.events:
			cuuid.SetConverged(false)
			if exit, send = obj.ReadEvent(&event); exit {
				return nil // exit
			}
			//dirty = false // these events don't invalidate state

		case <-cuuid.ConvergedTimer():
			cuuid.SetConverged(true) // converged!
			continue

		case <-Startup(startup):
			cuuid.SetConverged(false)
			send = true
			dirty = true
		}

		// do all our event sending all together to avoid duplicate msgs
		if send {
			startup = true // startup finished
			send = false
			// only invalid state on certain types of events
			if dirty {
				dirty = false
				obj.isStateOK = false // something made state dirty
			}
			if exit, err := obj.DoSend(processChan, ""); exit || err != nil {
				return err // we exit or bubble up a NACK...
			}
		}
	}
}
Example #3
0
// ConfigWatch writes on the channel everytime an event is seen for the path.
// XXX: it would be great if we could reuse code between this and the file resource
// XXX: patch this to submit it as part of go-fsnotify if they're interested...
func ConfigWatch(file string) chan bool {
	ch := make(chan bool)
	go func() {
		var safename = path.Clean(file) // no trailing slash

		watcher, err := fsnotify.NewWatcher()
		if err != nil {
			log.Fatal(err)
		}
		defer watcher.Close()

		patharray := util.PathSplit(safename) // tokenize the path
		var index = len(patharray)            // starting index
		var current string                    // current "watcher" location
		var deltaDepth int                    // depth delta between watcher and event
		var send = false                      // send event?

		for {
			current = strings.Join(patharray[0:index], "/")
			if current == "" { // the empty string top is the root dir ("/")
				current = "/"
			}
			if global.DEBUG {
				log.Printf("Watching: %v", current) // attempting to watch...
			}
			// initialize in the loop so that we can reset on rm-ed handles
			err = watcher.Add(current)
			if err != nil {
				if err == syscall.ENOENT {
					index-- // usually not found, move up one dir
				} else if err == syscall.ENOSPC {
					// XXX: occasionally: no space left on device,
					// XXX: probably due to lack of inotify watches
					log.Printf("Out of inotify watches for config(%v)", file)
					log.Fatal(err)
				} else {
					log.Printf("Unknown config(%v) error:", file)
					log.Fatal(err)
				}
				index = int(math.Max(1, float64(index)))
				continue
			}

			select {
			case event := <-watcher.Events:
				// the deeper you go, the bigger the deltaDepth is...
				// this is the difference between what we're watching,
				// and the event... doesn't mean we can't watch deeper
				if current == event.Name {
					deltaDepth = 0 // i was watching what i was looking for

				} else if util.HasPathPrefix(event.Name, current) {
					deltaDepth = len(util.PathSplit(current)) - len(util.PathSplit(event.Name)) // -1 or less

				} else if util.HasPathPrefix(current, event.Name) {
					deltaDepth = len(util.PathSplit(event.Name)) - len(util.PathSplit(current)) // +1 or more

				} else {
					// TODO different watchers get each others events!
					// https://github.com/go-fsnotify/fsnotify/issues/95
					// this happened with two values such as:
					// event.Name: /tmp/mgmt/f3 and current: /tmp/mgmt/f2
					continue
				}
				//log.Printf("The delta depth is: %v", deltaDepth)

				// if we have what we wanted, awesome, send an event...
				if event.Name == safename {
					//log.Println("Event!")
					// TODO: filter out some of the events, is Write a sufficient minimum?
					if event.Op&fsnotify.Write == fsnotify.Write {
						send = true
					}

					// file removed, move the watch upwards
					if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) {
						//log.Println("Removal!")
						watcher.Remove(current)
						index--
					}

					// we must be a parent watcher, so descend in
					if deltaDepth < 0 {
						watcher.Remove(current)
						index++
					}

					// if safename starts with event.Name, we're above, and no event should be sent
				} else if util.HasPathPrefix(safename, event.Name) {
					//log.Println("Above!")

					if deltaDepth >= 0 && (event.Op&fsnotify.Remove == fsnotify.Remove) {
						log.Println("Removal!")
						watcher.Remove(current)
						index--
					}

					if deltaDepth < 0 {
						log.Println("Parent!")
						if util.PathPrefixDelta(safename, event.Name) == 1 { // we're the parent dir
							//send = true
						}
						watcher.Remove(current)
						index++
					}

					// if event.Name startswith safename, send event, we're already deeper
				} else if util.HasPathPrefix(event.Name, safename) {
					//log.Println("Event2!")
					//send = true
				}

			case err := <-watcher.Errors:
				log.Printf("error: %v", err)
				log.Fatal(err)

			}

			// do our event sending all together to avoid duplicate msgs
			if send {
				send = false
				ch <- true
			}
		}
		//close(ch)
	}()
	return ch
}