<commit_before>#import <sys/event.h>
#import <sys/time.h>
#import <sys/param.h>
#import <fcntl.h>

#import "path_watcher.h"

static NSMutableArray *gPathWatchers;

@interface PathWatcher ()
- (bool)usesContext:(CefRefPtr<CefV8Context>)context;
- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback callbackId:(NSString *)callbackId;
- (void)stopWatching;
- (bool)isAtomicWrite:(struct kevent)event;
@end

@implementation PathWatcher

+ (PathWatcher *)pathWatcherForContext:(CefRefPtr<CefV8Context>)context {
  if (!gPathWatchers) gPathWatchers = [[NSMutableArray alloc] init];

  PathWatcher *pathWatcher = nil;
  for (PathWatcher *p in gPathWatchers) {
    if ([p usesContext:context]) {
      pathWatcher = p;
      break;
    }
  }

  if (!pathWatcher) {
    pathWatcher = [[[PathWatcher alloc] initWithContext:context] autorelease];
    [gPathWatchers addObject:pathWatcher];
  }

  return pathWatcher;
}

+ (void)removePathWatcherForContext:(CefRefPtr<CefV8Context>)context {
  PathWatcher *pathWatcher = nil;
  for (PathWatcher *p in gPathWatchers) {
    if ([p usesContext:context]) {
      pathWatcher = p;
      break;
    }
  }

  if (pathWatcher) {
    [pathWatcher stopWatching];
    [gPathWatchers removeObject:pathWatcher];
  }

}

- (void)dealloc {
  @synchronized(self) {
    close(_kq);
    for (NSString *path in [_callbacksByPath allKeys]) {
      [self removeKeventForPath:path];
    }
    [_callbacksByPath release];
    _context = nil;
    _keepWatching = false;
  }

  [super dealloc];
}

- (id)initWithContext:(CefRefPtr<CefV8Context>)context {
  self = [super init];

  _keepWatching = YES;
  _callbacksByPath = [[NSMutableDictionary alloc] init];
  _fileDescriptorsByPath = [[NSMutableDictionary alloc] init];
  _kq = kqueue();
  _context = context;

  if (_kq == -1) {
    [NSException raise:@"PathWatcher" format:@"Could not create kqueue"];
  }

  [self performSelectorInBackground:@selector(watch) withObject:NULL];
  return self;
}

- (bool)usesContext:(CefRefPtr<CefV8Context>)context {
  return _context->IsSame(context);
}

- (void)stopWatching {
  @synchronized(self) {
    [self unwatchAllPaths];
    _keepWatching = false;
  }
}

- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback {
  NSString *callbackId = [[NSProcessInfo processInfo] globallyUniqueString];
  return [self watchPath:path callback:callback callbackId:callbackId];
}

- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback callbackId:(NSString *)callbackId {
  path = [path stringByStandardizingPath];
  @synchronized(self) {
    if (![self createKeventForPath:path]) {
      NSLog(@"WARNING: Failed to create kevent for path '%@'", path);
      return nil;
    }

    NSMutableDictionary *callbacks = [_callbacksByPath objectForKey:path];
    if (!callbacks) {
      callbacks = [NSMutableDictionary dictionary];
      [_callbacksByPath setObject:callbacks forKey:path];
    }

    [callbacks setObject:callback forKey:callbackId];
  }

  return callbackId;
}

- (void)unwatchPath:(NSString *)path callbackId:(NSString *)callbackId error:(NSError **)error {
  path = [path stringByStandardizingPath];

  @synchronized(self) {
    NSMutableDictionary *callbacks = [_callbacksByPath objectForKey:path];

    if (callbacks) {
      if (callbackId) {
        [callbacks removeObjectForKey:callbackId];
      }
      else {
        [callbacks removeAllObjects];
      }

      if (callbacks.count == 0) {
        [self removeKeventForPath:path];
        [_callbacksByPath removeObjectForKey:path];
      }
    }
  }
}

- (NSArray *)watchedPaths {
  return [_callbacksByPath allKeys];
}

