FilesystemOperationExecutor.swift 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import Capacitor
  2. import Foundation
  3. import IONFilesystemLib
  4. import Combine
  5. class FilesystemOperationExecutor {
  6. let service: FileService
  7. private var cancellables = Set<AnyCancellable>()
  8. init(service: FileService) {
  9. self.service = service
  10. }
  11. func execute(_ operation: FilesystemOperation, _ call: CAPPluginCall) {
  12. do {
  13. var resultData: PluginCallResultData?
  14. switch operation {
  15. case .readFile(let url, let encoding):
  16. let data = try service.readEntireFile(atURL: url, withEncoding: encoding).textValue
  17. resultData = [Constants.ResultDataKey.data: data]
  18. case .readFileInChunks(let url, let encoding, let chunkSize):
  19. try processFileInChunks(at: url, withEncoding: encoding, chunkSize: chunkSize, for: operation, call)
  20. return
  21. case .write(let url, let encodingMapper, let recursive):
  22. try service.saveFile(atURL: url, withEncodingAndData: encodingMapper, includeIntermediateDirectories: recursive)
  23. resultData = [Constants.ResultDataKey.uri: url.absoluteString]
  24. case .append(let url, let encodingMapper, let recursive):
  25. try service.appendData(encodingMapper, atURL: url, includeIntermediateDirectories: recursive)
  26. case .delete(let url):
  27. try service.deleteFile(atURL: url)
  28. case .mkdir(let url, let recursive):
  29. try service.createDirectory(atURL: url, includeIntermediateDirectories: recursive)
  30. case .rmdir(let url, let recursive):
  31. try service.removeDirectory(atURL: url, includeIntermediateDirectories: recursive)
  32. case .readdir(let url):
  33. let directoryAttributes = try service.listDirectory(atURL: url)
  34. .map { try fetchItemAttributesJSObject(using: service, atURL: $0) }
  35. resultData = [Constants.ResultDataKey.files: directoryAttributes]
  36. case .stat(let url):
  37. resultData = try fetchItemAttributesJSObject(using: service, atURL: url)
  38. case .getUri(let url):
  39. resultData = [Constants.ResultDataKey.uri: url.absoluteString]
  40. case .rename(let source, let destination):
  41. try service.renameItem(fromURL: source, toURL: destination)
  42. case .copy(let source, let destination):
  43. try service.copyItem(fromURL: source, toURL: destination)
  44. resultData = [Constants.ResultDataKey.uri: destination.absoluteString]
  45. }
  46. call.handleSuccess(resultData)
  47. } catch {
  48. call.handleError(mapError(error, for: operation))
  49. }
  50. }
  51. }
  52. private extension FilesystemOperationExecutor {
  53. func processFileInChunks(at url: URL, withEncoding encoding: IONFILEEncoding, chunkSize: Int, for operation: FilesystemOperation, _ call: CAPPluginCall) throws {
  54. let chunkSizeToUse = chunkSizeToUse(basedOn: chunkSize, and: encoding)
  55. try service.readFileInChunks(atURL: url, withEncoding: encoding, andChunkSize: chunkSizeToUse)
  56. .sink(receiveCompletion: { completion in
  57. switch completion {
  58. case .finished:
  59. call.handleSuccess([Constants.ResultDataKey.data: Constants.ConfigurationValue.endOfFile])
  60. case .failure(let error):
  61. call.handleError(self.mapError(error, for: operation))
  62. }
  63. }, receiveValue: {
  64. call.handleSuccess([Constants.ResultDataKey.data: $0.textValue], true)
  65. })
  66. .store(in: &cancellables)
  67. }
  68. private func chunkSizeToUse(basedOn chunkSize: Int, and encoding: IONFILEEncoding) -> Int {
  69. // When dealing with byte buffers, we need chunk size that are multiples of 3
  70. // We're treating byte buffers as base64 data, and size multiple of 3 makes it so that chunks can be concatenated
  71. encoding == .byteBuffer ? chunkSize - chunkSize % 3 + 3 : chunkSize
  72. }
  73. func mapError(_ error: Error, for operation: FilesystemOperation) -> FilesystemError {
  74. var path = ""
  75. var method: IONFileMethod = IONFileMethod.getUri
  76. switch operation {
  77. case .readFile(let url, _): path = url.absoluteString; method = .readFile
  78. case .readFileInChunks(let url, _, _): path = url.absoluteString; method = .readFileInChunks
  79. case .write(let url, _, _): path = url.absoluteString; method = .writeFile
  80. case .append(let url, _, _): path = url.absoluteString; method = .appendFile
  81. case .delete(let url): path = url.absoluteString; method = .deleteFile
  82. case .mkdir(let url, _): path = url.absoluteString; method = .mkdir
  83. case .rmdir(let url, _): path = url.absoluteString; method = .rmdir
  84. case .readdir(let url): path = url.absoluteString; method = .readdir
  85. case .stat(let url): path = url.absoluteString; method = .stat
  86. case .getUri(let url): return FilesystemError.invalidPath(url.absoluteString)
  87. case .rename(let sourceUrl, _): path = sourceUrl.absoluteString; method = .rename
  88. case .copy(let sourceUrl, _): path = sourceUrl.absoluteString; method = .copy
  89. }
  90. return mapError(error, withPath: path, andMethod: method)
  91. }
  92. private func mapError(_ error: Error, withPath path: String, andMethod method: IONFileMethod) -> FilesystemError {
  93. return switch error {
  94. case IONFILEDirectoryManagerError.notEmpty: .cannotDeleteChildren
  95. case IONFILEDirectoryManagerError.alreadyExists: .directoryAlreadyExists(path)
  96. case IONFILEFileManagerError.missingParentFolder: .parentDirectoryMissing
  97. case IONFILEFileManagerError.fileNotFound: .fileNotFound(method: method, path)
  98. default: .operationFailed(method: method, error)
  99. }
  100. }
  101. func fetchItemAttributesJSObject(using service: FileService, atURL url: URL) throws -> IONFILEItemAttributeModel.JSResult {
  102. let attributes = try service.getItemAttributes(atURL: url)
  103. return attributes.toJSResult(with: url)
  104. }
  105. }