NCNetworking+Download.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  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. self.downloadRequest.removeValue(forKey: fileNameLocalPath)
  101. var dateLastModified: NSDate?
  102. if let downloadTask = downloadTask {
  103. if let header = allHeaderFields, let dateString = header["Last-Modified"] as? String {
  104. dateLastModified = NextcloudKit.shared.nkCommonInstance.convertDate(dateString, format: "EEE, dd MMM y HH:mm:ss zzz")
  105. }
  106. if afError?.isExplicitlyCancelledError ?? false {
  107. error = NKError(errorCode: NCGlobal.shared.errorRequestExplicityCancelled, errorDescription: "error request explicity cancelled")
  108. }
  109. self.downloadComplete(fileName: metadata.fileName, serverUrl: metadata.serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, fileNameLocalPath: fileNameLocalPath, task: downloadTask, error: error)
  110. }
  111. completion(afError, error)
  112. }
  113. }
  114. private func downloadFileInBackground(metadata: tableMetadata,
  115. start: @escaping () -> Void = { },
  116. requestHandler: @escaping (_ request: DownloadRequest) -> Void = { _ in },
  117. progressHandler: @escaping (_ progress: Progress) -> Void = { _ in },
  118. completion: @escaping (_ afError: AFError?, _ error: NKError) -> Void = { _, _ in }) {
  119. let session: URLSession = sessionManagerDownloadBackground
  120. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  121. let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileNameView: metadata.fileNameView)
  122. start()
  123. if let task = nkBackground.download(serverUrlFileName: serverUrlFileName, fileNameLocalPath: fileNameLocalPath, session: session) {
  124. NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Download file \(metadata.fileNameView) with task with taskIdentifier \(task.taskIdentifier)")
  125. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  126. status: NCGlobal.shared.metadataStatusDownloading,
  127. taskIdentifier: task.taskIdentifier)
  128. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadStartFile),
  129. object: nil,
  130. userInfo: ["ocId": metadata.ocId,
  131. "serverUrl": metadata.serverUrl,
  132. "account": metadata.account])
  133. completion(nil, NKError())
  134. } else {
  135. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  136. session: "",
  137. sessionError: "",
  138. selector: "",
  139. status: NCGlobal.shared.metadataStatusNormal)
  140. completion(nil, NKError(errorCode: NCGlobal.shared.errorResourceNotFound, errorDescription: "task null"))
  141. }
  142. }
  143. func downloadComplete(fileName: String,
  144. serverUrl: String,
  145. etag: String?,
  146. date: NSDate?,
  147. dateLastModified: NSDate?,
  148. length: Int64,
  149. fileNameLocalPath: String?,
  150. task: URLSessionTask,
  151. error: NKError) {
  152. DispatchQueue.global().async {
  153. guard let metadata = NCManageDatabase.shared.getMetadataFromFileNameLocalPath(fileNameLocalPath) else { return }
  154. if error == .success {
  155. #if !EXTENSION
  156. if let result = NCManageDatabase.shared.getE2eEncryption(predicate: NSPredicate(format: "fileNameIdentifier == %@ AND serverUrl == %@", metadata.fileName, metadata.serverUrl)) {
  157. NCEndToEndEncryption.sharedManager()?.decryptFile(metadata.fileName, fileNameView: metadata.fileNameView, ocId: metadata.ocId, key: result.key, initializationVector: result.initializationVector, authenticationTag: result.authenticationTag)
  158. }
  159. #endif
  160. NCManageDatabase.shared.addLocalFile(metadata: metadata)
  161. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  162. session: "",
  163. sessionError: "",
  164. status: NCGlobal.shared.metadataStatusNormal,
  165. etag: etag)
  166. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
  167. object: nil,
  168. userInfo: ["ocId": metadata.ocId,
  169. "serverUrl": metadata.serverUrl,
  170. "account": metadata.account,
  171. "selector": metadata.sessionSelector,
  172. "error": error])
  173. } else if error.errorCode == NSURLErrorCancelled || error.errorCode == NCGlobal.shared.errorRequestExplicityCancelled {
  174. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  175. session: "",
  176. sessionError: "",
  177. selector: "",
  178. status: NCGlobal.shared.metadataStatusNormal)
  179. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadCancelFile),
  180. object: nil,
  181. userInfo: ["ocId": metadata.ocId,
  182. "serverUrl": metadata.serverUrl,
  183. "account": metadata.account])
  184. } else {
  185. NCManageDatabase.shared.setMetadataSession(ocId: metadata.ocId,
  186. session: "",
  187. sessionError: "",
  188. selector: "",
  189. status: NCGlobal.shared.metadataStatusNormal)
  190. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterDownloadedFile),
  191. object: nil,
  192. userInfo: ["ocId": metadata.ocId,
  193. "serverUrl": metadata.serverUrl,
  194. "account": metadata.account,
  195. "selector": metadata.sessionSelector,
  196. "error": error])
  197. }
  198. self.downloadMetadataInBackground.removeValue(forKey: FileNameServerUrl(fileName: fileName, serverUrl: serverUrl))
  199. self.delegate?.downloadComplete?(fileName: fileName, serverUrl: serverUrl, etag: etag, date: date, dateLastModified: dateLastModified, length: length, fileNameLocalPath: fileNameLocalPath, task: task, error: error)
  200. }
  201. }
  202. func downloadProgress(_ progress: Float,
  203. totalBytes: Int64,
  204. totalBytesExpected: Int64,
  205. fileName: String,
  206. serverUrl: String,
  207. session: URLSession,
  208. task: URLSessionTask) {
  209. DispatchQueue.global().async {
  210. self.delegate?.downloadProgress?(progress, totalBytes: totalBytes, totalBytesExpected: totalBytesExpected, fileName: fileName, serverUrl: serverUrl, session: session, task: task)
  211. var metadata: tableMetadata?
  212. if let metadataTmp = self.downloadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] {
  213. metadata = metadataTmp
  214. } else if let metadataTmp = NCManageDatabase.shared.getMetadataFromFileName(fileName, serverUrl: serverUrl) {
  215. self.downloadMetadataInBackground[FileNameServerUrl(fileName: fileName, serverUrl: serverUrl)] = metadataTmp
  216. metadata = metadataTmp
  217. }
  218. if let metadata = metadata, Int(floor(progress * 100)).isMultiple(of: 5) {
  219. NotificationCenter.default.post(name: Notification.Name(rawValue: NCGlobal.shared.notificationCenterProgressTask),
  220. object: nil,
  221. userInfo: ["account": metadata.account,
  222. "ocId": metadata.ocId,
  223. "fileName": metadata.fileName,
  224. "serverUrl": metadata.serverUrl,
  225. "status": NSNumber(value: NCGlobal.shared.metadataStatusDownloading),
  226. "progress": NSNumber(value: progress),
  227. "totalBytes": NSNumber(value: totalBytes),
  228. "totalBytesExpected": NSNumber(value: totalBytesExpected)])
  229. }
  230. }
  231. }
  232. #if !EXTENSION
  233. func downloadAvatar(user: String,
  234. dispalyName: String?,
  235. fileName: String,
  236. cell: NCCellProtocol,
  237. view: UIView?,
  238. cellImageView: UIImageView?) {
  239. let fileNameLocalPath = utilityFileSystem.directoryUserData + "/" + fileName
  240. if let image = NCManageDatabase.shared.getImageAvatarLoaded(fileName: fileName) {
  241. cellImageView?.image = image
  242. cell.fileAvatarImageView?.image = image
  243. return
  244. }
  245. if let account = NCManageDatabase.shared.getActiveAccount() {
  246. cellImageView?.image = utility.loadUserImage(for: user, displayName: dispalyName, userBaseUrl: account)
  247. }
  248. for case let operation as NCOperationDownloadAvatar in downloadAvatarQueue.operations where operation.fileName == fileName { return }
  249. downloadAvatarQueue.addOperation(NCOperationDownloadAvatar(user: user, fileName: fileName, fileNameLocalPath: fileNameLocalPath, cell: cell, view: view, cellImageView: cellImageView))
  250. }
  251. #endif
  252. func cancelDownloadTasks() {
  253. downloadRequest.removeAll()
  254. let sessionManager = NextcloudKit.shared.sessionManager
  255. sessionManager.session.getTasksWithCompletionHandler { _, _, downloadTasks in
  256. downloadTasks.forEach {
  257. $0.cancel()
  258. }
  259. }
  260. if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0 AND session == %@", NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload)) {
  261. NCManageDatabase.shared.clearMetadataSession(metadatas: results)
  262. }
  263. }
  264. func cancelDownloadBackgroundTask() {
  265. Task {
  266. let tasksBackground = await NCNetworking.shared.sessionManagerDownloadBackground.tasks
  267. for task in tasksBackground.2 { // ([URLSessionDataTask], [URLSessionUploadTask], [URLSessionDownloadTask])
  268. task.cancel()
  269. }
  270. if let results = NCManageDatabase.shared.getResultsMetadatas(predicate: NSPredicate(format: "status < 0 AND session == %@", NCNetworking.shared.sessionDownloadBackground)) {
  271. NCManageDatabase.shared.clearMetadataSession(metadatas: results)
  272. }
  273. }
  274. }
  275. }
  276. class NCOperationDownload: ConcurrentOperation {
  277. var metadata: tableMetadata
  278. var selector: String
  279. init(metadata: tableMetadata, selector: String) {
  280. self.metadata = tableMetadata.init(value: metadata)
  281. self.selector = selector
  282. }
  283. override func start() {
  284. guard !isCancelled else { return self.finish() }
  285. metadata.session = NextcloudKit.shared.nkCommonInstance.sessionIdentifierDownload
  286. metadata.sessionError = ""
  287. metadata.sessionSelector = selector
  288. metadata.sessionTaskIdentifier = 0
  289. metadata.status = NCGlobal.shared.metadataStatusWaitDownload
  290. NCManageDatabase.shared.addMetadata(metadata)
  291. NCNetworking.shared.download(metadata: metadata, withNotificationProgressTask: true) {
  292. } completion: { _, _ in
  293. self.finish()
  294. }
  295. }
  296. }