- (void)unwatchAllPaths {
  @synchronized(self) {
    NSArray *paths = [_callbacksByPath allKeys];
    for (NSString *path in paths) {
      [self unwatchPath:path callbackId:nil error:nil];
    }
  }
}

- (bool)createKeventForPath:(NSString *)path {
  path = [path stringByStandardizingPath];

  @synchronized(self) {
    if ([_fileDescriptorsByPath objectForKey:path]) {
      NSLog(@"we already have a kevent");
      return YES;
    }

    int fd = open([path fileSystemRepresentation], O_EVTONLY, 0);
    if (fd < 0) {
      NSLog(@"WARNING: Could create file descriptor for path '%@'", path);
      return NO;
    }

    [_fileDescriptorsByPath setObject:[NSNumber numberWithInt:fd] forKey:path];

    struct timespec timeout = { 0, 0 };
    struct kevent event;
    int filter = EVFILT_VNODE;
    int flags = EV_ADD | EV_ENABLE | EV_CLEAR;
    int filterFlags = NOTE_WRITE | NOTE_DELETE | NOTE_RENAME;
    EV_SET(&event, fd, filter, flags, filterFlags, 0, path);
    kevent(_kq, &event, 1, NULL, 0, &timeout);
    return YES;
  }
}

- (void)removeKeventForPath:(NSString *)path {
  path = [path stringByStandardizingPath];

  @synchronized(self) {
    NSNumber *fdNumber = [_fileDescriptorsByPath objectForKey:path];
    if (!fdNumber) {
      NSLog(@"WARNING: Could not find file descriptor for path '%@'", path);
      return;
    }
    close([fdNumber integerValue]);
    [_fileDescriptorsByPath removeObjectForKey:path];
  }
<commit_msg>Log a better error upon failing to `open` an fd in path watcher<commit_after>#import <sys/event.h>
#import <sys/time.h>
#import <sys/param.h>
#import <fcntl.h>

#import "path_watcher.h"

static NSMutableArray *gPathWatchers;

@interface PathWatcher ()
- (bool)usesContext:(CefRefPtr<CefV8Context>)context;
- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback callbackId:(NSString *)callbackId;
- (void)stopWatching;
- (bool)isAtomicWrite:(struct kevent)event;
@end

@implementation PathWatcher

+ (PathWatcher *)pathWatcherForContext:(CefRefPtr<CefV8Context>)context {
  if (!gPathWatchers) gPathWatchers = [[NSMutableArray alloc] init];

  PathWatcher *pathWatcher = nil;
  for (PathWatcher *p in gPathWatchers) {
    if ([p usesContext:context]) {
      pathWatcher = p;
      break;
    }
  }

  if (!pathWatcher) {
    pathWatcher = [[[PathWatcher alloc] initWithContext:context] autorelease];
    [gPathWatchers addObject:pathWatcher];
  }

  return pathWatcher;
}

+ (void)removePathWatcherForContext:(CefRefPtr<CefV8Context>)context {
  PathWatcher *pathWatcher = nil;
  for (PathWatcher *p in gPathWatchers) {
    if ([p usesContext:context]) {
      pathWatcher = p;
      break;
    }
  }

  if (pathWatcher) {
    [pathWatcher stopWatching];
    [gPathWatchers removeObject:pathWatcher];
  }

}

- (void)dealloc {
  @synchronized(self) {
    close(_kq);
    for (NSString *path in [_callbacksByPath allKeys]) {
      [self removeKeventForPath:path];
    }
    [_callbacksByPath release];
    _context = nil;
    _keepWatching = false;
  }

  [super dealloc];
}

- (id)initWithContext:(CefRefPtr<CefV8Context>)context {
  self = [super init];

  _keepWatching = YES;
  _callbacksByPath = [[NSMutableDictionary alloc] init];
  _fileDescriptorsByPath = [[NSMutableDictionary alloc] init];
  _kq = kqueue();
  _context = context;

  if (_kq == -1) {
    [NSException raise:@"PathWatcher" format:@"Could not create kqueue"];
  }

  [self performSelectorInBackground:@selector(watch) withObject:NULL];
  return self;
}

- (bool)usesContext:(CefRefPtr<CefV8Context>)context {
  return _context->IsSame(context);
}

- (void)stopWatching {
  @synchronized(self) {
    [self unwatchAllPaths];
    _keepWatching = false;
  }
}

- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback {
  NSString *callbackId = [[NSProcessInfo processInfo] globallyUniqueString];
  return [self watchPath:path callback:callback callbackId:callbackId];
}

- (NSString *)watchPath:(NSString *)path callback:(WatchCallback)callback callbackId:(NSString *)callbackId {
  path = [path stringByStandardizingPath];
  @synchronized(self) {
    if (![self createKeventForPath:path]) {
      NSLog(@"WARNING: Failed to create kevent for path '%@'", path);
      return nil;
    }

    NSMutableDictionary *callbacks = [_callbacksByPath objectForKey:path];
    if (!callbacks) {
      callbacks = [NSMutableDictionary dictionary];
      [_callbacksByPath setObject:callbacks forKey:path];
    }

    [callbacks setObject:callback forKey:callbackId];
  }

  return callbackId;
}

- (void)unwatchPath:(NSString *)path callbackId:(NSString *)callbackId error:(NSError **)error {
  path = [path stringByStandardizingPath];

  @synchronized(self) {
    NSMutableDictionary *callbacks = [_callbacksByPath objectForKey:path];

    if (callbacks) {
      if (callbackId) {
        [callbacks removeObjectForKey:callbackId];
      }
      else {
        [callbacks removeAllObjects];
      }

      if (callbacks.count == 0) {
        [self removeKeventForPath:path];
        [_callbacksByPath removeObjectForKey:path];
      }
    }
  }
}

- (NSArray *)watchedPaths {
  return [_callbacksByPath allKeys];
}

- (void)unwatchAllPaths {
  @synchronized(self) {
    NSArray *paths = [_callbacksByPath allKeys];
    for (NSString *path in paths) {
      [self unwatchPath:path callbackId:nil error:nil];
    }
  }
}

- (bool)createKeventForPath:(NSString *)path {
  path = [path stringByStandardizingPath];

  @synchronized(self) {
    if ([_fileDescriptorsByPath objectForKey:path]) {
      NSLog(@"we already have a kevent");
      return YES;
    }

    int fd = open([path fileSystemRepresentation], O_EVTONLY, 0);
    if (fd < 0) {
      NSLog(@"WARNING: Could not create file descriptor for path '%@'. Error code %d.", path, fd);
      return NO;
    }

    [_fileDescriptorsByPath setObject:[NSNumber numberWithInt:fd] forKey:path];

    struct timespec timeout = { 0, 0 };
    struct kevent event;
    int filter = EVFILT_VNODE;
    int flags = EV_ADD | EV_ENABLE | EV_CLEAR;
    int filterFlags = NOTE_WRITE | NOTE_DELETE | NOTE_RENAME;
    EV_SET(&event, fd, filter, flags, filterFlags, 0, path);
    kevent(_kq, &event, 1, NULL, 0, &timeout);
    return YES;
  }
}

- (void)removeKeventForPath:(NSString *)path {
  path = [path stringByStandardizingPath];

  @synchronized(self) {
    NSNumber *fdNumber = [_fileDescriptorsByPath objectForKey:path];
    if (!fdNumber) {
      NSLog(@"WARNING: Could not find file descriptor for path '%@'", path);
      return;
    }
    close([fdNumber integerValue]);
    [_fileDescriptorsByPath removeObjectForKey:path];
  }

{'fraction_non_alphanumeric': 0.12563303467082196, 'fraction_numerical': 0.0027269185820023374, 'mean_word_length': 4.119142572283151, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6575373', 'n_tokens_mistral': 3538, 'n_tokens_neox': 3348, 'n_words': 711}