NCNetworking+Download.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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 JGProgressHUD
  25. import NextcloudKit
  26. import Alamofire
  27. import Queuer
  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 == NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload {
  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. guard !metadata.isInTransfer else { return completion(nil, NKError()) }
  58. var downloadTask: URLSessionTask?
  59. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  60. let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)
  61. let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
  62. if NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) == nil {
  63. NCManageDatabase.shared.addMetadata(metadata)
  64. }
  65. NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, options: options, requestHandler: { request in
  66. self.downloadRequest[fileNameLocalPath] = request
  67. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  68. status: NCGlobal.shared.metadataStatusDownloading)
  69. requestHandler(request)
  70. }, taskHandler: { task in
  71. downloadTask = task
  72. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  73. taskIdentifier: task.taskIdentifier)
  74. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadStartFile),
  75. object: nil,
  76. userInfo: ["ocId": metadata.ocId,
  77. "serverUrl": metadata.serverUrl,
  78. "account": metadata.account])
  79. start()
  80. }, progressHandler: { progress in
  81. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
  82. object: nil,
  83. userInfo: ["account": metadata.account,
  84. "ocId": metadata.ocId,
  85. "fileName": metadata.fileName,
  86. "serverUrl": metadata.serverUrl,
  87. "status": NSNumber(value: NCGlobal.shared.metadataStatusDownloading),
  88. "progress": NSNumber(value: progress.fractionCompleted),
  89. "totalBytes": NSNumber(value: progress.totalUnitCount),
  90. "totalBytesExpected": NSNumber(value: progress.completedUnitCount)])
  91. progressHandler(progress)
  92. }) { _, etag, date, length, allHeaderFields, afError, error in
  93. var error = error
  94. var dateLastModified: Date?
  95. self.downloadRequest.removeValue(forKey: fileNameLocalPath)
  96. // this delay was added because for small file the "taskHandler: { task" is not called, so this part of code is not executed
  97. NextcloudKit.shared.nkCommonInstance.backgroundQueue.asyncAfter(deadline: .now() + 0.5) {
  98. if let downloadTask = downloadTask {
  99. if let header = allHeaderFields, let dateString = header["Last-Modified"] as? String {
  100. dateLastModified = NextcloudKit.shared.nkCommonInstance.convertDate(dateString, format: "EEE, dd MMM y HH:mm:ss zzz")
  101. }
  102. if afError?.isExplicitlyCancelledError ?? false {
  103. error = NKError(errorCode: NCGlobal.shared.errorRequestExplicityCancelled, errorDescription: "error request explicity cancelled")
  104. }
  105. self.downloadComplete(fileName: metadata.fileName, serverUrl: metadata.serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, task: downloadTask, error: error)
  106. }
  107. completion(afError, error)
  108. }
  109. }
  110. }
  111. private func downloadFileInBackground(metadata: tableMetadata,
  112. start: @escaping () -> Void = { },
  113. requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
  114. progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
  115. completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
  116. let session: URLSession = sessionManagerDownloadBackground
  117. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  118. let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
  119. start()
  120. if let task = nkBackground.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, session: session) {
  121. NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Download file \(metadata.fileNameView) with task with taskIdentifier \(task.taskIdentifier)")
  122. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  123. status: NCGlobal.shared.metadataStatusDownloading,
  124. taskIdentifier: task.taskIdentifier)
  125. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadStartFile),
  126. object: nil,
  127. userInfo: ["ocId": metadata.ocId,
  128. "serverUrl": metadata.serverUrl,
  129. "account": metadata.account])
  130. completion(nil, NKError())
  131. } else {
  132. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  133. session: "",
  134. sessionError: "",
  135. selector: "",
  136. status: NCGlobal.shared.metadataStatusNormal)
  137. completion(nil, NKError(errorCode: NCGlobal.shared.errorResourceNotFound, errorDescription: "task null"))
  138. }
  139. }
  140. func downloadingFinish(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
  141. if let httpResponse = (downloadTask.response as? HTTPURLResponse) {
  142. if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300,
  143. let url = downloadTask.currentRequest?.url,
  144. var serverUrl = url.deletingLastPathComponent().absoluteString.removingPercentEncoding {
  145. let fileName = url.lastPathComponent
  146. if serverUrl.hasSuffix("/") { serverUrl = String(serverUrl.dropLast()) }
  147. if let metadata = NCManageDatabase.shared.getResultMetadata(predicate: NSPredicate(format: "serverUrl == %@ AND fileName == %@", serverUrl, fileName)) {
  148. let destinationFilePath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)
  149. utilityFileSystem.copyFile(at: location, to: NSURL.fileURL(withPath: destinationFilePath))
  150. }
  151. }
  152. }
  153. }
  154. func downloadComplete(fileName: String,
  155. serverUrl: String,
  156. etag: String?,
  157. date: Date?,
  158. dateLastModified: Date?,
  159. length: Int64,
  160. task: URLSessionTask,
  161. error: NKError) {
  162. if let delegate {
  163. return delegate.downloadComplete(fileName: fileName, serverUrl: serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, task: task, error: error)
  164. }
  165. DispatchQueue.global(qos: .userInteractive).async {
  166. guard let url = task.currentRequest?.url,
  167. let metadata = NCManageDatabase.shared.getMetadata(from: url, sessionTaskIdentifier: task.taskIdentifier) else { return }
  168. self.downloadMetadataInBackground.removeValue(forKey: FileNameServerUrl(fileName: fileName, serverUrl: serverUrl))
  169. if error == .success {
  170. self.removeTransferInError(ocId: metadata.ocId)
  171. #if !EXTENSION
  172. if let result = NCManageDatabase.shared.getE2eEncryption(predicate: NSPredicate(format: "fileNameIdentifier == %@ AND serverUrl == %@", metadata.fileName, metadata.serverUrl)) {
  173. NCEndToEndEncryption.shared().decryptFile(metadata.fileName, fileNameView: metadata.fileNameView, ocId: metadata.ocId, key: result.key, initializationVector: result.initializationVector, authenticationTag: result.authenticationTag)
  174. }
  175. #endif
  176. NCManageDatabase.shared.addLocalFile(metadata: metadata)
  177. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  178. session: "",
  179. sessionError: "",
  180. status: NCGlobal.shared.metadataStatusNormal,
  181. etag: etag)
  182. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
  183. object: nil,
  184. userInfo: ["ocId": metadata.ocId,
  185. "serverUrl": metadata.serverUrl,
  186. "account": metadata.account,
  187. "selector": metadata.sessionSelector,
  188. "error": error])
  189. } else if error.errorCode == NSURLErrorCancelled || error.errorCode == NCGlobal.shared.errorRequestExplicityCancelled {
  190. self.removeTransferInError(ocId: metadata.ocId)
  191. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  192. session: "",
  193. sessionError: "",
  194. selector: "",
  195. status: NCGlobal.shared.metadataStatusNormal)
  196. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadCancelFile),
  197. object: nil,
  198. userInfo: ["ocId": metadata.ocId,
  199. "serverUrl": metadata.serverUrl,
  200. "account": metadata.account])
  201. } else {
  202. self.transferInError(ocId: metadata.ocId)
  203. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  204. session: "",
  205. sessionError: "",
  206. selector: "",
  207. status: NCGlobal.shared.metadataStatusNormal)
  208. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
  209. object: nil,
  210. userInfo: ["ocId": metadata.ocId,
  211. "serverUrl": metadata.serverUrl,
  212. "account": metadata.account,
  213. "selector": metadata.sessionSelector,
  214. "error": error])
  215. }
  216. }
  217. }
  218. func downloadProgress(_ progress: Float,
  219. totalBytes: Int64,
  220. totalBytesExpected: Int64,
  221. fileName: String,
  222. serverUrl: String,
  223. session: URLSession,
  224. task: URLSessionTask) {
  225. if let delegate {
  226. return delegate.downloadProgress(progress, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected, fileName: fileName, serverUrl: serverUrl, session: session, task: task)
  227. }
  228. DispatchQueue.global(qos: .userInteractive).async {
  229. var metadata: tableMetadata?
  230. if let metadataTmp = self.downloadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] {
  231. metadata = metadataTmp
  232. } else if let metadataTmp = NCManageDatabase.shared.getMetadataFromFileName(fileName, serverUrl: serverUrl) {
  233. self.downloadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] = metadataTmp
  234. metadata = metadataTmp
  235. }
  236. if let metadata {
  237. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
  238. object: nil,
  239. userInfo: ["account": metadata.account,
  240. "ocId": metadata.ocId,
  241. "fileName": metadata.fileName,
  242. "serverUrl": metadata.serverUrl,
  243. "status": NSNumber(value: NCGlobal.shared.metadataStatusDownloading),
  244. "progress": NSNumber(value: progress),
  245. "totalBytes": NSNumber(value: totalBytes),
  246. "totalBytesExpected": NSNumber(value: totalBytesExpected)])
  247. }
  248. }
  249. }
  250. #if !EXTENSION
  251. func downloadAvatar(user: String,
  252. dispalyName: String?,
  253. fileName: String,
  254. cell: NCCellProtocol,
  255. view: UIView?) {
  256. let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileName
  257. if let image = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) {
  258. cell.fileAvatarImageView?.image = image
  259. return
  260. }
  261. if let account = NCManageDatabase.shared.getActiveAccount() {
  262. cell.fileAvatarImageView?.image = utility.loadUserImage(for: user, displayName: dispalyName, userBaseUrl: account)
  263. }
  264. for case let operation as NCOperationDownloadAvatar in downloadAvatarQueue.operations where operation.fileName == fileName { return }
  265. downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: user, fileName: fileName, fileNameLocalPath: fileNameLocalPath, cell: cell, view: view))
  266. }
  267. #endif
  268. func cancelDownloadTasks() {
  269. downloadRequest.removeAll()
  270. let sessionManager = NextcloudKit.shared.sessionManager
  271. sessionManager.session.getTasksWithCompletionHandler { _, _, downloadTasks in
  272. downloadTasks.forEach {
  273. $0.cancel()
  274. }
  275. }
  276. if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0 AND session == %@", NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload)) {
  277. NCManageDatabase.shared.clearMetadataSession(metadatas: results)
  278. }
  279. }
  280. func cancelDownloadBackgroundTask() {
  281. Task {
  282. let tasksBackground = await NCNetworking.shared.sessionManagerDownloadBackground.tasks
  283. for task in tasksBackground.2 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
  284. task.cancel()
  285. }
  286. if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0 AND session == %@", NCNetworking.shared.sessionDownloadBackground)) {
  287. NCManageDatabase.shared.clearMetadataSession(metadatas: results)
  288. }
  289. }
  290. }
  291. }
  292. class NCOperationDownload: ConcurrentOperation {
  293. var metadata: tableMetadata
  294. var selector: String
  295. init(metadata: tableMetadata, selector: String) {
  296. self.metadata = tableMetadata.init(value: metadata)
  297. self.selector = selector
  298. }
  299. override func start() {
  300. guard !isCancelled else { return self.finish() }
  301. metadata.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload
  302. metadata.sessionError = ""
  303. metadata.sessionSelector = selector
  304. metadata.sessionTaskIdentifier = 0
  305. metadata.status = NCGlobal.shared.metadataStatusWaitDownload
  306. NCManageDatabase.shared.addMetadata(metadata)
  307. NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) {
  308. } completion: { _, _ in
  309. self.finish()
  310. }
  311. }
  312. }