Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to monitor a folder for new files in swift?

Tags:

macos

swift

How would I monitor a folder for new files in swift, without polling (which is very inefficient)? I've heard of APIs such as kqueue and FSEvents - but I'm not sure it's possible to implement them in swift?

like image 921
user3727570 Avatar asked Jun 10 '14 20:06

user3727570


2 Answers

GCD seems to be the way to go. NSFilePresenter classes doesn't work properly. They're buggy, broken, and Apple is haven't willing to fix them for last 4 years. Likely to be deprecated.

Here's a very nice posting which describes essentials of this technique.

"Handling Filesystem Events with GCD", by David Hamrick.

Sample code cited from the website. I translated his C code into Swift.

    let fildes = open("/path/to/config.plist", O_RDONLY)

    let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
    let source = dispatch_source_create(
        DISPATCH_SOURCE_TYPE_VNODE,
        UInt(fildes),
        DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB | DISPATCH_VNODE_LINK | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE,
        queue)

    dispatch_source_set_event_handler(source,
        {
            //Reload the config file
        })

    dispatch_source_set_cancel_handler(source,
        {
            //Handle the cancel
        })

    dispatch_resume(source);

    ...

        // sometime later
        dispatch_source_cancel(source);

For reference, here're another QAs posted by the author:

  • Grand Central Dispatch (GCD) dispatch source flags
  • Monitoring a directory in Cocoa/Cocoa Touch

If you're interested in watching directories, here's another posting which describes it.

"Monitoring a Folder with GCD" on Cocoanetics. (unfortunately, I couldn't find the author's name. I am sorry for lacking attribution)

The only noticeable difference is getting a file-descriptor. This makes event-notification-only file descriptor for a directory.

_fileDescriptor = open(path.fileSystemRepresentation(), O_EVTONLY)

Update

Previously I claimed FSEvents API is not working, but I was wrong. The API is working very well, and if you're interested in watching on deep file tree, than it can be better then GCD by its simplicity.

Anyway, FSEvents cannot be used in pure Swift programs. Because it requires passing of C callback function, and Swift does not support it currently (Xcode 6.1.1). Then I had to fallback to Objective-C and wrap it again.

Also, any of this kind API is all fully asynchronous. That means actual file system state can be different at the time you are receiving the notifications. Then precise or accurate notification is not really helpful, and useful only for marking a dirty flag.

Update 2

I finally ended up with writing a wrapper around FSEvents for Swift. Here's my work, and I hope this to be helpful.

  • https://github.com/eonil/FileSystemEvents
like image 113
eonil Avatar answered Nov 06 '22 22:11

eonil


I adapted Stanislav Smida's code to make it work with Xcode 8 and Swift 3

class DirectoryObserver {

    private let fileDescriptor: CInt
    private let source: DispatchSourceProtocol

    deinit {

      self.source.cancel()
      close(fileDescriptor)
    }

    init(URL: URL, block: @escaping ()->Void) {

      self.fileDescriptor = open(URL.path, O_EVTONLY)
      self.source = DispatchSource.makeFileSystemObjectSource(fileDescriptor: self.fileDescriptor, eventMask: .all, queue: DispatchQueue.global())
      self.source.setEventHandler { 
          block()
      }
      self.source.resume()
  }

}
like image 13
mdonati Avatar answered Nov 06 '22 20:11

mdonati