21

What is the best way to listen to a folder or file to see if it has been saved or if a new file has been added?

Garrett
  • 7,830
  • 2
  • 41
  • 42

6 Answers6

34

The FSEvents API is ideal if you just want to watch directories but it doesn't handle the monitoring of individual files. Stu Connolly has a great Objective-C wrapper for the FSEvents C API, it's called SCEvents and you can get it here:

http://stuconnolly.com/blog/scevents-011/

The nice thing about FSEvents is that you just need to watch one folder and you will be notified of any changes that occur anywhere in the subfolder hierarchy of that folder.

If you need file-level notifications you will need to use kqueues. Uli Kusterer has a great Objective-C wrapper:

http://zathras.de/angelweb/sourcecode.htm#UKKQueue

Either of these methods is a lot easier than wrangling with the C APIs directly, which are not particularly well documented and a bit obtuse.

If you need to support Tiger you'll need to use kqueues as the FSEvents API wasn't officially available in 10.4.

Rob Keniger
  • 45,830
  • 6
  • 101
  • 134
  • 6
    Things have changed since Mac OS 10.7 Lion. FSEvents now supports file-level granularity, use kFSEventStreamCreateFlagFileEvents flag when creating events stream to get informed about changes to particular files. FSEvent Guide doesn't reflect these changes, but [Reference](http://developer.apple.com/library/mac/#documentation/Darwin/Reference/FSEvents_Ref/Reference/reference.html#//apple_ref/c/econst/kFSEventStreamCreateFlagFileEvents) do. – Konstantin Pavlikhin Sep 14 '12 at 08:46
  • How about sandbox restrictions? Is it possible to get notifications for any path in the system? (After user selects it with the special dialog) – Vojto Nov 19 '13 at 21:01
  • Good question. I believe if you have selected a folder using an `NSOpenPanel` (i.e. the Powerbox) then you should be able to observe it for changes without any problems. – Rob Keniger Nov 20 '13 at 00:19
6

Try using FSEvents, although it is a C API

OS 10.5 or newer

cobbal
  • 69,903
  • 20
  • 143
  • 156
2

If you do need to use kqueue (as discussed in other answers) Google Toolbox for Mac has a nice Objective-C wrapper that I've used with no issues thus far.

Redwood
  • 66,744
  • 41
  • 126
  • 187
1

If you are changing a file or folder, I believe the Spotlight search engine will update its database to reflect your changes.

So you might set up a thread that listens for kMDQueryDidUpdateNotification notifications through a Spotlight query specific to that file or folder.

When you get those notifications, you could fire a selector that does something you want.

Alex Reynolds
  • 95,983
  • 54
  • 240
  • 345
  • 2
    This is a good idea, but the problem with Spotlight is that not all volumes support it (many network volumes in particular) and users also have a habit of turning it off. – Rob Keniger Sep 06 '09 at 23:40
  • Also you may have volumes in private mode in spotlight. Nice to have the idea on the table though. Thumbs up! – Sentry.co Apr 09 '16 at 07:26
0

Nowadays you can use GCD to monitor folders. You might be able to use the same technique for monitoring a file, but even if it only works on folders you can note the modification date of the file and check for changes each time the folder changes.

Here's a Swift class I wrote to monitor a folder using GCD:

import Foundation

@objc public class DirectoryWatcher : NSObject {
    override public init() {
        super.init()
    }
    
    deinit {
        stop()
    }
    
    public typealias Callback = (_ directoryWatcher: DirectoryWatcher) -> Void
    
    @objc public convenience init(withPath path: String, callback: @escaping Callback) {
        self.init()
        if !watch(path: path, callback: callback) {
            assert(false)
        }
    }
    
    private var dirFD : Int32 = -1 {
        didSet {
            if oldValue != -1 {
                close(oldValue)
            }
        }
    }
    private var dispatchSource : DispatchSourceFileSystemObject?
    
    @objc public func watch(path: String, callback: @escaping Callback) -> Bool {
        // Open the directory
        dirFD = open(path, O_EVTONLY)
        if dirFD < 0 {
            return false
        }
        
        // Create and configure a DispatchSource to monitor it
        let dispatchSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: dirFD, eventMask: .write, queue: DispatchQueue.main)
        dispatchSource.setEventHandler {[unowned self] in
            callback(self)
        }
        dispatchSource.setCancelHandler {[unowned self] in
            self.dirFD = -1
        }
        self.dispatchSource = dispatchSource

        // Start monitoring
        dispatchSource.resume()
        
        // Success
        return true
    }

    @objc public func stop() {
        // Leave if not monitoring
        guard let dispatchSource = dispatchSource else {
            return
        }
        
        // Don't listen to more events
        dispatchSource.setEventHandler(handler: nil)
        
        // Cancel the source (this will also close the directory)
        dispatchSource.cancel()
        self.dispatchSource = nil
    }
}

Use it like Apple's DirectoryWatcher example, something like this:

let directoryWatcher = DirectoryWatcher(withPath: "/path/to/the/folder/you/want/to/monitor/", callback: {
    print("the folder changed")
})

Destroying the object will stop watching, or you can stop it explicitly

directoryWatcher.stop()

It should be compatible with Objective C they way it's written (untested). Using it would be like this:

DirectoryWatcher *directoryWatcher = [DirectoryWatcher.alloc initWithPath: @"/path/to/the/folder/you/want/to/monitor/" callback: ^(DirectoryWatcher *directoryWatcher) {
    NSLog(@"the folder changed")
}];

Stopping it is similar

[directoryWatcher stop];
John Stephen
  • 7,625
  • 2
  • 31
  • 45
-3

Not sure what's the best way, but A way would be to fire up an NSThread that would regularly (for instance every second) check the creation dates of the files in the directory, and then have a delegate associated with that thread to perform some action when a new file has been added

niklassaers
  • 8,480
  • 20
  • 99
  • 146
  • 11
    That's polling and is discouraged. Use FSEvents or kqueues instead, which are APIs designed expressly for this purpose. – Rob Keniger Sep 06 '09 at 23:39
  • 2
    Poling is discouraged, however it is also the only reliable technique. Poling will work *all* the time, even over network volumes when the file is modified by a different machine. Everyone should at least consider using it. – Abhi Beckert Aug 18 '12 at 08:17
  • "only reliable technique" care to elaborate? Listening for central events seems the better choice. Battery wise. – Sentry.co Apr 09 '16 at 07:24