swiftxcodemacoscocoakqueue

open(FileManager.default.fileSystemRepresentation(withPath: path), O_EVTONLY) returns -1


I am using SKQueue to monitor some folders in the mac filesystem. As per the documentation, I have added the directory paths to the queues but I noticed that while adding the path, the following line of code in SKQueue is returning -1 and hence it is unable to monitor my folder.

This is the SKQueue Documentation.

The following is code from the documentation, written in the controller class.

import SKQueue

class SomeClass: SKQueueDelegate {
  func receivedNotification(_ notification: SKQueueNotification, path: String, queue: SKQueue) {
    print("\(notification.toStrings().map { $0.rawValue }) @ \(path)")
  }
}

let delegate = SomeClass()
let queue = SKQueue(delegate: delegate)!

queue.addPath("/Users/steve/Documents")
queue.addPath("/Users/steve/Documents/dog.jpg")

The following is code inside SKQueue dependency.

  public func addPath(_ path: String, notifyingAbout notification: SKQueueNotification = SKQueueNotification.Default) {
    var fileDescriptor: Int32! = watchedPaths[path]
    if fileDescriptor == nil {
      fileDescriptor = open(FileManager.default.fileSystemRepresentation(withPath: path), O_EVTONLY)
      guard fileDescriptor >= 0 else { return }
      watchedPaths[path] = fileDescriptor
    }

fileDescriptor = open(FileManager.default.fileSystemRepresentation(withPath: path), O_EVTONLY)

The above code is returning -1 and hence it is failing.


Solution

  • I was getting the same -1 return code and couldn't understand why. Whilst looking for a solution I stumbled upon SwiftFolderMonitor at https://github.com/MartinJNash/SwiftFolderMonitor. This class worked so I knew it wasn't a permission problem.

    SwiftFolderMonitor uses DispatchSource.makeFileSystemObjectSource rather than kevent, but it also takes a URL parameter rather than a String path. I amended SKQueue to take a URL instead of a String and it works.

    Here's my amended addPath:

    public func addPath(url: URL, notifyingAbout notification: SKQueueNotification = SKQueueNotification.Default) {
        let path = url.absoluteString
        var fileDescriptor: Int32! = watchedPaths[path]
        if fileDescriptor == nil {
            fileDescriptor = open((url as NSURL).fileSystemRepresentation, O_EVTONLY)
            guard fileDescriptor >= 0 else { return }
            watchedPaths[path] = fileDescriptor
        }
    
        var edit = kevent(
            ident: UInt(fileDescriptor),
            filter: Int16(EVFILT_VNODE),
            flags: UInt16(EV_ADD | EV_CLEAR),
            fflags: notification.rawValue,
            data: 0,
            udata: nil
        )
        kevent(kqueueId, &edit, 1, nil, 0, nil)
    
        if !keepWatcherThreadRunning {
            keepWatcherThreadRunning = true
            DispatchQueue.global().async(execute: watcherThread)
        }
    }
    

    I don't know why this works, perhaps someone else can shed some light on this.

    I'm still playing with both solutions but it looks like SwiftFolderMonitor does all I need (I just need to know when a specific file has changed) and it's code is clean and minimal so I think I'll use it over SKQueue.

    I hope this helps.