NCNetworking+Download.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. //
  2. // NCNetworking+Download.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 07/02/24.
  6. // Copyright © 2024 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. import UIKit
  24. import NextcloudKit
  25. import Alamofire
  26. import Queuer
  27. import RealmSwift
  28. extension NCNetworking {
  29. func download(metadata: tableMetadata,
  30. withNotificationProgressTask: Bool,
  31. start: @escaping () -> Void = { },
  32. requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
  33. progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
  34. completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
  35. if metadata.session == sessionDownload {
  36. downloadFile(metadata: metadata, withNotificationProgressTask: withNotificationProgressTask) {
  37. start()
  38. } requestHandler: { request in
  39. requestHandler(request)
  40. } progressHandler: { progress in
  41. progressHandler(progress)
  42. } completion: { afError, error in
  43. completion(afError, error)
  44. }
  45. } else {
  46. downloadFileInBackground(metadata: metadata, start: start, completion: { afError, error in
  47. completion(afError, error)
  48. })
  49. }
  50. }
  51. private func downloadFile(metadata: tableMetadata,
  52. withNotificationProgressTask: Bool,
  53. start: @escaping () -> Void = { },
  54. requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
  55. progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
  56. completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
  57. var downloadTask: URLSessionTask?
  58. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  59. let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)
  60. let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
  61. if metadata.status == global.metadataStatusDownloading || metadata.status == global.metadataStatusUploading {
  62. return completion(nil, NKError())
  63. }
  64. if database.getMetadataFromOcId(metadata.ocId) == nil {
  65. database.addMetadata(metadata)
  66. }
  67. NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, account: metadata.account, options: options, requestHandler: { request in
  68. self.database.setMetadataSession(ocId: metadata.ocId,
  69. status: self.global.metadataStatusDownloading)
  70. requestHandler(request)
  71. }, taskHandler: { task in
  72. downloadTask = task
  73. self.database.setMetadataSession(ocId: metadata.ocId,
  74. sessionTaskIdentifier: task.taskIdentifier,
  75. status: self.global.metadataStatusDownloading)
  76. NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterDownloadStartFile,
  77. object: nil,
  78. userInfo: ["ocId": metadata.ocId,
  79. "ocIdTransfer": metadata.ocIdTransfer,
  80. "session": metadata.session,
  81. "serverUrl": metadata.serverUrl,
  82. "account": metadata.account])
  83. start()
  84. }, progressHandler: { progress in
  85. NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterProgressTask,
  86. object: nil,
  87. userInfo: ["account": metadata.account,
  88. "ocId": metadata.ocId,
  89. "ocIdTransfer": metadata.ocIdTransfer,
  90. "session": metadata.session,
  91. "fileName": metadata.fileName,
  92. "serverUrl": metadata.serverUrl,
  93. "status": NSNumber(value: self.global.metadataStatusDownloading),
  94. "progress": NSNumber(value: progress.fractionCompleted),
  95. "totalBytes": NSNumber(value: progress.totalUnitCount),
  96. "totalBytesExpected": NSNumber(value: progress.completedUnitCount)])
  97. progressHandler(progress)
  98. }) { _, etag, date, length, responseData, afError, error in
  99. var error = error
  100. var dateLastModified: Date?
  101. // this delay was added because for small file the "taskHandler: { task" is not called, so this part of code is not executed
  102. NextcloudKit.shared.nkCommonInstance.backgroundQueue.asyncAfter(deadline: .now() + 0.5) {
  103. if let downloadTask = downloadTask {
  104. if let header = responseData?.response?.allHeaderFields,
  105. let dateString = header["Last-Modified"] as? String {
  106. dateLastModified = NextcloudKit.shared.nkCommonInstance.convertDate(dateString, format: "EEE, dd MMM y HH:mm:ss zzz")
  107. }
  108. if afError?.isExplicitlyCancelledError ?? false {
  109. error = NKError(errorCode: self.global.errorRequestExplicityCancelled, errorDescription: "error request explicity cancelled")
  110. }
  111. self.downloadComplete(fileName: metadata.fileName, serverUrl: metadata.serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, task: downloadTask, error: error)
  112. }
  113. completion(afError, error)
  114. }
  115. }
  116. }
  117. private func downloadFileInBackground(metadata: tableMetadata,
  118. start: @escaping () -> Void = { },
  119. requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
  120. progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
  121. completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
  122. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  123. let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
  124. start()
  125. if let task = NKBackground(nkCommonInstance: NextcloudKit.shared.nkCommonInstance).download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, account: metadata.account) {
  126. NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Download file \(metadata.fileNameView) with task with taskIdentifier \(task.taskIdentifier)")
  127. database.setMetadataSession(ocId: metadata.ocId,
  128. sessionTaskIdentifier: task.taskIdentifier,
  129. status: self.global.metadataStatusDownloading)
  130. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadStartFile,
  131. object: nil,
  132. userInfo: ["ocId": metadata.ocId,
  133. "ocIdTransfer": metadata.ocIdTransfer,
  134. "session": metadata.session,
  135. "serverUrl": metadata.serverUrl,
  136. "account": metadata.account])
  137. completion(nil, NKError())
  138. } else {
  139. database.setMetadataSession(ocId: metadata.ocId,
  140. session: "",
  141. sessionTaskIdentifier: 0,
  142. sessionError: "",
  143. selector: "",
  144. status: self.global.metadataStatusNormal)
  145. completion(nil, NKError(errorCode: self.global.errorResourceNotFound, errorDescription: "task null"))
  146. }
  147. }
  148. func downloadingFinish(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
  149. if let httpResponse = (downloadTask.response as? HTTPURLResponse) {
  150. if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300,
  151. let url = downloadTask.currentRequest?.url,
  152. var serverUrl = url.deletingLastPathComponent().absoluteString.removingPercentEncoding {
  153. let fileName = url.lastPathComponent
  154. if serverUrl.hasSuffix("/") { serverUrl = String(serverUrl.dropLast()) }
  155. if let metadata = database.getResultMetadata(predicate: NSPredicate(format: "serverUrl == %@ AND fileName == %@", serverUrl, fileName)) {
  156. let destinationFilePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)
  157. utilityFileSystem.copyFile(at: location, to: NSURL.fileURL(withPath: destinationFilePath))
  158. }
  159. }
  160. }
  161. }
  162. func downloadComplete(fileName: String,
  163. serverUrl: String,
  164. etag: String?,
  165. date: Date?,
  166. dateLastModified: Date?,
  167. length: Int64,
  168. task: URLSessionTask,
  169. error: NKError) {
  170. if let delegate {
  171. return delegate.downloadComplete(fileName: fileName, serverUrl: serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, task: task, error: error)
  172. }
  173. DispatchQueue.global().async {
  174. guard let url = task.currentRequest?.url,
  175. let metadata = self.database.getMetadata(from: url, sessionTaskIdentifier: task.taskIdentifier) else { return }
  176. if error == .success {
  177. NCTransferProgress.shared.clearCountError(ocIdTransfer: metadata.ocIdTransfer)
  178. #if !EXTENSION
  179. if let result = self.database.getE2eEncryption(predicate: NSPredicate(format: "fileNameIdentifier == %@ AND serverUrl == %@", metadata.fileName, metadata.serverUrl)) {
  180. NCEndToEndEncryption.shared().decryptFile(metadata.fileName, fileNameView: metadata.fileNameView, ocId: metadata.ocId, key: result.key, initializationVector: result.initializationVector, authenticationTag: result.authenticationTag)
  181. }
  182. #endif
  183. self.database.addLocalFile(metadata: metadata)
  184. self.database.setMetadataSession(ocId: metadata.ocId,
  185. session: "",
  186. sessionTaskIdentifier: 0,
  187. sessionError: "",
  188. status: self.global.metadataStatusNormal,
  189. etag: etag)
  190. NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterDownloadedFile,
  191. object: nil,
  192. userInfo: ["ocId": metadata.ocId,
  193. "ocIdTransfer": metadata.ocIdTransfer,
  194. "session": metadata.session,
  195. "serverUrl": metadata.serverUrl,
  196. "account": metadata.account,
  197. "selector": metadata.sessionSelector,
  198. "error": error],
  199. second: 0.5)
  200. } else if error.errorCode == NSURLErrorCancelled || error.errorCode == self.global.errorRequestExplicityCancelled {
  201. NCTransferProgress.shared.clearCountError(ocIdTransfer: metadata.ocIdTransfer)
  202. self.database.setMetadataSession(ocId: metadata.ocId,
  203. session: "",
  204. sessionTaskIdentifier: 0,
  205. sessionError: "",
  206. selector: "",
  207. status: self.global.metadataStatusNormal)
  208. NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterDownloadCancelFile,
  209. object: nil,
  210. userInfo: ["ocId": metadata.ocId,
  211. "ocIdTransfer": metadata.ocIdTransfer,
  212. "session": metadata.session,
  213. "serverUrl": metadata.serverUrl,
  214. "account": metadata.account],
  215. second: 0.5)
  216. } else {
  217. NCTransferProgress.shared.clearCountError(ocIdTransfer: metadata.ocIdTransfer)
  218. self.database.setMetadataSession(ocId: metadata.ocId,
  219. session: "",
  220. sessionTaskIdentifier: 0,
  221. sessionError: "",
  222. selector: "",
  223. status: self.global.metadataStatusNormal)
  224. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterDownloadedFile,
  225. object: nil,
  226. userInfo: ["ocId": metadata.ocId,
  227. "ocIdTransfer": metadata.ocIdTransfer,
  228. "session": metadata.session,
  229. "serverUrl": metadata.serverUrl,
  230. "account": metadata.account,
  231. "selector": metadata.sessionSelector,
  232. "error": error],
  233. second: 0.5)
  234. }
  235. }
  236. }
  237. func downloadProgress(_ progress: Float,
  238. totalBytes: Int64,
  239. totalBytesExpected: Int64,
  240. fileName: String,
  241. serverUrl: String,
  242. session: URLSession,
  243. task: URLSessionTask) {
  244. if let delegate {
  245. return delegate.downloadProgress(progress, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected, fileName: fileName, serverUrl: serverUrl, session: session, task: task)
  246. }
  247. DispatchQueue.global().async {
  248. if let metadata = self.database.getResultMetadataFromFileName(fileName, serverUrl: serverUrl, sessionTaskIdentifier: task.taskIdentifier) {
  249. NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterProgressTask,
  250. object: nil,
  251. userInfo: ["account": metadata.account,
  252. "ocId": metadata.ocId,
  253. "ocIdTransfer": metadata.ocIdTransfer,
  254. "session": metadata.session,
  255. "fileName": metadata.fileName,
  256. "serverUrl": metadata.serverUrl,
  257. "status": NSNumber(value: self.global.metadataStatusDownloading),
  258. "progress": NSNumber(value: progress),
  259. "totalBytes": NSNumber(value: totalBytes),
  260. "totalBytesExpected": NSNumber(value: totalBytesExpected)])
  261. }
  262. }
  263. }
  264. }
  265. class NCOperationDownload: ConcurrentOperation, @unchecked Sendable {
  266. var metadata: tableMetadata
  267. var selector: String
  268. init(metadata: tableMetadata, selector: String) {
  269. self.metadata = tableMetadata.init(value: metadata)
  270. self.selector = selector
  271. }
  272. override func start() {
  273. guard !isCancelled else { return self.finish() }
  274. metadata.session = NCNetworking.shared.sessionDownload
  275. metadata.sessionError = ""
  276. metadata.sessionSelector = selector
  277. metadata.sessionTaskIdentifier = 0
  278. metadata.status = NCGlobal.shared.metadataStatusWaitDownload
  279. NCManageDatabase.shared.addMetadata(metadata)
  280. NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) {
  281. } completion: { _, _ in
  282. self.finish()
  283. }
  284. }
  285. }