예제 #1
0
bool wxFsEventsFileSystemWatcher::RemoveTree(const wxFileName& path)
{
    wxString canonical = GetCanonicalPath(path);
    if ( canonical.empty() )
    {
        return false;
    }

    // Remove any kqueue watches created with Add()
    // RemoveTree() should remove all watches no matter
    // if they are tree watches or single directory watches.
    wxArrayString dirsWatched;
    wxKqueueFileSystemWatcher::GetWatchedPaths(&dirsWatched);
    for ( size_t i = 0; i < dirsWatched.size(); i++ )
    {
        if (dirsWatched[i].Find(canonical) == 0)
        {
            wxKqueueFileSystemWatcher::Remove(dirsWatched[i]);
        }
    }

    FSEventStreamRefMap::iterator it = m_streams.find(canonical);
    bool removed = false;
    if ( it != m_streams.end() )
    {
        FSEventStreamStop(it->second);
        FSEventStreamInvalidate(it->second);
        FSEventStreamRelease(it->second);
        m_streams.erase(it);
        removed = true;
    }
    return removed;
}
예제 #2
0
void event_cb(ConstFSEventStreamRef streamRef,
              void *ctx,
              size_t count,
              void *paths,
              const FSEventStreamEventFlags flags[],
              const FSEventStreamEventId ids[]) {

    file_paths_t *file_paths = (file_paths_t *)ctx;
    size_t i;
    size_t ignored_paths = 0;

    for (i = 0; i < count; i++) {
        char *path = ((char **)paths)[i];
        /* flags are unsigned long, IDs are uint64_t */
        printf("Change %llu in %s, flags %lu\n", ids[i], path, (long)flags[i]);
        size_t j;
        for (j = 0; j < file_paths->len; j++) {
            char *file_path = file_paths->paths[j];
            printf("%s %s\n", file_path, path);
            if (strcmp(file_path, path) == 0) {
                printf("File %s changed.\n", file_path);
                exit(0);
            }
        }
    }
    if (count > ignored_paths) {
        /* OS X occasionally leaks event streams. Manually stop the stream just to make sure. */
        FSEventStreamStop((FSEventStreamRef)streamRef);
        exit(0);
    }
}
static void stopFSStream(FSEventStreamRef stream)
{
    if (stream) {
        FSEventStreamStop(stream);
        FSEventStreamInvalidate(stream);
    }
}
예제 #4
0
static VALUE t_stop(VALUE self) {
  FSEventStreamStop(stream);
  FSEventStreamInvalidate(stream);
  FSEventStreamRelease(stream);
  CFRunLoopStop(CFRunLoopGetCurrent());
  return self;
}
예제 #5
0
int main(int argc, const char *argv[])
{
  /*
   * a subprocess will initially inherit the process group of its parent. the
   * process group may have a control terminal associated with it, which would
   * be the first tty device opened by the group leader. typically the group
   * leader is your shell and the control terminal is your login device. a
   * subset of signals triggered on the control terminal are sent to all members
   * of the process group, in large part to facilitate sane and consistent
   * cleanup (ex: control terminal was closed).
   *
   * so why the overly descriptive lecture style comment?
   *   1. SIGINT and SIGQUIT are among the signals with this behavior
   *   2. a number of applications gank the above for their own use
   *   3. ruby's insanely useful "guard" is one of these applications
   *   4. despite having some level of understanding of POSIX signals and a few
   *      of the scenarios that might cause problems, i learned this one only
   *      after reading ruby 1.9's process.c
   *   5. if left completely undocumented, even slightly obscure bugfixes
   *      may be removed as cruft by a future maintainer
   *
   * hindsight is 20/20 addition: if you're single-threaded and blocking on IO
   * with a subprocess, then handlers for deferrable signals might not get run
   * when you expect them to. In the case of Ruby 1.8, that means making use of
   * IO::select, which will preserve correct signal handling behavior.
   */
  if (setpgid(0,0) < 0) {
    fprintf(stderr, "Unable to set new process group.\n");
    return 1;
  }

  parse_cli_settings(argc, argv);

  FSEventStreamContext context = {0, NULL, NULL, NULL, NULL};
  FSEventStreamRef stream;
  stream = FSEventStreamCreate(kCFAllocatorDefault,
                               (FSEventStreamCallback)&callback,
                               &context,
                               config.paths,
                               config.sinceWhen,
                               config.latency,
                               config.flags);

#ifdef DEBUG
  FSEventStreamShow(stream);
  fprintf(stderr, "\n");
  fflush(stderr);
#endif

  FSEventStreamScheduleWithRunLoop(stream,
                                   CFRunLoopGetCurrent(),
                                   kCFRunLoopDefaultMode);
  FSEventStreamStart(stream);
  CFRunLoopRun();
  FSEventStreamFlushSync(stream);
  FSEventStreamStop(stream);

  return 0;
}
예제 #6
0
static int handle_rm_repo (SeafWTMonitorPriv *priv, gpointer handle)
{
    FSEventStreamRef stream = (FSEventStreamRef)handle;
    FSEventStreamStop (stream);
    FSEventStreamInvalidate (stream);
    FSEventStreamRelease (stream);
    return 0;
}
예제 #7
0
void FileSystem::deinitializeInternalApple() {
    for (auto d : _directories) {
        DirectoryHandle* dh = d.second;
        FSEventStreamStop(dh->_eventStream);
        FSEventStreamInvalidate(dh->_eventStream);
        FSEventStreamRelease(dh->_eventStream);
        delete dh;
    }
}
예제 #8
0
fse_stream::~fse_stream() {
  if (stream) {
    FSEventStreamStop(stream);
    FSEventStreamInvalidate(stream);
    FSEventStreamRelease(stream);
  }
  if (uuid) {
    CFRelease(uuid);
  }
}
AppleReloadManager::~AppleReloadManager()
{
   //Unregister from file system
   FSEventStreamStop(stream);
   FSEventStreamUnscheduleFromRunLoop(stream,CFRunLoopGetCurrent(),kCFRunLoopDefaultMode);
   FSEventStreamInvalidate(stream);
   FSEventStreamRelease(stream);

   //Destory the lock
   pthread_mutex_destroy(&AppleReloadManager::reloadMutex);
}
예제 #10
0
MacFileSystemChangeNotifier::~MacFileSystemChangeNotifier()
{
    for( U32 i = 0, num = mEvents.size(); i < num; ++ i )
    {
        FSEventStreamStop( mEvents[ i ]->mStream );
        FSEventStreamInvalidate( mEvents[ i ]->mStream );
        FSEventStreamRelease( mEvents[ i ]->mStream );

        SAFE_DELETE( mEvents[ i ] );
    }
}
예제 #11
0
void DeleteEventStream()
{
  if ( g_Stream != NULL )
  {
    FSEventStreamStop(g_Stream);
    FSEventStreamInvalidate(g_Stream);
    FSEventStreamRelease(g_Stream);

    g_Stream = NULL;
  }
}
예제 #12
0
static PyObject* pyfsevents_unschedule(PyObject* self, PyObject* stream) {
    PyObject* value = PyDict_GetItem(streams, stream);
    PyDict_DelItem(streams, stream);
    FSEventStreamRef fsstream = PyCObject_AsVoidPtr(value);

    FSEventStreamStop(fsstream);
    FSEventStreamInvalidate(fsstream);
    FSEventStreamRelease(fsstream);

    Py_INCREF(Py_None);
    return Py_None;
}
VError XMacFileSystemNotification::StopWatchingForChanges( VFileSystemNotifier::VChangeData *inChangeData, bool inIsLastOne)
{	
	// First, we need to find the folder's path so that we can locate our entry
	// in the watch list

	XMacChangeData *data = dynamic_cast<XMacChangeData*>( inChangeData);

	// We are watching this, so we need to stop that
	FSEventStreamStop( data->fStreamRef );
	FSEventStreamInvalidate( data->fStreamRef );
	
	return VE_OK;
}
예제 #14
0
  void fsevents_monitor::on_stop()
  {
    lock_guard<mutex> run_loop_lock(run_mutex);
    if (!run_loop) throw libfsw_exception(_("run loop is null"));

    FSW_ELOG(_("Stopping event stream...\n"));
    FSEventStreamStop(stream);
    stream = nullptr;

    FSW_ELOG(_("Stopping run loop...\n"));
    CFRunLoopStop(run_loop);
    run_loop = nullptr;
  }
