packages feed

streamly-0.8.0: src/Streamly/Internal/FileSystem/Event/Darwin.m

/*
 * Code adapted from the Haskell "hfsevents" package.
 *
 * Copyright (c) 2012, Luite Stegeman
 *
 */

#include <config.h>

#if HAVE_DECL_KFSEVENTSTREAMCREATEFLAGFILEEVENTS

#include <CoreServices/CoreServices.h>
#include <pthread.h>
#include <unistd.h>
#include <FileSystem/Event/Darwin.h>

/*
 * For reference documentaion see:
 * https://developer.apple.com/documentation/coreservices/file_system_events?language=objc
 * https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/FSEvents_ProgGuide/UsingtheFSEventsFramework/UsingtheFSEventsFramework.html
 *
 * An OS thread is started which runs the event loop. A pipe is created
 * and the events are sent to the pipes. The receiver can read the pipe
 * output end to get the events.
 */

/******************************************************************************
 * Create Flags
 *****************************************************************************/

UInt32 FSEventStreamCreateFlagNoDefer () {
  return kFSEventStreamCreateFlagNoDefer;
}
UInt32 FSEventStreamCreateFlagWatchRoot () {
  return kFSEventStreamCreateFlagWatchRoot;
}
UInt32 FSEventStreamCreateFlagFileEvents () {
  return kFSEventStreamCreateFlagFileEvents;
}
UInt32 FSEventStreamCreateFlagIgnoreSelf () {
  return kFSEventStreamCreateFlagIgnoreSelf;
}
#if 0
UInt32 FSEventStreamCreateFlagFullHistory = kFSEventStreamCreateFlagFullHistory;
#endif

/******************************************************************************
 * Event Flags
 *****************************************************************************/

UInt32 FSEventStreamEventFlagEventIdsWrapped () {
  return kFSEventStreamEventFlagEventIdsWrapped;
}
UInt32 FSEventStreamEventFlagMustScanSubDirs () {
  return kFSEventStreamEventFlagMustScanSubDirs;
}
UInt32 FSEventStreamEventFlagKernelDropped () {
  return kFSEventStreamEventFlagKernelDropped;
}
UInt32 FSEventStreamEventFlagUserDropped () {
  return kFSEventStreamEventFlagUserDropped;
}
UInt32 FSEventStreamEventFlagHistoryDone () {
  return kFSEventStreamEventFlagHistoryDone;
}
UInt32 FSEventStreamEventFlagRootChanged () {
  return kFSEventStreamEventFlagRootChanged;
}
UInt32 FSEventStreamEventFlagMount () {
  return kFSEventStreamEventFlagMount;
}
UInt32 FSEventStreamEventFlagUnmount () {
  return kFSEventStreamEventFlagUnmount;
}
UInt32 FSEventStreamEventFlagItemChangeOwner () {
  return kFSEventStreamEventFlagItemChangeOwner;
}
UInt32 FSEventStreamEventFlagItemInodeMetaMod () {
  return kFSEventStreamEventFlagItemInodeMetaMod;
}
UInt32 FSEventStreamEventFlagItemFinderInfoMod () {
  return kFSEventStreamEventFlagItemFinderInfoMod;
}
UInt32 FSEventStreamEventFlagItemXattrMod () {
  return kFSEventStreamEventFlagItemXattrMod;
}
UInt32 FSEventStreamEventFlagItemCreated () {
  return kFSEventStreamEventFlagItemCreated;
}
UInt32 FSEventStreamEventFlagItemRemoved () {
  return kFSEventStreamEventFlagItemRemoved;
}
UInt32 FSEventStreamEventFlagItemRenamed () {
  return kFSEventStreamEventFlagItemRenamed;
}
UInt32 FSEventStreamEventFlagItemModified () {
  return kFSEventStreamEventFlagItemModified;
}
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
UInt32 FSEventStreamEventFlagItemCloned () {
  return kFSEventStreamEventFlagItemCloned;
}
#endif
UInt32 FSEventStreamEventFlagItemIsDir () {
  return kFSEventStreamEventFlagItemIsDir;
}
UInt32 FSEventStreamEventFlagItemIsFile () {
  return kFSEventStreamEventFlagItemIsFile;
}
UInt32 FSEventStreamEventFlagItemIsSymlink () {
  return kFSEventStreamEventFlagItemIsSymlink;
}
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
UInt32 FSEventStreamEventFlagItemIsHardlink () {
  return kFSEventStreamEventFlagItemIsHardlink;
}
#endif
UInt32 FSEventStreamEventFlagItemIsLastHardlink () {
  return kFSEventStreamEventFlagItemIsLastHardlink;
}

/******************************************************************************
 * Event watch
 *****************************************************************************/

