iCloud NSMetadataQuery and updates (NSMetadataQueryUpdateChangedItemsKey)

494 Views Asked by At

I am monitoring my iCloud sandbox (iOS) using an NSMetaDataQuery are recommended - and all is working well.

I'm attempting to use the NSMetadataQueryUpdateChangedItemsKey in the NSMetadataQueryDidUpdateNotification in order to efficiently update my internal model of the file system. Challenge I have is that when a file is moved/renamed, how can I know the original file path - so I can update my model?

It appears that the NSMetaDataItem objects are persistent (i.e. the same object instance is updated when the path changes) so I could use the pointer value as a kind of index into my model. However - I'd be taking advantage of an apparent implementation detail (which could change.) Perhaps NSMetaDataItems are recycled when memory runs low?

Anyone know how this should be done (or if it is actually the case that NSMetaDataItem objects persist for the lifetime of the NSMetaDataQuery - and stay 'attached' to the same file system item.)

2

There are 2 best solutions below

0
On

Documentation mentions that results are suitable for Cocoa Bindings, which means that most likely those objects are persistent.

I use more hardcore combination of NSFilePresenter and NSMetadataQuery running side by side to monitor documents in container. NSFilePresenter has convenient API for detecting when files were being moved:

func presentedSubitem(at oldURL: URL, didMoveTo newURL: URL)

For that to work though when you move files in container you have to explicitly notify file coordinator that you're moving file (see points 1-3):

let fc = NSFileCoordinator()
var error: NSError?

fc.coordinate(writingItemAt: from, options: .forMoving, writingItemAt: to, options: .forReplacing, error: &error, byAccessor: {
    (fromURL, toURL) in
    do {
        // 1
        fc.item(at: fromURL, willMoveTo: toURL)

        try FileManager.default.moveItem(at: fromURL, to: toURL)

        // 2
        fc.item(at: fromURL, didMoveTo: toURL)
    } catch {
        // 3
        fc.item(at: fromURL, didMoveTo: fromURL)
    }
})
1
On

Yes, the NSMetadataQuery doesn't provide a way to consult the previous path.

When an item is moved, its index in the NSMetadataQuery results remains the same. So we can duplicate the path of the results and when the update kicks in, we only need to check the NSMetadataItem at the exact position of the duplicated array.

    if let updatedObj = obj.userInfo?[NSMetadataQueryUpdateChangedItemsKey] as! [NSMetadataItem]? {

        for it in updatedObj {

            let url = it.valueForAttribute(NSMetadataItemURLKey) as! NSURL
            let value = it.valueForAttribute(NSMetadataUbiquitousItemIsUploadedKey) as! NSNumber

            print("Path: " + url.path!)
            print("Updated: " + value.stringValue)

            let index = metaDataQuery.indexOfResult(it)
            let prevPath = duplicatedPathArray[index]

            if (prevPath != url.path!) {
                print("File Moved. Previous path: " + prevPath)
                duplicatePath()
            }
        }
    }

Make sure you update the array each time a file is added or removed.