NCNetworking+Download.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  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. hudView: UIView? = nil,
  32. hud: JGProgressHUD? = nil,
  33. start: @escaping () -> Void = { },
  34. requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
  35. progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
  36. completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
  37. if metadata.session == NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload {
  38. downloadFile(metadata: metadata, withNotificationProgressTask: withNotificationProgressTask, hudView: hudView, hud: hud) {
  39. start()
  40. } requestHandler: { request in
  41. requestHandler(request)
  42. } progressHandler: { progress in
  43. progressHandler(progress)
  44. } completion: { afError, error in
  45. completion(afError, error)
  46. }
  47. } else {
  48. downloadFileInBackground(metadata: metadata, start: start, completion: { afError, error in
  49. completion(afError, error)
  50. })
  51. }
  52. }
  53. private func downloadFile(metadata: tableMetadata,
  54. withNotificationProgressTask: Bool,
  55. hudView: UIView?,
  56. hud: JGProgressHUD?,
  57. start: @escaping () -> Void = { },
  58. requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
  59. progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
  60. completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
  61. guard !metadata.isInTransfer else { return completion(nil, NKError()) }
  62. var downloadTask: URLSessionTask?
  63. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  64. let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileName)
  65. let options = NKRequestOptions(queue: NextcloudKit.shared.nkCommonInstance.backgroundQueue)
  66. if NCManageDatabase.shared.getMetadataFromOcId(metadata.ocId) == nil {
  67. NCManageDatabase.shared.addMetadata(metadata)
  68. }
  69. NextcloudKit.shared.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, options: options, requestHandler: { request in
  70. self.downloadRequest[fileNameLocalPath] = request
  71. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  72. status: NCGlobal.shared.metadataStatusDownloading)
  73. requestHandler(request)
  74. }, taskHandler: { task in
  75. downloadTask = task
  76. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  77. taskIdentifier: task.taskIdentifier)
  78. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadStartFile),
  79. object: nil,
  80. userInfo: ["ocId": metadata.ocId,
  81. "serverUrl": metadata.serverUrl,
  82. "account": metadata.account])
  83. start()
  84. }, progressHandler: { progress in
  85. if withNotificationProgressTask, Int(floor(progress.fractionCompleted * 100)).isMultiple(of: 5) {
  86. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
  87. object: nil,
  88. userInfo: ["account": metadata.account,
  89. "ocId": metadata.ocId,
  90. "fileName": metadata.fileName,
  91. "serverUrl": metadata.serverUrl,
  92. "status": NSNumber(value: NCGlobal.shared.metadataStatusDownloading),
  93. "progress": NSNumber(value: progress.fractionCompleted),
  94. "totalBytes": NSNumber(value: progress.totalUnitCount),
  95. "totalBytesExpected": NSNumber(value: progress.completedUnitCount)])
  96. }
  97. progressHandler(progress)
  98. }) { _, etag, date, length, allHeaderFields, afError, error in
  99. var error = error
  100. var dateLastModified: NSDate?
  101. self.downloadRequest.removeValue(forKey: fileNameLocalPath)
  102. // this delay was added because for small file the "taskHandler: { task" is not called, so this part of code is not executed
  103. NextcloudKit.shared.nkCommonInstance.backgroundQueue.asyncAfter(deadline: .now() + 0.5) {
  104. if let downloadTask = downloadTask {
  105. if let header = allHeaderFields, 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: NCGlobal.shared.errorRequestExplicityCancelled, errorDescription: "error request explicity cancelled")
  110. }
  111. self.downloadComplete(fileName: metadata.fileName, serverUrl: metadata.serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, fileNameLocalPath: fileNameLocalPath, 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 session: URLSession = sessionManagerDownloadBackground
  123. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  124. let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
  125. start()
  126. if let task = nkBackground.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, session: session) {
  127. NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Download file \(metadata.fileNameView) with task with taskIdentifier \(task.taskIdentifier)")
  128. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  129. status: NCGlobal.shared.metadataStatusDownloading,
  130. taskIdentifier: task.taskIdentifier)
  131. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadStartFile),
  132. object: nil,
  133. userInfo: ["ocId": metadata.ocId,
  134. "serverUrl": metadata.serverUrl,
  135. "account": metadata.account])
  136. completion(nil, NKError())
  137. } else {
  138. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  139. session: "",
  140. sessionError: "",
  141. selector: "",
  142. status: NCGlobal.shared.metadataStatusNormal)
  143. completion(nil, NKError(errorCode: NCGlobal.shared.errorResourceNotFound, errorDescription: "task null"))
  144. }
  145. }
  146. func downloadComplete(fileName: String,
  147. serverUrl: String,
  148. etag: String?,
  149. date: NSDate?,
  150. dateLastModified: NSDate?,
  151. length: Int64,
  152. fileNameLocalPath: String?,
  153. task: URLSessionTask,
  154. error: NKError) {
  155. DispatchQueue.global().async {
  156. guard let metadata = NCManageDatabase.shared.getMetadataFromFileNameLocalPath(fileNameLocalPath) else { return }
  157. if error == .success {
  158. #if !EXTENSION
  159. if let result = NCManageDatabase.shared.getE2eEncryption(predicate: NSPredicate(format: "fileNameIdentifier == %@ AND serverUrl == %@", metadata.fileName, metadata.serverUrl)) {
  160. NCEndToEndEncryption.sharedManager()?.decryptFile(metadata.fileName, fileNameView: metadata.fileNameView, ocId: metadata.ocId, key: result.key, initializationVector: result.initializationVector, authenticationTag: result.authenticationTag)
  161. }
  162. #endif
  163. NCManageDatabase.shared.addLocalFile(metadata: metadata)
  164. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  165. session: "",
  166. sessionError: "",
  167. status: NCGlobal.shared.metadataStatusNormal,
  168. etag: etag)
  169. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
  170. object: nil,
  171. userInfo: ["ocId": metadata.ocId,
  172. "serverUrl": metadata.serverUrl,
  173. "account": metadata.account,
  174. "selector": metadata.sessionSelector,
  175. "error": error])
  176. } else if error.errorCode == NSURLErrorCancelled || error.errorCode == NCGlobal.shared.errorRequestExplicityCancelled {
  177. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  178. session: "",
  179. sessionError: "",
  180. selector: "",
  181. status: NCGlobal.shared.metadataStatusNormal)
  182. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadCancelFile),
  183. object: nil,
  184. userInfo: ["ocId": metadata.ocId,
  185. "serverUrl": metadata.serverUrl,
  186. "account": metadata.account])
  187. } else {
  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.notificationCenterDownloadedFile),
  194. object: nil,
  195. userInfo: ["ocId": metadata.ocId,
  196. "serverUrl": metadata.serverUrl,
  197. "account": metadata.account,
  198. "selector": metadata.sessionSelector,
  199. "error": error])
  200. }
  201. self.downloadMetadataInBackground.removeValue(forKey: FileNameServerUrl(fileName: fileName, serverUrl: serverUrl))
  202. self.delegate?.downloadComplete?(fileName: fileName, serverUrl: serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, fileNameLocalPath: fileNameLocalPath, task: task, error: error)
  203. }
  204. }
  205. func downloadProgress(_ progress: Float,
  206. totalBytes: Int64,
  207. totalBytesExpected: Int64,
  208. fileName: String,
  209. serverUrl: String,
  210. session: URLSession,
  211. task: URLSessionTask) {
  212. DispatchQueue.global().async {
  213. self.delegate?.downloadProgress?(progress, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected, fileName: fileName, serverUrl: serverUrl, session: session, task: task)
  214. var metadata: tableMetadata?
  215. if let metadataTmp = self.downloadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] {
  216. metadata = metadataTmp
  217. } else if let metadataTmp = NCManageDatabase.shared.getMetadataFromFileName(fileName, serverUrl: serverUrl) {
  218. self.downloadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] = metadataTmp
  219. metadata = metadataTmp
  220. }
  221. if let metadata = metadata, Int(floor(progress * 100)).isMultiple(of: 5) {
  222. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
  223. object: nil,
  224. userInfo: ["account": metadata.account,
  225. "ocId": metadata.ocId,
  226. "fileName": metadata.fileName,
  227. "serverUrl": metadata.serverUrl,
  228. "status": NSNumber(value: NCGlobal.shared.metadataStatusDownloading),
  229. "progress": NSNumber(value: progress),
  230. "totalBytes": NSNumber(value: totalBytes),
  231. "totalBytesExpected": NSNumber(value: totalBytesExpected)])
  232. }
  233. }
  234. }
  235. #if !EXTENSION
  236. func downloadAvatar(user: String,
  237. dispalyName: String?,
  238. fileName: String,
  239. cell: NCCellProtocol,
  240. view: UIView?,
  241. cellImageView: UIImageView?) {
  242. let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileName
  243. if let image = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) {
  244. cellImageView?.image = image
  245. cell.fileAvatarImageView?.image = image
  246. return
  247. }
  248. if let account = NCManageDatabase.shared.getActiveAccount() {
  249. cellImageView?.image = utility.loadUserImage(for: user, displayName: dispalyName, userBaseUrl: account)
  250. }
  251. for case let operation as NCOperationDownloadAvatar in downloadAvatarQueue.operations where operation.fileName == fileName { return }
  252. downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: user, fileName: fileName, fileNameLocalPath: fileNameLocalPath, cell: cell, view: view, cellImageView: cellImageView))
  253. }
  254. #endif
  255. func cancelDownloadTasks() {
  256. downloadRequest.removeAll()
  257. let sessionManager = NextcloudKit.shared.sessionManager
  258. sessionManager.session.getTasksWithCompletionHandler { _, _, downloadTasks in
  259. downloadTasks.forEach {
  260. $0.cancel()
  261. }
  262. }
  263. if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0 AND session == %@", NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload)) {
  264. NCManageDatabase.shared.clearMetadataSession(metadatas: results)
  265. }
  266. }
  267. func cancelDownloadBackgroundTask() {
  268. Task {
  269. let tasksBackground = await NCNetworking.shared.sessionManagerDownloadBackground.tasks
  270. for task in tasksBackground.2 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
  271. task.cancel()
  272. }
  273. if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0 AND session == %@", NCNetworking.shared.sessionDownloadBackground)) {
  274. NCManageDatabase.shared.clearMetadataSession(metadatas: results)
  275. }
  276. }
  277. }
  278. }
  279. class NCOperationDownload: ConcurrentOperation {
  280. var metadata: tableMetadata
  281. var selector: String
  282. init(metadata: tableMetadata, selector: String) {
  283. self.metadata = tableMetadata.init(value: metadata)
  284. self.selector = selector
  285. }
  286. override func start() {
  287. guard !isCancelled else { return self.finish() }
  288. metadata.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload
  289. metadata.sessionError = ""
  290. metadata.sessionSelector = selector
  291. metadata.sessionTaskIdentifier = 0
  292. metadata.status = NCGlobal.shared.metadataStatusWaitDownload
  293. NCManageDatabase.shared.addMetadata(metadata)
  294. NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) {
  295. } completion: { _, _ in
  296. self.finish()
  297. }
  298. }
  299. }