NCNetworkingProcess.swift 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. //
  2. // NCNetworkingProcess.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 25/06/2020.
  6. // Copyright © 2020 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 Photos
  26. import RealmSwift
  27. class NCNetworkingProcess {
  28. static let shared = NCNetworkingProcess()
  29. private let utilityFileSystem = NCUtilityFileSystem()
  30. private let database = NCManageDatabase.shared
  31. private let global = NCGlobal.shared
  32. private let networking = NCNetworking.shared
  33. private var notificationToken: NotificationToken?
  34. private var hasRun: Bool = false
  35. private let lockQueue = DispatchQueue(label: "com.nextcloud.networkingprocess.lockqueue")
  36. private var timerProcess: Timer?
  37. private init() {
  38. self.startTimer()
  39. self.startObserveTableMetadata()
  40. }
  41. private func startObserveTableMetadata() {
  42. do {
  43. let realm = try Realm()
  44. let results = realm.objects(tableMetadata.self).filter(NSPredicate(format: "status IN %@", global.metadataStatusObserve))
  45. notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
  46. switch changes {
  47. case .initial:
  48. print("Initial")
  49. case .update(_, _, let insertions, let modifications):
  50. if insertions.count > 0 || modifications.count > 0 {
  51. guard let self else { return }
  52. self.startTimer()
  53. self.lockQueue.async {
  54. guard !self.hasRun, self.networking.isOnline else { return }
  55. self.hasRun = true
  56. Task { [weak self] in
  57. guard let self else { return }
  58. await self.start()
  59. self.hasRun = false
  60. }
  61. }
  62. }
  63. case .error(let error):
  64. print("Error: \(error.localizedDescription)")
  65. }
  66. }
  67. } catch let error {
  68. print("Error: \(error.localizedDescription)")
  69. }
  70. }
  71. private func startTimer() {
  72. self.timerProcess?.invalidate()
  73. self.timerProcess = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { _ in
  74. self.lockQueue.async {
  75. guard !self.hasRun,
  76. self.networking.isOnline,
  77. let results = self.database.getResultsMetadatas(predicate: NSPredicate(format: "status != %d", self.global.metadataStatusNormal))?.freeze()
  78. else { return }
  79. self.hasRun = true
  80. /// Keep screen awake
  81. ///
  82. /*
  83. Task {
  84. let tasks = await self.networking.getAllDataTask()
  85. let hasSynchronizationTask = tasks.contains { $0.taskDescription == NCGlobal.shared.taskDescriptionSynchronization }
  86. let resultsTransfer = results.filter { self.global.metadataStatusInTransfer.contains($0.status) }
  87. if resultsTransfer.isEmpty && !hasSynchronizationTask {
  88. ScreenAwakeManager.shared.mode = .off
  89. } else {
  90. ScreenAwakeManager.shared.mode = NCKeychain().screenAwakeMode
  91. }
  92. }
  93. */
  94. if results.isEmpty {
  95. /// Remove Photo CameraRoll
  96. ///
  97. if NCKeychain().removePhotoCameraRoll,
  98. UIApplication.shared.applicationState == .active,
  99. let localIdentifiers = self.database.getAssetLocalIdentifiersUploaded(),
  100. !localIdentifiers.isEmpty {
  101. PHPhotoLibrary.shared().performChanges({
  102. PHAssetChangeRequest.deleteAssets(PHAsset.fetchAssets(withLocalIdentifiers: localIdentifiers, options: nil) as NSFastEnumeration)
  103. }, completionHandler: { _, _ in
  104. self.database.clearAssetLocalIdentifiers(localIdentifiers)
  105. self.hasRun = false
  106. })
  107. } else {
  108. self.hasRun = false
  109. }
  110. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterUpdateBadgeNumber,
  111. object: nil,
  112. userInfo: ["counterDownload": 0,
  113. "counterUpload": 0])
  114. } else {
  115. Task { [weak self] in
  116. guard let self else { return }
  117. await self.start()
  118. self.hasRun = false
  119. }
  120. }
  121. }
  122. })
  123. }
  124. @discardableResult
  125. private func start() async -> (counterDownloading: Int, counterUploading: Int) {
  126. let applicationState = await checkApplicationState()
  127. let maxConcurrentOperationDownload = NCBrandOptions.shared.maxConcurrentOperationDownload
  128. var maxConcurrentOperationUpload = NCBrandOptions.shared.maxConcurrentOperationUpload
  129. let sessionUploadSelectors = [global.selectorUploadFileNODelete, global.selectorUploadFile, global.selectorUploadAutoUpload, global.selectorUploadAutoUploadAll]
  130. let metadatasDownloading = self.database.getMetadatas(predicate: NSPredicate(format: "status == %d", global.metadataStatusDownloading))
  131. let metadatasUploading = self.database.getMetadatas(predicate: NSPredicate(format: "status == %d", global.metadataStatusUploading))
  132. let metadatasUploadError: [tableMetadata] = self.database.getMetadatas(predicate: NSPredicate(format: "status == %d", global.metadataStatusUploadError), sortedByKeyPath: "sessionDate", ascending: true) ?? []
  133. let isWiFi = networking.networkReachability == NKCommon.TypeReachability.reachableEthernetOrWiFi
  134. var counterDownloading = metadatasDownloading.count
  135. var counterUploading = metadatasUploading.count
  136. /// ------------------------ WEBDAV
  137. ///
  138. let metadatas = database.getMetadatas(predicate: NSPredicate(format: "status IN %@", global.metadataStatusWaitWebDav))
  139. if !metadatas.isEmpty {
  140. let stop = await metadataStatusWaitWebDav()
  141. if stop {
  142. return (counterDownloading, counterUploading)
  143. }
  144. }
  145. /// ------------------------ DOWNLOAD
  146. ///
  147. let limitDownload = maxConcurrentOperationDownload - counterDownloading
  148. let metadatasWaitDownload = self.database.getMetadatas(predicate: NSPredicate(format: "session == %@ AND status == %d", networking.sessionDownloadBackground, global.metadataStatusWaitDownload), numItems: limitDownload, sorted: "sessionDate", ascending: true)
  149. for metadata in metadatasWaitDownload where counterDownloading < maxConcurrentOperationDownload {
  150. counterDownloading += 1
  151. networking.download(metadata: metadata, withNotificationProgressTask: true)
  152. }
  153. if counterDownloading == 0 {
  154. let metadatasDownloadError: [tableMetadata] = self.database.getMetadatas(predicate: NSPredicate(format: "session == %@ AND status == %d", networking.sessionDownloadBackground, global.metadataStatusDownloadError), sortedByKeyPath: "sessionDate", ascending: true) ?? []
  155. for metadata in metadatasDownloadError {
  156. // Verify COUNTER ERROR
  157. if let transfer = NCTransferProgress.shared.get(ocIdTransfer: metadata.ocIdTransfer),
  158. transfer.countError > 3 {
  159. continue
  160. }
  161. self.database.setMetadataSession(ocId: metadata.ocId,
  162. sessionError: "",
  163. status: global.metadataStatusWaitDownload)
  164. }
  165. }
  166. /// ------------------------ UPLOAD
  167. ///
  168. /// In background max 2 upload otherwise iOS Termination Reason: RUNNINGBOARD 0xdead10cc
  169. if applicationState == .background {
  170. maxConcurrentOperationUpload = 2
  171. }
  172. /// E2EE - only one for time
  173. for metadata in metadatasUploading.unique(map: { $0.serverUrl }) {
  174. if metadata.isDirectoryE2EE {
  175. return (counterDownloading, counterUploading)
  176. }
  177. }
  178. /// CHUNK - only one for time
  179. if !metadatasUploading.filter({ $0.chunk > 0 }).isEmpty {
  180. return (counterDownloading, counterUploading)
  181. }
  182. for sessionSelector in sessionUploadSelectors where counterUploading < maxConcurrentOperationUpload {
  183. let limitUpload = maxConcurrentOperationUpload - counterUploading
  184. let metadatasWaitUpload = self.database.getMetadatas(predicate: NSPredicate(format: "sessionSelector == %@ AND status == %d", sessionSelector, global.metadataStatusWaitUpload), numItems: limitUpload, sorted: "sessionDate", ascending: true)
  185. if !metadatasWaitUpload.isEmpty {
  186. NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] PROCESS (UPLOAD) find \(metadatasWaitUpload.count) items")
  187. }
  188. for metadata in metadatasWaitUpload where counterUploading < maxConcurrentOperationUpload {
  189. if NCTransferProgress.shared.get(ocIdTransfer: metadata.ocIdTransfer) != nil {
  190. NextcloudKit.shared.nkCommonInstance.writeLog("[INFO] Process auto upload skipped file: \(metadata.serverUrl)/\(metadata.fileNameView), because is already in session.")
  191. continue
  192. }
  193. let metadatas = await NCCameraRoll().extractCameraRoll(from: metadata)
  194. if metadatas.isEmpty {
  195. self.database.deleteMetadataOcId(metadata.ocId)
  196. }
  197. for metadata in metadatas where counterUploading < maxConcurrentOperationUpload {
  198. /// isE2EE
  199. let isInDirectoryE2EE = metadata.isDirectoryE2EE
  200. /// NO WiFi
  201. if !isWiFi && metadata.session == networking.sessionUploadBackgroundWWan { continue }
  202. if applicationState != .active && (isInDirectoryE2EE || metadata.chunk > 0) { continue }
  203. if let metadata = self.database.setMetadataStatus(ocId: metadata.ocId, status: global.metadataStatusUploading) {
  204. /// find controller
  205. var controller: NCMainTabBarController?
  206. if let sceneIdentifier = metadata.sceneIdentifier, !sceneIdentifier.isEmpty {
  207. controller = SceneManager.shared.getController(sceneIdentifier: sceneIdentifier)
  208. } else {
  209. for ctlr in SceneManager.shared.getControllers() {
  210. let account = await ctlr.account
  211. if account == metadata.account {
  212. controller = ctlr
  213. }
  214. }
  215. if controller == nil {
  216. controller = await UIApplication.shared.firstWindow?.rootViewController as? NCMainTabBarController
  217. }
  218. }
  219. networking.upload(metadata: metadata, controller: controller)
  220. if isInDirectoryE2EE || metadata.chunk > 0 {
  221. maxConcurrentOperationUpload = 1
  222. }
  223. counterUploading += 1
  224. }
  225. }
  226. }
  227. }
  228. /// No upload available ? --> Retry Upload in Error
  229. ///
  230. if counterUploading == 0 {
  231. for metadata in metadatasUploadError {
  232. // Verify COUNTER ERROR
  233. if let transfer = NCTransferProgress.shared.get(ocIdTransfer: metadata.ocIdTransfer),
  234. transfer.countError > 3 {
  235. continue
  236. }
  237. /// Verify QUOTA
  238. if metadata.sessionError.contains("\(global.errorQuota)") {
  239. NextcloudKit.shared.getUserProfile(account: metadata.account) { _, userProfile, _, error in
  240. if error == .success, let userProfile, userProfile.quotaFree > 0, userProfile.quotaFree > metadata.size {
  241. self.database.setMetadataSession(ocId: metadata.ocId,
  242. session: self.networking.sessionUploadBackground,
  243. sessionError: "",
  244. status: self.global.metadataStatusWaitUpload)
  245. }
  246. }
  247. } else {
  248. self.database.setMetadataSession(ocId: metadata.ocId,
  249. session: self.networking.sessionUploadBackground,
  250. sessionError: "",
  251. status: global.metadataStatusWaitUpload)
  252. }
  253. }
  254. }
  255. return (counterDownloading, counterUploading)
  256. }
  257. private func checkApplicationState() async -> UIApplication.State {
  258. await withCheckedContinuation { continuation in
  259. DispatchQueue.main.async {
  260. let appState = UIApplication.shared.applicationState
  261. continuation.resume(returning: appState)
  262. }
  263. }
  264. }
  265. private func metadataStatusWaitWebDav() async -> Bool {
  266. var returnValue: Bool = false
  267. /// ------------------------ COPY
  268. ///
  269. if let metadatasWaitCopy = self.database.getMetadatas(predicate: NSPredicate(format: "status == %d", global.metadataStatusWaitCopy), sortedByKeyPath: "serverUrl", ascending: true), !metadatasWaitCopy.isEmpty {
  270. for metadata in metadatasWaitCopy {
  271. let serverUrlTo = metadata.serverUrlTo
  272. let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName
  273. let serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName
  274. let overwrite = (metadata.storeFlag as? NSString)?.boolValue ?? false
  275. let result = await networking.copyFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: overwrite, account: metadata.account)
  276. database.setMetadataCopyMove(ocId: metadata.ocId, serverUrlTo: "", overwrite: nil, status: global.metadataStatusNormal)
  277. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterCopyMoveFile, userInfo: ["serverUrl": metadata.serverUrl, "account": metadata.account, "dragdrop": false, "type": "copy"])
  278. if result.error == .success {
  279. NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterGetServerData, userInfo: ["serverUrl": metadata.serverUrl])
  280. NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterGetServerData, userInfo: ["serverUrl": serverUrlTo])
  281. } else {
  282. NCContentPresenter().showError(error: result.error)
  283. }
  284. }
  285. }
  286. /// ------------------------ MOVE
  287. ///
  288. if let metadatasWaitMove = self.database.getMetadatas(predicate: NSPredicate(format: "status == %d", global.metadataStatusWaitMove), sortedByKeyPath: "serverUrl", ascending: true), !metadatasWaitMove.isEmpty {
  289. for metadata in metadatasWaitMove {
  290. let serverUrlTo = metadata.serverUrlTo
  291. let serverUrlFileNameSource = metadata.serverUrl + "/" + metadata.fileName
  292. let serverUrlFileNameDestination = serverUrlTo + "/" + metadata.fileName
  293. let overwrite = (metadata.storeFlag as? NSString)?.boolValue ?? false
  294. let result = await networking.moveFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: overwrite, account: metadata.account)
  295. database.setMetadataCopyMove(ocId: metadata.ocId, serverUrlTo: "", overwrite: nil, status: global.metadataStatusNormal)
  296. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterCopyMoveFile, userInfo: ["serverUrl": metadata.serverUrl, "account": metadata.account, "dragdrop": false, "type": "move"])
  297. if result.error == .success {
  298. if metadata.directory {
  299. self.database.deleteDirectoryAndSubDirectory(serverUrl: utilityFileSystem.stringAppendServerUrl(metadata.serverUrl, addFileName: metadata.fileName), account: result.account)
  300. } else {
  301. do {
  302. try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId))
  303. } catch { }
  304. self.database.deleteVideo(metadata: metadata)
  305. self.database.deleteMetadataOcId(metadata.ocId)
  306. self.database.deleteLocalFileOcId(metadata.ocId)
  307. // LIVE PHOTO
  308. if let metadataLive = self.database.getMetadataLivePhoto(metadata: metadata) {
  309. do {
  310. try FileManager.default.removeItem(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadataLive.ocId))
  311. } catch { }
  312. self.database.deleteVideo(metadata: metadataLive)
  313. self.database.deleteMetadataOcId(metadataLive.ocId)
  314. self.database.deleteLocalFileOcId(metadataLive.ocId)
  315. }
  316. }
  317. NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterGetServerData, userInfo: ["serverUrl": metadata.serverUrl])
  318. NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterGetServerData, userInfo: ["serverUrl": serverUrlTo])
  319. } else {
  320. NCContentPresenter().showError(error: result.error)
  321. }
  322. }
  323. }
  324. /// ------------------------ FAVORITE
  325. ///
  326. if let metadatasWaitFavorite = self.database.getMetadatas(predicate: NSPredicate(format: "status == %d", global.metadataStatusWaitFavorite), sortedByKeyPath: "serverUrl", ascending: true), !metadatasWaitFavorite.isEmpty {
  327. for metadata in metadatasWaitFavorite {
  328. let session = NCSession.Session(account: metadata.account, urlBase: metadata.urlBase, user: metadata.user, userId: metadata.userId)
  329. let fileName = utilityFileSystem.getFileNamePath(metadata.fileName, serverUrl: metadata.serverUrl, session: session)
  330. let error = await networking.setFavorite(fileName: fileName, favorite: metadata.favorite, account: metadata.account)
  331. if error == .success {
  332. database.setMetadataFavorite(ocId: metadata.ocId, favorite: nil, saveOldFavorite: nil, status: global.metadataStatusNormal)
  333. } else {
  334. let favorite = (metadata.storeFlag as? NSString)?.boolValue ?? false
  335. database.setMetadataFavorite(ocId: metadata.ocId, favorite: favorite, saveOldFavorite: nil, status: global.metadataStatusNormal)
  336. }
  337. NotificationCenter.default.postOnMainThread(name: self.global.notificationCenterFavoriteFile, userInfo: ["ocId": metadata.ocId, "serverUrl": metadata.serverUrl])
  338. }
  339. }
  340. /// ------------------------ RENAME
  341. ///
  342. if let metadatasWaitRename = self.database.getMetadatas(predicate: NSPredicate(format: "status == %d", global.metadataStatusWaitRename), sortedByKeyPath: "serverUrl", ascending: true), !metadatasWaitRename.isEmpty {
  343. for metadata in metadatasWaitRename {
  344. let serverUrlFileNameSource = metadata.serveUrlFileName
  345. let serverUrlFileNameDestination = metadata.serverUrl + "/" + metadata.fileName
  346. let result = await networking.moveFileOrFolder(serverUrlFileNameSource: serverUrlFileNameSource, serverUrlFileNameDestination: serverUrlFileNameDestination, overwrite: false, account: metadata.account)
  347. if result.error == .success {
  348. database.setMetadataServeUrlFileNameStatusNormal(ocId: metadata.ocId)
  349. } else {
  350. database.restoreMetadataFileName(ocId: metadata.ocId)
  351. }
  352. NotificationCenter.default.postOnMainThread(name: NCGlobal.shared.notificationCenterRenameFile, userInfo: ["serverUrl": metadata.serverUrl, "account": metadata.account, "error": result.error])
  353. }
  354. }
  355. /// ------------------------ DELETE
  356. ///
  357. if let metadatasWaitDelete = self.database.getMetadatas(predicate: NSPredicate(format: "status == %d", global.metadataStatusWaitDelete), sortedByKeyPath: "serverUrl", ascending: true), !metadatasWaitDelete.isEmpty {
  358. for metadata in metadatasWaitDelete {
  359. if networking.deleteFileOrFolderQueue.operations.filter({ ($0 as? NCOperationDeleteFileOrFolder)?.ocId == metadata.ocId }).isEmpty {
  360. networking.deleteFileOrFolderQueue.addOperation(NCOperationDeleteFileOrFolder(metadata: metadata))
  361. }
  362. }
  363. }
  364. /// ------------------------ CREATE FOLDER
  365. ///
  366. if let metadatasWaitCreateFolder = self.database.getMetadatas(predicate: NSPredicate(format: "status == %d", global.metadataStatusWaitCreateFolder), sortedByKeyPath: "serverUrl", ascending: true), !metadatasWaitCreateFolder.isEmpty {
  367. for metadata in metadatasWaitCreateFolder {
  368. let error = await networking.createFolder(metadata: metadata)
  369. if error != .success {
  370. if metadata.sessionError.isEmpty {
  371. let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName
  372. let message = String(format: NSLocalizedString("_create_folder_error_", comment: ""), serverUrlFileName)
  373. NCContentPresenter().messageNotification(message, error: error, delay: NCGlobal.shared.dismissAfterSecond, type: NCContentPresenter.messageType.error, priority: .max)
  374. }
  375. returnValue = true
  376. }
  377. }
  378. }
  379. return returnValue
  380. }
  381. // MARK: - Public
  382. func startProcess() {
  383. startTimer()
  384. startObserveTableMetadata()
  385. }
  386. func stopProcess() {
  387. timerProcess?.invalidate()
  388. timerProcess = nil
  389. notificationToken?.invalidate()
  390. notificationToken = nil
  391. }
  392. func refreshProcessingTask() async -> (counterDownloading: Int, counterUploading: Int) {
  393. await withCheckedContinuation { continuation in
  394. self.lockQueue.sync {
  395. guard !self.hasRun, networking.isOnline else { return }
  396. self.hasRun = true
  397. Task { [weak self] in
  398. guard let self else { return }
  399. let result = await self.start()
  400. self.hasRun = false
  401. continuation.resume(returning: result)
  402. }
  403. }
  404. }
  405. }
  406. func createProcessUploads(metadatas: [tableMetadata], verifyAlreadyExists: Bool = false, completion: @escaping (_ items: Int) -> Void = {_ in}) {
  407. var metadatasForUpload: [tableMetadata] = []
  408. for metadata in metadatas {
  409. if verifyAlreadyExists {
  410. if self.database.getMetadata(predicate: NSPredicate(format: "account == %@ && serverUrl == %@ && fileName == %@ && session != ''",
  411. metadata.account,
  412. metadata.serverUrl,
  413. metadata.fileName)) != nil {
  414. continue
  415. }
  416. }
  417. metadatasForUpload.append(metadata)
  418. }
  419. self.database.addMetadatas(metadatasForUpload)
  420. completion(metadatasForUpload.count)
  421. }
  422. }