diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index fe25e45088..8bfb69c8f0 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -5946,7 +5946,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -6012,7 +6012,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = NKUJUXUJ3B; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; diff --git a/iOSClient/Networking/NCNetworking+Download.swift b/iOSClient/Networking/NCNetworking+Download.swift index 7b28e8aa18..36a029fe3a 100644 --- a/iOSClient/Networking/NCNetworking+Download.swift +++ b/iOSClient/Networking/NCNetworking+Download.swift @@ -79,6 +79,9 @@ extension NCNetworking { downloadTask = task } progressHandler: { progress in Task { + guard await self.progressQuantizer.shouldEmit(serverUrlFileName: metadata.serverUrlFileName, fraction: progress.fractionCompleted) else { + return + } await NCManageDatabase.shared.setMetadataProgress(ocId: metadata.ocId, progress: progress.fractionCompleted) await self.transferDispatcher.notifyAllDelegates { delegate in delegate.transferProgressDidUpdate(progress: Float(progress.fractionCompleted), @@ -91,6 +94,10 @@ extension NCNetworking { progressHandler(progress) } + Task { + await progressQuantizer.clear(serverUrlFileName: metadata.serverUrlFileName) + } + if withDownloadComplete { var error = NKError() var dateLastModified: Date? @@ -115,9 +122,7 @@ extension NCNetworking { @discardableResult func downloadFileInBackground(metadata: tableMetadata, taskHandler: @escaping (_ task: URLSessionDownloadTask?) -> Void = { _ in }, - start: @escaping () -> Void = { }) - async -> NKError { - + start: @escaping () -> Void = { }) async -> NKError { let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileName: metadata.fileNameView, userId: metadata.userId, urlBase: metadata.urlBase) start() @@ -164,9 +169,10 @@ extension NCNetworking { length: Int64, task: URLSessionTask, error: NKError) { - - #if EXTENSION_FILE_PROVIDER_EXTENSION Task { + await progressQuantizer.clear(serverUrlFileName: serverUrl + "/" + fileName) + + #if EXTENSION_FILE_PROVIDER_EXTENSION await FileProviderData.shared.downloadComplete(fileName: fileName, serverUrl: serverUrl, etag: etag, @@ -176,10 +182,8 @@ extension NCNetworking { task: task, error: error) return - } - #endif + #endif - Task { guard let metadata = await NCManageDatabase.shared.getMetadataAsync(predicate: NSPredicate(format: "serverUrl == %@ AND fileName == %@", serverUrl, fileName)) else { return } @@ -288,6 +292,9 @@ extension NCNetworking { task: URLSessionTask) { Task { + guard await progressQuantizer.shouldEmit(serverUrlFileName: serverUrl + "/" + fileName, fraction: Double(progress)) else { + return + } await NCManageDatabase.shared.setMetadataProgress(fileName: fileName, serverUrl: serverUrl, taskIdentifier: task.taskIdentifier, progress: Double(progress)) await self.transferDispatcher.notifyAllDelegates { delegate in delegate.transferProgressDidUpdate(progress: progress, diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 561c54da17..6842924267 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -50,6 +50,10 @@ extension NCNetworking { taskHandler(task) } progressHandler: { progress in Task { + guard await self.progressQuantizer.shouldEmit(serverUrlFileName: serverUrlFileName, fraction: progress.fractionCompleted) else { + return + } + if let metadata { await NCManageDatabase.shared.setMetadataProgress(ocId: metadata.ocId, progress: progress.fractionCompleted) await self.transferDispatcher.notifyAllDelegates { delegate in @@ -64,6 +68,10 @@ extension NCNetworking { progressHandler(progress.completedUnitCount, progress.totalUnitCount, progress.fractionCompleted) } + Task { + await progressQuantizer.clear(serverUrlFileName: serverUrlFileName) + } + if withUploadComplete, let metadata { await self.uploadComplete(withMetadata: metadata, ocId: results.ocId, etag: results.etag, date: results.date, size: results.size, error: results.error) } @@ -138,6 +146,9 @@ extension NCNetworking { taskHandler(task) } progressHandler: { totalBytesExpected, totalBytes, fractionCompleted in Task { + guard await self.progressQuantizer.shouldEmit(serverUrlFileName: metadata.serverUrlFileName, fraction: fractionCompleted) else { + return + } await NCManageDatabase.shared.setMetadataProgress(ocId: metadata.ocId, progress: fractionCompleted) await self.transferDispatcher.notifyAllDelegates { delegate in delegate.transferProgressDidUpdate(progress: Float(fractionCompleted), @@ -451,21 +462,21 @@ extension NCNetworking { size: Int64, task: URLSessionTask, error: NKError) { - #if EXTENSION_FILE_PROVIDER_EXTENSION Task { - await FileProviderData.shared.uploadComplete(fileName: fileName, - serverUrl: serverUrl, - ocId: ocId, - etag: etag, - date: date, - size: size, - task: task, - error: error) - return - } - #endif + await progressQuantizer.clear(serverUrlFileName: serverUrl + "/" + fileName) + + #if EXTENSION_FILE_PROVIDER_EXTENSION + await FileProviderData.shared.uploadComplete(fileName: fileName, + serverUrl: serverUrl, + ocId: ocId, + etag: etag, + date: date, + size: size, + task: task, + error: error) + return + #endif - Task { if let metadata = await NCManageDatabase.shared.getMetadataAsync(predicate: NSPredicate(format: "serverUrl == %@ AND fileName == %@", serverUrl, fileName)) { await uploadComplete(withMetadata: metadata, ocId: ocId, etag: etag, date: date, size: size, error: error) } else { @@ -483,6 +494,9 @@ extension NCNetworking { session: URLSession, task: URLSessionTask) { Task { + guard await progressQuantizer.shouldEmit(serverUrlFileName: serverUrl + "/" + fileName, fraction: Double(progress)) else { + return + } await NCManageDatabase.shared.setMetadataProgress(fileName: fileName, serverUrl: serverUrl, taskIdentifier: task.taskIdentifier, progress: Double(progress)) await self.transferDispatcher.notifyAllDelegates { delegate in delegate.transferProgressDidUpdate(progress: progress, diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index 00713a0350..51284cecfc 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -220,6 +220,35 @@ actor NetworkingTasks { } } +/// Quantizes per-task progress updates to integer percentages (0...100). +/// Each (serverUrlFileName) pair is tracked separately, so you get +/// at most one update per integer percent for each transfer. +actor ProgressQuantizer { + private var lastPercent: [String: Int] = [:] + + /// Returns `true` only when integer percent changes (or hits 100). + /// + /// - Parameters: + /// - serverUrlFileName: The name of the file being transferred. + /// - fraction: Progress fraction [0.0 ... 1.0]. + func shouldEmit(serverUrlFileName: String, fraction: Double) -> Bool { + let percent = min(max(Int((fraction * 100).rounded(.down)), 0), 100) + + let last = lastPercent[serverUrlFileName] ?? -1 + guard percent != last || percent == 100 else { + return false + } + + lastPercent[serverUrlFileName] = percent + return true + } + + /// Clears stored state for a finished transfer. + func clear(serverUrlFileName: String) { + lastPercent.removeValue(forKey: serverUrlFileName) + } +} + class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { static let shared = NCNetworking() @@ -265,6 +294,8 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { let transferDispatcher = NCTransferDelegateDispatcher() + let progressQuantizer = ProgressQuantizer() + // OPERATIONQUEUE let downloadThumbnailQueue = Queuer(name: "downloadThumbnailQueue", maxConcurrentOperationCount: 10, qualityOfService: .default) let downloadThumbnailActivityQueue = Queuer(name: "downloadThumbnailActivityQueue", maxConcurrentOperationCount: 10, qualityOfService: .default) diff --git a/iOSClient/Networking/NCNetworkingProcess.swift b/iOSClient/Networking/NCNetworkingProcess.swift index 29458befdf..a932c078bb 100644 --- a/iOSClient/Networking/NCNetworkingProcess.swift +++ b/iOSClient/Networking/NCNetworkingProcess.swift @@ -585,7 +585,6 @@ actor NCNetworkingProcess { } else { await database.setMetadataSessionAsync(ocId: metadata.ocId, status: global.metadataStatusNormal) - metadatasError[metadata] = resultDelete.error returnError = resultDelete.error } diff --git a/iOSClient/Networking/NCService.swift b/iOSClient/Networking/NCService.swift index d7e51518ab..768f05e02a 100644 --- a/iOSClient/Networking/NCService.swift +++ b/iOSClient/Networking/NCService.swift @@ -273,7 +273,8 @@ class NCService: NSObject { etag: metadata.etag, metadatasInDownload: metadatasInDownload, userId: metadata.userId, - urlBase: metadata.urlBase) { + urlBase: metadata.urlBase), + metadata.status == self.global.metadataStatusNormal { await self.database.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, session: NCNetworking.shared.sessionDownloadBackground, selector: NCGlobal.shared.selectorSynchronizationOffline)