pike.git / lib / modules / Filesystem.pmod / Monitor.pmod / basic.pike

version» Context lines:

pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:3:   //   // Basic filesystem monitor.   //   //   // 2009-07-09 Henrik Grubbström   //   //! Basic filesystem monitor.   //!   //! This module is intended to be used for incremental scanning of   //! a filesystem. + //! + //! 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 + // + #if constant(Public.System.FSEvents.EventStream) + #define HAVE_EVENTSTREAM 1 + #endif +  + #if constant(Public.System.___Inotify) + #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 */ +    //! 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()].
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:170:    MonitorFlags flags,    int max_dir_check_interval,    int file_interval_factor,    int stable_time)   {    int next_poll;    Stdio.Stat st;    int last_change = 0x7fffffff; // Future...    array(string) files;    + #ifdef HAVE_INOTIFY +  int wd; + #endif +     int `<(mixed m) { return next_poll < m; }    int `>(mixed m) { return next_poll > m; }    -  +  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 */ +  } +     //! 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].
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:232:    //!    //! @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)    { +  int t = time(1);    call_callback(global::file_exists, path, st); -  +  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;    } -  +  }       //! 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.
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:374:    {    global::monitor(path, flags, max_dir_check_interval,    file_interval_factor, stable_time);    }       //! Called when the status has changed for an existing file.    protected int(0..1) status_change(Stdio.Stat old_st, Stdio.Stat st,    int orig_flags, int flags)    {    if (st->isdir) { +  int res = 0;    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) { -  +  res = 1;    file = canonic_path(Stdio.append_path(path, file)); -  +  if(filter_file(file)) continue;    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) {    monitor(file, orig_flags | MF_AUTO | MF_HARD,    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) { -  +  res = 1;    file = canonic_path(Stdio.append_path(path, file)); -  +  if(filter_file(file)) continue;    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,
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:425:    // don't need to do anything more here.    } else if (!m2) {    file_deleted(file);    }    if (err) throw(err);    }    if (flags & MF_RECURSE) {    // Check the remaining files in the directory soon.    foreach(((files - new_files) - deleted_files), string file) {    file = canonic_path(Stdio.append_path(path, file)); +  if(filter_file(file)) continue;    Monitor m2 = monitors[file];    if (m2) {    m2->bump(flags); -  +  } 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();    }    }    } -  if (sizeof(new_files) || sizeof(deleted_files)) return 1; +  return res;    } else {    attr_changed(path, st);    return 1;    }    return 0;    }       //! Check for changes.    //!    //! @param flags
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:470:    //!    //! @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)    { -  // werror("Checking monitor %O...\n", this); +     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) {    array(string) files = get_dir(path) || ({});    this_program::files = files;    foreach(files, string file) {    file = canonic_path(Stdio.append_path(path, file)); -  +  if(filter_file(file)) continue;    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) {    monitor(file, orig_flags | MF_AUTO | MF_HARD,    max_dir_check_interval,    file_interval_factor,    stable_time);
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:649:   //!   //! @param file_interval_factor   //! Override of @[default_file_interval_factor].   //!   //! @param stable_time   //! Override of @[default_stable_time].   protected void create(int|void max_dir_check_interval,    int|void file_interval_factor,    int|void stable_time)   { + #if HAVE_EVENTSTREAM +  eventstream->callback_func = eventstream_callback; + #elseif HAVE_INOTIFY +  instance = Inotify._Instance(); +  file = Stdio.File(instance->get_fd(), "r"); +  file->set_nonblocking(); +  file->set_read_callback(inotify_parse); + #endif +     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;    }    if (stable_time > 0) {    this_program::stable_time = stable_time;    }    clear();
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:762:   //! or subtree.   //!   //! @seealso   //! @[release()]   void monitor(string path, MonitorFlags|void flags,    int(0..)|void max_dir_check_interval,    int(0..)|void file_interval_factor,    int(0..)|void stable_time)   {    path = canonic_path(path); +  if(filter_file(path)) return;    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;    m->stable_time = stable_time;    m->next_poll = 0;
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:787:    // For the other cases there's no need to do anything,    // since we can keep the monitor as-is.    } else {    m = monitor_factory(path, flags, max_dir_check_interval,    file_interval_factor, stable_time);    monitors[path] = m;    monitor_queue->push(m);    }   }    + 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; + } +    //! Release a @[path] from monitoring.   //!   //! @param path   //! Path to stop monitoring.   //!   //! @param flags   //! @int   //! @value 0   //! Don't recurse.   //! @value 1
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:969:   }      //! Backend to use.   //!   //! If @expr{0@} (zero) - use the default backend.   protected Pike.Backend backend;      //! Call-out identifier for @[backend_check()] if in   //! nonblocking mode.   //! + //! Set to @expr{1@} when non_blocking mode without call_outs + //! is in use. + //!   //! @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)   {
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:1010:   //! 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()]   protected void backend_check()   { -  co_id = 0; +  if (co_id != 1) co_id = 0;    int t;    mixed err = catch {    t = check(0);    };    set_nonblocking(t);    if (err) throw(err);   }      //! Turn on nonblocking mode.   //!
pike.git/lib/modules/Filesystem.pmod/Monitor.pmod/basic.pike:1045:   //! @[set_blocking()], @[check()].   void set_nonblocking(int|void t)   {    if (co_id) return;    if (zero_type(t)) {    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;    } + #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 */   }      //! 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;    }