From bd690b21a5c6b10f2a872686a7e7de077640b8fb Mon Sep 17 00:00:00 2001 From: Aaron Madlon-Kay Date: Tue, 27 Aug 2024 21:22:14 +0900 Subject: [PATCH 1/5] Clean whitespace --- .../SwiftFilePickerWritablePlugin.swift | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/ios/Classes/SwiftFilePickerWritablePlugin.swift b/ios/Classes/SwiftFilePickerWritablePlugin.swift index 271d99f..ace3226 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)) @@ -116,7 +116,7 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin { 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 +128,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 +154,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 +215,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 +231,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 [ @@ -270,7 +270,7 @@ extension SwiftFilePickerWritablePlugin : UIDocumentPickerDelegate { // 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() @@ -282,13 +282,13 @@ extension SwiftFilePickerWritablePlugin : UIDocumentPickerDelegate { _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 +305,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 +324,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 +337,7 @@ extension SwiftFilePickerWritablePlugin: FlutterApplicationLifeCycleDelegate { _handleUrl(url: url, persistable: persistable) return true } - + private func _handleUrl(url: URL, persistable: Bool) { do { if (url.isFileURL) { @@ -386,12 +386,12 @@ 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) @@ -399,5 +399,5 @@ extension SwiftFilePickerWritablePlugin: FlutterStreamHandler { _eventQueue.append(event) } } - + } From d5b42d23765a380fb5caf070eef4dee91d015596 Mon Sep 17 00:00:00 2001 From: Aaron Madlon-Kay Date: Tue, 27 Aug 2024 20:36:24 +0900 Subject: [PATCH 2/5] Do long-running tasks off of UI thread on iOS --- .../SwiftFilePickerWritablePlugin.swift | 71 +++++++++++-------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/ios/Classes/SwiftFilePickerWritablePlugin.swift b/ios/Classes/SwiftFilePickerWritablePlugin.swift index ace3226..69684a4 100644 --- a/ios/Classes/SwiftFilePickerWritablePlugin.swift +++ b/ios/Classes/SwiftFilePickerWritablePlugin.swift @@ -113,8 +113,16 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin { if !securityScope { logDebug("Warning: startAccessingSecurityScopedResource is false for \(url).") } - let copiedFile = try _copyToTempDirectory(url: url) - result(_fileInfoResult(tempFile: copiedFile, originalURL: url, bookmark: bookmark)) + DispatchQueue.global(qos: .userInitiated).async { [self] in + do { + let copiedFile = try _copyToTempDirectory(url: url) + DispatchQueue.main.async { [self] in + result(_fileInfoResult(tempFile: copiedFile, originalURL: url, bookmark: bookmark)) + } + } catch { + result(FlutterError(code: "UnknownError", message: "\(error)", details: nil)) + } + } } func writeFile(identifier: String, path: String, result: @escaping FlutterResult) throws { @@ -244,45 +252,48 @@ 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) { From 8ebcd7b0bef5cc0bf14284a07782917dcbd97144 Mon Sep 17 00:00:00 2001 From: Aaron Madlon-Kay Date: Tue, 10 Sep 2024 08:20:51 +0900 Subject: [PATCH 3/5] Ensure error is reported on main thread --- ios/Classes/SwiftFilePickerWritablePlugin.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ios/Classes/SwiftFilePickerWritablePlugin.swift b/ios/Classes/SwiftFilePickerWritablePlugin.swift index 69684a4..5303fb1 100644 --- a/ios/Classes/SwiftFilePickerWritablePlugin.swift +++ b/ios/Classes/SwiftFilePickerWritablePlugin.swift @@ -120,7 +120,9 @@ public class SwiftFilePickerWritablePlugin: NSObject, FlutterPlugin { result(_fileInfoResult(tempFile: copiedFile, originalURL: url, bookmark: bookmark)) } } catch { - result(FlutterError(code: "UnknownError", message: "\(error)", details: nil)) + DispatchQueue.main.async { + result(FlutterError(code: "UnknownError", message: "\(error)", details: nil)) + } } } } From e66bc58bb3e7b6ace03b06a16877a404573b34e6 Mon Sep 17 00:00:00 2001 From: Aaron Madlon-Kay Date: Tue, 10 Sep 2024 08:34:33 +0900 Subject: [PATCH 4/5] Access security scope in same thread so defer fires at right time --- .../SwiftFilePickerWritablePlugin.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ios/Classes/SwiftFilePickerWritablePlugin.swift b/ios/Classes/SwiftFilePickerWritablePlugin.swift index 5303fb1..8d5450a 100644 --- a/ios/Classes/SwiftFilePickerWritablePlugin.swift +++ b/ios/Classes/SwiftFilePickerWritablePlugin.swift @@ -104,16 +104,16 @@ 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() - } - } - if !securityScope { - logDebug("Warning: startAccessingSecurityScopedResource is false for \(url).") - } 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 From 31586534bc0f62f16d4dee650750fb7ee5deb905 Mon Sep 17 00:00:00 2001 From: Aaron Madlon-Kay Date: Tue, 10 Sep 2024 08:46:18 +0900 Subject: [PATCH 5/5] Ensure events are sent on main thread --- ios/Classes/SwiftFilePickerWritablePlugin.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ios/Classes/SwiftFilePickerWritablePlugin.swift b/ios/Classes/SwiftFilePickerWritablePlugin.swift index 8d5450a..1af4004 100644 --- a/ios/Classes/SwiftFilePickerWritablePlugin.swift +++ b/ios/Classes/SwiftFilePickerWritablePlugin.swift @@ -407,7 +407,9 @@ extension SwiftFilePickerWritablePlugin: FlutterStreamHandler { private func sendEvent(event: [String: String]) { if let _eventSink = _eventSink { - _eventSink(event) + DispatchQueue.main.async { + _eventSink(event) + } } else { _eventQueue.append(event) }