diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 6a5bdb798c..40a35fe685 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -5754,7 +5754,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; @@ -5820,7 +5820,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/Share/NCShareExtension.swift b/Share/NCShareExtension.swift index 50b4b35a3b..ffa414d5ac 100644 --- a/Share/NCShareExtension.swift +++ b/Share/NCShareExtension.swift @@ -446,12 +446,11 @@ extension NCShareExtension { userId: metadata.userId, urlBase: metadata.urlBase) - let results = await NCNetworking.shared.uploadFile(fileNameLocalPath: fileNameLocalPath, + let results = await NCNetworking.shared.uploadFile(account: metadata.account, + fileNameLocalPath: fileNameLocalPath, serverUrlFileName: metadata.serverUrlFileName, creationDate: metadata.creationDate as Date, - dateModificationFile: metadata.date as Date, - account: metadata.account, - metadata: metadata) { _ in + dateModificationFile: metadata.date as Date) { _ in } progressHandler: { _, _, fractionCompleted in self.hud.progress(fractionCompleted) } diff --git a/iOSClient/Data/NCManageDatabase+LivePhoto.swift b/iOSClient/Data/NCManageDatabase+LivePhoto.swift index bd840af2bb..bc1861cdcc 100644 --- a/iOSClient/Data/NCManageDatabase+LivePhoto.swift +++ b/iOSClient/Data/NCManageDatabase+LivePhoto.swift @@ -102,7 +102,6 @@ extension NCManageDatabase { return results.map { tableLivePhoto(value: $0) } // detached copy } } - // swiftlint:enable empty_string /// Returns true if at least one valid Live Photo record exists for the given account. func hasLivePhotos() async -> Bool { @@ -117,4 +116,5 @@ extension NCManageDatabase { return !results.isEmpty } ?? false } + // swiftlint:enable empty_string } diff --git a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift index 311fe0f9af..541a627688 100644 --- a/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift +++ b/iOSClient/Main/Collection Common/NCCollectionViewCommon.swift @@ -713,7 +713,7 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene let token = showHudBanner( scene: scene, - title: NSLocalizedString("_delete_in_progress_", comment: "")) + title: NSLocalizedString("_upload_in_progress_", comment: "")) for (index, items) in UIPasteboard.general.items.enumerated() { for item in items { @@ -739,26 +739,15 @@ class NCCollectionViewCommon: UIViewController, UIGestureRecognizerDelegate, UIS continue } - let resultsUpload = await NextcloudKit.shared.uploadAsync( - serverUrlFileName: serverUrlFileName, - fileNameLocalPath: fileNameLocalPath, - account: session.account) { _ in - } taskHandler: { task in - Task { - let identifier = await NCNetworking.shared.networkingTasks.createIdentifier( - account: self.session.account, - path: serverUrlFileName, - name: "upload") - await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - } - } progressHandler: { progress in - Task {@MainActor in - LucidBanner.shared.update( - title: "", - progress: progress.fractionCompleted, - for: token) - } + let resultsUpload = await NCNetworking.shared.uploadFile(account: session.account, + fileNameLocalPath: fileNameLocalPath, + serverUrlFileName: serverUrlFileName) { _ in + } progressHandler: { _, _, fractionCompleted in + Task {@MainActor in + LucidBanner.shared.update(progress: fractionCompleted, for: token) } + } + if resultsUpload.error == .success, let etag = resultsUpload.etag, let ocId = resultsUpload.ocId { diff --git a/iOSClient/Main/Create cloud/NCCreate.swift b/iOSClient/Main/Create cloud/NCCreate.swift index 90acf0466c..684ebd6707 100644 --- a/iOSClient/Main/Create cloud/NCCreate.swift +++ b/iOSClient/Main/Create cloud/NCCreate.swift @@ -228,84 +228,137 @@ class NCCreate: NSObject { } } + /// Creates and presents a UIActivityViewController for the given metadata list. + /// - Parameters: + /// - selectedMetadata: List of tableMetadata items selected by the user. + /// - controller: Main tab bar controller used to present the activity view. + /// - sender: The UI element that triggered the action (for iPad popover anchoring). @MainActor - func createActivityViewController(selectedMetadata: [tableMetadata], - controller: NCMainTabBarController?, - sender: Any?) async { + func createActivityViewController(selectedMetadata: [tableMetadata], controller: NCMainTabBarController?, sender: Any?) async { guard let controller else { return } - let metadatas = selectedMetadata.filter({ !$0.directory }) - var urls: [URL] = [] + + let metadatas = selectedMetadata.filter { !$0.directory } + var exportURLs: [URL] = [] var downloadMetadata: [(tableMetadata, URL)] = [] + let scene = SceneManager.shared.getWindow(controller: controller)?.windowScene let token = showHudBanner( scene: scene, - title: NSLocalizedString("_download_in_progress_", comment: "")) + title: NSLocalizedString("_download_in_progress_", comment: "") + ) for metadata in metadatas { - let fileURL = URL(fileURLWithPath: utilityFileSystem.getDirectoryProviderStorageOcId( + let localPath = utilityFileSystem.getDirectoryProviderStorageOcId( metadata.ocId, fileName: metadata.fileNameView, userId: metadata.userId, - urlBase: metadata.urlBase) + urlBase: metadata.urlBase ) + let fileURL = URL(fileURLWithPath: localPath) + if utilityFileSystem.fileProviderStorageExists(metadata) { - urls.append(fileURL) + downloadMetadata.append((metadata, fileURL)) } else { downloadMetadata.append((metadata, fileURL)) } } - for (metadata, url) in downloadMetadata { + // Download missing files + for (originalMetadata, localFileURL) in downloadMetadata { guard let metadata = await NCManageDatabase.shared.setMetadataSessionInWaitDownloadAsync( - ocId: metadata.ocId, + ocId: originalMetadata.ocId, session: NCNetworking.shared.sessionDownload, selector: "", - sceneIdentifier: controller.sceneIdentifier) - else { + sceneIdentifier: controller.sceneIdentifier + ) else { + LucidBanner.shared.dismiss(for: token) return } let results = await NCNetworking.shared.downloadFile( - metadata: metadata) { _ in - } progressHandler: { progress in - Task {@MainActor in - LucidBanner.shared.update(progress: progress.fractionCompleted, for: token) - } + metadata: metadata + ) { _ in + // downloadStartHandler not used here + } progressHandler: { progress in + Task { @MainActor in + LucidBanner.shared.update( + progress: progress.fractionCompleted, + for: token + ) } + } + if results.nkError == .success { - urls.append(url) + if let exportedURL = exportFileForSharing(from: localFileURL) { + exportURLs.append(exportedURL) + } } else { - Task {@MainActor in - showErrorBanner(scene: scene, - errorDescription: results.nkError.errorDescription, - errorCode: results.nkError.errorCode) + Task { @MainActor in + showErrorBanner( + scene: scene, + errorDescription: results.nkError.errorDescription, + errorCode: results.nkError.errorCode + ) } } } LucidBanner.shared.dismiss(for: token) - guard !urls.isEmpty else { - return - } + guard !exportURLs.isEmpty else { return } - let activityViewController = UIActivityViewController(activityItems: urls, applicationActivities: nil) + let activityViewController = UIActivityViewController(activityItems: exportURLs, applicationActivities: nil) - // iPad + // iPad popover configuration if let popover = activityViewController.popoverPresentationController { if let view = sender as? UIView { popover.sourceView = view popover.sourceRect = view.bounds } else { popover.sourceView = controller.view - popover.sourceRect = CGRect(x: controller.view.bounds.midX, - y: controller.view.bounds.midY, - width: 0, - height: 0) + popover.sourceRect = CGRect( + x: controller.view.bounds.midX, + y: controller.view.bounds.midY, + width: 0, + height: 0 + ) popover.permittedArrowDirections = [] } } controller.present(activityViewController, animated: true) } + + // MARK: - Private helper + + /// Copies a file from internal/provider storage to a shareable temporary location. + /// This makes the URL safe to pass to UIActivityViewController, "Copy", etc. + private func exportFileForSharing(from sourceURL: URL) -> URL? { + let fileManager = FileManager.default + let exportBaseURL = fileManager.temporaryDirectory.appendingPathComponent("ShareExports", isDirectory: true) + + do { + if !fileManager.fileExists(atPath: exportBaseURL.path) { + try fileManager.createDirectory( + at: exportBaseURL, + withIntermediateDirectories: true, + attributes: nil + ) + } + + // Destination file path (we can just reuse lastPathComponent) + let destinationURL = exportBaseURL.appendingPathComponent(sourceURL.lastPathComponent, isDirectory: false) + + // Remove previous copy if it exists + if fileManager.fileExists(atPath: destinationURL.path) { + try fileManager.removeItem(at: destinationURL) + } + + try fileManager.copyItem(at: sourceURL, to: destinationURL) + + return destinationURL + } catch { + return nil + } + } } diff --git a/iOSClient/Main/NCDragDrop.swift b/iOSClient/Main/NCDragDrop.swift index fc17e71e24..d155e16c00 100644 --- a/iOSClient/Main/NCDragDrop.swift +++ b/iOSClient/Main/NCDragDrop.swift @@ -233,12 +233,11 @@ class NCDragDrop: NSObject { let fileName = await NCNetworking.shared.createFileName(fileNameBase: metadata.fileName, account: session.account, serverUrl: destination) let serverUrlFileName = utilityFileSystem.createServerUrl(serverUrl: destination, fileName: fileName) - let results = await NCNetworking.shared.uploadFile(fileNameLocalPath: fileNameLocalPath, + let results = await NCNetworking.shared.uploadFile(account: session.account, + fileNameLocalPath: fileNameLocalPath, serverUrlFileName: serverUrlFileName, creationDate: metadata.creationDate as Date, - dateModificationFile: metadata.date as Date, - account: session.account, - performPostProcessing: false) { request in + dateModificationFile: metadata.date as Date) { request in uploadRequest = request } progressHandler: { _, _, fractionCompleted in let status = NSLocalizedString("_status_uploading_", comment: "").lowercased() diff --git a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift index 4e28874a3d..7f094118fa 100644 --- a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift +++ b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift @@ -279,13 +279,11 @@ class NCNetworkingE2EEUpload: NSObject { userId: metadata.userId, urlBase: metadata.urlBase) - let results = await NCNetworking.shared.uploadFile(fileNameLocalPath: fileNameLocalPath, + let results = await NCNetworking.shared.uploadFile(account: metadata.account, + fileNameLocalPath: fileNameLocalPath, serverUrlFileName: metadata.serverUrlFileName, creationDate: metadata.creationDate as Date, dateModificationFile: metadata.date as Date, - account: metadata.account, - metadata: metadata, - performPostProcessing: false, customHeaders: ["e2e-token": e2eToken]) { request in self.request = request } progressHandler: { _, _, fractionCompleted in diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 4017359b8e..93ea6b9597 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -11,13 +11,11 @@ extension NCNetworking { // MARK: - Upload file in foreground @discardableResult - func uploadFile(fileNameLocalPath: String, + func uploadFile(account: String, + fileNameLocalPath: String, serverUrlFileName: String, - creationDate: Date, - dateModificationFile: Date, - account: String, - metadata: tableMetadata? = nil, - performPostProcessing: Bool = true, + creationDate: Date? = nil, + dateModificationFile: Date? = nil, customHeaders: [String: String]? = nil, requestHandler: @escaping (_ request: UploadRequest) -> Void = { _ in }, taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }, @@ -38,54 +36,12 @@ extension NCNetworking { path: serverUrlFileName, name: "upload") await NCNetworking.shared.networkingTasks.track(identifier: identifier, task: task) - - if let metadata { - await NCManageDatabase.shared.setMetadataSessionAsync(ocId: metadata.ocId, - sessionTaskIdentifier: task.taskIdentifier, - status: self.global.metadataStatusUploading) - - await self.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferChange(status: self.global.networkingStatusUploading, - account: metadata.account, - fileName: metadata.fileName, - serverUrl: metadata.serverUrl, - selector: metadata.sessionSelector, - ocId: metadata.ocId, - destination: nil, - error: .success) - } - } } taskHandler(task) } progressHandler: { progress in - Task { - guard let metadata, - await self.progressQuantizer.shouldEmit(serverUrlFileName: serverUrlFileName, fraction: progress.fractionCompleted) else { - return - } - await self.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferProgressDidUpdate(progress: Float(progress.fractionCompleted), - totalBytes: progress.totalUnitCount, - totalBytesExpected: progress.completedUnitCount, - fileName: metadata.fileName, - serverUrl: metadata.serverUrl) - } - } progressHandler(progress.completedUnitCount, progress.totalUnitCount, progress.fractionCompleted) } - Task { - await progressQuantizer.clear(serverUrlFileName: serverUrlFileName) - } - - if performPostProcessing, let metadata { - if results.error == .success, let ocId = results.ocId { - await uploadSuccess(withMetadata: metadata, ocId: ocId, etag: results.etag, date: results.date) - } else { - await uploadError(withMetadata: metadata, error: results.error) - } - } - return results } diff --git a/iOSClient/StatusMessage/NCStatusMessageModel.swift b/iOSClient/StatusMessage/NCStatusMessageModel.swift index 63d52662b9..f6fc8d5ae7 100644 --- a/iOSClient/StatusMessage/NCStatusMessageModel.swift +++ b/iOSClient/StatusMessage/NCStatusMessageModel.swift @@ -227,5 +227,3 @@ import NextcloudKit return [meeting, commuting] } } - - diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 78fa460528..fb08fe86bd 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -662,6 +662,7 @@ "_update_in_progress_" = "Update in progress …"; "_delete_in_progress_" = "Delete in progress …"; "_download_in_progress_" = "Download in progress …"; +"_upload_in_progress_" = "Upload in progress …"; "_in_waiting_" = "In waiting"; "_in_progress_" = "In progress"; "_in_error_" = "In error"; diff --git a/iOSClient/Utility/NCUtility+Image.swift b/iOSClient/Utility/NCUtility+Image.swift index 1e8b7c30c0..c06468f8a2 100644 --- a/iOSClient/Utility/NCUtility+Image.swift +++ b/iOSClient/Utility/NCUtility+Image.swift @@ -378,7 +378,7 @@ extension NCUtility { } statusMessage = statusMessage.trimmingCharacters(in: .whitespaces) - + if statusMessage.isEmpty { statusMessage = messageUserDefined }