Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 70 additions & 55 deletions ios/Classes/SwiftFilePickerWritablePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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))
Expand All @@ -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.")
Expand All @@ -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)")
Expand All @@ -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))
Expand Down Expand Up @@ -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 {
Expand All @@ -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 [
Expand All @@ -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..
Expand All @@ -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
Expand All @@ -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.")
Expand All @@ -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) {
Expand Down Expand Up @@ -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)
}
}

}