void FDirectoryWatchRequestMac::Shutdown( void )
{
	if( bRunning )
	{
		check(EventStream);

		FSEventStreamStop(EventStream);
		FSEventStreamUnscheduleFromRunLoop(EventStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
		FSEventStreamInvalidate(EventStream);
		FSEventStreamRelease(EventStream);

		bRunning = false;
	}
}
예제 #16
0
static int
handle_rm_repo (SeafWTMonitor *monitor, const char *repo_id, gpointer handle)
{
    SeafWTMonitorPriv *priv = monitor->priv;
    FSEventStreamRef stream = (FSEventStreamRef)handle;
    FSEventStreamStop (stream);
    FSEventStreamInvalidate (stream);
    FSEventStreamRelease (stream);

    pthread_mutex_lock (&priv->hash_lock);
    g_hash_table_remove (priv->handle_hash, repo_id);
    g_hash_table_remove (priv->info_hash, (gpointer)(long)stream);
    pthread_mutex_unlock (&priv->hash_lock);
    return 0;
}
예제 #17
0
파일: eyed.c 프로젝트: rsms/eye
int
main(int argc, const char * argv[])
{
  int result = 0;
  FSEventStreamRef streamRef;
  Boolean startedOK;
  int flush_seconds = 3600; // When to force-flush any queued events
  
  if(argc < 2 || strcasecmp(argv[1], "--help") == 0) {
    fprintf(stderr, "usage: %s path ...\n", argv[0]);
    exit(1);
  }
  
  const char **paths = &argv[1];
  streamRef = my_FSEventStreamCreate(paths, argc-1);
  
  FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
  
  startedOK = FSEventStreamStart(streamRef);
  if (!startedOK) {
    log_error("FSEventStreamStart(streamRef) failed");
    goto out;
  }
  
  if (flush_seconds >= 0) {
    log_debug("CFAbsoluteTimeGetCurrent() => %.3f", CFAbsoluteTimeGetCurrent());
    CFAllocatorRef allocator = kCFAllocatorDefault;
    CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent() + /*settings->*/flush_seconds;
    CFTimeInterval interval = /*settings->*/flush_seconds;
    CFOptionFlags flags = 0;
    CFIndex order = 0;
    CFRunLoopTimerCallBack callback = (CFRunLoopTimerCallBack)timer_callback;
    CFRunLoopTimerContext context = { 0, streamRef, NULL, NULL, NULL };
    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(allocator, fireDate, interval, flags, order, callback, &context);
    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
  }
  
  // Run
  CFRunLoopRun();
  
  // Stop / Invalidate / Release
  FSEventStreamStop(streamRef);
out:
  FSEventStreamInvalidate(streamRef);
  FSEventStreamRelease(streamRef);
  
  return result;
}
예제 #18
0
bool wxFsEventsFileSystemWatcher::RemoveAll()
{
    // remove all watches created with Add()
    bool ret = wxKqueueFileSystemWatcher::RemoveAll();
    FSEventStreamRefMap::iterator it = m_streams.begin();
    while ( it != m_streams.end() )
    {
        FSEventStreamStop(it->second);
        FSEventStreamInvalidate(it->second);
        FSEventStreamRelease(it->second);
        it++;
        ret |= true;
    }
    m_streams.clear();
    return ret;
}
예제 #19
0
  fsevents_monitor::~fsevents_monitor()
  {
    if (stream)
    {
      FSW_ELOG(_("Stopping event stream...\n"));
      FSEventStreamStop(stream);

      FSW_ELOG(_("Invalidating event stream...\n"));
      FSEventStreamInvalidate(stream);

      FSW_ELOG(_("Releasing event stream...\n"));
      FSEventStreamRelease(stream);
    }

    stream = nullptr;
  }
예제 #20
0
void FSEventsEventPublisher::stop() {
  // Stop the stream.
  if (stream_ != nullptr) {
    FSEventStreamStop(stream_);
    stream_started_ = false;
    FSEventStreamUnscheduleFromRunLoop(
        stream_, run_loop_, kCFRunLoopDefaultMode);
    FSEventStreamInvalidate(stream_);
    FSEventStreamRelease(stream_);
    stream_ = nullptr;
  }

  // Stop the run loop.
  if (run_loop_ != nullptr) {
    CFRunLoopStop(run_loop_);
  }
}
예제 #21
0
void start_watches()
{
    CFStringRef watch_paths[100];
    int         path_count = 0;
    int         i;

    for (i = 0; i < 100; i += 1) {
        if (NULL != FSEVENTS_GLOBAL(watches)[i].path) {
            watch_paths[i] = CFStringCreateWithCString(NULL, FSEVENTS_GLOBAL(watches)[i].path, kCFStringEncodingUTF8);
            path_count++;
        }
    }

    void *callback_info = NULL; // could put stream-specific data here.

    CFArrayRef       watch_path_array = CFArrayCreate(NULL, (void *) watch_paths, path_count, NULL);
    CFAbsoluteTime   latency          = .75; /* Latency in seconds */
    CFRunLoopRef     run_loop         = CFRunLoopGetMain();
    FSEventStreamRef stream;

    /* Create the stream, passing in a callback */
    stream = FSEventStreamCreate(
        NULL,
        (FSEventStreamCallback)&handle_events,
        callback_info,
        watch_path_array,
        kFSEventStreamEventIdSinceNow,
        latency,
        kFSEventStreamCreateFlagNone
    );

    FSEventStreamScheduleWithRunLoop(
        stream,
        run_loop,
        kCFRunLoopDefaultMode
    );

    FSEventStreamStart(stream);
    CFRunLoopRun();
    FSEventStreamFlushSync(stream);
    FSEventStreamStop(stream);
}
예제 #22
0
void FSEventsEventPublisher::stop() {
  // Stop the stream.
  WriteLock lock(mutex_);
  if (run_loop_ == nullptr) {
    // No need to stop if there is not run loop.
    return;
  }

  if (stream_ != nullptr) {
    FSEventStreamStop(stream_);
    stream_started_ = false;
    FSEventStreamUnscheduleFromRunLoop(
        stream_, run_loop_, kCFRunLoopDefaultMode);
    FSEventStreamInvalidate(stream_);
    FSEventStreamRelease(stream_);
    stream_ = nullptr;
  }

  // Stop the run loop.
  CFRunLoopStop(run_loop_);
}
예제 #23
0
bool MacFileSystemChangeNotifier::internalRemoveNotification( const Torque::Path& dir )
{
    for( U32 i = 0, num = mEvents.size(); i < num; ++ i )
        if( mEvents[ i ]->mDir == dir )
        {
#ifdef DEBUG_SPEW
            Platform::outputDebugString( "[MacFileSystemChangeNotifier] Removing change notification %i from '%s'",
                                         i + 1, dir.getFullPath().c_str() );
#endif

            FSEventStreamStop( mEvents[ i ]->mStream );
            FSEventStreamInvalidate( mEvents[ i ]->mStream );
            FSEventStreamRelease( mEvents[ i ]->mStream );

            SAFE_DELETE( mEvents[ i ] );

            mEvents.erase( i );

            return true;
        }

    return false;
}
예제 #24
0
FSEventsWatcher::~FSEventsWatcher() {
  FSEventStreamStop(stream_);
  FSEventStreamInvalidate(stream_); /* will remove from runloop */
  FSEventStreamRelease(stream_);
  delete context_;
}
예제 #25
0
FolderWatcherPrivate::~FolderWatcherPrivate()
{
    FSEventStreamStop(_stream);
    FSEventStreamInvalidate(_stream);
    FSEventStreamRelease(_stream);
}
예제 #26
0
파일: fsevents.c 프로젝트: kwlzn/watchman
static void *fsevents_thread(void *arg)
{
  w_root_t *root = arg;
  FSEventStreamContext ctx;
  CFMutableArrayRef parray;
  CFStringRef cpath;
  FSEventStreamRef fs_stream = NULL;
  CFFileDescriptorContext fdctx;
  CFFileDescriptorRef fdref;
  struct fsevents_root_state *state = root->watch;
  double latency;

  w_set_thread_name("fsevents %.*s", root->root_path->len,
      root->root_path->buf);

  // Block until fsevents_root_start is waiting for our initialization
  pthread_mutex_lock(&state->fse_mtx);

  memset(&ctx, 0, sizeof(ctx));
  ctx.info = root;

  memset(&fdctx, 0, sizeof(fdctx));
  fdctx.info = root;

  fdref = CFFileDescriptorCreate(NULL, state->fse_pipe[0], true,
      fse_pipe_callback, &fdctx);
  CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
  {
    CFRunLoopSourceRef fdsrc;

    fdsrc = CFFileDescriptorCreateRunLoopSource(NULL, fdref, 0);
    if (!fdsrc) {
      root->failure_reason = w_string_new(
          "CFFileDescriptorCreateRunLoopSource failed");
      goto done;
    }
    CFRunLoopAddSource(CFRunLoopGetCurrent(), fdsrc, kCFRunLoopDefaultMode);
    CFRelease(fdsrc);
  }

  parray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
  if (!parray) {
    root->failure_reason = w_string_new("CFArrayCreateMutable failed");
    goto done;
  }

  cpath = CFStringCreateWithBytes(NULL, (const UInt8*)root->root_path->buf,
      root->root_path->len, kCFStringEncodingUTF8,
      false);
  if (!cpath) {
    root->failure_reason = w_string_new("CFStringCreateWithBytes failed");
    goto done;
  }

  CFArrayAppendValue(parray, cpath);
  CFRelease(cpath);

  latency = cfg_get_double(root, "fsevents_latency", 0.01),
  w_log(W_LOG_DBG,
      "FSEventStreamCreate for path %.*s with latency %f seconds\n",
      root->root_path->len,
      root->root_path->buf,
      latency);

  fs_stream = FSEventStreamCreate(NULL, fse_callback,
      &ctx, parray, kFSEventStreamEventIdSinceNow,
      latency,
      kFSEventStreamCreateFlagNoDefer|
      kFSEventStreamCreateFlagWatchRoot|
      kFSEventStreamCreateFlagFileEvents);

  if (!fs_stream) {
    root->failure_reason = w_string_new("FSEventStreamCreate failed");
    goto done;
  }

  FSEventStreamScheduleWithRunLoop(fs_stream,
      CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
  if (!FSEventStreamStart(fs_stream)) {
    root->failure_reason = w_string_make_printf(
        "FSEventStreamStart failed, look at your log file %s for "
        "lines mentioning FSEvents and see "
        "https://facebook.github.io/watchman/docs/troubleshooting.html#"
        "fsevents for more information\n", log_name);
    goto done;
  }

  // Signal to fsevents_root_start that we're done initializing
  pthread_cond_signal(&state->fse_cond);
  pthread_mutex_unlock(&state->fse_mtx);

  // Process the events stream until we get signalled to quit
  CFRunLoopRun();

  // Since the goto's above hold fse_mtx, we should grab it here
  pthread_mutex_lock(&state->fse_mtx);
done:
  if (fs_stream) {
    FSEventStreamStop(fs_stream);
    FSEventStreamInvalidate(fs_stream);
    FSEventStreamRelease(fs_stream);
  }
  if (fdref) {
    CFRelease(fdref);
  }

  // Signal to fsevents_root_start that we're done initializing in
  // the failure path.  We'll also do this after we've completed
  // the run loop in the success path; it's a spurious wakeup but
  // harmless and saves us from adding and setting a control flag
  // in each of the failure `goto` statements. fsevents_root_dtor
  // will `pthread_join` us before `state` is freed.
  pthread_cond_signal(&state->fse_cond);
  pthread_mutex_unlock(&state->fse_mtx);

  w_log(W_LOG_DBG, "fse_thread done\n");
  w_root_delref(root);
  return NULL;
}
예제 #27
0
void WatcherData::perform(void* thread)
{
    WatcherData* watcher = static_cast<WatcherData*>(thread);
    std::unique_lock<std::mutex> locker(watcher->mutex);
    if (watcher->flags & Stop) {
        if (watcher->fss) {
            FSEventStreamStop(watcher->fss);
            FSEventStreamInvalidate(watcher->fss);
        }
        CFRunLoopSourceInvalidate(watcher->source);
        CFRunLoopStop(watcher->loop);
        return;
    } else if (watcher->flags & Clear) {
        watcher->flags &= ~Clear;

        if (watcher->fss) {
            FSEventStreamStop(watcher->fss);
            FSEventStreamInvalidate(watcher->fss);
            watcher->fss = 0;
        }

        // We might have paths added since the clear operation was inititated
        if (watcher->paths.empty())
            return;
    }

    // ### might make sense to have multiple streams instead of recreating one for each change
    // ### and then merge them if the stream count reaches a given treshold

    const int pathSize = watcher->paths.size();
    FSEventStreamRef newfss = 0;

    if (pathSize) {
        StackBuffer<1024, CFStringRef> refs(pathSize);
        int i = 0;
        const Set<Path> copy = watcher->paths;
        for (const Path &path : copy) {
            refs[i++] = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault,
                                                        path.constData(),
                                                        kCFStringEncodingUTF8,
                                                        kCFAllocatorNull);
        }

        // don't need to hold the mutex beyond this point
        locker.unlock();

        CFArrayRef list = CFArrayCreate(kCFAllocatorDefault,
                                        reinterpret_cast<const void**>(&refs[0]),
                                        pathSize,
                                        &kCFTypeArrayCallBacks);

        for (int j = 0; j < i; ++j)
            CFRelease(refs[j]);

        FSEventStreamContext ctx = { 0, watcher, 0, 0, 0 };
        newfss = FSEventStreamCreate(kCFAllocatorDefault,
                                     notifyCallback,
                                     &ctx,
                                     list,
                                     watcher->since,
                                     .1,
                                     kFSEventStreamCreateFlagIgnoreSelf
                                     | kFSEventStreamCreateFlagFileEvents
                                     );

        CFRelease(list);
    }

    if (!newfss)
        return;

    if (watcher->fss) {
        FSEventStreamStop(watcher->fss);
        FSEventStreamInvalidate(watcher->fss);
    }

    watcher->fss = newfss;

    FSEventStreamScheduleWithRunLoop(watcher->fss, watcher->loop, kCFRunLoopDefaultMode);
    FSEventStreamStart(watcher->fss);
}
void WatcherThread::perform(void* thread)
{
    WatcherThread* watcher = static_cast<WatcherThread*>(thread);
    MutexLocker locker(&watcher->mutex);
    if (watcher->flags & Stop) {
        if (watcher->fss) {
            FSEventStreamStop(watcher->fss);
            FSEventStreamInvalidate(watcher->fss);
        }
        CFRunLoopSourceInvalidate(watcher->source);
        CFRunLoopStop(watcher->loop);
        return;
    } else if (watcher->flags & Clear) {
        watcher->flags &= ~Clear;

        if (watcher->fss) {
            FSEventStreamStop(watcher->fss);
            FSEventStreamInvalidate(watcher->fss);
            watcher->fss = 0;
        }

        // We might have paths added since the clear operation was inititated
        if (watcher->paths.empty())
            return;
    }

    // ### might make sense to have multiple streams instead of recreating one for each change
    // ### and then merge them if the stream count reaches a given treshold

    const int pathSize = watcher->paths.size();
    FSEventStreamRef newfss = 0;

    if (pathSize) {
        CFStringRef refs[pathSize];
        int i = 0;
        Set<Path>::const_iterator path = watcher->paths.begin();
        const Set<Path>::const_iterator end = watcher->paths.end();
        while (path != end) {
            // CFStringCreateWithCString copies the string data
            // ### use CFStringCreateWithCStringNoCopy instead?
            refs[i++] = CFStringCreateWithCString(kCFAllocatorDefault,
                                                  path->nullTerminated(),
                                                  kCFStringEncodingUTF8);
            ++path;
        }

        // don't need to hold the mutex beyond this point
        locker.unlock();

        CFArrayRef list = CFArrayCreate(kCFAllocatorDefault,
                                        reinterpret_cast<const void**>(refs),
                                        pathSize,
                                        0);

        FSEventStreamContext ctx = { 0, watcher, 0, 0, 0 };
        newfss = FSEventStreamCreate(kCFAllocatorDefault,
                                     notifyCallback,
                                     &ctx,
                                     list,
                                     watcher->since,
                                     .5,
                                     kFSEventStreamCreateFlagWatchRoot
                                     | kFSEventStreamCreateFlagIgnoreSelf
                                     | kFSEventStreamCreateFlagFileEvents);
    }

    if (!newfss)
        return;

    if (watcher->fss) {
        FSEventStreamStop(watcher->fss);
        FSEventStreamInvalidate(watcher->fss);
    }

    watcher->fss = newfss;

    FSEventStreamScheduleWithRunLoop(watcher->fss, watcher->loop, kCFRunLoopDefaultMode);
    FSEventStreamStart(watcher->fss);
}