LegacyFilesystemImplementation.swift 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import Foundation
  2. import Capacitor
  3. @objc public class LegacyFilesystemImplementation: NSObject {
  4. public typealias ProgressEmitter = (_ bytes: Int64, _ contentLength: Int64) -> Void
  5. // swiftlint:disable function_body_length
  6. @objc public func downloadFile(call: CAPPluginCall, emitter: @escaping ProgressEmitter, config: InstanceConfiguration?) throws {
  7. let directory = call.getString("directory", "DOCUMENTS")
  8. guard let path = call.getString("path") else {
  9. call.reject("Invalid file path")
  10. return
  11. }
  12. guard var urlString = call.getString("url") else { throw URLError(.badURL) }
  13. func handleDownload(downloadLocation: URL?, response: URLResponse?, error: Error?) {
  14. if let error = error {
  15. CAPLog.print("Error on download file", String(describing: downloadLocation), String(describing: response), String(describing: error))
  16. call.reject(error.localizedDescription, "DOWNLOAD", error, nil)
  17. return
  18. }
  19. if let httpResponse = response as? HTTPURLResponse {
  20. if !(200...299).contains(httpResponse.statusCode) {
  21. CAPLog.print("Error downloading file:", urlString, httpResponse)
  22. call.reject("Error downloading file: \(urlString)", "DOWNLOAD")
  23. return
  24. }
  25. HttpRequestHandler.setCookiesFromResponse(httpResponse, config)
  26. }
  27. guard let location = downloadLocation else {
  28. call.reject("Unable to get file after downloading")
  29. return
  30. }
  31. let fileManager = FileManager.default
  32. if let foundDir = getDirectory(directory: directory) {
  33. let dir = fileManager.urls(for: foundDir, in: .userDomainMask).first
  34. do {
  35. let dest = dir!.appendingPathComponent(path)
  36. CAPLog.print("Attempting to write to file destination: \(dest.absoluteString)")
  37. if !FileManager.default.fileExists(atPath: dest.deletingLastPathComponent().absoluteString) {
  38. try FileManager.default.createDirectory(at: dest.deletingLastPathComponent(), withIntermediateDirectories: true, attributes: nil)
  39. }
  40. if FileManager.default.fileExists(atPath: dest.relativePath) {
  41. do {
  42. CAPLog.print("File already exists. Attempting to remove file before writing.")
  43. try fileManager.removeItem(at: dest)
  44. } catch let error {
  45. call.reject("Unable to remove existing file: \(error.localizedDescription)")
  46. return
  47. }
  48. }
  49. try fileManager.moveItem(at: location, to: dest)
  50. CAPLog.print("Downloaded file successfully! \(dest.absoluteString)")
  51. call.resolve(["path": dest.absoluteString])
  52. } catch let error {
  53. call.reject("Unable to download file: \(error.localizedDescription)", "DOWNLOAD", error)
  54. return
  55. }
  56. } else {
  57. call.reject("Unable to download file. Couldn't find directory \(directory)")
  58. }
  59. }
  60. let method = call.getString("method", "GET")
  61. let headers = (call.getObject("headers") ?? [:]) as [String: Any]
  62. let params = (call.getObject("params") ?? [:]) as [String: Any]
  63. let responseType = call.getString("responseType", "text")
  64. let connectTimeout = call.getDouble("connectTimeout")
  65. let readTimeout = call.getDouble("readTimeout")
  66. if urlString == urlString.removingPercentEncoding {
  67. guard let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { throw URLError(.badURL) }
  68. urlString = encodedUrlString
  69. }
  70. let progress = call.getBool("progress", false)
  71. let request = try HttpRequestHandler.CapacitorHttpRequestBuilder()
  72. .setUrl(urlString)
  73. .setMethod(method)
  74. .setUrlParams(params)
  75. .openConnection()
  76. .build()
  77. request.setRequestHeaders(headers)
  78. // Timeouts in iOS are in seconds. So read the value in millis and divide by 1000
  79. let timeout = (connectTimeout ?? readTimeout ?? 600000.0) / 1000.0
  80. request.setTimeout(timeout)
  81. if let data = call.options["data"] as? JSValue {
  82. do {
  83. try request.setRequestBody(data)
  84. } catch {
  85. // Explicitly reject if the http request body was not set successfully,
  86. // so as to not send a known malformed request, and to provide the developer with additional context.
  87. call.reject(error.localizedDescription, (error as NSError).domain, error, nil)
  88. return
  89. }
  90. }
  91. var session: URLSession!
  92. var task: URLSessionDownloadTask!
  93. let urlRequest = request.getUrlRequest()
  94. if progress {
  95. class ProgressDelegate: NSObject, URLSessionDataDelegate, URLSessionDownloadDelegate {
  96. private var handler: (URL?, URLResponse?, Error?) -> Void
  97. private var downloadLocation: URL?
  98. private var response: URLResponse?
  99. private var emitter: (Int64, Int64) -> Void
  100. private var lastEmitTimestamp: TimeInterval = 0.0
  101. init(downloadHandler: @escaping (URL?, URLResponse?, Error?) -> Void, progressEmitter: @escaping (Int64, Int64) -> Void) {
  102. handler = downloadHandler
  103. emitter = progressEmitter
  104. }
  105. func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
  106. let currentTimestamp = Date().timeIntervalSince1970
  107. let timeElapsed = currentTimestamp - lastEmitTimestamp
  108. if totalBytesExpectedToWrite > 0 {
  109. if timeElapsed >= 0.1 {
  110. emitter(totalBytesWritten, totalBytesExpectedToWrite)
  111. lastEmitTimestamp = currentTimestamp
  112. }
  113. } else {
  114. emitter(totalBytesWritten, 0)
  115. lastEmitTimestamp = currentTimestamp
  116. }
  117. }
  118. func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
  119. downloadLocation = location
  120. handler(downloadLocation, downloadTask.response, downloadTask.error)
  121. }
  122. func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
  123. if error != nil {
  124. handler(downloadLocation, task.response, error)
  125. }
  126. }
  127. }
  128. let progressDelegate = ProgressDelegate(downloadHandler: handleDownload, progressEmitter: emitter)
  129. session = URLSession(configuration: .default, delegate: progressDelegate, delegateQueue: nil)
  130. task = session.downloadTask(with: urlRequest)
  131. } else {
  132. task = URLSession.shared.downloadTask(with: urlRequest, completionHandler: handleDownload)
  133. }
  134. task.resume()
  135. }
  136. // swiftlint:enable function_body_length
  137. /**
  138. * Get the SearchPathDirectory corresponding to the JS string
  139. */
  140. private func getDirectory(directory: String?) -> FileManager.SearchPathDirectory? {
  141. if let directory = directory {
  142. switch directory {
  143. case "CACHE":
  144. return .cachesDirectory
  145. case "LIBRARY":
  146. return .libraryDirectory
  147. default:
  148. return .documentDirectory
  149. }
  150. }
  151. return nil
  152. }
  153. }