text: //
  //          Copyright (c) 2017, Scientific Toolworks, Inc.
  //
  // This software is licensed under the MIT License. The LICENSE.md file
  // describes the conditions under which this software may be distributed.
  //
  // Author: Jason Haslam
  //
  
  #include "RepositoryWatcher.h"
  #include <CoreServices/CoreServices.h>
  
  class RepositoryWatcherPrivate : public QObject
  {
    Q_OBJECT
  
  public:
    RepositoryWatcherPrivate(
      const git::Repository &repo,
      QObject *parent = nullptr)
      : QObject(parent), mRepo(repo)
    {
      // Create dispatch queue.
      mQueue = dispatch_queue_create("com.gitahead.RepositoryWatcher", nullptr);
  
      // Create stream to watch the workdir.
      FSEventStreamContext context = { 0, this, nullptr, nullptr, nullptr };
  
      CFStringRef wd = repo.workdir().path().toCFString();
      CFArrayRef wds = CFArrayCreate(nullptr, (const void **) &wd, 1, nullptr);
      mStream = FSEventStreamCreate(nullptr, &notify, &context, wds,
        kFSEventStreamEventIdSinceNow, 0, kFSEventStreamCreateFlagNone);
      CFRelease(wds);
      CFRelease(wd);
  
      // Exclude the .git dir.
      CFStringRef gd = repo.dir().path().toCFString();
      CFArrayRef gds = CFArrayCreate(nullptr, (const void **) &gd, 1, nullptr);
      FSEventStreamSetExclusionPaths(mStream, gds);
      CFRelease(gds);
      CFRelease(gd);
  
      // Register with queue.
      FSEventStreamSetDispatchQueue(mStream, mQueue);
  
      // Start the stream.
      FSEventStreamStart(mStream);
    }
  
    ~RepositoryWatcherPrivate()
    {
      // Stop stream.
      FSEventStreamStop(mStream);
  
      // Release stream.
      FSEventStreamInvalidate(mStream);
      FSEventStreamRelease(mStream);
  
      // Release queue
      dispatch_release(mQueue);
    }
  
    git::Repository repo() const { return mRepo; }
  
    static void notify(
      ConstFSEventStreamRef streamRef,
      void *clientCallBackInfo,
      size_t numEvents,
      void *eventPaths,
      const FSEventStreamEventFlags eventFlags[],
      const FSEventStreamEventId eventIds[])
    {
      RepositoryWatcherPrivate *watcher =
        static_cast<RepositoryWatcherPrivate *>(clientCallBackInfo);
  
      // Filter out ignored directories.
      git::Repository repo = watcher->repo();
      const char **paths = static_cast<const char **>(eventPaths);
      for (int i = 0; i < numEvents; ++i) {
        if (!repo.isIgnored(paths[i])) {
          emit watcher->notificationReceived();
          return;
        }
      }
    }
  
  signals:
    void notificationReceived();
  
  private:
    git::Repository mRepo;
    dispatch_queue_t mQueue;
    FSEventStreamRef mStream;
  };
  
  RepositoryWatcher::RepositoryWatcher(
    const git::Repository &repo,
    QObject *parent)
    : QObject(parent), d(new RepositoryWatcherPrivate(repo, this))
  {
    init(repo);
    connect(d, &RepositoryWatcherPrivate::notificationReceived,
            &mTimer, QOverload<>::of(&QTimer::start));
  }
  
  RepositoryWatcher::~RepositoryWatcher() {}
  
  #include "RepositoryWatcher_mac.moc"
  