d629562011-11-05Martin Nilsson #pike __REAL_VERSION__
fe76e22009-07-13Henrik Grubbström (Grubba) // // Basic filesystem monitor. // // // 2009-07-09 Henrik Grubbström //
90569b2009-07-13Henrik Grubbström (Grubba) //! Basic filesystem monitor. //! //! This module is intended to be used for incremental scanning of //! a filesystem.
15c9822012-06-29Bill Welliver //! //! Supports FSEvents on MacOS X and Inotify on Linux to provide low overhead monitoring; other //! systems use a less efficient polling approach. // // some necessary setup activities for systems that provide filesystem event monitoring //
a6bb452013-04-04Bill Welliver #if constant(System.FSEvents.EventStream)
15c9822012-06-29Bill Welliver #define HAVE_EVENTSTREAM 1 #endif
a6bb452013-04-04Bill Welliver #if constant(System.Inotify)
15c9822012-06-29Bill Welliver #define HAVE_INOTIFY 1 #endif #if HAVE_EVENTSTREAM System.FSEvents.EventStream eventstream = System.FSEvents.EventStream(({}), 3.0, System.FSEvents.kFSEventStreamEventIdSinceNow, System.FSEvents.kFSEventStreamCreateFlagNone); array eventstream_paths = ({}); // This function is called when the FSEvents EventStream detects a change in one of the monitored directories. void eventstream_callback(string path, int flags, int event_id) { if(path[-1] == '/') path = path[0..<1]; if(monitors[path]) { monitors[path]->check(0); } else check(0); } #elseif HAVE_INOTIFY object instance; object file; void inotify_parse(mixed id, string data) { while (sizeof(data)) { array a; mixed err = catch { a = System.Inotify.parse_event(data); }; if (err) { // TODO: might have a partial even struct here which gets completed // by the next call?? maybe add an internal buffer. werror("Could not parse inotify event: %s\n", describe_error(err)); return; } string path; path = a[3]; if(path && monitors[path]) { monitors[path]->check(0); } else { check(0); // no need to look at the others if we're going to do a full scan. return; } data = data[a[4]..]; } } #endif /* HAVE_EVENTSTREAM */
90569b2009-07-13Henrik Grubbström (Grubba) 
fe76e22009-07-13Henrik Grubbström (Grubba) //! The default maximum number of seconds between checks of directories //! in seconds. //! //! This value is multiplied with @[default_file_interval_factor] to //! get the corresponding default maximum number of seconds for files. //! //! The value can be changed by calling @[create()]. //! //! The value can be overridden for individual files or directories //! by calling @[monitor()]. //! //! Overload this constant to change the default. protected constant default_max_dir_check_interval = 60; //! The default factor to multiply @[default_max_dir_check_interval] //! with to get the maximum number of seconds between checks of files. //! //! The value can be changed by calling @[create()]. //! //! The value can be overridden for individual files or directories //! by calling @[monitor()]. //! //! Overload this constant to change the default. protected constant default_file_interval_factor = 5;
075cf22009-07-16Henrik Grubbström (Grubba) //! The default minimum number of seconds without changes for a change //! to be regarded as stable (see @[stable_data_change()].
f3483e2009-07-16Henrik Grubbström (Grubba) protected constant default_stable_time = 5;
fe76e22009-07-13Henrik Grubbström (Grubba)  protected int max_dir_check_interval = default_max_dir_check_interval; protected int file_interval_factor = default_file_interval_factor;
f3483e2009-07-16Henrik Grubbström (Grubba) protected int stable_time = default_stable_time;
fe76e22009-07-13Henrik Grubbström (Grubba)  // Callbacks //! File content changed callback. //! //! @param path //! Path of the file which has had content changed. //! //! This function is called when a change has been detected for a //! monitored file. //! //! Called by @[check()] and @[check_monitor()]. //! //! Overload this to do something useful. void data_changed(string path); //! File attribute changed callback. //! //! @param path //! Path of the file or directory which has changed attributes. //! //! @param st //! Status information for @[path] as obtained by @expr{file_stat(path, 1)@}. //! //! This function is called when a change has been detected for an //! attribute for a monitored file or directory. //! //! Called by @[check()] and @[check_monitor()]. //! //! @note //! If there is a @[data_changed()] callback, it may supersede this //! callback if the file content also has changed. //! //! Overload this to do something useful. void attr_changed(string path, Stdio.Stat st);
249a3f2009-07-13Henrik Grubbström (Grubba) //! File existance callback. //! //! @param path //! Path of the file or directory. //! //! @param st //! Status information for @[path] as obtained by @expr{file_stat(path, 1)@}. //! //! This function is called during initialization for all monitored paths, //! and subpaths for monitored directories. It represents the initial state //! for the monitor. //! //! @note //! For directories, @[file_created()] will be called for the subpaths //! before the call for the directory itself. This can be used to detect //! when the initialization for a directory is finished. //! //! Called by @[check()] and @[check_monitor()] the first time a monitored //! path is checked (and only if it exists). //! //! Overload this to do something useful. void file_exists(string path, Stdio.Stat st);
fe76e22009-07-13Henrik Grubbström (Grubba) //! File creation callback. //! //! @param path //! Path of the new file or directory. //! //! @param st //! Status information for @[path] as obtained by @expr{file_stat(path, 1)@}. //! //! This function is called when either a monitored path has started //! existing, or when a new file or directory has been added to a //! monitored directory. //! //! Called by @[check()] and @[check_monitor()]. //! //! Overload this to do something useful. void file_created(string path, Stdio.Stat st); //! File deletion callback. //! //! @param path //! Path of the new file or directory that has been deleted. //! //! This function is called when either a monitored path has stopped //! to exist, or when a file or directory has been deleted from a //! monitored directory. //! //! Called by @[check()] and @[check_monitor()]. //! //! Overload this to do something useful. void file_deleted(string path); //! Stable change callback. //! //! @param path //! Path of the file or directory that has stopped changing. //!
7ef0192009-07-15Henrik Grubbström (Grubba) //! @param st //! Status information for @[path] as obtained by @expr{file_stat(path, 1)@}. //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! This function is called when previous changes to @[path] are //! considered "stable". //! //! "Stable" in this case means that there have been no detected //! changes for at lease @[stable_time] seconds. //! //! Called by @[check()] and @[check_monitor()]. //! //! Overload this to do something useful.
7ef0192009-07-15Henrik Grubbström (Grubba) void stable_data_change(string path, Stdio.Stat st);
fe76e22009-07-13Henrik Grubbström (Grubba) 
249a3f2009-07-13Henrik Grubbström (Grubba) //! Flags for @[Monitor]s. enum MonitorFlags { MF_RECURSE = 1, MF_AUTO = 2, MF_INITED = 4,
b1f9342010-02-02Henrik Grubbström (Grubba)  MF_HARD = 8,
249a3f2009-07-13Henrik Grubbström (Grubba) };
aa68fa2010-01-28Henrik Grubbström (Grubba) protected constant S_IFMT = 0x7ffff000;
915a1a2009-07-22Henrik Grubbström (Grubba) //! Monitoring information for a single filesystem path. //! //! @seealso //! @[monitor()]
249a3f2009-07-13Henrik Grubbström (Grubba) protected class Monitor(string path, MonitorFlags flags,
fe76e22009-07-13Henrik Grubbström (Grubba)  int max_dir_check_interval,
f3483e2009-07-16Henrik Grubbström (Grubba)  int file_interval_factor, int stable_time)
fe76e22009-07-13Henrik Grubbström (Grubba) { int next_poll; Stdio.Stat st; int last_change = 0x7fffffff; // Future... array(string) files;
15c9822012-06-29Bill Welliver #ifdef HAVE_INOTIFY int wd; #endif
fe76e22009-07-13Henrik Grubbström (Grubba)  int `<(mixed m) { return next_poll < m; } int `>(mixed m) { return next_poll > m; }
15c9822012-06-29Bill Welliver  void create() { #if HAVE_EVENTSTREAM int already_added = 0; foreach(eventstream_paths;;string p) { if(has_prefix(path, p)) already_added = 1; } if(already_added) return; eventstream_paths += ({path}); if(eventstream->is_started()) eventstream->stop(); eventstream->add_path(path); eventstream->start(); #elseif HAVE_INOTIFY wd = instance->add_watch(path, Inotify.IN_MOVED_FROM | Inotify.IN_UNMOUNT | Inotify.IN_MOVED_TO | Inotify.IN_MASK_ADD | Inotify.IN_MOVE_SELF | Inotify.IN_DELETE | Inotify.IN_MOVE | Inotify.IN_MODIFY | Inotify.IN_ATTRIB | Inotify.IN_DELETE_SELF | Inotify.IN_CREATE); #endif } void destroy() { #if HAVE_INOTIFY instance->rm_watch(wd); #endif /* HAVE_INOTIFY */ }
c79e292010-01-28Henrik Grubbström (Grubba)  //! Call a notification callback. //! //! @param cb //! Callback to call or @[UNDEFINED] for no operation. //! //! @param path //! Path to notify on. //! //! @param st //! Stat for the @[path]. protected void call_callback(function(string, Stdio.Stat|void:void) cb, string path, Stdio.Stat|void st) { if (!cb) return; cb(path, st); } //! File attribute or content changed callback. //! //! @param st //! Status information for @[path] as obtained by //! @expr{file_stat(path, 1)@}. //! //! This function is called when a change has been detected for an //! attribute for a monitored file or directory. //! //! Called by @[check()] and @[check_monitor()]. //! //! @note //! If there is a @[data_changed()] callback, it may supersede this //! callback if the file content also has changed. protected void attr_changed(string path, Stdio.Stat st) { if (global::data_changed) { call_callback(global::data_changed, path); } else { call_callback(global::attr_changed, path, st); } } //! File existance callback. //! //! @param st //! Status information for @[path] as obtained by //! @expr{file_stat(path, 1)@}. //! //! This function is called during initialization for all monitored paths, //! and subpaths for monitored directories. It represents the initial state //! for the monitor. //! //! @note //! For directories, @[file_created()] will be called for the subpaths //! before the call for the directory itself. This can be used to detect //! when the initialization for a directory is finished. //! //! Called by @[check()] and @[check_monitor()] the first time a monitored //! path is checked (and only if it exists). protected void file_exists(string path, Stdio.Stat st) {
5b12eb2012-07-04Henrik Grubbström (Grubba)  int t = time(1);
c79e292010-01-28Henrik Grubbström (Grubba)  call_callback(global::file_exists, path, st);
5b12eb2012-07-04Henrik Grubbström (Grubba)  if (st->mtime + (stable_time || global::stable_time) >= t) { // Not stable yet! We guess that the mtime is a // fair indication of when the file last changed. last_change = st->mtime; }
c79e292010-01-28Henrik Grubbström (Grubba)  } //! File creation callback. //! //! @param st //! Status information for @[path] as obtained by //! @expr{file_stat(path, 1)@}. //! //! This function is called when either a monitored path has started //! existing, or when a new file or directory has been added to a //! monitored directory. //! //! Called by @[check()] and @[check_monitor()]. protected void file_created(string path, Stdio.Stat st) { call_callback(global::file_created, path, st); } //! File deletion callback. //! //! @param path //! Path of the new file or directory that has been deleted. //! //! @param old_st //! Stat for the file prior to deletion (if known). Note that //! this argument is not passed along to top level function. //! //! This function is called when either a monitored path has stopped //! to exist, or when a file or directory has been deleted from a //! monitored directory. //! //! Called by @[check()] and @[check_monitor()]. protected void file_deleted(string path, Stdio.Stat|void old_st) { call_callback(global::file_deleted, path); } //! Stable change callback. //! //! @param st //! Status information for @[path] as obtained by //! @expr{file_stat(path, 1)@}. //! //! This function is called when previous changes to @[path] are //! considered "stable". //! //! "Stable" in this case means that there have been no detected //! changes for at lease @[stable_time] seconds. //! //! Called by @[check()] and @[check_monitor()]. protected void stable_data_change(string path, Stdio.Stat st) { call_callback(global::stable_data_change, path, st); }
fe76e22009-07-13Henrik Grubbström (Grubba)  protected string _sprintf(int c) { return sprintf("Monitor(%O, %O, next: %s, st: %O)",
249a3f2009-07-13Henrik Grubbström (Grubba)  path, flags, ctime(next_poll) - "\n", st);
fe76e22009-07-13Henrik Grubbström (Grubba)  }
915a1a2009-07-22Henrik Grubbström (Grubba) 
5812c12010-04-27Henrik Grubbström (Grubba)  //! Bump the monitor to an earlier scan time. //! //! @param seconds //! Number of seconds to bump. Defaults to @expr{30@}. void bump(int|void flags, int|void seconds) { next_poll -= seconds || 30; monitor_queue->adjust(this); if ((flags & MF_RECURSE) && st->isdir && files) { // Bump the files in the directory as well. foreach(files, string file) { file = canonic_path(Stdio.append_path(path, file)); Monitor m2 = monitors[file]; if (m2) { m2->bump(flags, seconds); } } } }
c51c362009-08-05Henrik Grubbström (Grubba)  //! Calculate and set a suitable time for the next poll of this monitor.
915a1a2009-07-22Henrik Grubbström (Grubba)  //! //! @param st //! New stat for the monitor. //! //! This function is called by @[check()] to schedule the //! next check. protected void update(Stdio.Stat st) { int delta = max_dir_check_interval || global::max_dir_check_interval; this_program::st = st; if (!st || !st->isdir) { delta *= file_interval_factor || global::file_interval_factor; } if (!next_poll) { // Attempt to distribute polls evenly at startup. delta = 1 + random(delta); } if (st) {
be796b2010-01-28Henrik Grubbström (Grubba)  int d = 1 + ((time(1) - st->mtime)>>8);
915a1a2009-07-22Henrik Grubbström (Grubba)  if (d < 0) d = max_dir_check_interval || global::max_dir_check_interval; if (d < delta) delta = d;
be796b2010-01-28Henrik Grubbström (Grubba)  d = 1 + ((time(1) - st->ctime)>>8);
915a1a2009-07-22Henrik Grubbström (Grubba)  if (d < 0) d = max_dir_check_interval || global::max_dir_check_interval; if (d < delta) delta = d; }
c51c362009-08-05Henrik Grubbström (Grubba)  if (last_change <= time(1)) { // Time until stable.
067fe62009-08-07Henrik Grubbström (Grubba)  int d = last_change + (stable_time || global::stable_time) - time(1);
c51c362009-08-05Henrik Grubbström (Grubba)  d >>= 1; if (d < 0) d = 1; if (d < delta) delta = d; }
915a1a2009-07-22Henrik Grubbström (Grubba)  next_poll = time(1) + (delta || 1); monitor_queue->adjust(this); }
b1f9342010-02-02Henrik Grubbström (Grubba)  //! Check if this monitor should be removed automatically. void check_for_release(int mask, int flags) { if ((this_program::flags & mask) == flags) { m_delete(monitors, path); release_monitor(this); } }
cafbb92010-02-03Henrik Grubbström (Grubba)  //! Called to create a sub monitor. protected void monitor(string path, int flags, int max_dir_interval, int file_interval_factor, int stable_time) { global::monitor(path, flags, max_dir_check_interval, file_interval_factor, stable_time); }
c79e292010-01-28Henrik Grubbström (Grubba)  //! Called when the status has changed for an existing file. protected int(0..1) status_change(Stdio.Stat old_st, Stdio.Stat st,
7639942010-04-27Henrik Grubbström (Grubba)  int orig_flags, int flags)
c79e292010-01-28Henrik Grubbström (Grubba)  { if (st->isdir) {
2ddd922012-07-04Henrik Grubbström (Grubba)  int res = 0;
c79e292010-01-28Henrik Grubbström (Grubba)  array(string) files = get_dir(path) || ({}); array(string) new_files = files; array(string) deleted_files = ({}); if (this_program::files) { new_files -= this_program::files; deleted_files = this_program::files - files; } this_program::files = files; foreach(new_files, string file) {
2ddd922012-07-04Henrik Grubbström (Grubba)  res = 1;
c79e292010-01-28Henrik Grubbström (Grubba)  file = canonic_path(Stdio.append_path(path, file));
2ddd922012-07-04Henrik Grubbström (Grubba)  if(filter_file(file)) continue;
c79e292010-01-28Henrik Grubbström (Grubba)  Monitor m2 = monitors[file]; mixed err = catch { if (m2) { // We have a separate monitor on the created file. // Let it handle the notification. m2->check(flags); } }; if (this_program::flags & MF_RECURSE) {
b1f9342010-02-02Henrik Grubbström (Grubba)  monitor(file, orig_flags | MF_AUTO | MF_HARD,
c79e292010-01-28Henrik Grubbström (Grubba)  max_dir_check_interval, file_interval_factor, stable_time); monitors[file]->check(); } else if (!m2) { file_created(file, file_stat(file, 1)); } } foreach(deleted_files, string file) {
2ddd922012-07-04Henrik Grubbström (Grubba)  res = 1;
c79e292010-01-28Henrik Grubbström (Grubba)  file = canonic_path(Stdio.append_path(path, file));
2ddd922012-07-04Henrik Grubbström (Grubba)  if(filter_file(file)) continue;
c79e292010-01-28Henrik Grubbström (Grubba)  Monitor m2 = monitors[file]; mixed err = catch { if (m2) { // We have a separate monitor on the deleted file. // Let it handle the notification. m2->check(flags); } }; if (this_program::flags & MF_RECURSE) { // The monitor for the file has probably removed itself, // or the user has done it by hand, in either case we // don't need to do anything more here. } else if (!m2) { file_deleted(file); } if (err) throw(err); } if (flags & MF_RECURSE) {
5812c12010-04-27Henrik Grubbström (Grubba)  // Check the remaining files in the directory soon.
c79e292010-01-28Henrik Grubbström (Grubba)  foreach(((files - new_files) - deleted_files), string file) { file = canonic_path(Stdio.append_path(path, file));
2ddd922012-07-04Henrik Grubbström (Grubba)  if(filter_file(file)) continue;
c79e292010-01-28Henrik Grubbström (Grubba)  Monitor m2 = monitors[file]; if (m2) {
5812c12010-04-27Henrik Grubbström (Grubba)  m2->bump(flags);
2ddd922012-07-04Henrik Grubbström (Grubba)  } else { // Lost update due to race-condition: // // Exist ==> Deleted ==> Exists // // with no update of directory inbetween. // // Create the lost submonitor again. res = 1; monitor(file, orig_flags | MF_AUTO | MF_HARD, max_dir_check_interval, file_interval_factor, stable_time); monitors[file]->check();
c79e292010-01-28Henrik Grubbström (Grubba)  } } }
2ddd922012-07-04Henrik Grubbström (Grubba)  return res;
c79e292010-01-28Henrik Grubbström (Grubba)  } else { attr_changed(path, st); return 1; } return 0; }
915a1a2009-07-22Henrik Grubbström (Grubba)  //! Check for changes. //! //! @param flags //! @int //! @value 0 //! Don't recurse. //! @value 1 //! Check all monitors for the entire subtree rooted in @[m]. //! @endint //! //! This function is called by @[check()] for the @[Monitor]s //! it considers need checking. If it detects any changes an //! appropriate callback will be called. //! //! @returns //! Returns @expr{1@} if a change was detected and @expr{0@} (zero) //! otherwise. //! //! @note //! Any callbacks will be called from the same thread as the one //! calling @[check_monitor()]. //! //! @note //! The return value can not be trusted to return @expr{1@} for all //! detected changes in recursive mode. //! //! @seealso //! @[check()], @[data_changed()], @[attr_changed()], @[file_created()], //! @[file_deleted()], @[stable_data_change()] int(0..1) check(MonitorFlags|void flags) { Stdio.Stat st = file_stat(path, 1); Stdio.Stat old_st = this_program::st; int orig_flags = this_program::flags; this_program::flags |= MF_INITED; update(st); if (!(orig_flags & MF_INITED)) { // Initialize. if (st) { if (st->isdir) {
21e5002009-10-13Henrik Grubbström (Grubba)  array(string) files = get_dir(path) || ({});
915a1a2009-07-22Henrik Grubbström (Grubba)  this_program::files = files; foreach(files, string file) {
4627b62009-10-20Henrik Grubbström (Grubba)  file = canonic_path(Stdio.append_path(path, file));
2ddd922012-07-04Henrik Grubbström (Grubba)  if(filter_file(file)) continue;
915a1a2009-07-22Henrik Grubbström (Grubba)  if (monitors[file]) { // There's already a monitor for the file. // Assume it has already notified about existance. continue; } if (this_program::flags & MF_RECURSE) {
b1f9342010-02-02Henrik Grubbström (Grubba)  monitor(file, orig_flags | MF_AUTO | MF_HARD,
915a1a2009-07-22Henrik Grubbström (Grubba)  max_dir_check_interval, file_interval_factor, stable_time); check_monitor(monitors[file]);
c79e292010-01-28Henrik Grubbström (Grubba)  } else {
915a1a2009-07-22Henrik Grubbström (Grubba)  file_exists(file, file_stat(file, 1)); } } } // Signal file_exists for path as an end marker.
c79e292010-01-28Henrik Grubbström (Grubba)  file_exists(path, st);
915a1a2009-07-22Henrik Grubbström (Grubba)  } return 1; }
aa68fa2010-01-28Henrik Grubbström (Grubba)  if (old_st) { if (!st || ((old_st->mode & S_IFMT) != (st->mode & S_IFMT))) { // File deleted or changed type.
b0a5fc2009-10-19Henrik Grubbström (Grubba)  int delay; // Propagate deletions to any submonitors. if (files) { foreach(files, string file) { file = canonic_path(Stdio.append_path(path, file)); if (monitors[file]) { // Adjust next_poll, so that the monitor will be checked soon. monitors[file]->next_poll = time(1)-1; monitor_queue->adjust(monitors[file]); delay = 1; } }
915a1a2009-07-22Henrik Grubbström (Grubba)  }
b0a5fc2009-10-19Henrik Grubbström (Grubba)  if (delay) { // Delay the notification until the submonitors have notified. st = old_st; next_poll = time(1); monitor_queue->adjust(this); } else {
aa68fa2010-01-28Henrik Grubbström (Grubba)  if (st) { // Avoid race when a file has been replaced with a directory // or vice versa or similar. st = UNDEFINED; // We will catch the new file at the next poll. next_poll = time(1); monitor_queue->adjust(this);
b1f9342010-02-02Henrik Grubbström (Grubba)  } else { // The monitor no longer has a link from its parent directory. this_program::flags &= ~MF_HARD; // Check if we should remove the monitor. check_for_release(MF_AUTO, MF_AUTO);
b0a5fc2009-10-19Henrik Grubbström (Grubba)  }
aa68fa2010-01-28Henrik Grubbström (Grubba) 
c79e292010-01-28Henrik Grubbström (Grubba)  file_deleted(path, old_st);
b0a5fc2009-10-19Henrik Grubbström (Grubba)  return 1;
915a1a2009-07-22Henrik Grubbström (Grubba)  }
aa68fa2010-01-28Henrik Grubbström (Grubba)  return 0;
915a1a2009-07-22Henrik Grubbström (Grubba)  }
aa68fa2010-01-28Henrik Grubbström (Grubba)  } else if (st) { // File created.
915a1a2009-07-22Henrik Grubbström (Grubba)  last_change = time(1);
c79e292010-01-28Henrik Grubbström (Grubba)  file_created(path, st);
4627b62009-10-20Henrik Grubbström (Grubba)  if (st->isdir) { array(string) files = get_dir(path) || ({}); this_program::files = files; foreach(files, string file) { file = canonic_path(Stdio.append_path(path, file)); if (monitors[file]) { // There's already a monitor for the file. // Assume it has already notified about existance. continue; } if (this_program::flags & MF_RECURSE) {
b1f9342010-02-02Henrik Grubbström (Grubba)  monitor(file, orig_flags | MF_AUTO | MF_HARD,
4627b62009-10-20Henrik Grubbström (Grubba)  max_dir_check_interval, file_interval_factor, stable_time); check_monitor(monitors[file]);
c79e292010-01-28Henrik Grubbström (Grubba)  } else {
4627b62009-10-20Henrik Grubbström (Grubba)  file_created(file, file_stat(file, 1)); } } }
915a1a2009-07-22Henrik Grubbström (Grubba)  return 1;
20b62e2010-02-01Henrik Grubbström (Grubba)  } else { return 0;
915a1a2009-07-22Henrik Grubbström (Grubba)  }
dd6a362010-07-14Jonas Wallden  // Note: ctime seems to change unexpectedly when running ImageMagick // on NFS disk so we disable it for the moment [bug 5587]. if ((st->mtime != old_st->mtime) || /* (st->ctime != old_st->ctime) || */
915a1a2009-07-22Henrik Grubbström (Grubba)  (st->size != old_st->size)) { last_change = time(1);
7639942010-04-27Henrik Grubbström (Grubba)  if (status_change(old_st, st, orig_flags, flags)) return 1;
61c4532012-10-24Henrik Grubbström (Grubba)  } else if (last_change < time(1) - (stable_time || global::stable_time)) {
915a1a2009-07-22Henrik Grubbström (Grubba)  last_change = 0x7fffffff;
c79e292010-01-28Henrik Grubbström (Grubba)  stable_data_change(path, st);
915a1a2009-07-22Henrik Grubbström (Grubba)  return 1;
61c4532012-10-24Henrik Grubbström (Grubba)  } else if (st->isdir && status_change(old_st, st, orig_flags, flags)) { // Directory not stable yet. last_change = time(1); return 1;
915a1a2009-07-22Henrik Grubbström (Grubba)  } return 0; }
fe76e22009-07-13Henrik Grubbström (Grubba) }
d5ecf22009-07-22Henrik Grubbström (Grubba) //! Canonicalize a path. //! //! @param path //! Path to canonicalize. //! //! @returns //! The default implementation returns @expr{combine_path(path, ".")@}, //! i.e. no trailing slashes. protected string canonic_path(string path) { return combine_path(path, "."); }
fe76e22009-07-13Henrik Grubbström (Grubba) //! Mapping from monitored path to corresponding @[Monitor].
3d59782009-07-15Henrik Grubbström (Grubba) //!
d5ecf22009-07-22Henrik Grubbström (Grubba) //! The paths are normalized to @expr{canonic_path(path)@},
3d59782009-07-15Henrik Grubbström (Grubba) //! //! @note //! All filesystems are handled as if case-sensitive. This should //! not be a problem for case-insensitive filesystems as long as //! case is maintained.
fe76e22009-07-13Henrik Grubbström (Grubba) protected mapping(string:Monitor) monitors = ([]); //! Heap containing all active @[Monitor]s. //! //! The heap is sorted on @[Monitor()->next_poll]. protected ADT.Heap monitor_queue = ADT.Heap(); //! Create a new monitor. //! //! @param max_dir_check_interval //! Override of @[default_max_dir_check_interval]. //! //! @param file_interval_factor //! Override of @[default_file_interval_factor].
f3483e2009-07-16Henrik Grubbström (Grubba) //! //! @param stable_time //! Override of @[default_stable_time].
fe76e22009-07-13Henrik Grubbström (Grubba) protected void create(int|void max_dir_check_interval,
f3483e2009-07-16Henrik Grubbström (Grubba)  int|void file_interval_factor, int|void stable_time)
fe76e22009-07-13Henrik Grubbström (Grubba) {
15c9822012-06-29Bill Welliver #if HAVE_EVENTSTREAM eventstream->callback_func = eventstream_callback; #elseif HAVE_INOTIFY instance = Inotify._Instance();
4d9db42013-02-24Arne Goedeke  file = Stdio.File(); file->assign(instance->fd());
15c9822012-06-29Bill Welliver  file->set_nonblocking(); file->set_read_callback(inotify_parse); #endif
fe76e22009-07-13Henrik Grubbström (Grubba)  if (max_dir_check_interval > 0) { this_program::max_dir_check_interval = max_dir_check_interval; } if (file_interval_factor > 0) { this_program::file_interval_factor = file_interval_factor; }
f3483e2009-07-16Henrik Grubbström (Grubba)  if (stable_time > 0) { this_program::stable_time = stable_time; }
fe76e22009-07-13Henrik Grubbström (Grubba)  clear(); } //! Clear the set of monitored files and directories.
915a1a2009-07-22Henrik Grubbström (Grubba) //! //! @note //! Due to circular datastructures, it's recomended //! to call this function prior to discarding the object.
fe76e22009-07-13Henrik Grubbström (Grubba) void clear() { monitors = ([]); monitor_queue = ADT.Heap(); } //! Calculate a suitable time for the next poll of this monitor.
3d59782009-07-15Henrik Grubbström (Grubba) //! //! @param m //! Monitor to update. //! //! @param st //! New stat for the monitor. //! //! This function is called by @[check_monitor()] to schedule the //! next check.
fe76e22009-07-13Henrik Grubbström (Grubba) protected void update_monitor(Monitor m, Stdio.Stat st) {
915a1a2009-07-22Henrik Grubbström (Grubba)  m->update(st);
fe76e22009-07-13Henrik Grubbström (Grubba) } //! Release a single @[Monitor] from monitoring. //! //! @seealso //! @[release()] protected void release_monitor(Monitor m) { m->next_poll = -1000; monitor_queue->adjust(m); while (monitor_queue->peek() < 0) {
f6976e2009-08-11Henrik Grubbström (Grubba) #if __VERSION__ < 7.8 monitor_queue->top(); #else
fe76e22009-07-13Henrik Grubbström (Grubba)  monitor_queue->pop();
f6976e2009-08-11Henrik Grubbström (Grubba) #endif
fe76e22009-07-13Henrik Grubbström (Grubba)  } }
915a1a2009-07-22Henrik Grubbström (Grubba) //! Create a new @[Monitor] for a @[path]. //! //! This function is called by @[monitor()] to create a new @[Monitor] //! object. //! //! The default implementation just calls @[Monitor()] with the same //! arguments. //! //! @seealso //! @[monitor()] protected Monitor monitor_factory(string path, MonitorFlags|void flags, int(0..)|void max_dir_check_interval, int(0..)|void file_interval_factor, int(0..)|void stable_time) { return Monitor(path, flags, max_dir_check_interval, file_interval_factor, stable_time); }
fe76e22009-07-13Henrik Grubbström (Grubba) //! Register a @[path] for monitoring. //! //! @param path //! Path to monitor. //!
249a3f2009-07-13Henrik Grubbström (Grubba) //! @param flags
fe76e22009-07-13Henrik Grubbström (Grubba) //! @int //! @value 0 //! Don't recurse. //! @value 1 //! Monitor the entire subtree, and any directories //! or files that may appear later.
249a3f2009-07-13Henrik Grubbström (Grubba) //! @value 3 //! Monitor the entire subtree, and any directories //! or files that may appear later. Remove the monitor //! automatically when @[path] is deleted.
fe76e22009-07-13Henrik Grubbström (Grubba) //! @endint //! //! @param max_dir_check_interval //! Override of @[default_max_dir_check_interval] for this path //! or subtree. //! //! @param file_interval_factor //! Override of @[default_file_interval_factor] for this path //! or subtree. //!
f3483e2009-07-16Henrik Grubbström (Grubba) //! @param stable_time //! Override of @[default_stable_time] for this path //! or subtree. //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! @seealso //! @[release()]
249a3f2009-07-13Henrik Grubbström (Grubba) void monitor(string path, MonitorFlags|void flags,
fe76e22009-07-13Henrik Grubbström (Grubba)  int(0..)|void max_dir_check_interval,
f3483e2009-07-16Henrik Grubbström (Grubba)  int(0..)|void file_interval_factor, int(0..)|void stable_time)
fe76e22009-07-13Henrik Grubbström (Grubba) {
d5ecf22009-07-22Henrik Grubbström (Grubba)  path = canonic_path(path);
15c9822012-06-29Bill Welliver  if(filter_file(path)) return;
52367a2009-07-14Henrik Grubbström (Grubba)  Monitor m = monitors[path]; if (m) { if (!(flags & MF_AUTO)) { // The new monitor is added by hand. // Adjust the monitor. m->flags = flags; m->max_dir_check_interval = max_dir_check_interval; m->file_interval_factor = file_interval_factor;
f3483e2009-07-16Henrik Grubbström (Grubba)  m->stable_time = stable_time;
52367a2009-07-14Henrik Grubbström (Grubba)  m->next_poll = 0; monitor_queue->adjust(m); }
b1f9342010-02-02Henrik Grubbström (Grubba)  if (flags & MF_HARD) { m->flags |= MF_HARD; }
52367a2009-07-14Henrik Grubbström (Grubba)  // For the other cases there's no need to do anything, // since we can keep the monitor as-is. } else {
915a1a2009-07-22Henrik Grubbström (Grubba)  m = monitor_factory(path, flags, max_dir_check_interval, file_interval_factor, stable_time);
52367a2009-07-14Henrik Grubbström (Grubba)  monitors[path] = m; monitor_queue->push(m); }
fe76e22009-07-13Henrik Grubbström (Grubba) }
15c9822012-06-29Bill Welliver int filter_file(string path) { array x = path/"/"; foreach(x;; string pc) if(pc && strlen(pc) && pc[0]=='.') {/* werror("skipping %O\n", path); */ return 1; } return 0; }
fe76e22009-07-13Henrik Grubbström (Grubba) //! Release a @[path] from monitoring. //! //! @param path //! Path to stop monitoring. //!
249a3f2009-07-13Henrik Grubbström (Grubba) //! @param flags
fe76e22009-07-13Henrik Grubbström (Grubba) //! @int //! @value 0 //! Don't recurse. //! @value 1 //! Release the entire subtree.
249a3f2009-07-13Henrik Grubbström (Grubba) //! @value 3 //! Release the entire subtree, but only those paths that were //! added automatically by a recursive monitor.
fe76e22009-07-13Henrik Grubbström (Grubba) //! @endint //! //! @seealso //! @[monitor()]
249a3f2009-07-13Henrik Grubbström (Grubba) void release(string path, MonitorFlags|void flags)
fe76e22009-07-13Henrik Grubbström (Grubba) {
d5ecf22009-07-22Henrik Grubbström (Grubba)  path = canonic_path(path);
fe76e22009-07-13Henrik Grubbström (Grubba)  Monitor m = m_delete(monitors, path); if (m) { release_monitor(m); }
249a3f2009-07-13Henrik Grubbström (Grubba)  if (flags && m->st && m->st->isdir) {
d5ecf22009-07-22Henrik Grubbström (Grubba)  if (!sizeof(path) || path[-1] != '/') { path += "/"; }
fe76e22009-07-13Henrik Grubbström (Grubba)  foreach(monitors; string mpath; m) {
b1f9342010-02-02Henrik Grubbström (Grubba)  if (has_prefix(mpath, path)) { m->check_for_release(flags, flags);
fe76e22009-07-13Henrik Grubbström (Grubba)  } } } }
9fc4072009-09-21Henrik Grubbström (Grubba) //! Check whether a path is monitored or not.
136b3b2009-09-21Henrik Grubbström (Grubba) //! //! @param path
9fc4072009-09-21Henrik Grubbström (Grubba) //! Path to check. //! //! @returns //! Returns @expr{1@} if there is a monitor on @[path], //! and @expr{0@} (zero) otherwise. //! //! @seealso //! @[monitor()], @[release()]
136b3b2009-09-21Henrik Grubbström (Grubba) int(0..1) is_monitored(string path) { return !!monitors[canonic_path(path)]; }
fe76e22009-07-13Henrik Grubbström (Grubba) //! Check a single @[Monitor] for changes. //! //! @param m //! @[Monitor] to check. //!
80f66e2009-07-14Henrik Grubbström (Grubba) //! @param flags //! @int //! @value 0 //! Don't recurse. //! @value 1 //! Check all monitors for the entire subtree rooted in @[m]. //! @endint //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! This function is called by @[check()] for the @[Monitor]s //! it considers need checking. If it detects any changes an //! appropriate callback will be called. //! //! @returns //! Returns @expr{1@} if a change was detected and @expr{0@} (zero) //! otherwise. //! //! @note //! Any callbacks will be called from the same thread as the one //! calling @[check_monitor()]. //!
80f66e2009-07-14Henrik Grubbström (Grubba) //! @note //! The return value can not be trusted to return @expr{1@} for all //! detected changes in recursive mode. //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! @seealso //! @[check()], @[data_changed()], @[attr_changed()], @[file_created()], //! @[file_deleted()], @[stable_data_change()]
80f66e2009-07-14Henrik Grubbström (Grubba) protected int(0..1) check_monitor(Monitor m, MonitorFlags|void flags)
fe76e22009-07-13Henrik Grubbström (Grubba) {
915a1a2009-07-22Henrik Grubbström (Grubba)  return m->check(flags);
fe76e22009-07-13Henrik Grubbström (Grubba) } //! Check for changes. //! //! @param max_wait
fb4faa2009-08-04Henrik Grubbström (Grubba) //! Maximum time in seconds to wait for changes. @expr{-1@}
fe76e22009-07-13Henrik Grubbström (Grubba) //! for infinite wait. //!
010d1c2009-08-06Henrik Grubbström (Grubba) //! @param max_cnt //! Maximum number of paths to check in this call. @expr{0@} //! (zero) for unlimited. //!
1819432009-10-12Henrik Grubbström (Grubba) //! @param ret_stats //! Optional mapping that will be filled with statistics (see below). //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! A suitable subset of the monitored files will be checked //! for changes. //! //! @returns //! The function returns when either a change has been detected
fb4faa2009-08-04Henrik Grubbström (Grubba) //! or when @[max_wait] has expired. The returned value indicates //! the number of seconds until the next call of @[check()].
fe76e22009-07-13Henrik Grubbström (Grubba) //!
1819432009-10-12Henrik Grubbström (Grubba) //! If @[ret_stats] has been provided, it will be filled with //! the following entries: //! @mapping //! @member int "num_monitors" //! The total number of active monitors when the scan completed. //! @member int "scanned_monitors" //! The number of monitors that were scanned for updates during the call. //! @member int "updated_monitors" //! The number of monitors that were updated during the call. //! @member int "idle_time" //! The number of seconds that the call slept. //! @endmapping //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! @note //! Any callbacks will be called from the same thread as the one //! calling @[check()]. //! //! @seealso //! @[monitor()]
1819432009-10-12Henrik Grubbström (Grubba) int check(int|void max_wait, int|void max_cnt, mapping(string:int)|void ret_stats)
fe76e22009-07-13Henrik Grubbström (Grubba) {
1819432009-10-12Henrik Grubbström (Grubba)  int scan_cnt = max_cnt; int scan_wait = max_wait;
fe76e22009-07-13Henrik Grubbström (Grubba)  while(1) {
fb4faa2009-08-04Henrik Grubbström (Grubba)  int ret = max_dir_check_interval;
fe76e22009-07-13Henrik Grubbström (Grubba)  int cnt; int t = time();
3d59782009-07-15Henrik Grubbström (Grubba)  if (sizeof(monitors)) {
21e5002009-10-13Henrik Grubbström (Grubba)  Monitor m; while ((m = monitor_queue->peek()) && (m->next_poll <= t)) {
3d59782009-07-15Henrik Grubbström (Grubba)  cnt += check_monitor(m);
21e5002009-10-13Henrik Grubbström (Grubba)  if (!(--scan_cnt)) { m = monitor_queue->peek(); break; }
3d59782009-07-15Henrik Grubbström (Grubba)  }
fb4faa2009-08-04Henrik Grubbström (Grubba)  if (m) { ret = m->next_poll - t; if (ret <= 0) ret = 1;
010d1c2009-08-06Henrik Grubbström (Grubba)  } else {
1819432009-10-12Henrik Grubbström (Grubba)  scan_cnt--; } } if (cnt || !scan_wait || !scan_cnt) { if (ret_stats) { ret_stats->num_monitors = sizeof(monitors); ret_stats->scanned_monitors = max_cnt - scan_cnt; ret_stats->updated_monitors = cnt; ret_stats->idle_time = max_wait - scan_wait;
fb4faa2009-08-04Henrik Grubbström (Grubba)  }
1819432009-10-12Henrik Grubbström (Grubba)  return ret;
fb4faa2009-08-04Henrik Grubbström (Grubba)  }
1819432009-10-12Henrik Grubbström (Grubba)  if (ret < scan_wait) { scan_wait -= ret;
fb4faa2009-08-04Henrik Grubbström (Grubba)  sleep(ret); } else {
1819432009-10-12Henrik Grubbström (Grubba)  if (scan_wait > 0) scan_wait--;
fb4faa2009-08-04Henrik Grubbström (Grubba)  sleep(1);
fe76e22009-07-13Henrik Grubbström (Grubba)  } } } //! Backend to use.
90569b2009-07-13Henrik Grubbström (Grubba) //! //! If @expr{0@} (zero) - use the default backend.
fe76e22009-07-13Henrik Grubbström (Grubba) protected Pike.Backend backend; //! Call-out identifier for @[backend_check()] if in //! nonblocking mode. //!
8431122012-07-04Henrik Grubbström (Grubba) //! Set to @expr{1@} when non_blocking mode without call_outs //! is in use. //!
fe76e22009-07-13Henrik Grubbström (Grubba) //! @seealso //! @[set_nonblocking()], @[set_blocking()] protected mixed co_id; //! Change backend. //! //! @param backend //! Backend to use. @expr{0@} (zero) for the default backend. void set_backend(Pike.Backend|void backend) { int was_nonblocking = !!co_id; set_blocking(); this_program::backend = backend; if (was_nonblocking) { set_nonblocking(); } }
90569b2009-07-13Henrik Grubbström (Grubba) //! Turn off nonblocking mode. //! //! @seealso //! @[set_nonblocking()]
fe76e22009-07-13Henrik Grubbström (Grubba) void set_blocking() { if (co_id) { if (backend) backend->remove_call_out(co_id); else remove_call_out(co_id); co_id = 0; } }
90569b2009-07-13Henrik Grubbström (Grubba) //! Backend check callback function. //! //! This function is intended to be called from a backend, //! and performs a @[check()] followed by rescheduling //! itself via a call to @[set_nonblocking()]. //! //! @seealso //! @[check()], @[set_nonblocking()]
fe76e22009-07-13Henrik Grubbström (Grubba) protected void backend_check() {
8431122012-07-04Henrik Grubbström (Grubba)  if (co_id != 1) co_id = 0;
fb4faa2009-08-04Henrik Grubbström (Grubba)  int t;
fe76e22009-07-13Henrik Grubbström (Grubba)  mixed err = catch {
fb4faa2009-08-04Henrik Grubbström (Grubba)  t = check(0);
fe76e22009-07-13Henrik Grubbström (Grubba)  };
fb4faa2009-08-04Henrik Grubbström (Grubba)  set_nonblocking(t);
fe76e22009-07-13Henrik Grubbström (Grubba)  if (err) throw(err); }
90569b2009-07-13Henrik Grubbström (Grubba) //! Turn on nonblocking mode. //!
fb4faa2009-08-04Henrik Grubbström (Grubba) //! @param t //! Suggested time in seconds until next call of @[check()]. //!
90569b2009-07-13Henrik Grubbström (Grubba) //! Register suitable callbacks with the backend to automatically //! call @[check()]. //! //! @[check()] and thus all the callbacks will be called from the //! backend thread. //!
fb4faa2009-08-04Henrik Grubbström (Grubba) //! @note //! If nonblocking mode is already active, this function will //! be a noop. //!
90569b2009-07-13Henrik Grubbström (Grubba) //! @seealso //! @[set_blocking()], @[check()].
fb4faa2009-08-04Henrik Grubbström (Grubba) void set_nonblocking(int|void t)
fe76e22009-07-13Henrik Grubbström (Grubba) { if (co_id) return;
cebf392009-10-22Henrik Grubbström (Grubba)  if (zero_type(t)) {
fb4faa2009-08-04Henrik Grubbström (Grubba)  Monitor m = monitor_queue->peek(); t = (m && m->next_poll - time(1)) || max_dir_check_interval; if (t > max_dir_check_interval) t = max_dir_check_interval; if (t < 0) t = 0; }
8431122012-07-04Henrik Grubbström (Grubba) #if HAVE_EVENTSTREAM // If we are using FSEvents, we don't need any call_outs. co_id = 1; #elseif HAVE_INOTIFY // If we are using Inotify, we don't need any call_outs. co_id = 1; #else if (backend) co_id = backend->call_out(backend_check, t); else co_id = call_out(backend_check, t); #endif /* HAVE_EVENTSTREAM */
fe76e22009-07-13Henrik Grubbström (Grubba) }
d951102009-07-17Henrik Grubbström (Grubba)  //! Set the @[default_max_dir_check_interval]. void set_max_dir_check_interval(int max_dir_check_interval) { if (max_dir_check_interval > 0) { this_program::max_dir_check_interval = max_dir_check_interval; } else { this_program::max_dir_check_interval = default_max_dir_check_interval; } } //! Set the @[default_file_interval_factor]. void set_file_interval_factor(int file_interval_factor) { if (file_interval_factor > 0) { this_program::file_interval_factor = file_interval_factor; } else { this_program::file_interval_factor = default_file_interval_factor; } }