func fetchThumbnails(for itemIdentifiers: [NSFileProviderItemIdentifier], requestedSize size: CGSize, perThumbnailCompletionHandler: @escaping (NSFileProviderItemIdentifier, Data?, Error?) -> Void, completionHandler: @escaping (Error?) -> Void) -> Progress { os_log("fetchThumbnails: Called with itemIdentifiers = %{public}@, requestedSize = (width: %ld, height: x%ld)", log: OSLog.fileprovider, FileProviderUtils.toString(itemIdentifiers), size.width, size.height) let progress = Progress(totalUnitCount: Int64(itemIdentifiers.count)) let group = DispatchGroup() for itemIdentifier in itemIdentifiers { let identifierComponents = itemIdentifier.rawValue.components(separatedBy: "/") let photoIdentifier = identifierComponents[0] let filename = identifierComponents[1] os_log("fetchThumbnails: Fetching thumbnail for %{public}@", log: OSLog.fileprovider, photoIdentifier) let request = ThumbnailerClient.shared.thumbnailRequest(identifier: photoIdentifier) let urlSession = URLSession(configuration: ThumbnailerClient.shared.getConfiguration()) // Download the thumbnail to disk into a temporary location. group.enter() let urlSessionDownloadTask = urlSession.downloadTask(with: request) { temporaryURL, response, error in if let error = error { os_log("fetchThumbnails: Error: %{public}@", log: OSLog.fileprovider, error.localizedDescription) completionHandler(error) return } guard let httpStatusCode = HTTPUtils.getStatusCode(from: response) else { os_log("fetchThumbnails: This is not supposed to happen. httpStatusCode was nil.") return } if (httpStatusCode != 200) { os_log("fetchThumbnails: Download task for item identifier %{public}@ failed with http status code %ld", log: OSLog.fileprovider, itemIdentifier.rawValue, httpStatusCode) completionHandler(NSError.fileProviderErrorForNonExistentItem(withIdentifier: itemIdentifier)) return } os_log("fetchThumbnaisl: Download task for item identifier %{public}@ succeeded with http status code %ld", log: OSLog.fileprovider, itemIdentifier.rawValue, httpStatusCode) // If the download succeeds, map a data object to the file. assert(temporaryURL != nil, "temporaryURL is nil") var thumbnailData: Data? = nil do { if let temporaryURL = temporaryURL { thumbnailData = try Data(contentsOf: temporaryURL, options: Data.ReadingOptions.alwaysMapped) } } catch let mappingError { perThumbnailCompletionHandler(itemIdentifier, nil, mappingError) group.leave() return } // The thumbnail download was successful, so we can call the perThumbnailCompletionHandler // and leave the dispatch group perThumbnailCompletionHandler(itemIdentifier, thumbnailData, nil) group.leave() } // Add the download task's progress as a child to the overall progress. progress.addChild(urlSessionDownloadTask.progress, withPendingUnitCount: 1) // Start the download task. urlSessionDownloadTask.resume() } // Run the completionHandler as soon as all thumbnails have been downloaded. group.notify(queue: DispatchQueue.global()) { completionHandler(nil) } return progress }