diff --git a/ios/Classes/SwiftFilePickerWritablePlugin.swift b/ios/Classes/SwiftFilePickerWritablePlugin.swift index 271d99f..1af4004 100644 --- a/ios/Classes/SwiftFilePickerWritablePlugin.swift +++ b/ios/Classes/SwiftFilePickerWritablePlugin.swift @@ -40,7 +40,7 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin { let eventChannel = FlutterEventChannel(name: "design.codeux.file_picker_writable/events", binaryMessenger: registrar.messenger()) eventChannel.setStreamHandler(self) } - + private func logDebug(_ message: String) { print("DEBUG", "FilePickerWritablePlugin:", message) sendEvent(event: ["type": "log", "level": "DEBUG", "message": message]) @@ -95,7 +95,7 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin { result(FlutterError(code: "UnknownError", message: "\(error)", details: nil)) } } - + func readFile(identifier: String, result: @escaping FlutterResult) throws { guard let bookmark = Data(base64Encoded: identifier) else { result(FlutterError(code: "InvalidDataError", message: "Unable to decode bookmark.", details: nil)) @@ -104,19 +104,29 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin { var isStale: Bool = false let url = try URL(resolvingBookmarkData: bookmark, bookmarkDataIsStale: &isStale) logDebug("url: \(url) / isStale: \(isStale)"); - let securityScope = url.startAccessingSecurityScopedResource() - defer { - if securityScope { - url.stopAccessingSecurityScopedResource() + DispatchQueue.global(qos: .userInitiated).async { [self] in + let securityScope = url.startAccessingSecurityScopedResource() + defer { + if securityScope { + url.stopAccessingSecurityScopedResource() + } + } + if !securityScope { + logDebug("Warning: startAccessingSecurityScopedResource is false for \(url).") + } + do { + let copiedFile = try _copyToTempDirectory(url: url) + DispatchQueue.main.async { [self] in + result(_fileInfoResult(tempFile: copiedFile, originalURL: url, bookmark: bookmark)) + } + } catch { + DispatchQueue.main.async { + result(FlutterError(code: "UnknownError", message: "\(error)", details: nil)) + } } } - if !securityScope { - logDebug("Warning: startAccessingSecurityScopedResource is false for \(url).") - } - let copiedFile = try _copyToTempDirectory(url: url) - result(_fileInfoResult(tempFile: copiedFile, originalURL: url, bookmark: bookmark)) } - + func writeFile(identifier: String, path: String, result: @escaping FlutterResult) throws { guard let bookmark = Data(base64Encoded: identifier) else { throw FilePickerError.invalidArguments(message: "Unable to decode bookmark/identifier.") @@ -128,11 +138,11 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin { let sourceFile = URL(fileURLWithPath: path) result(_fileInfoResult(tempFile: sourceFile, originalURL: url, bookmark: bookmark)) } - + // TODO: skipDestinationStartAccess is not doing anything right now. maybe get rid of it. private func _writeFile(path: String, destination: URL, skipDestinationStartAccess: Bool = false) throws { let sourceFile = URL(fileURLWithPath: path) - + let destAccess = destination.startAccessingSecurityScopedResource() if !destAccess { logDebug("Warning: startAccessingSecurityScopedResource is false for \(destination) (destination); skipDestinationStartAccess=\(skipDestinationStartAccess)") @@ -154,7 +164,7 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin { let data = try Data(contentsOf: sourceFile) try data.write(to: destination, options: .atomicWrite) } - + func openFilePickerForCreate(path: String, result: @escaping FlutterResult) { if (_filePickerResult != nil) { result(FlutterError(code: "DuplicatedCall", message: "Only one file open call at a time.", details: nil)) @@ -215,7 +225,7 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin { } return tempFile } - + private func _prepareUrlForReading(url: URL, persistable: Bool) throws -> [String: String] { let securityScope = url.startAccessingSecurityScopedResource() defer { @@ -231,7 +241,7 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin { let bookmark = try url.bookmarkData() return _fileInfoResult(tempFile: tempFile, originalURL: url, bookmark: bookmark, persistable: persistable) } - + private func _fileInfoResult(tempFile: URL, originalURL: URL, bookmark: Data, persistable: Bool = true) -> [String: String] { let identifier = bookmark.base64EncodedString() return [ @@ -244,51 +254,54 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin { } private func _sendFilePickerResult(_ result: Any?) { - if let _result = _filePickerResult { - _result(result) + DispatchQueue.main.async { [self] in + if let _result = _filePickerResult { + _result(result) + } + _filePickerResult = nil } - _filePickerResult = nil } } extension SwiftFilePickerWritablePlugin : UIDocumentPickerDelegate { public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) { - do { - if let path = _filePickerPath { - _filePickerPath = nil - guard url.startAccessingSecurityScopedResource() else { - throw FilePickerError.readError(message: "Unable to acquire acces to \(url)") + DispatchQueue.global(qos: .userInitiated).async { [self] in + do { + if let path = _filePickerPath { + _filePickerPath = nil + guard url.startAccessingSecurityScopedResource() else { + throw FilePickerError.readError(message: "Unable to acquire acces to \(url)") + } + logDebug("Need to write \(path) to \(url)") + let sourceFile = URL(fileURLWithPath: path) + let targetFile = url.appendingPathComponent(sourceFile.lastPathComponent) +// if !targetFile.startAccessingSecurityScopedResource() { +// logDebug("Warning: Unnable to acquire acces to \(targetFile)") +// } +// defer { +// targetFile.stopAccessingSecurityScopedResource() +// } + try _writeFile(path: path, destination: targetFile, skipDestinationStartAccess: true) + + let tempFile = try _copyToTempDirectory(url: targetFile) + // Get bookmark *after* ensuring file has been created! + let bookmark = try targetFile.bookmarkData() + _sendFilePickerResult(_fileInfoResult(tempFile: tempFile, originalURL: targetFile, bookmark: bookmark)) + return } - logDebug("Need to write \(path) to \(url)") - let sourceFile = URL(fileURLWithPath: path) - let targetFile = url.appendingPathComponent(sourceFile.lastPathComponent) -// if !targetFile.startAccessingSecurityScopedResource() { -// logDebug("Warning: Unnable to acquire acces to \(targetFile)") -// } -// defer { -// targetFile.stopAccessingSecurityScopedResource() -// } - try _writeFile(path: path, destination: targetFile, skipDestinationStartAccess: true) - - let tempFile = try _copyToTempDirectory(url: targetFile) - // Get bookmark *after* ensuring file has been created! - let bookmark = try targetFile.bookmarkData() - _sendFilePickerResult(_fileInfoResult(tempFile: tempFile, originalURL: targetFile, bookmark: bookmark)) + _sendFilePickerResult(try _prepareUrlForReading(url: url, persistable: true)) + } catch { + _sendFilePickerResult(FlutterError(code: "ErrorProcessingResult", message: "Error handling result url \(url): \(error)", details: nil)) return } - _sendFilePickerResult(try _prepareUrlForReading(url: url, persistable: true)) - } catch { - _sendFilePickerResult(FlutterError(code: "ErrorProcessingResult", message: "Error handling result url \(url): \(error)", details: nil)) - return } - } - + public func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { _sendFilePickerResult(nil) } - + } // application delegate methods.. @@ -305,13 +318,13 @@ extension SwiftFilePickerWritablePlugin: FlutterApplicationLifeCycleDelegate { } return _handle(url: url, persistable: persistable) } - + public func application(_ application: UIApplication, handleOpen url: URL) -> Bool { logDebug("handleOpen for \(url)") // This is an old API predating open-in-place support(?) return _handle(url: url, persistable: false) } - + public func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]) -> Void) -> Bool { // (handle universal links) // Get URL components from the incoming user activity @@ -324,7 +337,7 @@ extension SwiftFilePickerWritablePlugin: FlutterApplicationLifeCycleDelegate { // TODO: Confirm that persistable should be true here return _handle(url: incomingURL, persistable: true) } - + private func _handle(url: URL, persistable: Bool) -> Bool { // if (!url.isFileURL) { // logDebug("url \(url) is not a file url. ignoring it for now.") @@ -337,7 +350,7 @@ extension SwiftFilePickerWritablePlugin: FlutterApplicationLifeCycleDelegate { _handleUrl(url: url, persistable: persistable) return true } - + private func _handleUrl(url: URL, persistable: Bool) { do { if (url.isFileURL) { @@ -386,18 +399,20 @@ extension SwiftFilePickerWritablePlugin: FlutterStreamHandler { } return nil } - + public func onCancel(withArguments arguments: Any?) -> FlutterError? { _eventSink = nil return nil } - + private func sendEvent(event: [String: String]) { if let _eventSink = _eventSink { - _eventSink(event) + DispatchQueue.main.async { + _eventSink(event) + } } else { _eventQueue.append(event) } } - + }