I'm using NSMetadataQuery
and NotificationCenter
, to perform file downloading from iCloud.
- Construct
NSMetadataQuery
with predicateNSPredicate(format: "%K == %@", NSMetadataItemFSNameKey, filename)
- Observe
NSMetadataQueryDidUpdate
&NSMetadataQueryDidFinishGathering
usingNotificationCenter
. - Check file status
NSMetadataUbiquitousItemDownloadingStatusKey
. If the file is up-to-date, copy the file to destination directory, and jump to step 6. - Perform
FileManager.default.startDownloadingUbiquitousItem
- Receive file downloading status in
NSMetadataQueryDidUpdate
callback. If the file is up-to-date, copy the file to destination directory, and jump to step 6. - Perform cleanup by removing all observers.
If file is not available in iCloud, no notification received.
We wish, even if the file doesn't exist, we will still be notified, so that we have chance to perform cleanup (step 6)
Here's the code snippet to perform iCloud download.
DownloadManager.swift
class DownloadManager {
static let INSTANCE = DownloadManager()
var downloaders = [iCloudDocumentDownloader]()
private init() {
}
func append(filename: String, destinationDirectory: URL) {
let downloader = iCloudDocumentDownloader(filename: filename, destinationDirectory: destinationDirectory)
downloaders.append(downloader)
}
func removeAll(_ downloader: iCloudDocumentDownloader) {
downloaders.removeAll{$0 === downloader}
}
}
iCloudDocumentDownloader.swift
class iCloudDocumentDownloader {
private let filename: String
private let destinationDirectory: URL
private let metadataQuery = NSMetadataQuery()
private static let operationQueue: OperationQueue = {
let operationQueue = OperationQueue()
operationQueue.name = "com.yocto.wenote.operationQueueForiCloudDocument"
operationQueue.maxConcurrentOperationCount = 1
operationQueue.qualityOfService = .userInitiated
return operationQueue
}()
deinit {
NotificationCenter.default.removeObserver(self)
}
private func bye() {
DownloadManager.INSTANCE.removeAll(self)
}
init(filename: String, destinationDirectory: URL) {
self.filename = filename
self.destinationDirectory = destinationDirectory
metadataQuery.operationQueue = iCloudDocumentDownloader.operationQueue
metadataQuery.predicate = NSPredicate(format: "%K == %@", NSMetadataItemFSNameKey, filename)
metadataQuery.searchScopes = [
NSMetadataQueryUbiquitousDocumentsScope
]
NotificationCenter.default.addObserver(self, selector: #selector(didUpdate), name: NSNotification.Name.NSMetadataQueryDidUpdate, object: metadataQuery)
NotificationCenter.default.addObserver(self, selector: #selector(didFinishGathering), name: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: metadataQuery)
metadataQuery.start()
}
@objc func didUpdate(_ notification: Notification) {
guard let metadataQuery = notification.object as? NSMetadataQuery else { return }
metadataQuery.enumerateResults { [weak self] (item: Any, index: Int, stop: UnsafeMutablePointer<ObjCBool>) in
guard let self = self else { return }
guard let metadataItem = item as? NSMetadataItem else { return }
guard let status = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String else { return }
guard let url = metadataItem.value(forAttribute: NSMetadataItemURLKey) as? URL else { return }
if status == NSMetadataUbiquitousItemDownloadingStatusCurrent {
if !destinationDirectory.createCompleteDirectoryHierarchyIfDoesNotExist() {
self.bye()
// Early return.
return
}
let destinationURL = destinationDirectory.appendingPathComponent(filename, isDirectory: false)
do {
try FileManager.default.copyItem(at: url, to: destinationURL)
} catch {
error_log(error)
}
self.bye()
} else if let error = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingErrorKey) as? NSError {
error_log(error)
self.bye()
} else {
}
}
}
@objc func didFinishGathering(_ notification: Notification) {
guard let metadataQuery = notification.object as? NSMetadataQuery else { return }
metadataQuery.enumerateResults { [weak self] (item: Any, index: Int, stop: UnsafeMutablePointer<ObjCBool>) in
guard let self = self else { return }
guard let metadataItem = item as? NSMetadataItem else { return }
guard let status = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String else { return }
guard let url = metadataItem.value(forAttribute: NSMetadataItemURLKey) as? URL else { return }
if status == NSMetadataUbiquitousItemDownloadingStatusCurrent {
if !destinationDirectory.createCompleteDirectoryHierarchyIfDoesNotExist() {
self.bye()
// Early return.
return
}
let destinationURL = destinationDirectory.appendingPathComponent(filename, isDirectory: false)
do {
try FileManager.default.copyItem(at: url, to: destinationURL)
} catch {
error_log(error)
}
self.bye()
} else if let error = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingErrorKey) as? NSError {
error_log(error)
self.bye()
} else {
do {
try FileManager.default.startDownloadingUbiquitousItem(at: url)
} catch {
error_log(error)
self.bye()
}
}
}
}
}