NCNetworking+Download.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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: NSDate?
  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: NSDate?,
  158. dateLastModified: NSDate?,
  159. length: Int64,
  160. task: URLSessionTask,
  161. error: NKError) {
  162. DispatchQueue.global().async {
  163. guard let url = task.currentRequest?.url,
  164. let metadata = NCManageDatabase.shared.getMetadata(from: url) else { return }
  165. self.downloadMetadataInBackground.removeValue(forKey: FileNameServerUrl(fileName: fileName, serverUrl: serverUrl))
  166. if error == .success {
  167. self.removeTransferInError(ocId: metadata.ocId)
  168. #if !EXTENSION
  169. if let result = NCManageDatabase.shared.getE2eEncryption(predicate: NSPredicate(format: "fileNameIdentifier == %@ AND serverUrl == %@", metadata.fileName, metadata.serverUrl)) {
  170. NCEndToEndEncryption.sharedManager()?.decryptFile(metadata.fileName, fileNameView: metadata.fileNameView, ocId: metadata.ocId, key: result.key, initializationVector: result.initializationVector, authenticationTag: result.authenticationTag)
  171. }
  172. #endif
  173. NCManageDatabase.shared.addLocalFile(metadata: metadata)
  174. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  175. session: "",
  176. sessionError: "",
  177. status: NCGlobal.shared.metadataStatusNormal,
  178. etag: etag)
  179. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
  180. object: nil,
  181. userInfo: ["ocId": metadata.ocId,
  182. "serverUrl": metadata.serverUrl,
  183. "account": metadata.account,
  184. "selector": metadata.sessionSelector,
  185. "error": error])
  186. } else if error.errorCode == NSURLErrorCancelled || error.errorCode == NCGlobal.shared.errorRequestExplicityCancelled {
  187. self.removeTransferInError(ocId: metadata.ocId)
  188. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  189. session: "",
  190. sessionError: "",
  191. selector: "",
  192. status: NCGlobal.shared.metadataStatusNormal)
  193. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadCancelFile),
  194. object: nil,
  195. userInfo: ["ocId": metadata.ocId,
  196. "serverUrl": metadata.serverUrl,
  197. "account": metadata.account])
  198. } else {
  199. self.transferInError(ocId: metadata.ocId)
  200. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  201. session: "",
  202. sessionError: "",
  203. selector: "",
  204. status: NCGlobal.shared.metadataStatusNormal)
  205. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
  206. object: nil,
  207. userInfo: ["ocId": metadata.ocId,
  208. "serverUrl": metadata.serverUrl,
  209. "account": metadata.account,
  210. "selector": metadata.sessionSelector,
  211. "error": error])
  212. }
  213. }
  214. }
  215. func downloadProgress(_ progress: Float,
  216. totalBytes: Int64,
  217. totalBytesExpected: Int64,
  218. fileName: String,
  219. serverUrl: String,
  220. session: URLSession,
  221. task: URLSessionTask) {
  222. DispatchQueue.global().async {
  223. var metadata: tableMetadata?
  224. if let metadataTmp = self.downloadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] {
  225. metadata = metadataTmp
  226. } else if let metadataTmp = NCManageDatabase.shared.getMetadataFromFileName(fileName, serverUrl: serverUrl) {
  227. self.downloadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] = metadataTmp
  228. metadata = metadataTmp
  229. }
  230. if let metadata {
  231. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
  232. object: nil,
  233. userInfo: ["account": metadata.account,
  234. "ocId": metadata.ocId,
  235. "fileName": metadata.fileName,
  236. "serverUrl": metadata.serverUrl,
  237. "status": NSNumber(value: NCGlobal.shared.metadataStatusDownloading),
  238. "progress": NSNumber(value: progress),
  239. "totalBytes": NSNumber(value: totalBytes),
  240. "totalBytesExpected": NSNumber(value: totalBytesExpected)])
  241. }
  242. }
  243. }
  244. #if !EXTENSION
  245. func downloadAvatar(user: String,
  246. dispalyName: String?,
  247. fileName: String,
  248. cell: NCCellProtocol,
  249. view: UIView?) {
  250. let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileName
  251. if let image = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) {
  252. cell.fileAvatarImageView?.image = image
  253. return
  254. }
  255. if let account = NCManageDatabase.shared.getActiveAccount() {
  256. cell.fileAvatarImageView?.image = utility.loadUserImage(for: user, displayName: dispalyName, userBaseUrl: account)
  257. }
  258. for case let operation as NCOperationDownloadAvatar in downloadAvatarQueue.operations where operation.fileName == fileName { return }
  259. downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: user, fileName: fileName, fileNameLocalPath: fileNameLocalPath, cell: cell, view: view))
  260. }
  261. #endif
  262. func cancelDownloadTasks() {
  263. downloadRequest.removeAll()
  264. let sessionManager = NextcloudKit.shared.sessionManager
  265. sessionManager.session.getTasksWithCompletionHandler { _, _, downloadTasks in
  266. downloadTasks.forEach {
  267. $0.cancel()
  268. }
  269. }
  270. if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0 AND session == %@", NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload)) {
  271. NCManageDatabase.shared.clearMetadataSession(metadatas: results)
  272. }
  273. }
  274. func cancelDownloadBackgroundTask() {
  275. Task {
  276. let tasksBackground = await NCNetworking.shared.sessionManagerDownloadBackground.tasks
  277. for task in tasksBackground.2 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
  278. task.cancel()
  279. }
  280. if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0 AND session == %@", NCNetworking.shared.sessionDownloadBackground)) {
  281. NCManageDatabase.shared.clearMetadataSession(metadatas: results)
  282. }
  283. }
  284. }
  285. }
  286. class NCOperationDownload: ConcurrentOperation {
  287. var metadata: tableMetadata
  288. var selector: String
  289. init(metadata: tableMetadata, selector: String) {
  290. self.metadata = tableMetadata.init(value: metadata)
  291. self.selector = selector
  292. }
  293. override func start() {
  294. guard !isCancelled else { return self.finish() }
  295. metadata.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload
  296. metadata.sessionError = ""
  297. metadata.sessionSelector = selector
  298. metadata.sessionTaskIdentifier = 0
  299. metadata.status = NCGlobal.shared.metadataStatusWaitDownload
  300. NCManageDatabase.shared.addMetadata(metadata)
  301. NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) {
  302. } completion: { _, _ in
  303. self.finish()
  304. }
  305. }
  306. }