/* Write an event to the pipe input fd */
static void writeEvent(int fd, UInt64 eventId, UInt64 eventFlags, char* path) 
{
    UInt64 buf[3];
    buf[0] = eventId;
    buf[1] = eventFlags;
    /* XXX Is the path string in UTF-8? */
    buf[2] = (UInt64)strlen(path);
    write(fd, buf, 3 * sizeof(UInt64));
    write(fd, path, strlen(path));
}

/* thread state */
struct watch
{
    FSEventStreamRef eventStream;
    CFRunLoopRef runLoop;
    int writefd;
    pthread_mutex_t mut;
};

/* Just writes the event to the pipe input fd */
static void watchCallback
    ( ConstFSEventStreamRef streamRef
    , void *clientCallBackInfo
    , size_t n
    , void *eventPaths
    , const FSEventStreamEventFlags eventFlags[]
    , const FSEventStreamEventId eventIds[]
    )
{
    int i;
    struct watch *w = clientCallBackInfo;
    char **paths = eventPaths;

    for (i = 0; i < n; i++) {
        writeEvent(w->writefd, eventIds[i], eventFlags[i], paths[i]);
    }
}

/******************************************************************************
 * Start a watch event loop
 *****************************************************************************/

/* Event loop run in a pthread */
static void *watchRunLoop(void *vw)
{
    struct watch* w = (struct watch*) vw;
    CFRunLoopRef rl = CFRunLoopGetCurrent();
    CFRetain(rl);
    w->runLoop = rl;
    FSEventStreamScheduleWithRunLoop(w->eventStream, rl, kCFRunLoopDefaultMode);
    FSEventStreamStart(w->eventStream);
    pthread_mutex_unlock(&w->mut);
    CFRunLoopRun();
    pthread_exit(NULL);
}

#define MAX_WATCH_PATHS 4096

int createWatch
    ( struct pathName* folders
    , int n  /* number of entries in folders */
    , UInt32 createFlags
    , UInt64 since
    , double latency
    , int* fd
    , void** wp
    )
{
    if (n > MAX_WATCH_PATHS) {
      return -1;
    }

    int pfds[2];
    if (pipe (pfds)) {
      return -1;
    }

    /*
     * XXX We can possibly use since == 0 to get all events since
     * beginning of time
     */
    if (!since) {
      since = kFSEventStreamEventIdSinceNow;
    }

    /* Setup paths array */
    CFStringRef *cffolders = malloc(n * sizeof(CFStringRef));
    int i;
    for(i = 0; i < n; i++) {
      cffolders[i] = CFStringCreateWithBytes
          ( NULL
          , folders[i].pathBytes
          , folders[i].pathLen
          , kCFStringEncodingUTF8
          , false
          );
    }
    CFArrayRef paths = CFArrayCreate(NULL, (const void **)cffolders, n, NULL);

    /* Setup context */
    struct watch *w = malloc(sizeof(struct watch));
    FSEventStreamContext ctx;
    ctx.version = 0;
    ctx.info = (void*)w;
    ctx.retain = NULL;
    ctx.release = NULL;
    ctx.copyDescription = NULL;

    /* Create watch using paths and context*/
    FSEventStreamRef es = FSEventStreamCreate
        (NULL, &watchCallback, &ctx, paths, since, latency, createFlags);

    /* Run the event loop in a pthread */
    int retval;
    if(es != NULL) {
        /* Success */
        w->writefd = pfds[1];
        w->eventStream = es;
        w->runLoop = NULL;

        /* Lock to prevent race against watch destroy */
        pthread_mutex_init(&w->mut, NULL);
        pthread_mutex_lock(&w->mut);
        pthread_t t;
        pthread_create(&t, NULL, &watchRunLoop, (void*)w);

        /* return the out fd and the watch struct */
        *fd = pfds[0];
        *wp = w;
        retval = 0;
    } else {
        /* Failure */
        close(pfds[0]);
        close(pfds[1]);
        free(w);
        retval = -1;
    }

    /* Cleanup */
    for (i = 0; i < n; i++) {
        CFRelease (cffolders[i]);
    }
    free(cffolders);
    CFRelease(paths);
    return retval;
}

/******************************************************************************
 * Stop a watch event loop
 *****************************************************************************/

void destroyWatch(struct watch* w) {
    /* Stop the loop so the thread will exit */
    pthread_mutex_lock(&w->mut);
    FSEventStreamStop(w->eventStream);
    FSEventStreamInvalidate(w->eventStream);
    CFRunLoopStop(w->runLoop);
    CFRelease(w->runLoop);
    FSEventStreamRelease(w->eventStream);
    close(w->writefd);
    pthread_mutex_unlock(&w->mut);

    /* Cleanup */
    pthread_mutex_destroy(&w->mut);
    free(w);
}
#endif