From 88327b37fdc1743104c5c6c533c513b9ccd51b88 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 09:24:37 +0200 Subject: [PATCH 01/74] cod Signed-off-by: Marino Faggiana --- .../Networking/NCNetworking+Upload.swift | 30 ++++++----- iOSClient/Networking/NCNetworking.swift | 54 ++++++++++++------- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index ed69105452..8c610fa097 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -222,6 +222,17 @@ extension NCNetworking { if let task, error == .success { nkLog(debug: "Upload file \(metadata.fileNameView) with taskIdentifier \(task.taskIdentifier)") + + addUploadItem(UploadItemDisk(fileName: metadata.fileName, + ocIdTransfer: metadata.ocIdTransfer, + progress: 0, + selector: metadata.sessionSelector, + serverUrl: metadata.serverUrl, + session: metadata.session, + status: metadata.status, + size: metadata.size, + taskIdentifier: task.taskIdentifier)) + if let metadata = await NCManageDatabase.shared.setMetadataSessionAsync(ocId: metadata.ocId, sessionTaskIdentifier: task.taskIdentifier, status: self.global.metadataStatusUploading) { @@ -493,18 +504,13 @@ extension NCNetworking { #endif if error == .success { - // TODO: upload item - /* - let uploadItem = UploadItemDisk(fileName: fileName, - serverUrl: serverUrl, - ocId: ocId, - ocIdTransfer: nil, - etag: etag, - date: date, - size: size, - taskIdentifier: task.taskIdentifier) - addUploadItem(uploadItem, fileName: fileName, serverUrl: serverUrl) - */ + addUploadItem(UploadItemDisk(date: date, + etag: etag, + fileName: fileName, + ocId: ocId, + serverUrl: serverUrl, + size: size, + taskIdentifier: task.taskIdentifier)) } else { } diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index 2681af2192..4033883c4d 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -258,20 +258,18 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { } struct UploadItemDisk: Codable { - let date: Date? - let errorCode: Int? - let etag: String? - let fileName: String - let ocId: String? - let ocIdTransfer: String? - let progress: Double? - let selector: String? - let serverUrl: String - let session: String? - let sessionError: String? - let status: Int? - let size: Int64 - let taskIdentifier: Int? + var date: Date? + var etag: String? + var fileName: String? + var ocId: String? + var ocIdTransfer: String? + var progress: Double? + var selector: String? + var serverUrl: String? + var session: String? + var status: Int? + var size: Int64? + var taskIdentifier: Int? } let networkingTasks = NetworkingTasks() @@ -501,14 +499,16 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { // MARK: - Upload Item - func addUploadItem(_ item: UploadItemDisk, fileName: String, serverUrl: String) { + func addUploadItem(_ item: UploadItemDisk) { guard let url = self.uploadStoreURL else { return } uploadStoreIO.sync { - // Upsert by (serverUrl + fileName) - if let idx = uploadItemsCache.firstIndex(where: { $0.serverUrl == serverUrl && $0.fileName == fileName }) { - uploadItemsCache[idx] = item + // Upsert by (serverUrl + fileName + taskIdentifier) + if let idx = uploadItemsCache.firstIndex(where: { $0.serverUrl == item.serverUrl && $0.fileName == item.fileName && $0.taskIdentifier == item.taskIdentifier}) { + let existing = uploadItemsCache[idx] + let merged = mergeUploadItem(existing: existing, with: item) + uploadItemsCache[idx] = merged } else { uploadItemsCache.append(item) } @@ -539,6 +539,24 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { } } + /// Merges two UploadItemDisk objects, updating only non-nil fields from `new`. + private func mergeUploadItem(existing: UploadItemDisk, with new: UploadItemDisk) -> UploadItemDisk { + return UploadItemDisk( + date: new.date ?? existing.date, + etag: new.etag ?? existing.etag, + fileName: existing.fileName ?? new.fileName, + ocId: new.ocId ?? existing.ocId, + ocIdTransfer: new.ocIdTransfer ?? existing.ocIdTransfer, + progress: new.progress ?? existing.progress, + selector: new.selector ?? existing.selector, + serverUrl: existing.serverUrl ?? new.serverUrl, + session: new.session ?? existing.session, + status: new.status ?? existing.status, + size: new.size ?? existing.size, + taskIdentifier: new.taskIdentifier ?? existing.taskIdentifier + ) + } + /// Read a snapshot for batch processing (e.g., flush to Realm); no disk I/O, returns the cache. func readAllUploadItems() -> [UploadItemDisk] { uploadStoreIO.sync { From b673b21b9d133f8778edd0a1a986c2161f2ed086 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 09:43:27 +0200 Subject: [PATCH 02/74] code Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 10 ++ .../NCBackgroundLocationUploadManager.swift | 10 +- iOSClient/Networking/NCConfigServer.swift | 25 +---- .../Networking/NCNetworking+Download.swift | 25 +---- .../Networking/NCNetworking+LivePhoto.swift | 25 +---- .../NCNetworking+Synchronization.swift | 25 +---- iOSClient/Networking/NCNetworking+Task.swift | 25 +---- .../Networking/NCNetworking+Upload.swift | 5 +- .../Networking/NCNetworking+WebDAV.swift | 25 +---- iOSClient/Networking/NCNetworking.swift | 91 +++++++++++-------- iOSClient/Networking/NCService.swift | 25 +---- iOSClient/Networking/NCUploadStore.swift | 7 ++ 12 files changed, 96 insertions(+), 202 deletions(-) create mode 100644 iOSClient/Networking/NCUploadStore.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 0d081f4036..f2592c0945 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -722,6 +722,10 @@ F7864AD02A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; F7864AD12A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; F7864AD22A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; + F786891A2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78689192E93A9D5000E39B3 /* NCUploadStore.swift */; }; + F786891B2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78689192E93A9D5000E39B3 /* NCUploadStore.swift */; }; + F786891C2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78689192E93A9D5000E39B3 /* NCUploadStore.swift */; }; + F786891D2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78689192E93A9D5000E39B3 /* NCUploadStore.swift */; }; F787704F22E7019900F287A9 /* NCShareLinkCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F787704E22E7019900F287A9 /* NCShareLinkCell.xib */; }; F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F787AC08298BCB4A0001BB00 /* SVGKitSwift */; }; F787AC0B298BCB540001BB00 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F787AC0A298BCB540001BB00 /* SVGKitSwift */; }; @@ -1656,6 +1660,7 @@ F785129A2D79899E0087DDD0 /* NCNetworking+TermsOfService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+TermsOfService.swift"; sourceTree = ""; }; F785EE9C246196DF00B3F945 /* NCNetworkingE2EE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNetworkingE2EE.swift; sourceTree = ""; }; F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+LocalFile.swift"; sourceTree = ""; }; + F78689192E93A9D5000E39B3 /* NCUploadStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCUploadStore.swift; sourceTree = ""; }; F787704E22E7019900F287A9 /* NCShareLinkCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCShareLinkCell.xib; sourceTree = ""; }; F78A10BE29322E8A008499B8 /* NCManageDatabase+Directory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Directory.swift"; sourceTree = ""; }; F78A18B523CDD07D00F681F3 /* NCViewerRichWorkspaceWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCViewerRichWorkspaceWebView.swift; sourceTree = ""; }; @@ -2539,6 +2544,7 @@ F70D8D8024A4A9BF000A5756 /* NCNetworkingProcess.swift */, F755BD9A20594AC7008C5FBB /* NCService.swift */, F7A3DB8F2DDE238C008F7EC8 /* NCDebouncer.swift */, + F78689192E93A9D5000E39B3 /* NCUploadStore.swift */, ); path = Networking; sourceTree = ""; @@ -4430,6 +4436,7 @@ AF730AFA27843E4C00B7520E /* NCShareExtension+NCAccountRequestDelegate.swift in Sources */, F7327E232B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */, F749B64D297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */, + F786891A2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */, F72FD3B8297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */, F7A76DC8256A71CD00119AB3 /* UIImage+Extension.swift in Sources */, F3E173C32C9B1067006D177A /* AwakeMode.swift in Sources */, @@ -4489,6 +4496,7 @@ F72EA95A28B7BD0D00C88F0C /* FilesWidgetView.swift in Sources */, F768823C2C0DD231001CF441 /* NCPreferences.swift in Sources */, F71F6D082B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */, + F786891C2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */, F78302FE28B4C44700B84583 /* NCBrand.swift in Sources */, F749B64B297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */, AA8D31572D41052300FE2775 /* NCManageDatabase+DownloadLimit.swift in Sources */, @@ -4590,6 +4598,7 @@ F771E3D520E2392D00AFB62D /* FileProviderItem.swift in Sources */, F3E173C42C9B1067006D177A /* AwakeMode.swift in Sources */, F7D7A76F2DCDD437003D2007 /* NCManageDatabase+AutoUpload.swift in Sources */, + F786891B2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */, AF4BF616275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */, F343A4B72A1E084300DDA874 /* PHAsset+Extension.swift in Sources */, F7434B3620E23FE000417916 /* NCManageDatabase.swift in Sources */, @@ -4700,6 +4709,7 @@ F7A3DB932DDE23B5008F7EC8 /* NCDebouncer.swift in Sources */, F72CD63A25C19EBF00F46F9A /* NCAutoUpload.swift in Sources */, AF93471D27E2361E002537EE /* NCShareAdvancePermissionFooter.swift in Sources */, + F786891D2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */, AF1A9B6427D0CA1E00F17A9E /* UIAlertController+Extension.swift in Sources */, F7FA80012C0F4F3B0072FC60 /* NCUploadAssetsView.swift in Sources */, 371B5A2E23D0B04500FAFAE9 /* NCMenu.swift in Sources */, diff --git a/iOSClient/Networking/NCBackgroundLocationUploadManager.swift b/iOSClient/Networking/NCBackgroundLocationUploadManager.swift index 4613f8fe6c..3bea5f4204 100644 --- a/iOSClient/Networking/NCBackgroundLocationUploadManager.swift +++ b/iOSClient/Networking/NCBackgroundLocationUploadManager.swift @@ -1,10 +1,6 @@ -// -// NCBackgroundLocationUploadManager.swift -// Nextcloud -// -// Created by Marino Faggiana on 06/06/25. -// Copyright © 2025 Marino Faggiana. All rights reserved. -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import CoreLocation import NextcloudKit diff --git a/iOSClient/Networking/NCConfigServer.swift b/iOSClient/Networking/NCConfigServer.swift index 0d466c370c..2214a85b0a 100644 --- a/iOSClient/Networking/NCConfigServer.swift +++ b/iOSClient/Networking/NCConfigServer.swift @@ -1,25 +1,6 @@ -// -// NCConfigServer.swift -// Nextcloud -// -// Created by Marino Faggiana on 05/12/22. -// Copyright © 2022 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import Foundation import UIKit diff --git a/iOSClient/Networking/NCNetworking+Download.swift b/iOSClient/Networking/NCNetworking+Download.swift index 5828e86520..eb455be18c 100644 --- a/iOSClient/Networking/NCNetworking+Download.swift +++ b/iOSClient/Networking/NCNetworking+Download.swift @@ -1,25 +1,6 @@ -// -// NCNetworking+Download.swift -// Nextcloud -// -// Created by Marino Faggiana on 07/02/24. -// Copyright © 2024 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2024 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import NextcloudKit diff --git a/iOSClient/Networking/NCNetworking+LivePhoto.swift b/iOSClient/Networking/NCNetworking+LivePhoto.swift index 70ebb46fe5..53fac2299d 100644 --- a/iOSClient/Networking/NCNetworking+LivePhoto.swift +++ b/iOSClient/Networking/NCNetworking+LivePhoto.swift @@ -1,25 +1,6 @@ -// -// NCNetworking+LivePhoto.swift -// Nextcloud -// -// Created by Marino Faggiana on 07/02/24. -// Copyright © 2024 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2024 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import NextcloudKit diff --git a/iOSClient/Networking/NCNetworking+Synchronization.swift b/iOSClient/Networking/NCNetworking+Synchronization.swift index 270c19864b..47f0fd9b0b 100644 --- a/iOSClient/Networking/NCNetworking+Synchronization.swift +++ b/iOSClient/Networking/NCNetworking+Synchronization.swift @@ -1,25 +1,6 @@ -// -// NCNetworking+Synchronization.swift -// Nextcloud -// -// Created by Marino Faggiana on 07/02/24. -// Copyright © 2024 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2024 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import NextcloudKit diff --git a/iOSClient/Networking/NCNetworking+Task.swift b/iOSClient/Networking/NCNetworking+Task.swift index a007b0efef..415a1b51a3 100644 --- a/iOSClient/Networking/NCNetworking+Task.swift +++ b/iOSClient/Networking/NCNetworking+Task.swift @@ -1,25 +1,6 @@ -// -// NCNetworking+Task.swift -// Nextcloud -// -// Created by Marino Faggiana on 24/08/24. -// Copyright © 2024 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2024 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import Foundation import UIKit diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 8c610fa097..22fe1af19e 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2024 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + import UIKit import NextcloudKit import Alamofire @@ -222,7 +226,6 @@ extension NCNetworking { if let task, error == .success { nkLog(debug: "Upload file \(metadata.fileNameView) with taskIdentifier \(task.taskIdentifier)") - addUploadItem(UploadItemDisk(fileName: metadata.fileName, ocIdTransfer: metadata.ocIdTransfer, progress: 0, diff --git a/iOSClient/Networking/NCNetworking+WebDAV.swift b/iOSClient/Networking/NCNetworking+WebDAV.swift index 0d6c023237..7e976bda5a 100644 --- a/iOSClient/Networking/NCNetworking+WebDAV.swift +++ b/iOSClient/Networking/NCNetworking+WebDAV.swift @@ -1,25 +1,6 @@ -// -// NCNetworking+WebDAV.swift -// Nextcloud -// -// Created by Marino Faggiana on 07/02/24. -// Copyright © 2024 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2024 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import NextcloudKit diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index 4033883c4d..238c858c6b 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -1,25 +1,6 @@ -// -// NCNetworking.swift -// Nextcloud -// -// Created by Marino Faggiana on 23/10/19. -// Copyright © 2019 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2019 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit import OpenSSL @@ -499,6 +480,21 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { // MARK: - Upload Item + /// Adds or updates an `UploadItemDisk` entry in the local upload cache. + /// + /// This function performs an **upsert** operation (update or insert) inside the `uploadItemsCache` + /// by matching the combination of `(serverUrl, fileName, taskIdentifier)`. + /// If an existing entry is found, it is **merged** with the new one — only non-nil fields + /// from the incoming `item` overwrite the existing values. + /// The cache is then serialized and saved atomically to disk. + /// + /// - Parameter item: The `UploadItemDisk` instance containing upload information to insert or update. + /// + /// Behavior details: + /// - Thread-safe: Executed synchronously inside `uploadStoreIO`. + /// - Persistence: The JSON file at `uploadStoreURL` is updated atomically. + /// - Merge logic: Only non-nil properties of `item` replace existing ones. + /// - Error handling: Logs failures via `nkLog` without throwing exceptions. func addUploadItem(_ item: UploadItemDisk) { guard let url = self.uploadStoreURL else { return @@ -522,6 +518,21 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { } } + /// Removes an `UploadItemDisk` entry from the local upload cache. + /// + /// This function deletes the first cached upload record that matches the provided + /// `serverUrl` and `fileName` combination. After removal, the cache is persisted + /// atomically to disk to maintain consistency. + /// + /// - Parameters: + /// - serverUrl: The server URL associated with the upload item to remove. + /// - fileName: The file name of the upload item to remove. + /// + /// Behavior details: + /// - Thread-safe: Executed synchronously inside `uploadStoreIO`. + /// - Persistence: The JSON file at `uploadStoreURL` is updated atomically after removal. + /// - Matching: The record is matched using both `serverUrl` and `fileName`. + /// - Error handling: Logs persistence errors using `nkLog` without throwing exceptions. func removeUploadItem(serverUrl: String, fileName: String) { guard let url = self.uploadStoreURL else { return @@ -539,24 +550,6 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { } } - /// Merges two UploadItemDisk objects, updating only non-nil fields from `new`. - private func mergeUploadItem(existing: UploadItemDisk, with new: UploadItemDisk) -> UploadItemDisk { - return UploadItemDisk( - date: new.date ?? existing.date, - etag: new.etag ?? existing.etag, - fileName: existing.fileName ?? new.fileName, - ocId: new.ocId ?? existing.ocId, - ocIdTransfer: new.ocIdTransfer ?? existing.ocIdTransfer, - progress: new.progress ?? existing.progress, - selector: new.selector ?? existing.selector, - serverUrl: existing.serverUrl ?? new.serverUrl, - session: new.session ?? existing.session, - status: new.status ?? existing.status, - size: new.size ?? existing.size, - taskIdentifier: new.taskIdentifier ?? existing.taskIdentifier - ) - } - /// Read a snapshot for batch processing (e.g., flush to Realm); no disk I/O, returns the cache. func readAllUploadItems() -> [UploadItemDisk] { uploadStoreIO.sync { @@ -586,4 +579,22 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { } } } + + /// Merges two UploadItemDisk objects, updating only non-nil fields from `new`. + private func mergeUploadItem(existing: UploadItemDisk, with new: UploadItemDisk) -> UploadItemDisk { + return UploadItemDisk( + date: new.date ?? existing.date, + etag: new.etag ?? existing.etag, + fileName: existing.fileName ?? new.fileName, + ocId: new.ocId ?? existing.ocId, + ocIdTransfer: new.ocIdTransfer ?? existing.ocIdTransfer, + progress: new.progress ?? existing.progress, + selector: new.selector ?? existing.selector, + serverUrl: existing.serverUrl ?? new.serverUrl, + session: new.session ?? existing.session, + status: new.status ?? existing.status, + size: new.size ?? existing.size, + taskIdentifier: new.taskIdentifier ?? existing.taskIdentifier + ) + } } diff --git a/iOSClient/Networking/NCService.swift b/iOSClient/Networking/NCService.swift index 768f05e02a..d27f536e70 100644 --- a/iOSClient/Networking/NCService.swift +++ b/iOSClient/Networking/NCService.swift @@ -1,25 +1,6 @@ -// -// NCService.swift -// Nextcloud -// -// Created by Marino Faggiana on 14/03/18. -// Copyright © 2018 Marino Faggiana. All rights reserved. -// -// Author Marino Faggiana -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2018 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later import UIKit @preconcurrency import NextcloudKit diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift new file mode 100644 index 0000000000..9747b2453f --- /dev/null +++ b/iOSClient/Networking/NCUploadStore.swift @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2025 Marino Faggiana +// SPDX-License-Identifier: GPL-3.0-or-later + +import UIKit + + From 5171002c7a2d9159e3e5db251f567a7dc961ceea Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 10:02:23 +0200 Subject: [PATCH 03/74] code Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCNetworking.swift | 52 +------ iOSClient/Networking/NCUploadStore.swift | 189 +++++++++++++++++++++++ 2 files changed, 192 insertions(+), 49 deletions(-) diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index 238c858c6b..7f908ffd8e 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -238,21 +238,6 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { var serverUrl: String } - struct UploadItemDisk: Codable { - var date: Date? - var etag: String? - var fileName: String? - var ocId: String? - var ocIdTransfer: String? - var progress: Double? - var selector: String? - var serverUrl: String? - var session: String? - var status: Int? - var size: Int64? - var taskIdentifier: Int? - } - let networkingTasks = NetworkingTasks() let sessionDownload = NextcloudKit.shared.nkCommonInstance.identifierSessionDownload @@ -285,13 +270,6 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { return networkReachability == NKTypeReachability.reachableEthernetOrWiFi || networkReachability == NKTypeReachability.reachableCellular } - // Upload store (single live file + in-memory cache) - private let uploadStoreIO = DispatchQueue(label: "com.nextcloud.uploadStore.io") // serializes all access - private var uploadStoreURL: URL? - private let encoderUploadItem: JSONEncoder - private let decoderUploadItem: JSONDecoder - var uploadItemsCache: [UploadItemDisk] = [] - // Capabilities var capabilities = ThreadSafeDictionary() @@ -309,33 +287,7 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { // MARK: - init - init() { - // Configure JSON codecs for Upload item - self.encoderUploadItem = JSONEncoder() - self.decoderUploadItem = JSONDecoder() - - self.encoderUploadItem.dateEncodingStrategy = .iso8601 - self.decoderUploadItem.dateDecodingStrategy = .iso8601 - - guard let groupDirectory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: NCBrandOptions.shared.capabilitiesGroup) else { - return - } - let backupDirectory = groupDirectory.appendingPathComponent(NCGlobal.shared.appDatabaseNextcloud) - self.uploadStoreURL = backupDirectory.appendingPathComponent(fileUploadStore) - - // Ensure directory exists and load once - self.uploadStoreIO.sync { - // Load existing file - if let url = self.uploadStoreURL, - let data = try? Data(contentsOf: url), - !data.isEmpty, - let items = try? self.decoderUploadItem.decode([UploadItemDisk].self, from: data) { - self.uploadItemsCache = items - } else { - self.uploadItemsCache = [] - } - } - } + init() { } // MARK: - Communication Delegate @@ -478,6 +430,7 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { (self.p12Data, self.p12Password) = NCPreferences().getClientCertificate(account: account) } + /* // MARK: - Upload Item /// Adds or updates an `UploadItemDisk` entry in the local upload cache. @@ -597,4 +550,5 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { taskIdentifier: new.taskIdentifier ?? existing.taskIdentifier ) } + */ } diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index 9747b2453f..6ce231ad38 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -3,5 +3,194 @@ // SPDX-License-Identifier: GPL-3.0-or-later import UIKit +import NextcloudKit +// MARK: - Upload Store (batched persistence) +final class NCUploadStore { + static let shared = NCUploadStore() + + struct UploadItemDisk: Codable { + var date: Date? + var etag: String? + var fileName: String? + var ocId: String? + var ocIdTransfer: String? + var progress: Double? + var selector: String? + var serverUrl: String? + var session: String? + var status: Int? + var size: Int64? + var taskIdentifier: Int? + } + + // Shared state + private var uploadItemsCache: [UploadItemDisk] = [] + private let uploadStoreIO = DispatchQueue(label: "UploadStore.IO", qos: .utility) + private let encoderUploadItem = JSONEncoder() + private let decoderUploadItem = JSONDecoder() + private(set) var uploadStoreURL: URL? + + // Batching controls + private var changeCounter: Int = 0 + private var lastPersist: TimeInterval = 0 + private let batchThreshold: Int = 100 // <- persist every 100 changes + private let maxLatencySec: TimeInterval = 5 // <- or every 5s at most + private var debounceTimer: DispatchSourceTimer? + + init() { + self.encoderUploadItem.dateEncodingStrategy = .iso8601 + self.decoderUploadItem.dateDecodingStrategy = .iso8601 + + guard let groupDirectory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: NCBrandOptions.shared.capabilitiesGroup) else { + return + } + let backupDirectory = groupDirectory.appendingPathComponent(NCGlobal.shared.appDatabaseNextcloud) + self.uploadStoreURL = backupDirectory.appendingPathComponent(fileUploadStore) + + // Ensure directory exists and load once + self.uploadStoreIO.sync { + // Load existing file + if let url = self.uploadStoreURL, + let data = try? Data(contentsOf: url), + !data.isEmpty, + let items = try? self.decoderUploadItem.decode([UploadItemDisk].self, from: data) { + self.uploadItemsCache = items + } else { + self.uploadItemsCache = [] + } + } + + self.lastPersist = CFAbsoluteTimeGetCurrent() + setupLifecycleFlush() + startDebounceTimer() + } + + deinit { + stopDebounceTimer() + forceFlush() + } + + // MARK: Public API + + /// Adds or merges an item, then schedules a batched commit. + func addUploadItem(_ item: UploadItemDisk) { + guard let url = self.uploadStoreURL else { return } + + uploadStoreIO.sync { + // Upsert by (serverUrl + fileName + taskIdentifier) + if let idx = uploadItemsCache.firstIndex(where: { + $0.serverUrl == item.serverUrl && + $0.fileName == item.fileName && + $0.taskIdentifier == item.taskIdentifier + }) { + let merged = mergeUploadItem(existing: uploadItemsCache[idx], with: item) + uploadItemsCache[idx] = merged + } else { + uploadItemsCache.append(item) + } + + changeCounter &+= 1 + maybeCommit(url: url) + } + } + + /// Removes the first match by (serverUrl + fileName); batched commit. + func removeUploadItem(serverUrl: String, fileName: String) { + guard let url = self.uploadStoreURL else { return } + + uploadStoreIO.sync { + if let idx = uploadItemsCache.firstIndex(where: { + $0.serverUrl == serverUrl && $0.fileName == fileName + }) { + uploadItemsCache.remove(at: idx) + changeCounter &+= 1 + maybeCommit(url: url) + } + } + } + + /// Forces an immediate flush to disk (e.g., app background/terminate). + func forceFlush() { + guard let url = self.uploadStoreURL else { return } + uploadStoreIO.sync { + do { + let data = try encoderUploadItem.encode(uploadItemsCache) + try data.write(to: url, options: .atomic) + lastPersist = CFAbsoluteTimeGetCurrent() + changeCounter = 0 + } catch { + nkLog(tag: "UploadStore", message: "Force flush failed: \(error)") + } + } + } + + // MARK: - Private + + /// Merge: only non-nil fields from `new` overwrite existing values. + private func mergeUploadItem(existing: UploadItemDisk, with new: UploadItemDisk) -> UploadItemDisk { + return UploadItemDisk( + date: new.date ?? existing.date, + etag: new.etag ?? existing.etag, + fileName: existing.fileName ?? new.fileName, + ocId: new.ocId ?? existing.ocId, + ocIdTransfer: new.ocIdTransfer ?? existing.ocIdTransfer, + progress: new.progress ?? existing.progress, + selector: new.selector ?? existing.selector, + serverUrl: existing.serverUrl ?? new.serverUrl, + session: new.session ?? existing.session, + status: new.status ?? existing.status, + size: new.size ?? existing.size, + taskIdentifier: new.taskIdentifier ?? existing.taskIdentifier + ) + } + + /// Persist if threshold reached or max latency exceeded. + private func maybeCommit(url: URL) { + let now = CFAbsoluteTimeGetCurrent() + let tooManyChanges = changeCounter >= batchThreshold + let tooOld = (now - lastPersist) >= maxLatencySec + + guard tooManyChanges || tooOld else { return } + + do { + let data = try encoderUploadItem.encode(uploadItemsCache) + try data.write(to: url, options: .atomic) + lastPersist = now + changeCounter = 0 + } catch { + nkLog(tag: "UploadStore", message: "Persist failed: \(error)") + } + } + + private func startDebounceTimer() { + let t = DispatchSource.makeTimerSource(queue: uploadStoreIO) + t.schedule(deadline: .now() + .seconds(Int(maxLatencySec)), repeating: .seconds(Int(maxLatencySec))) + t.setEventHandler { [weak self] in + guard let self, let url = self.uploadStoreURL else { return } + // Periodic check to enforce max latency even without new changes burst + self.maybeCommit(url: url) + } + t.resume() + debounceTimer = t + } + + private func stopDebounceTimer() { + debounceTimer?.cancel() + debounceTimer = nil + } + + private func setupLifecycleFlush() { + // Ensure a flush when app goes background/terminates + NotificationCenter.default.addObserver( + forName: UIApplication.willResignActiveNotification, + object: nil, queue: nil + ) { [weak self] _ in self?.forceFlush() } + + NotificationCenter.default.addObserver( + forName: UIApplication.willTerminateNotification, + object: nil, queue: nil + ) { [weak self] _ in self?.forceFlush() } + } +} From 49edb281be6883c229d5e01b7055f4f9efff5d66 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 10:28:27 +0200 Subject: [PATCH 04/74] cod Signed-off-by: Marino Faggiana --- .../Networking/NCNetworking+Upload.swift | 39 +++--- iOSClient/Networking/NCNetworking.swift | 122 ------------------ .../Networking/NCNetworkingProcess.swift | 7 - iOSClient/Networking/NCUploadStore.swift | 68 +++++++--- 4 files changed, 72 insertions(+), 164 deletions(-) diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 22fe1af19e..491b628412 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -226,15 +226,15 @@ extension NCNetworking { if let task, error == .success { nkLog(debug: "Upload file \(metadata.fileNameView) with taskIdentifier \(task.taskIdentifier)") - addUploadItem(UploadItemDisk(fileName: metadata.fileName, - ocIdTransfer: metadata.ocIdTransfer, - progress: 0, - selector: metadata.sessionSelector, - serverUrl: metadata.serverUrl, - session: metadata.session, - status: metadata.status, - size: metadata.size, - taskIdentifier: task.taskIdentifier)) + NCUploadStore.shared.addUploadItem(UploadItemDisk(fileName: metadata.fileName, + ocIdTransfer: metadata.ocIdTransfer, + progress: 0, + selector: metadata.sessionSelector, + serverUrl: metadata.serverUrl, + session: metadata.session, + status: metadata.status, + size: metadata.size, + taskIdentifier: task.taskIdentifier)) if let metadata = await NCManageDatabase.shared.setMetadataSessionAsync(ocId: metadata.ocId, sessionTaskIdentifier: task.taskIdentifier, @@ -377,6 +377,7 @@ extension NCNetworking { } func uploadCancelFile(metadata: tableMetadata) async { + NCUploadStore.shared.removeUploadItem(serverUrl: metadata.serverUrl, fileName: metadata.fileName) self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocIdTransfer, userId: metadata.userId, urlBase: metadata.urlBase)) await NCManageDatabase.shared.deleteMetadataAsync(id: metadata.ocIdTransfer) await self.transferDispatcher.notifyAllDelegates { delegate in @@ -507,13 +508,13 @@ extension NCNetworking { #endif if error == .success { - addUploadItem(UploadItemDisk(date: date, - etag: etag, - fileName: fileName, - ocId: ocId, - serverUrl: serverUrl, - size: size, - taskIdentifier: task.taskIdentifier)) + NCUploadStore.shared.addUploadItem(UploadItemDisk(date: date, + etag: etag, + fileName: fileName, + ocId: ocId, + serverUrl: serverUrl, + size: size, + taskIdentifier: task.taskIdentifier)) } else { } @@ -538,6 +539,12 @@ extension NCNetworking { guard await progressQuantizer.shouldEmit(serverUrlFileName: serverUrl + "/" + fileName, fraction: Double(progress)) else { return } + + NCUploadStore.shared.updateUploadProgress(serverUrl: serverUrl, + fileName: fileName, + taskIdentifier: task.taskIdentifier, + progress: Double(progress)) + 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 7f908ffd8e..2ce94e46b9 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -429,126 +429,4 @@ class NCNetworking: @unchecked Sendable, NextcloudKitDelegate { func activeAccountCertificate(account: String) { (self.p12Data, self.p12Password) = NCPreferences().getClientCertificate(account: account) } - - /* - // MARK: - Upload Item - - /// Adds or updates an `UploadItemDisk` entry in the local upload cache. - /// - /// This function performs an **upsert** operation (update or insert) inside the `uploadItemsCache` - /// by matching the combination of `(serverUrl, fileName, taskIdentifier)`. - /// If an existing entry is found, it is **merged** with the new one — only non-nil fields - /// from the incoming `item` overwrite the existing values. - /// The cache is then serialized and saved atomically to disk. - /// - /// - Parameter item: The `UploadItemDisk` instance containing upload information to insert or update. - /// - /// Behavior details: - /// - Thread-safe: Executed synchronously inside `uploadStoreIO`. - /// - Persistence: The JSON file at `uploadStoreURL` is updated atomically. - /// - Merge logic: Only non-nil properties of `item` replace existing ones. - /// - Error handling: Logs failures via `nkLog` without throwing exceptions. - func addUploadItem(_ item: UploadItemDisk) { - guard let url = self.uploadStoreURL else { - return - } - uploadStoreIO.sync { - // Upsert by (serverUrl + fileName + taskIdentifier) - if let idx = uploadItemsCache.firstIndex(where: { $0.serverUrl == item.serverUrl && $0.fileName == item.fileName && $0.taskIdentifier == item.taskIdentifier}) { - let existing = uploadItemsCache[idx] - let merged = mergeUploadItem(existing: existing, with: item) - uploadItemsCache[idx] = merged - } else { - uploadItemsCache.append(item) - } - // Persist atomically - do { - let data = try encoderUploadItem.encode(uploadItemsCache) - try data.write(to: url, options: .atomic) - } catch { - nkLog(tag: "UploadComplete", message: "Persist upsert failed: \(error)") - } - } - } - - /// Removes an `UploadItemDisk` entry from the local upload cache. - /// - /// This function deletes the first cached upload record that matches the provided - /// `serverUrl` and `fileName` combination. After removal, the cache is persisted - /// atomically to disk to maintain consistency. - /// - /// - Parameters: - /// - serverUrl: The server URL associated with the upload item to remove. - /// - fileName: The file name of the upload item to remove. - /// - /// Behavior details: - /// - Thread-safe: Executed synchronously inside `uploadStoreIO`. - /// - Persistence: The JSON file at `uploadStoreURL` is updated atomically after removal. - /// - Matching: The record is matched using both `serverUrl` and `fileName`. - /// - Error handling: Logs persistence errors using `nkLog` without throwing exceptions. - func removeUploadItem(serverUrl: String, fileName: String) { - guard let url = self.uploadStoreURL else { - return - } - uploadStoreIO.sync { - let before = uploadItemsCache.count - uploadItemsCache.removeAll { $0.serverUrl == serverUrl && $0.fileName == fileName } - guard uploadItemsCache.count != before else { return } - do { - let data = try encoderUploadItem.encode(uploadItemsCache) - try data.write(to: url, options: .atomic) - } catch { - nkLog(tag: "UploadComplete", message: "Persist remove failed: \(error)") - } - } - } - - /// Read a snapshot for batch processing (e.g., flush to Realm); no disk I/O, returns the cache. - func readAllUploadItems() -> [UploadItemDisk] { - uploadStoreIO.sync { - uploadItemsCache - } - } - - /// Clear the file (e.g., after a successful batch flush). - func clearUploadItemsFile() { - guard let url = self.uploadStoreURL else { - return - } - - if uploadItemsCache.isEmpty, - let attrs = try? FileManager.default.attributesOfItem(atPath: url.path), - let size = attrs[.size] as? NSNumber, - size.intValue == 0 { - return - } - - uploadStoreIO.sync { - uploadItemsCache.removeAll() - do { - try Data().write(to: url, options: .atomic) - } catch { - nkLog(tag: "UploadComplete", message: "Persist clear failed: \(error)") - } - } - } - - /// Merges two UploadItemDisk objects, updating only non-nil fields from `new`. - private func mergeUploadItem(existing: UploadItemDisk, with new: UploadItemDisk) -> UploadItemDisk { - return UploadItemDisk( - date: new.date ?? existing.date, - etag: new.etag ?? existing.etag, - fileName: existing.fileName ?? new.fileName, - ocId: new.ocId ?? existing.ocId, - ocIdTransfer: new.ocIdTransfer ?? existing.ocIdTransfer, - progress: new.progress ?? existing.progress, - selector: new.selector ?? existing.selector, - serverUrl: existing.serverUrl ?? new.serverUrl, - session: new.session ?? existing.session, - status: new.status ?? existing.status, - size: new.size ?? existing.size, - taskIdentifier: new.taskIdentifier ?? existing.taskIdentifier - ) - } - */ } diff --git a/iOSClient/Networking/NCNetworkingProcess.swift b/iOSClient/Networking/NCNetworkingProcess.swift index 56ede9776c..2b5ada70b5 100644 --- a/iOSClient/Networking/NCNetworkingProcess.swift +++ b/iOSClient/Networking/NCNetworkingProcess.swift @@ -132,8 +132,6 @@ actor NCNetworkingProcess { await startTimer(interval: minInterval) } } else { - // Clear upload Item files - networking.clearUploadItemsFile() // Remove upload asset await removeUploadedAssetsIfNeeded() @@ -176,11 +174,6 @@ actor NCNetworkingProcess { let counterUploading = metadatas.filter { $0.status == self.global.metadataStatusUploading }.count let processRate: Double = Double(counterDownloading + counterUploading) / Double(NCBrandOptions.shared.numMaximumProcess) - // Verify Upload - if networking.uploadItemsCache.count >= 10 { - - } - // if less than 20% exit if processRate > 0.2 { nkLog(debug: "Process rate \(processRate)") diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index 6ce231ad38..6157b42484 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -7,24 +7,24 @@ import NextcloudKit // MARK: - Upload Store (batched persistence) +struct UploadItemDisk: Codable { + var date: Date? + var etag: String? + var fileName: String? + var ocId: String? + var ocIdTransfer: String? + var progress: Double? + var selector: String? + var serverUrl: String? + var session: String? + var status: Int? + var size: Int64? + var taskIdentifier: Int? +} + final class NCUploadStore { static let shared = NCUploadStore() - struct UploadItemDisk: Codable { - var date: Date? - var etag: String? - var fileName: String? - var ocId: String? - var ocIdTransfer: String? - var progress: Double? - var selector: String? - var serverUrl: String? - var session: String? - var status: Int? - var size: Int64? - var taskIdentifier: Int? - } - // Shared state private var uploadItemsCache: [UploadItemDisk] = [] private let uploadStoreIO = DispatchQueue(label: "UploadStore.IO", qos: .utility) @@ -76,7 +76,9 @@ final class NCUploadStore { /// Adds or merges an item, then schedules a batched commit. func addUploadItem(_ item: UploadItemDisk) { - guard let url = self.uploadStoreURL else { return } + guard let url = self.uploadStoreURL else { + return + } uploadStoreIO.sync { // Upsert by (serverUrl + fileName + taskIdentifier) @@ -96,9 +98,32 @@ final class NCUploadStore { } } + /// Updates only the `progress` field of an existing upload item, then schedules a batched commit. + /// If no matching item is found, nothing happens. + func updateUploadProgress(serverUrl: String?, fileName: String?, taskIdentifier: Int?, progress: Double) { + guard let url = self.uploadStoreURL else { + return + } + + uploadStoreIO.sync { + if let idx = uploadItemsCache.firstIndex(where: { + $0.serverUrl == serverUrl && + $0.fileName == fileName && + $0.taskIdentifier == taskIdentifier + }) { + // Update only progress field + uploadItemsCache[idx].progress = progress + changeCounter &+= 1 + maybeCommit(url: url) + } + } + } + /// Removes the first match by (serverUrl + fileName); batched commit. func removeUploadItem(serverUrl: String, fileName: String) { - guard let url = self.uploadStoreURL else { return } + guard let url = self.uploadStoreURL else { + return + } uploadStoreIO.sync { if let idx = uploadItemsCache.firstIndex(where: { @@ -113,7 +138,10 @@ final class NCUploadStore { /// Forces an immediate flush to disk (e.g., app background/terminate). func forceFlush() { - guard let url = self.uploadStoreURL else { return } + guard let url = self.uploadStoreURL else { + return + } + uploadStoreIO.sync { do { let data = try encoderUploadItem.encode(uploadItemsCache) @@ -152,7 +180,9 @@ final class NCUploadStore { let tooManyChanges = changeCounter >= batchThreshold let tooOld = (now - lastPersist) >= maxLatencySec - guard tooManyChanges || tooOld else { return } + guard tooManyChanges || tooOld else { + return + } do { let data = try encoderUploadItem.encode(uploadItemsCache) From 06b57632977a49d48994f1dce61fbcd1ad880a40 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 11:28:30 +0200 Subject: [PATCH 05/74] cod Signed-off-by: Marino Faggiana --- .../Networking/NCNetworking+Upload.swift | 1 + iOSClient/Networking/NCUploadStore.swift | 39 ++++++++++++------- iOSClient/SceneDelegate.swift | 1 + 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 491b628412..961cef0094 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -546,6 +546,7 @@ extension NCNetworking { progress: Double(progress)) await NCManageDatabase.shared.setMetadataProgress(fileName: fileName, serverUrl: serverUrl, taskIdentifier: task.taskIdentifier, progress: Double(progress)) + await self.transferDispatcher.notifyAllDelegates { delegate in delegate.transferProgressDidUpdate(progress: progress, totalBytes: totalBytes, diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index 6157b42484..3cf9371647 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -72,6 +72,32 @@ final class NCUploadStore { forceFlush() } + private func setupLifecycleFlush() { + NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { [weak self] _ in + self?.flushWithBackgroundTime() + } + + NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in + self?.stopDebounceTimer() + self?.flushWithBackgroundTime() + } + + NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in + self?.startDebounceTimer() + } + } + + private func flushWithBackgroundTime() { + var bgTask: UIBackgroundTaskIdentifier = .invalid + bgTask = UIApplication.shared.beginBackgroundTask(withName: "NCUploadStore.flush") { + UIApplication.shared.endBackgroundTask(bgTask) + bgTask = .invalid + } + forceFlush() + UIApplication.shared.endBackgroundTask(bgTask) + bgTask = .invalid + } + // MARK: Public API /// Adds or merges an item, then schedules a batched commit. @@ -210,17 +236,4 @@ final class NCUploadStore { debounceTimer?.cancel() debounceTimer = nil } - - private func setupLifecycleFlush() { - // Ensure a flush when app goes background/terminates - NotificationCenter.default.addObserver( - forName: UIApplication.willResignActiveNotification, - object: nil, queue: nil - ) { [weak self] _ in self?.forceFlush() } - - NotificationCenter.default.addObserver( - forName: UIApplication.willTerminateNotification, - object: nil, queue: nil - ) { [weak self] _ in self?.forceFlush() } - } } diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index f8508ebaf0..ed8d3413bb 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -85,6 +85,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { _ = NCNetworking.shared _ = NCDownloadAction.shared _ = NCNetworkingProcess.shared + _ = NCUploadStore.shared if let activeTblAccount, !alreadyMigratedMultiDomains { // From 041d88e85d68e6153208f6efe6386999d14d83ba Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 11:42:48 +0200 Subject: [PATCH 06/74] normalized Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 6 ------ iOSClient/Files/NCFiles+SyncMetadata.swift | 16 ++++++++-------- iOSClient/NCGlobal.swift | 5 +++-- iOSClient/Networking/NCNetworking+Upload.swift | 9 +++++++++ iOSClient/Networking/NCNetworking.swift | 6 +++--- iOSClient/Networking/NCUploadStore.swift | 9 ++++----- 6 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index f2592c0945..2a695667e7 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -722,9 +722,6 @@ F7864AD02A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; F7864AD12A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; F7864AD22A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; - F786891A2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78689192E93A9D5000E39B3 /* NCUploadStore.swift */; }; - F786891B2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78689192E93A9D5000E39B3 /* NCUploadStore.swift */; }; - F786891C2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78689192E93A9D5000E39B3 /* NCUploadStore.swift */; }; F786891D2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78689192E93A9D5000E39B3 /* NCUploadStore.swift */; }; F787704F22E7019900F287A9 /* NCShareLinkCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F787704E22E7019900F287A9 /* NCShareLinkCell.xib */; }; F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F787AC08298BCB4A0001BB00 /* SVGKitSwift */; }; @@ -4436,7 +4433,6 @@ AF730AFA27843E4C00B7520E /* NCShareExtension+NCAccountRequestDelegate.swift in Sources */, F7327E232B73A42F00A462C7 /* NCNetworking+Download.swift in Sources */, F749B64D297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */, - F786891A2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */, F72FD3B8297ED49A00075D28 /* NCManageDatabase+E2EE.swift in Sources */, F7A76DC8256A71CD00119AB3 /* UIImage+Extension.swift in Sources */, F3E173C32C9B1067006D177A /* AwakeMode.swift in Sources */, @@ -4496,7 +4492,6 @@ F72EA95A28B7BD0D00C88F0C /* FilesWidgetView.swift in Sources */, F768823C2C0DD231001CF441 /* NCPreferences.swift in Sources */, F71F6D082B6A6A5E00F1EB15 /* ThreadSafeArray.swift in Sources */, - F786891C2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */, F78302FE28B4C44700B84583 /* NCBrand.swift in Sources */, F749B64B297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */, AA8D31572D41052300FE2775 /* NCManageDatabase+DownloadLimit.swift in Sources */, @@ -4598,7 +4593,6 @@ F771E3D520E2392D00AFB62D /* FileProviderItem.swift in Sources */, F3E173C42C9B1067006D177A /* AwakeMode.swift in Sources */, F7D7A76F2DCDD437003D2007 /* NCManageDatabase+AutoUpload.swift in Sources */, - F786891B2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */, AF4BF616275629E20081CEEF /* NCManageDatabase+Account.swift in Sources */, F343A4B72A1E084300DDA874 /* PHAsset+Extension.swift in Sources */, F7434B3620E23FE000417916 /* NCManageDatabase.swift in Sources */, diff --git a/iOSClient/Files/NCFiles+SyncMetadata.swift b/iOSClient/Files/NCFiles+SyncMetadata.swift index 2963a4411a..ff8f1debec 100644 --- a/iOSClient/Files/NCFiles+SyncMetadata.swift +++ b/iOSClient/Files/NCFiles+SyncMetadata.swift @@ -21,7 +21,7 @@ extension NCFiles { // If a sync task is already running, do not start a new one if let task = syncMetadatasTask, !task.isCancelled { - nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .info, message: "Exit: Another sync is already running. Skipping this one.", consoleOnly: true) + nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .info, message: "Exit: Another sync is already running. Skipping this one.", consoleOnly: true) return } @@ -42,9 +42,9 @@ extension NCFiles { func stopSyncMetadata() { if let task = syncMetadatasTask { if task.isCancelled { - nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .stop, message: "Sync Metadata for \(self.serverUrl) was already cancelled.", consoleOnly: true) + nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .stop, message: "Sync Metadata for \(self.serverUrl) was already cancelled.", consoleOnly: true) } else { - nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .stop, message: "Stopping active Sync Metadata for \(self.serverUrl).", consoleOnly: true) + nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .stop, message: "Stopping active Sync Metadata for \(self.serverUrl).", consoleOnly: true) } } @@ -71,7 +71,7 @@ extension NCFiles { return } let identifier = self.serverUrl + "_syncMetadata" - nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .start, message: "Start Sync Metadata for \(self.serverUrl)") + nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .start, message: "Start Sync Metadata for \(self.serverUrl)") // Always cancel and clear all tracked URLSessionTask on any exit path defer { @@ -82,7 +82,7 @@ extension NCFiles { // If a readFile for this serverUrl is already in-flight, do nothing if await networking.networkingTasks.isReading(identifier: identifier) { - nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .debug, message: "ReadFile for this \(self.serverUrl) is already in-flight.", consoleOnly: true) + nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .debug, message: "ReadFile for this \(self.serverUrl) is already in-flight.", consoleOnly: true) return } @@ -103,7 +103,7 @@ extension NCFiles { guard resultsReadFile.error == .success, let metadata = resultsReadFile.metadata, !metadata.e2eEncrypted else { - nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .info, message: "Exit: result error \(resultsReadFile.error.errorDescription) or e2ee directory \(resultsReadFile.metadata?.e2eEncrypted ?? false). Skipping this one.") + nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .info, message: "Exit: result error \(resultsReadFile.error.errorDescription) or e2ee directory \(resultsReadFile.metadata?.e2eEncrypted ?? false). Skipping this one.") return } @@ -131,9 +131,9 @@ extension NCFiles { // If this folder failed, skip it but keep processing others if resultsReadFolder.error == .success { - nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .network, message: "Read correctly: \(serverUrl)", consoleOnly: true) + nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .network, message: "Read correctly: \(serverUrl)", consoleOnly: true) } else { - nkLog(tag: global.logSpeedUpSyncMetadata, emoji: .error, message: "Read failed for \(serverUrl) with error: \(resultsReadFolder.error.errorDescription)") + nkLog(tag: global.logTagSpeedUpSyncMetadata, emoji: .error, message: "Read failed for \(serverUrl) with error: \(resultsReadFolder.error.errorDescription)") return } diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index 3f9ad29be1..68c1a42495 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -390,8 +390,9 @@ final class NCGlobal: Sendable { let logTagSync = "SYNC" let logTagServiceProficer = "SERVICE PROVIDER" let logTagDatabase = "DB" - let logSpeedUpSyncMetadata = "SYNC METADATA" - let logNetworkingTasks = "NETWORKING TASKS" + let logTagSpeedUpSyncMetadata = "SYNC METADATA" + let logTagNetworkingTasks = "NETWORKING TASKS" + let logTagUploadStore = "UPLOAD STORE" // USER DEFAULTS // diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 961cef0094..f72b34425d 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -226,6 +226,7 @@ extension NCNetworking { if let task, error == .success { nkLog(debug: "Upload file \(metadata.fileNameView) with taskIdentifier \(task.taskIdentifier)") + #if !EXTENSION NCUploadStore.shared.addUploadItem(UploadItemDisk(fileName: metadata.fileName, ocIdTransfer: metadata.ocIdTransfer, progress: 0, @@ -235,6 +236,7 @@ extension NCNetworking { status: metadata.status, size: metadata.size, taskIdentifier: task.taskIdentifier)) + #endif if let metadata = await NCManageDatabase.shared.setMetadataSessionAsync(ocId: metadata.ocId, sessionTaskIdentifier: task.taskIdentifier, @@ -377,7 +379,10 @@ extension NCNetworking { } func uploadCancelFile(metadata: tableMetadata) async { + #if !EXTENSION NCUploadStore.shared.removeUploadItem(serverUrl: metadata.serverUrl, fileName: metadata.fileName) + #endif + self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocIdTransfer, userId: metadata.userId, urlBase: metadata.urlBase)) await NCManageDatabase.shared.deleteMetadataAsync(id: metadata.ocIdTransfer) await self.transferDispatcher.notifyAllDelegates { delegate in @@ -507,6 +512,7 @@ extension NCNetworking { return #endif + #if !EXTENSION if error == .success { NCUploadStore.shared.addUploadItem(UploadItemDisk(date: date, etag: etag, @@ -518,6 +524,7 @@ extension NCNetworking { } else { } + #endif if let metadata = await NCManageDatabase.shared.getMetadataAsync(predicate: NSPredicate(format: "serverUrl == %@ AND fileName == %@ AND sessionTaskIdentifier == %d", serverUrl, fileName, task.taskIdentifier)) { await uploadComplete(withMetadata: metadata, ocId: ocId, etag: etag, date: date, size: size, error: error) @@ -540,10 +547,12 @@ extension NCNetworking { return } + #if !EXTENSION NCUploadStore.shared.updateUploadProgress(serverUrl: serverUrl, fileName: fileName, taskIdentifier: task.taskIdentifier, progress: Double(progress)) + #endif await NCManageDatabase.shared.setMetadataProgress(fileName: fileName, serverUrl: serverUrl, taskIdentifier: task.taskIdentifier, progress: Double(progress)) diff --git a/iOSClient/Networking/NCNetworking.swift b/iOSClient/Networking/NCNetworking.swift index 2ce94e46b9..da1ec08101 100644 --- a/iOSClient/Networking/NCNetworking.swift +++ b/iOSClient/Networking/NCNetworking.swift @@ -148,7 +148,7 @@ actor NetworkingTasks { $0.identifier == identifier && $0.task.state == .running } active.append((identifier, task)) - nkLog(tag: NCGlobal.shared.logNetworkingTasks, emoji: .start, message: "Start task for identifier: \(identifier)", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .start, message: "Start task for identifier: \(identifier)", consoleOnly: true) } /// create a Identifier @@ -173,7 +173,7 @@ actor NetworkingTasks { for element in active where element.identifier == identifier { element.task.cancel() - nkLog(tag: NCGlobal.shared.logNetworkingTasks, emoji: .cancel, message: "Cancel task for identifier: \(identifier)", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .cancel, message: "Cancel task for identifier: \(identifier)", consoleOnly: true) } active.removeAll { $0.identifier == identifier @@ -186,7 +186,7 @@ actor NetworkingTasks { func cancelAll() { active.forEach { $0.task.cancel() - nkLog(tag: NCGlobal.shared.logNetworkingTasks, emoji: .cancel, message: "Cancel task with identifier: \($0.identifier)", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logTagNetworkingTasks, emoji: .cancel, message: "Cancel task with identifier: \($0.identifier)", consoleOnly: true) } active.removeAll() } diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index 3cf9371647..6add93ea7f 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -35,8 +35,8 @@ final class NCUploadStore { // Batching controls private var changeCounter: Int = 0 private var lastPersist: TimeInterval = 0 - private let batchThreshold: Int = 100 // <- persist every 100 changes - private let maxLatencySec: TimeInterval = 5 // <- or every 5s at most + private let batchThreshold: Int = 20 // <- persist every 20 changes + private let maxLatencySec: TimeInterval = 5 // <- or every 5s at most private var debounceTimer: DispatchSourceTimer? init() { @@ -49,7 +49,6 @@ final class NCUploadStore { let backupDirectory = groupDirectory.appendingPathComponent(NCGlobal.shared.appDatabaseNextcloud) self.uploadStoreURL = backupDirectory.appendingPathComponent(fileUploadStore) - // Ensure directory exists and load once self.uploadStoreIO.sync { // Load existing file if let url = self.uploadStoreURL, @@ -175,7 +174,7 @@ final class NCUploadStore { lastPersist = CFAbsoluteTimeGetCurrent() changeCounter = 0 } catch { - nkLog(tag: "UploadStore", message: "Force flush failed: \(error)") + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "Force flush failed: \(error)") } } } @@ -216,7 +215,7 @@ final class NCUploadStore { lastPersist = now changeCounter = 0 } catch { - nkLog(tag: "UploadStore", message: "Persist failed: \(error)") + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Persist failed: \(error)") } } From b01dfe5ac5d73740d0ebb4d16f406349aaa96033 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 11:53:31 +0200 Subject: [PATCH 07/74] cod Signed-off-by: Marino Faggiana --- .../Networking/NCNetworking+Upload.swift | 6 +++-- iOSClient/Networking/NCUploadStore.swift | 23 +++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index f72b34425d..aea1e10082 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -380,7 +380,7 @@ extension NCNetworking { func uploadCancelFile(metadata: tableMetadata) async { #if !EXTENSION - NCUploadStore.shared.removeUploadItem(serverUrl: metadata.serverUrl, fileName: metadata.fileName) + NCUploadStore.shared.removeUploadItem(ocIdTransfer: metadata.ocIdTransfer) #endif self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocIdTransfer, userId: metadata.userId, urlBase: metadata.urlBase)) @@ -522,7 +522,9 @@ extension NCNetworking { size: size, taskIdentifier: task.taskIdentifier)) } else { - + NCUploadStore.shared.removeUploadItem(serverUrl: serverUrl, + fileName: fileName, + taskIdentifier: task.taskIdentifier) } #endif diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index 6add93ea7f..9fa918acff 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -145,14 +145,33 @@ final class NCUploadStore { } /// Removes the first match by (serverUrl + fileName); batched commit. - func removeUploadItem(serverUrl: String, fileName: String) { + func removeUploadItem(serverUrl: String, fileName: String, taskIdentifier: Int?) { guard let url = self.uploadStoreURL else { return } uploadStoreIO.sync { if let idx = uploadItemsCache.firstIndex(where: { - $0.serverUrl == serverUrl && $0.fileName == fileName + $0.serverUrl == serverUrl && + $0.fileName == fileName && + $0.taskIdentifier == taskIdentifier + }) { + uploadItemsCache.remove(at: idx) + changeCounter &+= 1 + maybeCommit(url: url) + } + } + } + + /// Removes the first match by (ocIdTransfer); batched commit. + func removeUploadItem(ocIdTransfer: String) { + guard let url = self.uploadStoreURL else { + return + } + + uploadStoreIO.sync { + if let idx = uploadItemsCache.firstIndex(where: { + $0.ocIdTransfer == ocIdTransfer }) { uploadItemsCache.remove(at: idx) changeCounter &+= 1 From 942d3c984b2e06660dc2a3762bd606f7bf478be4 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 12:37:31 +0200 Subject: [PATCH 08/74] flush metadata Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCUploadStore.swift | 96 ++++++++++++++++++++---- 1 file changed, 82 insertions(+), 14 deletions(-) diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index 9fa918acff..af8cd0117c 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -39,6 +39,10 @@ final class NCUploadStore { private let maxLatencySec: TimeInterval = 5 // <- or every 5s at most private var debounceTimer: DispatchSourceTimer? + // Realm batching controls + private let realmBatchThreshold: Int = 50 // <- sync Realm every 50 changes + private var realmPendingSync: Bool = false // <- request sync when we re-enter foreground + init() { self.encoderUploadItem.dateEncodingStrategy = .iso8601 self.decoderUploadItem.dateDecodingStrategy = .iso8601 @@ -73,28 +77,30 @@ final class NCUploadStore { private func setupLifecycleFlush() { NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { [weak self] _ in - self?.flushWithBackgroundTime() + guard let self else { return } + + self.flushWithBackgroundTime() } NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in - self?.stopDebounceTimer() - self?.flushWithBackgroundTime() + guard let self else { return } + + self.stopDebounceTimer() + self.flushWithBackgroundTime() } NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in - self?.startDebounceTimer() - } - } + guard let self else { return } - private func flushWithBackgroundTime() { - var bgTask: UIBackgroundTaskIdentifier = .invalid - bgTask = UIApplication.shared.beginBackgroundTask(withName: "NCUploadStore.flush") { - UIApplication.shared.endBackgroundTask(bgTask) - bgTask = .invalid + // If a Realm sync was pending while in background, execute it now. + if self.realmPendingSync { + Task { + await self.syncRealmNow() + } + } + + self.startDebounceTimer() } - forceFlush() - UIApplication.shared.endBackgroundTask(bgTask) - bgTask = .invalid } // MARK: Public API @@ -120,6 +126,7 @@ final class NCUploadStore { changeCounter &+= 1 maybeCommit(url: url) + maybeSyncRealm() } } @@ -159,6 +166,7 @@ final class NCUploadStore { uploadItemsCache.remove(at: idx) changeCounter &+= 1 maybeCommit(url: url) + maybeSyncRealm() } } } @@ -176,6 +184,7 @@ final class NCUploadStore { uploadItemsCache.remove(at: idx) changeCounter &+= 1 maybeCommit(url: url) + maybeSyncRealm() } } } @@ -254,4 +263,63 @@ final class NCUploadStore { debounceTimer?.cancel() debounceTimer = nil } + + private func flushWithBackgroundTime() { + var bgTask: UIBackgroundTaskIdentifier = .invalid + bgTask = UIApplication.shared.beginBackgroundTask(withName: "NCUploadStore.flush") { + UIApplication.shared.endBackgroundTask(bgTask) + bgTask = .invalid + } + forceFlush() + UIApplication.shared.endBackgroundTask(bgTask) + bgTask = .invalid + } + + + /// Schedules a Realm sync if threshold is hit and app is in foreground. + /// If the app is not in foreground, defers the sync until next didBecomeActive. + private func maybeSyncRealm() { + // Guard: threshold-based trigger + let shouldSync = (changeCounter % realmBatchThreshold == 0) + guard shouldSync else { return } + + if UIApplication.shared.applicationState == .active { + // Perform the sync on MainActor to respect any UI/Realm constraints + Task { + await syncRealmNow() + } + } else { + // Defer: mark as pending; it will run on didBecomeActive + realmPendingSync = true + } + } + + /// Performs the actual Realm write using your async APIs. + private func syncRealmNow() async { + // Snapshot the current cache to avoid holding the IO queue + let snapshot: [UploadItemDisk] = uploadStoreIO.sync { + uploadItemsCache + } + + // Example: use your centralized async Realm entry point + do { + try await NCManageDatabase.shared.performRealmWriteAsync { realm in + // TODO: Upsert your Realm objects using `snapshot`. + // - Map UploadItemDisk -> RealmObject + // - Use primary keys (serverUrl + fileName + taskIdentifier) for upsert + // - Keep objects detached if needed for your architecture + // + // Pseudocode: + // for item in snapshot { + // let obj = TableUploadItemDisk.from(item) // mapping function you own + // realm.add(obj, update: .modified) + // } + } + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "Realm sync completed") + realmPendingSync = false + } catch { + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Realm sync failed: \(error)") + // Keep realmPendingSync as-is; next foreground will try again when threshold hits again. + } + } } From ef2956fc9523e3b66797b6315d4215bcd8b310bc Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 15:01:59 +0200 Subject: [PATCH 09/74] cod Signed-off-by: Marino Faggiana --- .../Data/NCManageDatabase+Metadata.swift | 13 ++ iOSClient/Networking/NCUploadStore.swift | 140 +++++++++++------- 2 files changed, 102 insertions(+), 51 deletions(-) diff --git a/iOSClient/Data/NCManageDatabase+Metadata.swift b/iOSClient/Data/NCManageDatabase+Metadata.swift index 62a225a578..8540380db0 100644 --- a/iOSClient/Data/NCManageDatabase+Metadata.swift +++ b/iOSClient/Data/NCManageDatabase+Metadata.swift @@ -473,6 +473,19 @@ extension NCManageDatabase { } } + func replaceMetadataAsync(ocIdTransfers: [String], metadatas: [tableMetadata]) async { + guard !ocIdTransfers.isEmpty else { + return + } + + await performRealmWriteAsync { realm in + let result = realm.objects(tableMetadata.self) + .filter("ocIdTransfer IN %@", ocIdTransfers, ocIdTransfers) + realm.delete(result) + realm.add(metadatas, update: .all) + } + } + // Asynchronously deletes an array of `tableMetadata` entries from the Realm database. /// - Parameter metadatas: The `tableMetadata` objects to be deleted. func deleteMetadatasAsync(_ metadatas: [tableMetadata]) async { diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index af8cd0117c..30cc22f2a3 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -40,8 +40,7 @@ final class NCUploadStore { private var debounceTimer: DispatchSourceTimer? // Realm batching controls - private let realmBatchThreshold: Int = 50 // <- sync Realm every 50 changes - private var realmPendingSync: Bool = false // <- request sync when we re-enter foreground + private let realmBatchThreshold: Int = 20 // existing init() { self.encoderUploadItem.dateEncodingStrategy = .iso8601 @@ -66,40 +65,39 @@ final class NCUploadStore { } self.lastPersist = CFAbsoluteTimeGetCurrent() + setupLifecycleFlush() startDebounceTimer() } deinit { stopDebounceTimer() - forceFlush() + flushWithBackgroundTime() } private func setupLifecycleFlush() { NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { [weak self] _ in guard let self else { return } - self.flushWithBackgroundTime() + Task { + self.stopDebounceTimer() + self.flushWithBackgroundTime() + } } - NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in + NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in guard let self else { return } - self.stopDebounceTimer() - self.flushWithBackgroundTime() - } + // Force a synchronous reload before anything else + self.reloadFromDisk() - NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in - guard let self else { return } + Task { + try? await Task.sleep(nanoseconds: 1_000_000_000) - // If a Realm sync was pending while in background, execute it now. - if self.realmPendingSync { - Task { - await self.syncRealmNow() - } + await self.syncRealmNow() + self.startDebounceTimer() } - self.startDebounceTimer() } } @@ -252,8 +250,11 @@ final class NCUploadStore { t.schedule(deadline: .now() + .seconds(Int(maxLatencySec)), repeating: .seconds(Int(maxLatencySec))) t.setEventHandler { [weak self] in guard let self, let url = self.uploadStoreURL else { return } - // Periodic check to enforce max latency even without new changes burst + // JSON latency cap self.maybeCommit(url: url) + Task { + await self.syncRealmNow() + } } t.resume() debounceTimer = t @@ -270,56 +271,93 @@ final class NCUploadStore { UIApplication.shared.endBackgroundTask(bgTask) bgTask = .invalid } - forceFlush() + Task { + self.forceFlush() + await self.syncRealmNow() + } UIApplication.shared.endBackgroundTask(bgTask) bgTask = .invalid } + /// Reloads the entire JSON store from disk synchronously. + /// When this function returns, `uploadItemsCache` is guaranteed to be updated. + private func reloadFromDisk() { + guard let url = self.uploadStoreURL else { + return + } - /// Schedules a Realm sync if threshold is hit and app is in foreground. - /// If the app is not in foreground, defers the sync until next didBecomeActive. - private func maybeSyncRealm() { - // Guard: threshold-based trigger - let shouldSync = (changeCounter % realmBatchThreshold == 0) - guard shouldSync else { return } + uploadStoreIO.sync { + do { + let data = try Data(contentsOf: url) + guard !data.isEmpty else { + self.uploadItemsCache = [] + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "UploadStore: JSON empty, cache cleared") + return + } - if UIApplication.shared.applicationState == .active { - // Perform the sync on MainActor to respect any UI/Realm constraints - Task { - await syncRealmNow() + let items = try self.decoderUploadItem.decode([UploadItemDisk].self, from: data) + self.uploadItemsCache = items + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "UploadStore: JSON reloaded from disk (sync)") + } catch { + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "UploadStore: reload failed: \(error)") } - } else { - // Defer: mark as pending; it will run on didBecomeActive - realmPendingSync = true + } + } + + /// Schedules a Realm sync. If `force` is true, runs regardless of threshold (foreground only). + private func maybeSyncRealm() { + guard UIApplication.shared.applicationState == .active else { + return + } + + + // threshold mode: sync every N changes + guard changeCounter % realmBatchThreshold == 0 else { + return + } + + + Task { + await syncRealmNow() } } /// Performs the actual Realm write using your async APIs. private func syncRealmNow() async { - // Snapshot the current cache to avoid holding the IO queue let snapshot: [UploadItemDisk] = uploadStoreIO.sync { - uploadItemsCache + uploadItemsCache.filter { $0.ocId != nil && !$0.ocId!.isEmpty } } + let ocIdTransfers = snapshot.compactMap { $0.ocIdTransfer } + let predicate = NSPredicate(format: "ocIdTransfer IN %@", ocIdTransfers) + let metadatas = await NCManageDatabase.shared.getMetadatasAsync(predicate: predicate) + let utility = NCUtility() + var metadatasUploaded: [tableMetadata] = [] + + for metadata in metadatas { + guard let uploadItem = (uploadItemsCache.first { $0.ocIdTransfer == metadata.ocIdTransfer }), + let etag = uploadItem.etag, + let ocId = uploadItem.ocId else { + continue + } - // Example: use your centralized async Realm entry point - do { - try await NCManageDatabase.shared.performRealmWriteAsync { realm in - // TODO: Upsert your Realm objects using `snapshot`. - // - Map UploadItemDisk -> RealmObject - // - Use primary keys (serverUrl + fileName + taskIdentifier) for upsert - // - Keep objects detached if needed for your architecture - // - // Pseudocode: - // for item in snapshot { - // let obj = TableUploadItemDisk.from(item) // mapping function you own - // realm.add(obj, update: .modified) - // } + metadata.uploadDate = (uploadItem.date as? NSDate) ?? NSDate() + metadata.etag = etag + metadata.ocId = ocId + metadata.chunk = 0 + + if let fileId = utility.ocIdToFileId(ocId: metadata.ocId) { + metadata.fileId = fileId } - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "Realm sync completed") - realmPendingSync = false - } catch { - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Realm sync failed: \(error)") - // Keep realmPendingSync as-is; next foreground will try again when threshold hits again. + + metadata.session = "" + metadata.sessionError = "" + metadata.sessionTaskIdentifier = 0 + metadata.status = NCGlobal.shared.metadataStatusNormal + + metadatasUploaded.append(metadata) + removeUploadItem(ocIdTransfer: metadata.ocIdTransfer) } + + await NCManageDatabase.shared.replaceMetadataAsync(ocIdTransfers: ocIdTransfers, metadatas: metadatasUploaded) } } From 72ba6029a227ab0f4d702b2e6c9f5b4847fcef6c Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 17:13:21 +0200 Subject: [PATCH 10/74] Update NCUploadStore.swift --- iOSClient/Networking/NCUploadStore.swift | 63 ++++++------------------ 1 file changed, 14 insertions(+), 49 deletions(-) diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index 30cc22f2a3..3dccd172bb 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -39,9 +39,6 @@ final class NCUploadStore { private let maxLatencySec: TimeInterval = 5 // <- or every 5s at most private var debounceTimer: DispatchSourceTimer? - // Realm batching controls - private let realmBatchThreshold: Int = 20 // existing - init() { self.encoderUploadItem.dateEncodingStrategy = .iso8601 self.decoderUploadItem.dateDecodingStrategy = .iso8601 @@ -105,10 +102,6 @@ final class NCUploadStore { /// Adds or merges an item, then schedules a batched commit. func addUploadItem(_ item: UploadItemDisk) { - guard let url = self.uploadStoreURL else { - return - } - uploadStoreIO.sync { // Upsert by (serverUrl + fileName + taskIdentifier) if let idx = uploadItemsCache.firstIndex(where: { @@ -123,18 +116,13 @@ final class NCUploadStore { } changeCounter &+= 1 - maybeCommit(url: url) - maybeSyncRealm() + maybeCommit() } } /// Updates only the `progress` field of an existing upload item, then schedules a batched commit. /// If no matching item is found, nothing happens. func updateUploadProgress(serverUrl: String?, fileName: String?, taskIdentifier: Int?, progress: Double) { - guard let url = self.uploadStoreURL else { - return - } - uploadStoreIO.sync { if let idx = uploadItemsCache.firstIndex(where: { $0.serverUrl == serverUrl && @@ -144,17 +132,13 @@ final class NCUploadStore { // Update only progress field uploadItemsCache[idx].progress = progress changeCounter &+= 1 - maybeCommit(url: url) + maybeCommit() } } } /// Removes the first match by (serverUrl + fileName); batched commit. func removeUploadItem(serverUrl: String, fileName: String, taskIdentifier: Int?) { - guard let url = self.uploadStoreURL else { - return - } - uploadStoreIO.sync { if let idx = uploadItemsCache.firstIndex(where: { $0.serverUrl == serverUrl && @@ -163,26 +147,20 @@ final class NCUploadStore { }) { uploadItemsCache.remove(at: idx) changeCounter &+= 1 - maybeCommit(url: url) - maybeSyncRealm() + maybeCommit() } } } /// Removes the first match by (ocIdTransfer); batched commit. func removeUploadItem(ocIdTransfer: String) { - guard let url = self.uploadStoreURL else { - return - } - uploadStoreIO.sync { if let idx = uploadItemsCache.firstIndex(where: { $0.ocIdTransfer == ocIdTransfer }) { uploadItemsCache.remove(at: idx) changeCounter &+= 1 - maybeCommit(url: url) - maybeSyncRealm() + maybeCommit() } } } @@ -226,11 +204,13 @@ final class NCUploadStore { } /// Persist if threshold reached or max latency exceeded. - private func maybeCommit(url: URL) { + private func maybeCommit() { + guard let url = self.uploadStoreURL else { + return + } let now = CFAbsoluteTimeGetCurrent() let tooManyChanges = changeCounter >= batchThreshold let tooOld = (now - lastPersist) >= maxLatencySec - guard tooManyChanges || tooOld else { return } @@ -243,16 +223,19 @@ final class NCUploadStore { } catch { nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Persist failed: \(error)") } + + Task { + await syncRealmNow() + } } private func startDebounceTimer() { let t = DispatchSource.makeTimerSource(queue: uploadStoreIO) t.schedule(deadline: .now() + .seconds(Int(maxLatencySec)), repeating: .seconds(Int(maxLatencySec))) t.setEventHandler { [weak self] in - guard let self, let url = self.uploadStoreURL else { return } - // JSON latency cap - self.maybeCommit(url: url) + guard let self else { return } Task { + self.maybeCommit() await self.syncRealmNow() } } @@ -304,24 +287,6 @@ final class NCUploadStore { } } - /// Schedules a Realm sync. If `force` is true, runs regardless of threshold (foreground only). - private func maybeSyncRealm() { - guard UIApplication.shared.applicationState == .active else { - return - } - - - // threshold mode: sync every N changes - guard changeCounter % realmBatchThreshold == 0 else { - return - } - - - Task { - await syncRealmNow() - } - } - /// Performs the actual Realm write using your async APIs. private func syncRealmNow() async { let snapshot: [UploadItemDisk] = uploadStoreIO.sync { From 3954275fd2886d39fcec8ba62a6e416c5b6a89d8 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 17:48:52 +0200 Subject: [PATCH 11/74] clean --- .../Networking/NCNetworking+Upload.swift | 1 + iOSClient/Networking/NCUploadStore.swift | 58 ++++++++----------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index aea1e10082..d4277eb840 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -521,6 +521,7 @@ extension NCNetworking { serverUrl: serverUrl, size: size, taskIdentifier: task.taskIdentifier)) + return } else { NCUploadStore.shared.removeUploadItem(serverUrl: serverUrl, fileName: fileName, diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index 3dccd172bb..01b9faa31b 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -50,7 +50,6 @@ final class NCUploadStore { self.uploadStoreURL = backupDirectory.appendingPathComponent(fileUploadStore) self.uploadStoreIO.sync { - // Load existing file if let url = self.uploadStoreURL, let data = try? Data(contentsOf: url), !data.isEmpty, @@ -69,17 +68,13 @@ final class NCUploadStore { deinit { stopDebounceTimer() - flushWithBackgroundTime() } private func setupLifecycleFlush() { NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { [weak self] _ in guard let self else { return } - Task { - self.stopDebounceTimer() - self.flushWithBackgroundTime() - } + self.stopDebounceTimer() } NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in @@ -90,8 +85,6 @@ final class NCUploadStore { Task { try? await Task.sleep(nanoseconds: 1_000_000_000) - - await self.syncRealmNow() self.startDebounceTimer() } @@ -129,10 +122,7 @@ final class NCUploadStore { $0.fileName == fileName && $0.taskIdentifier == taskIdentifier }) { - // Update only progress field uploadItemsCache[idx].progress = progress - changeCounter &+= 1 - maybeCommit() } } } @@ -236,7 +226,6 @@ final class NCUploadStore { guard let self else { return } Task { self.maybeCommit() - await self.syncRealmNow() } } t.resume() @@ -248,20 +237,6 @@ final class NCUploadStore { debounceTimer = nil } - private func flushWithBackgroundTime() { - var bgTask: UIBackgroundTaskIdentifier = .invalid - bgTask = UIApplication.shared.beginBackgroundTask(withName: "NCUploadStore.flush") { - UIApplication.shared.endBackgroundTask(bgTask) - bgTask = .invalid - } - Task { - self.forceFlush() - await self.syncRealmNow() - } - UIApplication.shared.endBackgroundTask(bgTask) - bgTask = .invalid - } - /// Reloads the entire JSON store from disk synchronously. /// When this function returns, `uploadItemsCache` is guaranteed to be updated. private func reloadFromDisk() { @@ -272,17 +247,16 @@ final class NCUploadStore { uploadStoreIO.sync { do { let data = try Data(contentsOf: url) - guard !data.isEmpty else { + let items = try self.decoderUploadItem.decode([UploadItemDisk].self, from: data) + guard !items.isEmpty else { self.uploadItemsCache = [] - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "UploadStore: JSON empty, cache cleared") + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "Load JSON empty, cache cleared", consoleOnly: true) return } - - let items = try self.decoderUploadItem.decode([UploadItemDisk].self, from: data) self.uploadItemsCache = items - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "UploadStore: JSON reloaded from disk (sync)") + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "JSON loaded from disk (sync)", consoleOnly: true) } catch { - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "UploadStore: reload failed: \(error)") + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Load JSON failed: \(error)") } } } @@ -290,11 +264,29 @@ final class NCUploadStore { /// Performs the actual Realm write using your async APIs. private func syncRealmNow() async { let snapshot: [UploadItemDisk] = uploadStoreIO.sync { - uploadItemsCache.filter { $0.ocId != nil && !$0.ocId!.isEmpty } + return uploadItemsCache } + + // Extract all ocIdTransfers from JSON let ocIdTransfers = snapshot.compactMap { $0.ocIdTransfer } + guard !ocIdTransfers.isEmpty else { + return + } + + // Query Realm for all matching metadatas let predicate = NSPredicate(format: "ocIdTransfer IN %@", ocIdTransfers) let metadatas = await NCManageDatabase.shared.getMetadatasAsync(predicate: predicate) + let foundTransfers = Set(metadatas.compactMap { $0.ocIdTransfer }) + + // Remove any upload items whose ocIdTransfer is not found in Realm + let missingTransfers = ocIdTransfers.filter { !foundTransfers.contains($0) } + if !missingTransfers.isEmpty { + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .warning, message: "Removing \(missingTransfers.count) orphaned upload items not found in Realm", consoleOnly: true) + for transfer in missingTransfers { + removeUploadItem(ocIdTransfer: transfer) + } + } + let utility = NCUtility() var metadatasUploaded: [tableMetadata] = [] From 68d62b903894f41d88bfbf6ed399c6aeceb4c1ce Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 17:55:44 +0200 Subject: [PATCH 12/74] Update NCUploadStore.swift --- iOSClient/Networking/NCUploadStore.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index 01b9faa31b..6a232544ea 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -75,6 +75,7 @@ final class NCUploadStore { guard let self else { return } self.stopDebounceTimer() + self.forceFlush() } NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in @@ -167,8 +168,9 @@ final class NCUploadStore { try data.write(to: url, options: .atomic) lastPersist = CFAbsoluteTimeGetCurrent() changeCounter = 0 + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "Force flush to disk", consoleOnly: true) } catch { - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "Force flush failed: \(error)") + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Force flush to disk failed: \(error)") } } } @@ -211,7 +213,7 @@ final class NCUploadStore { lastPersist = now changeCounter = 0 } catch { - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Persist failed: \(error)") + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Flush to disk failed: \(error)") } Task { @@ -250,13 +252,13 @@ final class NCUploadStore { let items = try self.decoderUploadItem.decode([UploadItemDisk].self, from: data) guard !items.isEmpty else { self.uploadItemsCache = [] - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "Load JSON empty, cache cleared", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "Load JSON from disk empty, cache cleared", consoleOnly: true) return } self.uploadItemsCache = items - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "JSON loaded from disk (sync)", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "JSON loaded from disk)", consoleOnly: true) } catch { - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Load JSON failed: \(error)") + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Load JSON from disk failed: \(error)") } } } From 7d5708fdd023f7fe38dc60b6d9f2ee23eceab744 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 17:57:16 +0200 Subject: [PATCH 13/74] Update NCUploadStore.swift --- iOSClient/Networking/NCUploadStore.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index 6a232544ea..c9a45d8492 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -137,8 +137,6 @@ final class NCUploadStore { $0.taskIdentifier == taskIdentifier }) { uploadItemsCache.remove(at: idx) - changeCounter &+= 1 - maybeCommit() } } } @@ -150,8 +148,6 @@ final class NCUploadStore { $0.ocIdTransfer == ocIdTransfer }) { uploadItemsCache.remove(at: idx) - changeCounter &+= 1 - maybeCommit() } } } From dc34dbb7d0b01670670ec8e08419bc0d1c275600 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 18:28:19 +0200 Subject: [PATCH 14/74] code --- iOSClient/NCAppStateManager.swift | 4 +- iOSClient/Networking/NCUploadStore.swift | 57 +++++++++++++++--------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/iOSClient/NCAppStateManager.swift b/iOSClient/NCAppStateManager.swift index 572abceb58..2bec52253c 100644 --- a/iOSClient/NCAppStateManager.swift +++ b/iOSClient/NCAppStateManager.swift @@ -42,7 +42,9 @@ final class NCAppStateManager { NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { _ in let appDelegate = UIApplication.shared.delegate as? AppDelegate - isSuspendingDatabaseOperation = true + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + isSuspendingDatabaseOperation = true + } isAppInBackground = true // diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index c9a45d8492..ad0aeae7db 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -116,7 +116,7 @@ final class NCUploadStore { /// Updates only the `progress` field of an existing upload item, then schedules a batched commit. /// If no matching item is found, nothing happens. - func updateUploadProgress(serverUrl: String?, fileName: String?, taskIdentifier: Int?, progress: Double) { + func updateUploadProgress(serverUrl: String, fileName: String, taskIdentifier: Int, progress: Double) { uploadStoreIO.sync { if let idx = uploadItemsCache.firstIndex(where: { $0.serverUrl == serverUrl && @@ -129,7 +129,7 @@ final class NCUploadStore { } /// Removes the first match by (serverUrl + fileName); batched commit. - func removeUploadItem(serverUrl: String, fileName: String, taskIdentifier: Int?) { + func removeUploadItem(serverUrl: String, fileName: String, taskIdentifier: Int) { uploadStoreIO.sync { if let idx = uploadItemsCache.firstIndex(where: { $0.serverUrl == serverUrl && @@ -160,10 +160,29 @@ final class NCUploadStore { uploadStoreIO.sync { do { + // Remove orphaned upload items not found in Realm + let ocIdTransfers = Set(uploadItemsCache.compactMap { $0.ocIdTransfer }) + if !ocIdTransfers.isEmpty { + + let predicate = NSPredicate(format: "ocIdTransfer IN %@", ocIdTransfers) + let metadatas = NCManageDatabase.shared.getMetadatas(predicate: predicate) + let foundTransfers = Set(metadatas.compactMap { $0.ocIdTransfer }) + + let missingTransfers = Set(ocIdTransfers).subtracting(foundTransfers) + if !missingTransfers.isEmpty { + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .warning, message: "Removing \(missingTransfers.count) orphaned upload items not found in Realm", consoleOnly: true) + uploadItemsCache.removeAll { item in + guard let t = item.ocIdTransfer else { return false } + return missingTransfers.contains(t) + } + } + } + let data = try encoderUploadItem.encode(uploadItemsCache) try data.write(to: url, options: .atomic) lastPersist = CFAbsoluteTimeGetCurrent() changeCounter = 0 + nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "Force flush to disk", consoleOnly: true) } catch { nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Force flush to disk failed: \(error)") @@ -259,32 +278,19 @@ final class NCUploadStore { } } + + private func syncdsRealmNow() async { + + } + /// Performs the actual Realm write using your async APIs. private func syncRealmNow() async { let snapshot: [UploadItemDisk] = uploadStoreIO.sync { - return uploadItemsCache + uploadItemsCache.filter { $0.ocId != nil && !$0.ocId!.isEmpty } } - - // Extract all ocIdTransfers from JSON let ocIdTransfers = snapshot.compactMap { $0.ocIdTransfer } - guard !ocIdTransfers.isEmpty else { - return - } - - // Query Realm for all matching metadatas let predicate = NSPredicate(format: "ocIdTransfer IN %@", ocIdTransfers) let metadatas = await NCManageDatabase.shared.getMetadatasAsync(predicate: predicate) - let foundTransfers = Set(metadatas.compactMap { $0.ocIdTransfer }) - - // Remove any upload items whose ocIdTransfer is not found in Realm - let missingTransfers = ocIdTransfers.filter { !foundTransfers.contains($0) } - if !missingTransfers.isEmpty { - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .warning, message: "Removing \(missingTransfers.count) orphaned upload items not found in Realm", consoleOnly: true) - for transfer in missingTransfers { - removeUploadItem(ocIdTransfer: transfer) - } - } - let utility = NCUtility() var metadatasUploaded: [tableMetadata] = [] @@ -314,5 +320,14 @@ final class NCUploadStore { } await NCManageDatabase.shared.replaceMetadataAsync(ocIdTransfers: ocIdTransfers, metadatas: metadatasUploaded) + + // transferDispatcher + if !metadatasUploaded.isEmpty { + await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in + delegate.transferChange(status: NCGlobal.shared.networkingStatusUploaded, + metadata: tableMetadata(), + error: .success) + } + } } } From 7736e657799f24ad7ecc8aa5b77f72c464d6c535 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Mon, 6 Oct 2025 18:36:16 +0200 Subject: [PATCH 15/74] Update NCUploadStore.swift --- iOSClient/Networking/NCUploadStore.swift | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCUploadStore.swift index ad0aeae7db..5a14054154 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCUploadStore.swift @@ -278,11 +278,6 @@ final class NCUploadStore { } } - - private func syncdsRealmNow() async { - - } - /// Performs the actual Realm write using your async APIs. private func syncRealmNow() async { let snapshot: [UploadItemDisk] = uploadStoreIO.sync { @@ -293,6 +288,7 @@ final class NCUploadStore { let metadatas = await NCManageDatabase.shared.getMetadatasAsync(predicate: predicate) let utility = NCUtility() var metadatasUploaded: [tableMetadata] = [] + var serversUrl = Set() for metadata in metadatas { guard let uploadItem = (uploadItemsCache.first { $0.ocIdTransfer == metadata.ocIdTransfer }), @@ -316,17 +312,19 @@ final class NCUploadStore { metadata.status = NCGlobal.shared.metadataStatusNormal metadatasUploaded.append(metadata) + serversUrl.insert(metadata.serverUrl) + removeUploadItem(ocIdTransfer: metadata.ocIdTransfer) } await NCManageDatabase.shared.replaceMetadataAsync(ocIdTransfers: ocIdTransfers, metadatas: metadatasUploaded) - // transferDispatcher + // TransferDispatcher Reload Data if !metadatasUploaded.isEmpty { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in - delegate.transferChange(status: NCGlobal.shared.networkingStatusUploaded, - metadata: tableMetadata(), - error: .success) + for serverUrl in serversUrl { + delegate.transferReloadData(serverUrl: serverUrl, status: nil) + } } } } From 6e1d76c575677577dde0fd13eacf074b2016f45a Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 7 Oct 2025 11:50:19 +0200 Subject: [PATCH 16/74] test Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCNetworking+Upload.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index d4277eb840..95b4242af0 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -202,9 +202,18 @@ extension NCNetworking { @discardableResult func uploadFileInBackground(metadata: tableMetadata, + withFileExistsCheck: Bool = false, taskHandler: @escaping (_ task: URLSessionUploadTask?) -> Void = { _ in }, start: @escaping () -> Void = { }) async -> NKError { + if withFileExistsCheck { + let error = await self.fileExists(serverUrlFileName: metadata.serverUrlFileName, account: metadata.account) + if error == .success { + await uploadCancelFile(metadata: metadata) + return (.success) + } + } + let fileNameLocalPath = utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocId, fileName: metadata.fileName, userId: metadata.userId, urlBase: metadata.urlBase) start() From 9a6bea4e61036f46f24aa9e72a066cd3d40149fb Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 7 Oct 2025 13:38:23 +0200 Subject: [PATCH 17/74] cod Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCNetworking+Upload.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 95b4242af0..217cc38d25 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -206,7 +206,7 @@ extension NCNetworking { taskHandler: @escaping (_ task: URLSessionUploadTask?) -> Void = { _ in }, start: @escaping () -> Void = { }) async -> NKError { - if withFileExistsCheck { + if withFileExistsCheck || metadata.sessionSelector == global.selectorUploadAutoUpload { let error = await self.fileExists(serverUrlFileName: metadata.serverUrlFileName, account: metadata.account) if error == .success { await uploadCancelFile(metadata: metadata) From 3793502280cd29bd0d1dc2c82bbc2b6dde0e21f4 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 7 Oct 2025 13:59:51 +0200 Subject: [PATCH 18/74] rename to TransferItem Signed-off-by: Marino Faggiana --- Brand/Database.swift | 2 +- Nextcloud.xcodeproj/project.pbxproj | 8 +- iOSClient/NCGlobal.swift | 2 +- .../Networking/NCNetworking+Upload.swift | 48 +++---- ...ploadStore.swift => NCTransferStore.swift} | 134 +++++++++--------- iOSClient/SceneDelegate.swift | 2 +- 6 files changed, 98 insertions(+), 98 deletions(-) rename iOSClient/Networking/{NCUploadStore.swift => NCTransferStore.swift} (63%) diff --git a/Brand/Database.swift b/Brand/Database.swift index 851889d0d2..bf41fcdafe 100644 --- a/Brand/Database.swift +++ b/Brand/Database.swift @@ -8,5 +8,5 @@ import Foundation // let databaseName = "nextcloud.realm" let tableAccountBackup = "tableAccountBackup.json" -let fileUploadStore = "uploads.json" +let fileTransferStore = "transfer.json" let databaseSchemaVersion: UInt64 = 403 diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 2a695667e7..d3dc9dee77 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -722,7 +722,7 @@ F7864AD02A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; F7864AD12A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; F7864AD22A78FE73004870E0 /* NCManageDatabase+LocalFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */; }; - F786891D2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78689192E93A9D5000E39B3 /* NCUploadStore.swift */; }; + F786891D2E93A9DF000E39B3 /* NCTransferStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F78689192E93A9D5000E39B3 /* NCTransferStore.swift */; }; F787704F22E7019900F287A9 /* NCShareLinkCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F787704E22E7019900F287A9 /* NCShareLinkCell.xib */; }; F787AC09298BCB4A0001BB00 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F787AC08298BCB4A0001BB00 /* SVGKitSwift */; }; F787AC0B298BCB540001BB00 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F787AC0A298BCB540001BB00 /* SVGKitSwift */; }; @@ -1657,7 +1657,7 @@ F785129A2D79899E0087DDD0 /* NCNetworking+TermsOfService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCNetworking+TermsOfService.swift"; sourceTree = ""; }; F785EE9C246196DF00B3F945 /* NCNetworkingE2EE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCNetworkingE2EE.swift; sourceTree = ""; }; F7864ACB2A78FE73004870E0 /* NCManageDatabase+LocalFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+LocalFile.swift"; sourceTree = ""; }; - F78689192E93A9D5000E39B3 /* NCUploadStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCUploadStore.swift; sourceTree = ""; }; + F78689192E93A9D5000E39B3 /* NCTransferStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCTransferStore.swift; sourceTree = ""; }; F787704E22E7019900F287A9 /* NCShareLinkCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NCShareLinkCell.xib; sourceTree = ""; }; F78A10BE29322E8A008499B8 /* NCManageDatabase+Directory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+Directory.swift"; sourceTree = ""; }; F78A18B523CDD07D00F681F3 /* NCViewerRichWorkspaceWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCViewerRichWorkspaceWebView.swift; sourceTree = ""; }; @@ -2541,7 +2541,7 @@ F70D8D8024A4A9BF000A5756 /* NCNetworkingProcess.swift */, F755BD9A20594AC7008C5FBB /* NCService.swift */, F7A3DB8F2DDE238C008F7EC8 /* NCDebouncer.swift */, - F78689192E93A9D5000E39B3 /* NCUploadStore.swift */, + F78689192E93A9D5000E39B3 /* NCTransferStore.swift */, ); path = Networking; sourceTree = ""; @@ -4703,7 +4703,7 @@ F7A3DB932DDE23B5008F7EC8 /* NCDebouncer.swift in Sources */, F72CD63A25C19EBF00F46F9A /* NCAutoUpload.swift in Sources */, AF93471D27E2361E002537EE /* NCShareAdvancePermissionFooter.swift in Sources */, - F786891D2E93A9DF000E39B3 /* NCUploadStore.swift in Sources */, + F786891D2E93A9DF000E39B3 /* NCTransferStore.swift in Sources */, AF1A9B6427D0CA1E00F17A9E /* UIAlertController+Extension.swift in Sources */, F7FA80012C0F4F3B0072FC60 /* NCUploadAssetsView.swift in Sources */, 371B5A2E23D0B04500FAFAE9 /* NCMenu.swift in Sources */, diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index 68c1a42495..4695fc5c36 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -392,7 +392,7 @@ final class NCGlobal: Sendable { let logTagDatabase = "DB" let logTagSpeedUpSyncMetadata = "SYNC METADATA" let logTagNetworkingTasks = "NETWORKING TASKS" - let logTagUploadStore = "UPLOAD STORE" + let logTagTransferStore = "TRANSFER STORE" // USER DEFAULTS // diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 217cc38d25..433f15d3ce 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -236,15 +236,15 @@ extension NCNetworking { nkLog(debug: "Upload file \(metadata.fileNameView) with taskIdentifier \(task.taskIdentifier)") #if !EXTENSION - NCUploadStore.shared.addUploadItem(UploadItemDisk(fileName: metadata.fileName, - ocIdTransfer: metadata.ocIdTransfer, - progress: 0, - selector: metadata.sessionSelector, - serverUrl: metadata.serverUrl, - session: metadata.session, - status: metadata.status, - size: metadata.size, - taskIdentifier: task.taskIdentifier)) + NCTransferStore.shared.addItem(TransferItem(fileName: metadata.fileName, + ocIdTransfer: metadata.ocIdTransfer, + progress: 0, + selector: metadata.sessionSelector, + serverUrl: metadata.serverUrl, + session: metadata.session, + status: metadata.status, + size: metadata.size, + taskIdentifier: task.taskIdentifier)) #endif if let metadata = await NCManageDatabase.shared.setMetadataSessionAsync(ocId: metadata.ocId, @@ -389,7 +389,7 @@ extension NCNetworking { func uploadCancelFile(metadata: tableMetadata) async { #if !EXTENSION - NCUploadStore.shared.removeUploadItem(ocIdTransfer: metadata.ocIdTransfer) + NCTransferStore.shared.removeItem(ocIdTransfer: metadata.ocIdTransfer) #endif self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocIdTransfer, userId: metadata.userId, urlBase: metadata.urlBase)) @@ -523,18 +523,18 @@ extension NCNetworking { #if !EXTENSION if error == .success { - NCUploadStore.shared.addUploadItem(UploadItemDisk(date: date, - etag: etag, - fileName: fileName, - ocId: ocId, - serverUrl: serverUrl, - size: size, - taskIdentifier: task.taskIdentifier)) + NCTransferStore.shared.addItem(TransferItem(date: date, + etag: etag, + fileName: fileName, + ocId: ocId, + serverUrl: serverUrl, + size: size, + taskIdentifier: task.taskIdentifier)) return } else { - NCUploadStore.shared.removeUploadItem(serverUrl: serverUrl, - fileName: fileName, - taskIdentifier: task.taskIdentifier) + NCTransferStore.shared.removeItem(serverUrl: serverUrl, + fileName: fileName, + taskIdentifier: task.taskIdentifier) } #endif @@ -560,10 +560,10 @@ extension NCNetworking { } #if !EXTENSION - NCUploadStore.shared.updateUploadProgress(serverUrl: serverUrl, - fileName: fileName, - taskIdentifier: task.taskIdentifier, - progress: Double(progress)) + NCTransferStore.shared.transferProgress(serverUrl: serverUrl, + fileName: fileName, + taskIdentifier: task.taskIdentifier, + progress: Double(progress)) #endif await NCManageDatabase.shared.setMetadataProgress(fileName: fileName, serverUrl: serverUrl, taskIdentifier: task.taskIdentifier, progress: Double(progress)) diff --git a/iOSClient/Networking/NCUploadStore.swift b/iOSClient/Networking/NCTransferStore.swift similarity index 63% rename from iOSClient/Networking/NCUploadStore.swift rename to iOSClient/Networking/NCTransferStore.swift index 5a14054154..c9025215f7 100644 --- a/iOSClient/Networking/NCUploadStore.swift +++ b/iOSClient/Networking/NCTransferStore.swift @@ -5,9 +5,9 @@ import UIKit import NextcloudKit -// MARK: - Upload Store (batched persistence) +// MARK: - Transfer Store (batched persistence) -struct UploadItemDisk: Codable { +struct TransferItem: Codable { var date: Date? var etag: String? var fileName: String? @@ -22,15 +22,15 @@ struct UploadItemDisk: Codable { var taskIdentifier: Int? } -final class NCUploadStore { - static let shared = NCUploadStore() +final class NCTransferStore { + static let shared = NCTransferStore() // Shared state - private var uploadItemsCache: [UploadItemDisk] = [] - private let uploadStoreIO = DispatchQueue(label: "UploadStore.IO", qos: .utility) - private let encoderUploadItem = JSONEncoder() - private let decoderUploadItem = JSONDecoder() - private(set) var uploadStoreURL: URL? + private var transferItemsCache: [TransferItem] = [] + private let transferStoreIO = DispatchQueue(label: "TransferStore.IO", qos: .utility) + private let encoderTransferItem = JSONEncoder() + private let decoderTransferItem = JSONDecoder() + private(set) var transferStoreURL: URL? // Batching controls private var changeCounter: Int = 0 @@ -40,23 +40,23 @@ final class NCUploadStore { private var debounceTimer: DispatchSourceTimer? init() { - self.encoderUploadItem.dateEncodingStrategy = .iso8601 - self.decoderUploadItem.dateDecodingStrategy = .iso8601 + self.encoderTransferItem.dateEncodingStrategy = .iso8601 + self.decoderTransferItem.dateDecodingStrategy = .iso8601 guard let groupDirectory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: NCBrandOptions.shared.capabilitiesGroup) else { return } let backupDirectory = groupDirectory.appendingPathComponent(NCGlobal.shared.appDatabaseNextcloud) - self.uploadStoreURL = backupDirectory.appendingPathComponent(fileUploadStore) + self.transferStoreURL = backupDirectory.appendingPathComponent(fileTransferStore) - self.uploadStoreIO.sync { - if let url = self.uploadStoreURL, + self.transferStoreIO.sync { + if let url = self.transferStoreURL, let data = try? Data(contentsOf: url), !data.isEmpty, - let items = try? self.decoderUploadItem.decode([UploadItemDisk].self, from: data) { - self.uploadItemsCache = items + let items = try? self.decoderTransferItem.decode([TransferItem].self, from: data) { + self.transferItemsCache = items } else { - self.uploadItemsCache = [] + self.transferItemsCache = [] } } @@ -95,18 +95,18 @@ final class NCUploadStore { // MARK: Public API /// Adds or merges an item, then schedules a batched commit. - func addUploadItem(_ item: UploadItemDisk) { - uploadStoreIO.sync { + func addItem(_ item: TransferItem) { + transferStoreIO.sync { // Upsert by (serverUrl + fileName + taskIdentifier) - if let idx = uploadItemsCache.firstIndex(where: { + if let idx = transferItemsCache.firstIndex(where: { $0.serverUrl == item.serverUrl && $0.fileName == item.fileName && $0.taskIdentifier == item.taskIdentifier }) { - let merged = mergeUploadItem(existing: uploadItemsCache[idx], with: item) - uploadItemsCache[idx] = merged + let merged = mergeItem(existing: transferItemsCache[idx], with: item) + transferItemsCache[idx] = merged } else { - uploadItemsCache.append(item) + transferItemsCache.append(item) } changeCounter &+= 1 @@ -114,54 +114,54 @@ final class NCUploadStore { } } - /// Updates only the `progress` field of an existing upload item, then schedules a batched commit. + /// Updates only the `progress` field of an existing item, then schedules a batched commit. /// If no matching item is found, nothing happens. - func updateUploadProgress(serverUrl: String, fileName: String, taskIdentifier: Int, progress: Double) { - uploadStoreIO.sync { - if let idx = uploadItemsCache.firstIndex(where: { + func transferProgress(serverUrl: String, fileName: String, taskIdentifier: Int, progress: Double) { + transferStoreIO.sync { + if let idx = transferItemsCache.firstIndex(where: { $0.serverUrl == serverUrl && $0.fileName == fileName && $0.taskIdentifier == taskIdentifier }) { - uploadItemsCache[idx].progress = progress + transferItemsCache[idx].progress = progress } } } /// Removes the first match by (serverUrl + fileName); batched commit. - func removeUploadItem(serverUrl: String, fileName: String, taskIdentifier: Int) { - uploadStoreIO.sync { - if let idx = uploadItemsCache.firstIndex(where: { + func removeItem(serverUrl: String, fileName: String, taskIdentifier: Int) { + transferStoreIO.sync { + if let idx = transferItemsCache.firstIndex(where: { $0.serverUrl == serverUrl && $0.fileName == fileName && $0.taskIdentifier == taskIdentifier }) { - uploadItemsCache.remove(at: idx) + transferItemsCache.remove(at: idx) } } } /// Removes the first match by (ocIdTransfer); batched commit. - func removeUploadItem(ocIdTransfer: String) { - uploadStoreIO.sync { - if let idx = uploadItemsCache.firstIndex(where: { + func removeItem(ocIdTransfer: String) { + transferStoreIO.sync { + if let idx = transferItemsCache.firstIndex(where: { $0.ocIdTransfer == ocIdTransfer }) { - uploadItemsCache.remove(at: idx) + transferItemsCache.remove(at: idx) } } } /// Forces an immediate flush to disk (e.g., app background/terminate). func forceFlush() { - guard let url = self.uploadStoreURL else { + guard let url = self.transferStoreURL else { return } - uploadStoreIO.sync { + transferStoreIO.sync { do { - // Remove orphaned upload items not found in Realm - let ocIdTransfers = Set(uploadItemsCache.compactMap { $0.ocIdTransfer }) + // Remove orphaned items not found in Realm + let ocIdTransfers = Set(transferItemsCache.compactMap { $0.ocIdTransfer }) if !ocIdTransfers.isEmpty { let predicate = NSPredicate(format: "ocIdTransfer IN %@", ocIdTransfers) @@ -170,22 +170,22 @@ final class NCUploadStore { let missingTransfers = Set(ocIdTransfers).subtracting(foundTransfers) if !missingTransfers.isEmpty { - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .warning, message: "Removing \(missingTransfers.count) orphaned upload items not found in Realm", consoleOnly: true) - uploadItemsCache.removeAll { item in + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .warning, message: "Removing \(missingTransfers.count) orphaned items not found in Realm", consoleOnly: true) + transferItemsCache.removeAll { item in guard let t = item.ocIdTransfer else { return false } return missingTransfers.contains(t) } } } - let data = try encoderUploadItem.encode(uploadItemsCache) + let data = try encoderTransferItem.encode(transferItemsCache) try data.write(to: url, options: .atomic) lastPersist = CFAbsoluteTimeGetCurrent() changeCounter = 0 - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "Force flush to disk", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .info, message: "Force flush to disk", consoleOnly: true) } catch { - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Force flush to disk failed: \(error)") + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Force flush to disk failed: \(error)") } } } @@ -193,8 +193,8 @@ final class NCUploadStore { // MARK: - Private /// Merge: only non-nil fields from `new` overwrite existing values. - private func mergeUploadItem(existing: UploadItemDisk, with new: UploadItemDisk) -> UploadItemDisk { - return UploadItemDisk( + private func mergeItem(existing: TransferItem, with new: TransferItem) -> TransferItem { + return TransferItem( date: new.date ?? existing.date, etag: new.etag ?? existing.etag, fileName: existing.fileName ?? new.fileName, @@ -212,7 +212,7 @@ final class NCUploadStore { /// Persist if threshold reached or max latency exceeded. private func maybeCommit() { - guard let url = self.uploadStoreURL else { + guard let url = self.transferStoreURL else { return } let now = CFAbsoluteTimeGetCurrent() @@ -223,12 +223,12 @@ final class NCUploadStore { } do { - let data = try encoderUploadItem.encode(uploadItemsCache) + let data = try encoderTransferItem.encode(transferItemsCache) try data.write(to: url, options: .atomic) lastPersist = now changeCounter = 0 } catch { - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Flush to disk failed: \(error)") + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Flush to disk failed: \(error)") } Task { @@ -237,7 +237,7 @@ final class NCUploadStore { } private func startDebounceTimer() { - let t = DispatchSource.makeTimerSource(queue: uploadStoreIO) + let t = DispatchSource.makeTimerSource(queue: transferStoreIO) t.schedule(deadline: .now() + .seconds(Int(maxLatencySec)), repeating: .seconds(Int(maxLatencySec))) t.setEventHandler { [weak self] in guard let self else { return } @@ -255,33 +255,33 @@ final class NCUploadStore { } /// Reloads the entire JSON store from disk synchronously. - /// When this function returns, `uploadItemsCache` is guaranteed to be updated. + /// When this function returns, `transferItemsCache` is guaranteed to be updated. private func reloadFromDisk() { - guard let url = self.uploadStoreURL else { + guard let url = self.transferStoreURL else { return } - uploadStoreIO.sync { + transferStoreIO.sync { do { let data = try Data(contentsOf: url) - let items = try self.decoderUploadItem.decode([UploadItemDisk].self, from: data) + let items = try self.decoderTransferItem.decode([TransferItem].self, from: data) guard !items.isEmpty else { - self.uploadItemsCache = [] - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "Load JSON from disk empty, cache cleared", consoleOnly: true) + self.transferItemsCache = [] + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .info, message: "Load JSON from disk empty, cache cleared", consoleOnly: true) return } - self.uploadItemsCache = items - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .info, message: "JSON loaded from disk)", consoleOnly: true) + self.transferItemsCache = items + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .info, message: "JSON loaded from disk)", consoleOnly: true) } catch { - nkLog(tag: NCGlobal.shared.logTagUploadStore, emoji: .error, message: "Load JSON from disk failed: \(error)") + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Load JSON from disk failed: \(error)") } } } /// Performs the actual Realm write using your async APIs. private func syncRealmNow() async { - let snapshot: [UploadItemDisk] = uploadStoreIO.sync { - uploadItemsCache.filter { $0.ocId != nil && !$0.ocId!.isEmpty } + let snapshot: [TransferItem] = transferStoreIO.sync { + transferItemsCache.filter { $0.ocId != nil && !$0.ocId!.isEmpty } } let ocIdTransfers = snapshot.compactMap { $0.ocIdTransfer } let predicate = NSPredicate(format: "ocIdTransfer IN %@", ocIdTransfers) @@ -291,13 +291,13 @@ final class NCUploadStore { var serversUrl = Set() for metadata in metadatas { - guard let uploadItem = (uploadItemsCache.first { $0.ocIdTransfer == metadata.ocIdTransfer }), - let etag = uploadItem.etag, - let ocId = uploadItem.ocId else { + guard let transferItem = (transferItemsCache.first { $0.ocIdTransfer == metadata.ocIdTransfer }), + let etag = transferItem.etag, + let ocId = transferItem.ocId else { continue } - metadata.uploadDate = (uploadItem.date as? NSDate) ?? NSDate() + metadata.uploadDate = (transferItem.date as? NSDate) ?? NSDate() metadata.etag = etag metadata.ocId = ocId metadata.chunk = 0 @@ -314,7 +314,7 @@ final class NCUploadStore { metadatasUploaded.append(metadata) serversUrl.insert(metadata.serverUrl) - removeUploadItem(ocIdTransfer: metadata.ocIdTransfer) + removeItem(ocIdTransfer: metadata.ocIdTransfer) } await NCManageDatabase.shared.replaceMetadataAsync(ocIdTransfers: ocIdTransfers, metadatas: metadatasUploaded) diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index ed8d3413bb..5aa8acfc6a 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -85,7 +85,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { _ = NCNetworking.shared _ = NCDownloadAction.shared _ = NCNetworkingProcess.shared - _ = NCUploadStore.shared + _ = NCTransferStore.shared if let activeTblAccount, !alreadyMigratedMultiDomains { // From ef267bc3208e8cf15a6f19969261a37907d818df Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 7 Oct 2025 14:09:31 +0200 Subject: [PATCH 19/74] improvements Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCTransferStore.swift | 52 ++++++++++++++++------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/iOSClient/Networking/NCTransferStore.swift b/iOSClient/Networking/NCTransferStore.swift index c9025215f7..8995e22147 100644 --- a/iOSClient/Networking/NCTransferStore.swift +++ b/iOSClient/Networking/NCTransferStore.swift @@ -152,6 +152,17 @@ final class NCTransferStore { } } + /// Removes the first match by (ocId); batched commit. + func removeItem(ocId: String) { + transferStoreIO.sync { + if let idx = transferItemsCache.firstIndex(where: { + $0.ocId == ocId + }) { + transferItemsCache.remove(at: idx) + } + } + } + /// Forces an immediate flush to disk (e.g., app background/terminate). func forceFlush() { guard let url = self.transferStoreURL else { @@ -160,21 +171,34 @@ final class NCTransferStore { transferStoreIO.sync { do { - // Remove orphaned items not found in Realm - let ocIdTransfers = Set(transferItemsCache.compactMap { $0.ocIdTransfer }) - if !ocIdTransfers.isEmpty { + let transfers: Set = Set(transferItemsCache.compactMap { $0.ocIdTransfer }) + let ocids: Set = Set(transferItemsCache.compactMap { $0.ocId }) + + if !transfers.isEmpty || !ocids.isEmpty { - let predicate = NSPredicate(format: "ocIdTransfer IN %@", ocIdTransfers) + // Build a predicate that matches either ocIdTransfer or ocId + // Note: pass empty sets as arrays to keep format arguments consistent + let predicate = NSPredicate(format: "(ocIdTransfer IN %@) OR (ocId IN %@)",Array(transfers), Array(ocids)) + + // Query Realm once let metadatas = NCManageDatabase.shared.getMetadatas(predicate: predicate) - let foundTransfers = Set(metadatas.compactMap { $0.ocIdTransfer }) - - let missingTransfers = Set(ocIdTransfers).subtracting(foundTransfers) - if !missingTransfers.isEmpty { - nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .warning, message: "Removing \(missingTransfers.count) orphaned items not found in Realm", consoleOnly: true) - transferItemsCache.removeAll { item in - guard let t = item.ocIdTransfer else { return false } - return missingTransfers.contains(t) - } + + // Build found sets for quick lookup + let foundTransfers: Set = Set(metadatas.compactMap { $0.ocIdTransfer }) + let foundOcids: Set = Set(metadatas.compactMap { $0.ocId }) + + // Remove items that have NEITHER a matching ocIdTransfer NOR a matching ocId in Realm + let before = transferItemsCache.count + transferItemsCache.removeAll { item in + let ocIdTransfer = item.ocIdTransfer + let ocId = item.ocId + // Keep if either matches; remove only if both are missing + let hasMatch = (ocIdTransfer != nil && foundTransfers.contains(ocIdTransfer!)) || (ocId != nil && foundOcids.contains(ocId!)) + return !hasMatch + } + let removed = before - transferItemsCache.count + if removed > 0 { + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .warning, message: "Removed \(removed) orphaned items (no match on ocIdTransfer nor ocId)", consoleOnly: true) } } @@ -279,7 +303,7 @@ final class NCTransferStore { } /// Performs the actual Realm write using your async APIs. - private func syncRealmNow() async { + private func syncUploadRealm() async { let snapshot: [TransferItem] = transferStoreIO.sync { transferItemsCache.filter { $0.ocId != nil && !$0.ocId!.isEmpty } } From 1ae1868000d4e42ab9c747901bd185f469c92883 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 7 Oct 2025 14:19:23 +0200 Subject: [PATCH 20/74] code Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCNetworking+Upload.swift | 3 ++- iOSClient/Networking/NCTransferStore.swift | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 433f15d3ce..c519ddad8d 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -523,7 +523,8 @@ extension NCNetworking { #if !EXTENSION if error == .success { - NCTransferStore.shared.addItem(TransferItem(date: date, + NCTransferStore.shared.addItem(TransferItem(completed: true, + date: date, etag: etag, fileName: fileName, ocId: ocId, diff --git a/iOSClient/Networking/NCTransferStore.swift b/iOSClient/Networking/NCTransferStore.swift index 8995e22147..d3aa48892e 100644 --- a/iOSClient/Networking/NCTransferStore.swift +++ b/iOSClient/Networking/NCTransferStore.swift @@ -8,6 +8,7 @@ import NextcloudKit // MARK: - Transfer Store (batched persistence) struct TransferItem: Codable { + var completed: Bool = false var date: Date? var etag: String? var fileName: String? @@ -178,7 +179,7 @@ final class NCTransferStore { // Build a predicate that matches either ocIdTransfer or ocId // Note: pass empty sets as arrays to keep format arguments consistent - let predicate = NSPredicate(format: "(ocIdTransfer IN %@) OR (ocId IN %@)",Array(transfers), Array(ocids)) + let predicate = NSPredicate(format: "(ocIdTransfer IN %@) OR (ocId IN %@)", Array(transfers), Array(ocids)) // Query Realm once let metadatas = NCManageDatabase.shared.getMetadatas(predicate: predicate) @@ -256,7 +257,7 @@ final class NCTransferStore { } Task { - await syncRealmNow() + await syncUploadRealm() } } @@ -305,7 +306,15 @@ final class NCTransferStore { /// Performs the actual Realm write using your async APIs. private func syncUploadRealm() async { let snapshot: [TransferItem] = transferStoreIO.sync { - transferItemsCache.filter { $0.ocId != nil && !$0.ocId!.isEmpty } + transferItemsCache.filter { item in + if item.completed { + return item.session == NCNetworking.shared.sessionUpload + || item.session == NCNetworking.shared.sessionUploadBackground + || item.session == NCNetworking.shared.sessionUploadBackgroundExt + || item.session == NCNetworking.shared.sessionUploadBackgroundWWan + } + return false + } } let ocIdTransfers = snapshot.compactMap { $0.ocIdTransfer } let predicate = NSPredicate(format: "ocIdTransfer IN %@", ocIdTransfers) From 930852ead289570ff81b11dabc40bb9464f91e63 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 7 Oct 2025 14:25:27 +0200 Subject: [PATCH 21/74] cod Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCTransferStore.swift | 67 ++++++++++++---------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/iOSClient/Networking/NCTransferStore.swift b/iOSClient/Networking/NCTransferStore.swift index d3aa48892e..0136a5cf69 100644 --- a/iOSClient/Networking/NCTransferStore.swift +++ b/iOSClient/Networking/NCTransferStore.swift @@ -89,7 +89,6 @@ final class NCTransferStore { try? await Task.sleep(nanoseconds: 1_000_000_000) self.startDebounceTimer() } - } } @@ -172,36 +171,7 @@ final class NCTransferStore { transferStoreIO.sync { do { - let transfers: Set = Set(transferItemsCache.compactMap { $0.ocIdTransfer }) - let ocids: Set = Set(transferItemsCache.compactMap { $0.ocId }) - - if !transfers.isEmpty || !ocids.isEmpty { - - // Build a predicate that matches either ocIdTransfer or ocId - // Note: pass empty sets as arrays to keep format arguments consistent - let predicate = NSPredicate(format: "(ocIdTransfer IN %@) OR (ocId IN %@)", Array(transfers), Array(ocids)) - - // Query Realm once - let metadatas = NCManageDatabase.shared.getMetadatas(predicate: predicate) - - // Build found sets for quick lookup - let foundTransfers: Set = Set(metadatas.compactMap { $0.ocIdTransfer }) - let foundOcids: Set = Set(metadatas.compactMap { $0.ocId }) - - // Remove items that have NEITHER a matching ocIdTransfer NOR a matching ocId in Realm - let before = transferItemsCache.count - transferItemsCache.removeAll { item in - let ocIdTransfer = item.ocIdTransfer - let ocId = item.ocId - // Keep if either matches; remove only if both are missing - let hasMatch = (ocIdTransfer != nil && foundTransfers.contains(ocIdTransfer!)) || (ocId != nil && foundOcids.contains(ocId!)) - return !hasMatch - } - let removed = before - transferItemsCache.count - if removed > 0 { - nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .warning, message: "Removed \(removed) orphaned items (no match on ocIdTransfer nor ocId)", consoleOnly: true) - } - } + checkOrphaned() let data = try encoderTransferItem.encode(transferItemsCache) try data.write(to: url, options: .atomic) @@ -296,6 +266,8 @@ final class NCTransferStore { return } self.transferItemsCache = items + // check + self.checkOrphaned() nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .info, message: "JSON loaded from disk)", consoleOnly: true) } catch { nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Load JSON from disk failed: \(error)") @@ -303,6 +275,39 @@ final class NCTransferStore { } } + private func checkOrphaned() { + let transfers: Set = Set(transferItemsCache.compactMap { $0.ocIdTransfer }) + let ocids: Set = Set(transferItemsCache.compactMap { $0.ocId }) + + if !transfers.isEmpty || !ocids.isEmpty { + + // Build a predicate that matches either ocIdTransfer or ocId + // Note: pass empty sets as arrays to keep format arguments consistent + let predicate = NSPredicate(format: "(ocIdTransfer IN %@) OR (ocId IN %@)", Array(transfers), Array(ocids)) + + // Query Realm once + let metadatas = NCManageDatabase.shared.getMetadatas(predicate: predicate) + + // Build found sets for quick lookup + let foundTransfers: Set = Set(metadatas.compactMap { $0.ocIdTransfer }) + let foundOcids: Set = Set(metadatas.compactMap { $0.ocId }) + + // Remove items that have NEITHER a matching ocIdTransfer NOR a matching ocId in Realm + let before = transferItemsCache.count + transferItemsCache.removeAll { item in + let ocIdTransfer = item.ocIdTransfer + let ocId = item.ocId + // Keep if either matches; remove only if both are missing + let hasMatch = (ocIdTransfer != nil && foundTransfers.contains(ocIdTransfer!)) || (ocId != nil && foundOcids.contains(ocId!)) + return !hasMatch + } + let removed = before - transferItemsCache.count + if removed > 0 { + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .warning, message: "Removed \(removed) orphaned items (no match on ocIdTransfer nor ocId)", consoleOnly: true) + } + } + } + /// Performs the actual Realm write using your async APIs. private func syncUploadRealm() async { let snapshot: [TransferItem] = transferStoreIO.sync { From 18b3715f96cb03e16950a212cf7558d7b68b70bf Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 7 Oct 2025 14:49:34 +0200 Subject: [PATCH 22/74] fix Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCNetworking+Task.swift | 3 ++- iOSClient/Networking/NCTransferStore.swift | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/iOSClient/Networking/NCNetworking+Task.swift b/iOSClient/Networking/NCNetworking+Task.swift index 415a1b51a3..c508dae27b 100644 --- a/iOSClient/Networking/NCNetworking+Task.swift +++ b/iOSClient/Networking/NCNetworking+Task.swift @@ -321,7 +321,8 @@ extension NCNetworking { // if let metadatas = await NCManageDatabase.shared.getMetadatasAsync(predicate: NSPredicate(format: "(session == %@ OR session == %@) AND status == %d", sessionUploadBackground, - sessionUploadBackgroundWWan)) { + sessionUploadBackgroundWWan, + self.global.metadataStatusUploading)) { for metadata in metadatas { guard var nkSession = NextcloudKit.shared.nkCommonInstance.nksessions.session(forAccount: metadata.account) else { await NCManageDatabase.shared.deleteMetadataAsync(id: metadata.ocId) diff --git a/iOSClient/Networking/NCTransferStore.swift b/iOSClient/Networking/NCTransferStore.swift index 0136a5cf69..c5eac49933 100644 --- a/iOSClient/Networking/NCTransferStore.swift +++ b/iOSClient/Networking/NCTransferStore.swift @@ -8,7 +8,7 @@ import NextcloudKit // MARK: - Transfer Store (batched persistence) struct TransferItem: Codable { - var completed: Bool = false + var completed: Bool? var date: Date? var etag: String? var fileName: String? @@ -190,6 +190,7 @@ final class NCTransferStore { /// Merge: only non-nil fields from `new` overwrite existing values. private func mergeItem(existing: TransferItem, with new: TransferItem) -> TransferItem { return TransferItem( + completed: new.completed ?? existing.completed, date: new.date ?? existing.date, etag: new.etag ?? existing.etag, fileName: existing.fileName ?? new.fileName, @@ -312,7 +313,7 @@ final class NCTransferStore { private func syncUploadRealm() async { let snapshot: [TransferItem] = transferStoreIO.sync { transferItemsCache.filter { item in - if item.completed { + if let completed = item.completed, completed { return item.session == NCNetworking.shared.sessionUpload || item.session == NCNetworking.shared.sessionUploadBackground || item.session == NCNetworking.shared.sessionUploadBackgroundExt From 10f75e7dcb84ae25ca7997f5ffbfd77bcc14510f Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 7 Oct 2025 15:22:09 +0200 Subject: [PATCH 23/74] disabled Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCNetworking+Upload.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index c519ddad8d..5c744d50c6 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -235,6 +235,7 @@ extension NCNetworking { if let task, error == .success { nkLog(debug: "Upload file \(metadata.fileNameView) with taskIdentifier \(task.taskIdentifier)") + /* #if !EXTENSION NCTransferStore.shared.addItem(TransferItem(fileName: metadata.fileName, ocIdTransfer: metadata.ocIdTransfer, @@ -246,6 +247,7 @@ extension NCNetworking { size: metadata.size, taskIdentifier: task.taskIdentifier)) #endif + */ if let metadata = await NCManageDatabase.shared.setMetadataSessionAsync(ocId: metadata.ocId, sessionTaskIdentifier: task.taskIdentifier, @@ -388,9 +390,11 @@ extension NCNetworking { } func uploadCancelFile(metadata: tableMetadata) async { + /* #if !EXTENSION NCTransferStore.shared.removeItem(ocIdTransfer: metadata.ocIdTransfer) #endif + */ self.utilityFileSystem.removeFile(atPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(metadata.ocIdTransfer, userId: metadata.userId, urlBase: metadata.urlBase)) await NCManageDatabase.shared.deleteMetadataAsync(id: metadata.ocIdTransfer) @@ -521,6 +525,7 @@ extension NCNetworking { return #endif + /* #if !EXTENSION if error == .success { NCTransferStore.shared.addItem(TransferItem(completed: true, @@ -538,6 +543,7 @@ extension NCNetworking { taskIdentifier: task.taskIdentifier) } #endif + */ if let metadata = await NCManageDatabase.shared.getMetadataAsync(predicate: NSPredicate(format: "serverUrl == %@ AND fileName == %@ AND sessionTaskIdentifier == %d", serverUrl, fileName, task.taskIdentifier)) { await uploadComplete(withMetadata: metadata, ocId: ocId, etag: etag, date: date, size: size, error: error) @@ -560,12 +566,14 @@ extension NCNetworking { return } + /* #if !EXTENSION NCTransferStore.shared.transferProgress(serverUrl: serverUrl, fileName: fileName, taskIdentifier: task.taskIdentifier, progress: Double(progress)) #endif + */ await NCManageDatabase.shared.setMetadataProgress(fileName: fileName, serverUrl: serverUrl, taskIdentifier: task.taskIdentifier, progress: Double(progress)) From 308e856e57bcaacacc4e533dd8f0c614a9e47e3d Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 7 Oct 2025 16:51:02 +0200 Subject: [PATCH 24/74] fix Signed-off-by: Marino Faggiana --- .../FileProviderData.swift | 4 +- .../Data/NCManageDatabase+LocalFile.swift | 63 ++++++++++++------- .../E2EE/NCNetworkingE2EEUpload.swift | 2 +- iOSClient/Networking/NCDownloadAction.swift | 4 +- .../Networking/NCNetworking+Download.swift | 2 +- .../Networking/NCNetworking+Upload.swift | 2 +- iOSClient/Networking/NCTransferStore.swift | 55 +++++++++++++--- 7 files changed, 93 insertions(+), 39 deletions(-) diff --git a/File Provider Extension/FileProviderData.swift b/File Provider Extension/FileProviderData.swift index 6a04aefac6..1cd638bd62 100644 --- a/File Provider Extension/FileProviderData.swift +++ b/File Provider Extension/FileProviderData.swift @@ -164,7 +164,7 @@ class FileProviderData: NSObject { if error == .success { if let metadata = await self.database.getMetadataFromOcIdAsync(ocId) { - await self.database.addLocalFileAsync(metadata: metadata) + await self.database.addLocalFilesAsync(metadatas: [metadata]) } } @@ -228,7 +228,7 @@ class FileProviderData: NSObject { metadata.status = NCGlobal.shared.metadataStatusNormal await self.database.addMetadataAsync(metadata) - await self.database.addLocalFileAsync(metadata: metadata) + await self.database.addLocalFilesAsync(metadatas: [metadata]) await signalEnumerator(ocId: ocId, type: .update) diff --git a/iOSClient/Data/NCManageDatabase+LocalFile.swift b/iOSClient/Data/NCManageDatabase+LocalFile.swift index 167a9556b4..927dbddfde 100644 --- a/iOSClient/Data/NCManageDatabase+LocalFile.swift +++ b/iOSClient/Data/NCManageDatabase+LocalFile.swift @@ -29,35 +29,50 @@ extension NCManageDatabase { // MARK: - Realm Write + /// Adds or updates multiple local file entries corresponding to the given metadata array. + /// Uses async Realm read + single write transaction. Assumes `tableLocalFile` has a primary key. /// - Parameters: - /// - metadata: The `tableMetadata` containing file details. - /// - offline: Optional flag to mark the file as available offline. - /// - Returns: Nothing. Realm write is performed asynchronously. - func addLocalFileAsync(metadata: tableMetadata, offline: Bool? = nil) async { - // Read (non-blocking): safely detach from Realm thread - let existing: tableLocalFile? = performRealmRead { realm in - realm.objects(tableLocalFile.self) - .filter(NSPredicate(format: "ocId == %@", metadata.ocId)) - .first - .map { tableLocalFile(value: $0) } + /// - metadatas: Array of `tableMetadata` to map into `tableLocalFile`. + /// - offline: Optional override for the `offline` flag applied to all items. + func addLocalFilesAsync(metadatas: [tableMetadata], offline: Bool? = nil) async { + guard !metadatas.isEmpty else { + return + } + + // Extract ocIds for efficient lookup + let ocIds = metadatas.compactMap { $0.ocId } + guard !ocIds.isEmpty else { + return } + // Preload existing entries to avoid creating duplicates + let existingMap: [String: tableLocalFile] = await performRealmReadAsync { realm in + let existing = realm.objects(tableLocalFile.self) + .filter(NSPredicate(format: "ocId IN %@", ocIds)) + return Dictionary(uniqueKeysWithValues: + existing.map { ($0.ocId, tableLocalFile(value: $0)) } // detached copy via value init + ) + } ?? [:] + await performRealmWriteAsync { realm in - let addObject = existing ?? tableLocalFile() - - addObject.account = metadata.account - addObject.etag = metadata.etag - addObject.exifDate = NSDate() - addObject.exifLatitude = "-1" - addObject.exifLongitude = "-1" - addObject.ocId = metadata.ocId - addObject.fileName = metadata.fileName - - if let offline { - addObject.offline = offline - } + for metadata in metadatas { + // Reuse existing object or create a new one + let local = existingMap[metadata.ocId] ?? tableLocalFile() + + local.account = metadata.account + local.etag = metadata.etag + local.exifDate = NSDate() + local.exifLatitude = "-1" + local.exifLongitude = "-1" + local.ocId = metadata.ocId + local.fileName = metadata.fileName + + if let offline { + local.offline = offline + } - realm.add(addObject, update: .all) + realm.add(local, update: .all) + } } } diff --git a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift index 1736db1075..ead111c875 100644 --- a/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift +++ b/iOSClient/Networking/E2EE/NCNetworkingE2EEUpload.swift @@ -188,7 +188,7 @@ class NCNetworkingE2EEUpload: NSObject { metadata.status = NCGlobal.shared.metadataStatusNormal await self.database.addMetadataAsync(metadata) - await self.database.addLocalFileAsync(metadata: metadata) + await self.database.addLocalFilesAsync(metadatas: [metadata]) utility.createImageFileFrom(metadata: metadata) await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in diff --git a/iOSClient/Networking/NCDownloadAction.swift b/iOSClient/Networking/NCDownloadAction.swift index fe64ac0087..627f77685b 100644 --- a/iOSClient/Networking/NCDownloadAction.swift +++ b/iOSClient/Networking/NCDownloadAction.swift @@ -160,7 +160,7 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel if let metadata = await NCManageDatabase.shared.getMetadataLivePhotoAsync(metadata: metadata) { metadatasSynchronizationOffline.append(metadata) } - await NCManageDatabase.shared.addLocalFileAsync(metadata: metadata, offline: true) + await NCManageDatabase.shared.addLocalFilesAsync(metadatas: [metadata], offline: true) for metadata in metadatasSynchronizationOffline { await NCManageDatabase.shared.setMetadataSessionInWaitDownloadAsync(ocId: metadata.ocId, session: NCNetworking.shared.sessionDownloadBackground, @@ -257,7 +257,7 @@ class NCDownloadAction: NSObject, UIDocumentInteractionControllerDelegate, NCSel etag: download.etag) if download.nkError == .success { - await NCManageDatabase.shared.addLocalFileAsync(metadata: metadata) + await NCManageDatabase.shared.addLocalFilesAsync(metadatas: [metadata]) if let vc = await NCViewer().getViewerController(metadata: metadata, delegate: viewController) { viewController.navigationController?.pushViewController(vc, animated: true) } diff --git a/iOSClient/Networking/NCNetworking+Download.swift b/iOSClient/Networking/NCNetworking+Download.swift index eb455be18c..560c5c410d 100644 --- a/iOSClient/Networking/NCNetworking+Download.swift +++ b/iOSClient/Networking/NCNetworking+Download.swift @@ -185,7 +185,7 @@ extension NCNetworking { authenticationTag: result.authenticationTag) } #endif - await NCManageDatabase.shared.addLocalFileAsync(metadata: metadata) + await NCManageDatabase.shared.addLocalFilesAsync(metadatas: [metadata]) if let updatedMetadata = await NCManageDatabase.shared.setMetadataSessionAsync(ocId: metadata.ocId, session: "", diff --git a/iOSClient/Networking/NCNetworking+Upload.swift b/iOSClient/Networking/NCNetworking+Upload.swift index 5c744d50c6..122d1cd54e 100644 --- a/iOSClient/Networking/NCNetworking+Upload.swift +++ b/iOSClient/Networking/NCNetworking+Upload.swift @@ -304,7 +304,7 @@ extension NCNetworking { if selector == self.global.selectorUploadFileNODelete { await self.utilityFileSystem.moveFileAsync(atPath: fileNamePath, toPath: self.utilityFileSystem.getDirectoryProviderStorageOcId(ocId, userId: metadata.userId, urlBase: metadata.urlBase)) - await NCManageDatabase.shared.addLocalFileAsync(metadata: metadata) + await NCManageDatabase.shared.addLocalFilesAsync(metadatas: [metadata]) } else { self.utilityFileSystem.removeFile(atPath: fileNamePath) } diff --git a/iOSClient/Networking/NCTransferStore.swift b/iOSClient/Networking/NCTransferStore.swift index c5eac49933..61295af6cc 100644 --- a/iOSClient/Networking/NCTransferStore.swift +++ b/iOSClient/Networking/NCTransferStore.swift @@ -311,7 +311,7 @@ final class NCTransferStore { /// Performs the actual Realm write using your async APIs. private func syncUploadRealm() async { - let snapshot: [TransferItem] = transferStoreIO.sync { + let snapshotUpload: [TransferItem] = transferStoreIO.sync { transferItemsCache.filter { item in if let completed = item.completed, completed { return item.session == NCNetworking.shared.sessionUpload @@ -322,15 +322,26 @@ final class NCTransferStore { return false } } - let ocIdTransfers = snapshot.compactMap { $0.ocIdTransfer } - let predicate = NSPredicate(format: "ocIdTransfer IN %@", ocIdTransfers) - let metadatas = await NCManageDatabase.shared.getMetadatasAsync(predicate: predicate) + let snapshotDownload: [TransferItem] = transferStoreIO.sync { + transferItemsCache.filter { item in + if let completed = item.completed, completed { + return item.session == NCNetworking.shared.sessionDownload + || item.session == NCNetworking.shared.sessionDownloadBackground + || item.session == NCNetworking.shared.sessionDownloadBackgroundExt + } + return false + } + } + var serversUrl = Set() let utility = NCUtility() + + // Upload + let ocIdTransfers = snapshotUpload.compactMap { $0.ocIdTransfer } + let metadatasUpload = await NCManageDatabase.shared.getMetadatasAsync(predicate: NSPredicate(format: "ocIdTransfer IN %@", ocIdTransfers)) var metadatasUploaded: [tableMetadata] = [] - var serversUrl = Set() - for metadata in metadatas { - guard let transferItem = (transferItemsCache.first { $0.ocIdTransfer == metadata.ocIdTransfer }), + for metadata in metadatasUpload { + guard let transferItem = (snapshotUpload.first { $0.ocIdTransfer == metadata.ocIdTransfer }), let etag = transferItem.etag, let ocId = transferItem.ocId else { continue @@ -358,8 +369,36 @@ final class NCTransferStore { await NCManageDatabase.shared.replaceMetadataAsync(ocIdTransfers: ocIdTransfers, metadatas: metadatasUploaded) + // Download + let ocIds = snapshotDownload.compactMap { $0.ocId } + let metadatasDownload = await NCManageDatabase.shared.getMetadatasAsync(predicate: NSPredicate(format: "ocId IN %@", ocIds)) + var metadatasDownloaded: [tableMetadata] = [] + + if !metadatasDownload.isEmpty { + for metadata in metadatasDownload { + guard let transferItem = (snapshotDownload.first { $0.ocId == metadata.ocId }), + let etag = transferItem.etag else { + continue + } + + metadata.etag = etag + metadata.session = "" + metadata.sessionError = "" + metadata.sessionTaskIdentifier = 0 + metadata.status = NCGlobal.shared.metadataStatusNormal + + metadatasDownloaded.append(metadata) + serversUrl.insert(metadata.serverUrl) + + removeItem(ocId: metadata.ocId) + } + + await NCManageDatabase.shared.addMetadatasAsync(metadatasDownloaded) + await NCManageDatabase.shared.addLocalFilesAsync(metadatas: metadatasDownloaded) + } + // TransferDispatcher Reload Data - if !metadatasUploaded.isEmpty { + if !serversUrl.isEmpty { await NCNetworking.shared.transferDispatcher.notifyAllDelegates { delegate in for serverUrl in serversUrl { delegate.transferReloadData(serverUrl: serverUrl, status: nil) From 367b66395ccd89ace20d2dae9e47ff2a7322b69b Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 7 Oct 2025 17:38:04 +0200 Subject: [PATCH 25/74] improved Signed-off-by: Marino Faggiana --- iOSClient/NCAppStateManager.swift | 4 +- iOSClient/Networking/NCTransferStore.swift | 70 ++++++++++++---------- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/iOSClient/NCAppStateManager.swift b/iOSClient/NCAppStateManager.swift index 2bec52253c..572abceb58 100644 --- a/iOSClient/NCAppStateManager.swift +++ b/iOSClient/NCAppStateManager.swift @@ -42,9 +42,7 @@ final class NCAppStateManager { NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main) { _ in let appDelegate = UIApplication.shared.delegate as? AppDelegate - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - isSuspendingDatabaseOperation = true - } + isSuspendingDatabaseOperation = true isAppInBackground = true // diff --git a/iOSClient/Networking/NCTransferStore.swift b/iOSClient/Networking/NCTransferStore.swift index 61295af6cc..20e323e522 100644 --- a/iOSClient/Networking/NCTransferStore.swift +++ b/iOSClient/Networking/NCTransferStore.swift @@ -29,6 +29,7 @@ final class NCTransferStore { // Shared state private var transferItemsCache: [TransferItem] = [] private let transferStoreIO = DispatchQueue(label: "TransferStore.IO", qos: .utility) + private let debounceQueue = DispatchQueue(label: "TransferStore.Debounce", qos: .utility) private let encoderTransferItem = JSONEncoder() private let decoderTransferItem = JSONDecoder() private(set) var transferStoreURL: URL? @@ -36,7 +37,7 @@ final class NCTransferStore { // Batching controls private var changeCounter: Int = 0 private var lastPersist: TimeInterval = 0 - private let batchThreshold: Int = 20 // <- persist every 20 changes + private let batchThreshold: Int = NCBrandOptions.shared.numMaximumProcess / 2 private let maxLatencySec: TimeInterval = 5 // <- or every 5s at most private var debounceTimer: DispatchSourceTimer? @@ -72,10 +73,11 @@ final class NCTransferStore { } private func setupLifecycleFlush() { - NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { [weak self] _ in - guard let self else { return } - + NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { _ in self.stopDebounceTimer() + } + + NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in self.forceFlush() } @@ -110,8 +112,9 @@ final class NCTransferStore { } changeCounter &+= 1 - maybeCommit() } + + maybeCommit() } /// Updates only the `progress` field of an existing item, then schedules a batched commit. @@ -164,21 +167,27 @@ final class NCTransferStore { } /// Forces an immediate flush to disk (e.g., app background/terminate). - func forceFlush() { + private func forceFlush() { guard let url = self.transferStoreURL else { return } + var bgTask: UIBackgroundTaskIdentifier = .invalid + bgTask = UIApplication.shared.beginBackgroundTask(withName: "NCTransferStore.flush") { + UIApplication.shared.endBackgroundTask(bgTask); bgTask = .invalid + } + defer { + if bgTask != .invalid { + UIApplication.shared.endBackgroundTask(bgTask); bgTask = .invalid + } + } transferStoreIO.sync { do { - checkOrphaned() - let data = try encoderTransferItem.encode(transferItemsCache) try data.write(to: url, options: .atomic) lastPersist = CFAbsoluteTimeGetCurrent() changeCounter = 0 - - nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .info, message: "Force flush to disk", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .info, message: "Force flush to disk") } catch { nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Force flush to disk failed: \(error)") } @@ -208,23 +217,25 @@ final class NCTransferStore { /// Persist if threshold reached or max latency exceeded. private func maybeCommit() { - guard let url = self.transferStoreURL else { - return - } - let now = CFAbsoluteTimeGetCurrent() - let tooManyChanges = changeCounter >= batchThreshold - let tooOld = (now - lastPersist) >= maxLatencySec - guard tooManyChanges || tooOld else { - return - } + transferStoreIO.sync { + guard let url = self.transferStoreURL else { + return + } + let now = CFAbsoluteTimeGetCurrent() + let tooManyChanges = changeCounter >= batchThreshold + let tooOld = (now - lastPersist) >= maxLatencySec + guard tooManyChanges || tooOld else { + return + } - do { - let data = try encoderTransferItem.encode(transferItemsCache) - try data.write(to: url, options: .atomic) - lastPersist = now - changeCounter = 0 - } catch { - nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Flush to disk failed: \(error)") + do { + let data = try encoderTransferItem.encode(transferItemsCache) + try data.write(to: url, options: .atomic) + lastPersist = now + changeCounter = 0 + } catch { + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Flush to disk failed: \(error)") + } } Task { @@ -233,13 +244,10 @@ final class NCTransferStore { } private func startDebounceTimer() { - let t = DispatchSource.makeTimerSource(queue: transferStoreIO) + let t = DispatchSource.makeTimerSource(queue: debounceQueue) t.schedule(deadline: .now() + .seconds(Int(maxLatencySec)), repeating: .seconds(Int(maxLatencySec))) t.setEventHandler { [weak self] in - guard let self else { return } - Task { - self.maybeCommit() - } + self?.maybeCommit() } t.resume() debounceTimer = t From 910fb883a5ece1fbebd274d00a0019011206b4d0 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Tue, 7 Oct 2025 18:35:04 +0200 Subject: [PATCH 26/74] Update NCNetworking+Download.swift --- .../Networking/NCNetworking+Download.swift | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/iOSClient/Networking/NCNetworking+Download.swift b/iOSClient/Networking/NCNetworking+Download.swift index 560c5c410d..d974a8c8cf 100644 --- a/iOSClient/Networking/NCNetworking+Download.swift +++ b/iOSClient/Networking/NCNetworking+Download.swift @@ -140,7 +140,7 @@ extension NCNetworking { return(error) } - // MARK: - + // MARK: - Download NextcloudKitDelegate func downloadComplete(fileName: String, serverUrl: String, @@ -172,6 +172,26 @@ extension NCNetworking { await NextcloudKit.shared.nkCommonInstance.appendServerErrorAccount(metadata.account, errorCode: error.errorCode) if error == .success { + /* + #if !EXTENSION + if error == .success { + NCTransferStore.shared.addItem(TransferItem(completed: true, + date: date, + etag: etag, + fileName: fileName, + ocId: ocId, + serverUrl: serverUrl, + size: size, + taskIdentifier: task.taskIdentifier)) + return + } else { + NCTransferStore.shared.removeItem(serverUrl: serverUrl, + fileName: fileName, + taskIdentifier: task.taskIdentifier) + } + #endif + */ + nkLog(success: "Downloaded file: " + metadata.serverUrlFileName) #if !EXTENSION if let result = await NCManageDatabase.shared.getE2eEncryptionAsync(predicate: NSPredicate(format: "fileNameIdentifier == %@ AND serverUrl == %@", metadata.fileName, metadata.serverUrl)) { @@ -239,8 +259,6 @@ extension NCNetworking { } } - // MARK: - Download NextcloudKitDelegate - func downloadingFinish(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { if let httpResponse = (downloadTask.response as? HTTPURLResponse) { if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300, @@ -275,6 +293,16 @@ extension NCNetworking { guard await progressQuantizer.shouldEmit(serverUrlFileName: serverUrl + "/" + fileName, fraction: Double(progress)) else { return } + + /* + #if !EXTENSION + NCTransferStore.shared.transferProgress(serverUrl: serverUrl, + fileName: fileName, + taskIdentifier: task.taskIdentifier, + progress: Double(progress)) + #endif + */ + await NCManageDatabase.shared.setMetadataProgress(fileName: fileName, serverUrl: serverUrl, taskIdentifier: task.taskIdentifier, progress: Double(progress)) await self.transferDispatcher.notifyAllDelegates { delegate in delegate.transferProgressDidUpdate(progress: progress, From f24649cc55799501d6325492dac79ca31ec60e7e Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Wed, 8 Oct 2025 03:00:11 +0000 Subject: [PATCH 27/74] fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot --- .../be.lproj/Localizable.strings | Bin 89564 -> 89612 bytes .../sr.lproj/Localizable.strings | Bin 92734 -> 92852 bytes .../tr.lproj/Localizable.strings | Bin 92934 -> 92940 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/iOSClient/Supporting Files/be.lproj/Localizable.strings b/iOSClient/Supporting Files/be.lproj/Localizable.strings index 7b1e7e28ab4e9f0a695692dc677c521599be260c..c01fe6ffb526a11890dd0ef2aa959094659c85bd 100644 GIT binary patch delta 156 zcmcb!o3&>TYr_^sHuuQ}zAS1cEDkINEQT!pEaoh>K$atm8%r3A0)sh_Z34u$K_(xic=A?4!oPtIFWOpukW9l`7mLJYB$p z(PnZ$2OB?B5U59iA(J6*GJn13WEnxe>1`g22gF?&QhlCn4@z}q+Jc7eVFNW(yZ0!5~1rP3> zsrIzO-Mw!8=Nqld89e$_G0{}9Q&sIijjNLaqO8IiY!Ne|P*7v%?0_9IjirfX15%JD zil#p2*NI4DS)vn=AVSjZry5_j)fx|6%R7U@aT%~f=MogiDWsFCIOR={S*Izbk{SJ& zBaaDbs&k}TRLNWzQ@O`}lQxncb(R~_m`Rm=XD$m2~lq^#2fh8>^c z{;(Y4gu-cRLKcej=$I6|YtQ1v0E9D_{7G(pYs};D>BN#GK1sZwtul+vYtL6D{YU6vj`7E^dP08VlB?Xg3#!h@j$X#idJ|CbhO{LjEXS>^qo2@F|1}-JHbH zN%0k&dM%f zUwAiNB8f>yT!a2|xhK%a2v%@NUM)XmP5AQ5({{HTmXx+xat5-zx(26?F)x5eo904t zKIOR7x1l7Z5BXbAg^_4^IZI+DRRQW?dGptWp9|u))MQg^+^&J@Ql(2T- D$3cfp diff --git a/iOSClient/Supporting Files/tr.lproj/Localizable.strings b/iOSClient/Supporting Files/tr.lproj/Localizable.strings index eb99d489ee50beaec12d2592717c4ae4e988d6d6..6fc869356043d7d033f6f24b330c2a2218a36aa2 100644 GIT binary patch delta 344 zcmZp>#@cg@b;B+F$v*l@liB|AP5yF2ZL^L6i|yo`SOvz)$sa9*Cnw~3fq49t44DiH z4A~5c3RjPL{95)U=e zn9PJ+pQxdmA5>lk$|^rU-rreV?|mE=E`dc*cu4~f@I*2GujBK2SlwPnwvh4G$DjB-F@IAbs+vjPNkdZ^%E&}ivg&*`!KvYzX>J`~oo%vBNH|wgb z3dkwk6RZ!~uH!DUQqX`SWo=UR)dOWSa%UsFl}4MOG2LK$^bQgvUGZ!KrmCf2hHI-? zCm92YScUyl!51_dBiXPc@`kTqW4?5O*6wN3lN+3;DoD4!U8;UEz9BpTQ7XjK%m|Id zL>P&iL;4<2hzL9G?h+?cHUTz6C;l#-D6u21DX)S~n0KCRlT#+vx)jOSE6S>*(rf*E H<9X`~IJSn! From e55f00f15be8c39dff1e86ce8d0fa2832e15c900 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 10 May 2023 14:22:25 +0530 Subject: [PATCH 28/74] nmc 2023 - privacy policy localization changes added --- .../Supporting Files/en.lproj/Localizable.strings | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 9fb23b1958..56fca54a7a 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -733,3 +733,16 @@ You can stop it at any time, adjust the settings, and enable it again."; "_scanning_files_" = "Scanning files …"; "_moving_items_to_domain_" = "Moving items to correct domain …"; "_finishing_up_" = "Finishing up …"; + +// MARK: Privacy Policy +"_privacy_settings_title_" = "Privacy Settings"; +"_privacy_help_text_after_login_" = "This app uses Cookies and similiar technolgies (tools). By clicking Accept, you accept the processing and also the Transfer of your data to third parties. The data will be used for Analysis, retargeting and to Display personalized Content and Advertising on sites and third-party sites. You can find further informatin, including Information on data processing by third-party Providers, in the settings and in our Privacy Policy.You can reject the use of the Tools or customize them at any time in the Settings."; +"_key_privacy_help_" = "Privacy Policy"; +"_key_reject_help_" = "reject"; +"_key_settings_help_" = "Settings"; +"_accept_button_title_" = "Accept"; +"_privacy_settings_help_text_" = "To optimize your app, we collect anonymous data. For this we use software solutions of different partners. We would like to give you full transparency and decision-making power over the processin and collection of your anonymized usage data. You can also change your settings at any time later in the app settings under data protection. Please note, however, that data collection makes a considerable contribution to the optimization of this app and you prevent this optimization by preventing data transmission."; +"_required_data_collection_" = "Required data collection"; +"_required_data_collection_help_text_" = "The collection of this data is necessary to be able to use essential functions of the app."; +"_analysis_data_acqusition_" = "Analysis-data acqusition for the design"; +"_analysis_data_acqusition_help_text_" = "This data helps us to optimize the app usage for you and to identify system crashes and errors more quickly."; From 65ca207c65ee7d6ae5c8e8d57ac2272d97faccbe Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 10 May 2023 14:25:01 +0530 Subject: [PATCH 29/74] NMC 2023 german translation update (+35 squashed commits) Squashed commits: [353bf009c] NMC 2023 - update missing localised strings for german languages [a754bbf24] NMC 2023 - Localisation change for empty filename alert [3170e8b5b] NMC 2023 - Localisation changes for auto upload description [ab885ab8e] NMC 2023 - NMC 2580 App Updater strings added [bfd0d6653] NMC 2023 - (nmc 2397) Strings update [872a80881] nmc 2023 - re sharing string update [9a3e27542] NMC 2023 - share strings update [b2d3c952f] NMC 2023 "Details" string changed [3450bb2ab] NMC 2023 Two localizable strings added for dashboard [c0f74faf0] nmc 2023 - image video upload localisation related changes [668332a07] nmc 2023 - more tab localisation changes [3489a28ab] nmc 2023 - localisation update for share text field [d7eb28e6b] nmc 2023 - sharing feature localisation strings update [1857be058] nmc 2023 - E2e and onboarding Internet not available strings update [6a6534546] nmc 2023 - scan cluster localisation changes [ed6b28265] NMC 1990 Settings cluster Localization strings added [d951a89ff] nmc 2023 - collabora localization changes added [f9656c196] nmc 2023 - privacy policy localization changes added [39fb24a46] NMC 2023 - update missing localised strings for german languages [3a190b508] NMC 2023 - Localisation change for empty filename alert [1ce0969b6] NMC 2023 - Localisation changes for auto upload description [bc178c063] NMC 2023 - NMC 2580 App Updater strings added [daee67130] NMC 2023 - (nmc 2397) Strings update [0e170564d] nmc 2023 - re sharing string update [fb10cb28b] NMC 2023 - share strings update [2b6a76baf] NMC 2023 "Details" string changed [05e6c22f9] NMC 2023 Two localizable strings added for dashboard [9713606b7] nmc 2023 - image video upload localisation related changes [18332eb52] nmc 2023 - more tab localisation changes [9aeba2a4f] nmc 2023 - localisation update for share text field [92836cf7b] nmc 2023 - sharing feature localisation strings update [0ff7d2b7f] nmc 2023 - E2e and onboarding Internet not available strings update [e2d677b4d] nmc 2023 - scan cluster localisation changes [5d59992d2] NMC 1990 Settings cluster Localization strings added [a3d0a2217] nmc 2023 - collabora localization changes added --- .../en.lproj/Localizable.strings | 186 ++++++++++++++++-- 1 file changed, 170 insertions(+), 16 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 56fca54a7a..08c2945cf6 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -64,6 +64,12 @@ "_audio_" = "Audio"; "_unknown_" = "Unknown"; "_success_" = "Success"; +"_initialization_" = "Initialization"; +"_experimental_" = "Experimental"; +"_select_dir_media_tab_" = "Select as folder \"Media\""; +"_error_creation_file_" = "Oops! Could not create the file"; +"_save_path_" = "Storage path"; +"_save_settings_" = "Save settings"; "_mode_filename_" = "Filename mode"; "_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; "_warning_unsupported_" = "You are using an unsupported version of Nextcloud. For the safety of your data we strongly recommend updating to the latest stable Nextcloud."; @@ -77,11 +83,19 @@ "_the_account_" = "The account"; "_does_not_exist_" = "does not exist"; "_sharing_" = "Sharing"; -"_details_" = "Details"; -"_no_permission_add_file_" = "You don't have permission to add files."; -"_no_permission_delete_file_" = "You don't have permission to delete files."; -"_no_permission_modify_file_" = "You don't have permission to modify files."; -"_no_permission_favorite_file_" = "You don't have permission to add the file to your favorites."; +"_details_" = "Share"; +"_sub_details_" = "Subscription Details"; +"_subscriptions_" = "Subscriptions"; +"_dark_mode_" = "Dark mode"; +"_dark_mode_detect_" = "Detect iOS dark mode"; +"_screen_" = "Screen"; +"_wipe_account_" = "Account is wiped from server"; +"_appconfig_view_title_" = "Account configuration in progress …"; +"_no_permission_add_file_" = "You don't have permission to add files"; +"_no_permission_delete_file_" = "You don't have permission to delete files"; +"_no_permission_modify_file_" = "You don't have permission to modify files"; +"_no_permission_favorite_file_" = "You don't have permission to add the file to your favorites"; +"_request_upload_new_ver_" = "The file has been modified, do you want to upload the new version?"; "_add_folder_info_" = "Add folder info"; "_back_" = "Back"; "_search_" = "Search"; @@ -124,7 +138,6 @@ /* MARK: Files lock */ -"_lock_" = "Lock"; "_unlock_" = "Unlock"; "_lock_file_" = "Lock file"; "_unlock_file_" = "Unlock file"; @@ -147,6 +160,10 @@ "_source_code_" = "Get source code"; "_account_select_" = "Select the account"; "_account_select_to_add_" = "Select the account to add"; +"_host_insert_" = "Insert the host name, for example:"; +"_certificate_not_found_" = "File %@ in documents directory not found."; +"_copy_failed_" = "Copy failed"; +"_certificate_installed_" = "Certificate installed"; "_remove_local_account_" = "Remove local account"; "_want_delete_account_" = "Do you want to remove local account?"; "_prevent_http_redirection_"= "The redirection in HTTP is not permitted."; @@ -219,7 +236,9 @@ "_notifications_" = "Notifications"; "_quota_space_unlimited_" = "Unlimited"; "_quota_space_unknown_" = "Unknown"; -"_quota_using_" = "You are using %@ of %@"; +"_quota_using_" = "%@ "; +"_quota_using_of_" = "of %@"; +"_quota_using_percentage_" = "Memory to %@ occupied"; "_acknowledgements_" = "Acknowledgements"; "_settings_" = "Settings"; "_enter_password_" = "Enter password …"; @@ -228,17 +247,33 @@ "_lock_not_active_" = "Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; "_enable_touch_face_id_" = "Enable Touch/Face ID"; +"_security_" = "Security"; +"_data_protection_" = "Data protection"; +"_privacy_settings_" = "Privacy Settings"; +"_used_opensource_software_" = "OpenSource software used"; +"_service_" = "Service"; +"_imprint_" = "Imprint"; +"_magentacloud_version_" = "MagentaCLOUD Version"; +"_url_" = "URL"; +"_username_" = "Username"; +"_change_credentials_" = "Change your credentials"; "_wifi_only_" = "Only use Wi-Fi connection"; "_settings_autoupload_" = "Auto upload"; "_clear_cache_" = "Clear cache"; "_clear_cache_footer_" = "Clear downloaded and offline files from the app"; -"_exit_" = "Reset application"; "_exit_footer_" = "Remove all accounts and local data from the app."; +"_exit_" = "Logout"; +"_funct_not_enabled_" = "Functionality not enabled"; +"_passcode_activate_" = "Password lock on"; +"_disabling_passcode_" = "Removing password lock"; "_want_exit_" = "Attention! Will be reset to initial state. Continue?"; "_want_delete_cache_" = "Do you want to delete the cache (this also removes the transfers in progress)?"; "_add_account_" = "Add account"; "_want_delete_" = "You will delete the following: "; "_want_leave_share_" = "You will leave the following shares: "; +"_delete_account_" = "Remove account"; +"_delete_active_account_" = "Remove active account"; +//"_want_delete_" = "Do you really want to delete?"; "_no_delete_" = "No, do not delete"; "_yes_delete_" = "Yes, delete"; "_information_" = "Information"; @@ -254,7 +289,16 @@ "_autoupload_photos_" = "Auto upload photos"; "_autoupload_videos_" = "Auto upload videos"; "_autoupload_favorites_" = "Auto upload favorites only"; -"_autoupload_description_" = "New photos/videos will be automatically uploaded to your server."; +"_autoupload_description_" = "New photos/videos will be automatically uploaded to your MagentaCLOUD"; +"_autoupload_description_background_" = "This option requires the use of GPS to trigger the detection of new photos/videos in the camera roll once the location changes significantly"; +"_autoupload_background_title_" = "Limitations"; +"_autoupload_background_msg_" = "Due to iOS restrictions, it is not yet possible to perform background processes, unless GPS services are activated. Once the cell in the cellular network is changed, the system wakes up for a short time and checks for new photos to upload to the cloud."; +"_autoupload_change_location_" = "Change folder"; +"_autoupload_location_now_" = "Folder"; +"_autoupload_location_default_" = "Restore default folder"; +"_autoupload_change_location_footer_" = "Change folder used for \"Automatic upload of camera photos\" (if the option is enabled)"; +"_autoupload_not_select_home_" = "Select a folder"; +"_autoupload_save_album_" = "Copy photo or video into the photo album"; "_autoupload_fullphotos_" = "Upload the whole camera roll"; "_start_autoupload_" = "Turn on auto upload"; "_stop_autoupload_" = "Turn off auto upload"; @@ -289,6 +333,9 @@ "_login_url_error_" = "URL error, please verify your server URL"; "_favorites_" = "Favorites"; "_favorite_short_" = "Favorite"; +"_favorite_" = "Favorite"; +"_unfavorite_" = "Unfavorite"; +"_no_files_uploaded_" = "No files uploaded"; "_tutorial_favorite_view_" = "Files and folders you mark as favorites will show up here"; "_tutorial_offline_view_" = "Files copied here will be available offline.\n\nThey will be synchronized with your cloud."; "_tutorial_groupfolders_view_" = "No Group folders yet"; @@ -348,7 +395,6 @@ "_files_no_files_" = "No files in here"; "_files_no_folders_" = "No folders in here"; "_request_in_progress_" = "Request to server in progress …"; -"_personal_files_only_" = "Personal files only"; "audio" = "AUDIO"; "directory" = "FOLDERS"; @@ -374,6 +420,12 @@ "_media_viewimage_show_" = "Show only images"; "_media_viewvideo_show_" = "Show only video"; "_media_show_all_" = "Show both"; +"_media_by_created_date_" = "Sort by created date"; +"_media_by_upload_date_" = "Sort by upload date"; +"_media_by_modified_date_" = "Sort by modified date"; +"_insert_password_pfd_" = "Secured PDF. Enter password"; +"_password_pdf_error_" = "Wrong password"; +"_error_download_photobrowser_" = "Error: Unable to download photo"; "_good_morning_" = "Good morning"; "_good_day_" = "Good day"; "_good_afternoon_" = "Good afternoon"; @@ -392,20 +444,57 @@ "_user_sharee_footer_" = "Tap to change permissions"; "_enforce_password_protection_" = "Enforce password protection"; "_shared_with_you_by_" = "Shared with you by"; -"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; +//"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; +//"_new_comment_" = "New comment …"; +//"_edit_comment_" = "Edit comment"; +//"_delete_comment_" = "Delete comment"; +//"_share_read_only_" = "View only"; +//"_share_editing_" = "Can edit"; +"_share_reshare_allowed_" = "Resharing is allowed."; +"_share_reshare_not_allowed_" = "Resharing is not allowed."; +"_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; +"_create_link_" = "Create Link"; +"personal_share_by_mail" = "Personal share by mail"; +"_your_shares_" = "Your Shares"; +"_share_linklabel_" = "Link '%@'"; +"_share_link_folder_" = "Link to folder"; +"_share_link_file_" = "Link to file"; +"no_shares_created" = "No shares created yet."; +"_advance_permissions_" = "Advanced permissions"; +"_send_new_email_" = "Send new email"; +"_apply_changes_" = "Apply changes"; +"_send_share_" = "Send share"; +"_PERMISSIONS_" = "PERMISSIONS"; +"share_editing_message" = "There are no editing functions for files unless you share them with MagentaCLOUD users."; +"_file_drop_message_" = "With File drop, only uploading is allowed. Only you can see files and folders that have been uploaded."; +"_custom_link_label" = "Your custom link label"; +"_set_password_" = "Set password"; +"_share_expiration_date_placeholder_"= "Expiration date for this share"; +"_share_download_limit_" = "Download Limit"; +"_share_download_limit_placeholder_" = "Enter download limit"; +"_share_download_limit_alert_empty_" = "Download limit cannot be empty."; +"_share_download_limit_alert_zero_" = "Download limit should be greater than 0."; +"_share_remaining_download_" = "Downloads:"; +"_share_read_only_" = "Read only"; +"_share_editing_" = "Can edit"; +"_share_file_drop_" = "Filedrop only"; +//"_share_file_drop_" = "File request"; +"_share_hide_download_" = "Prevent download"; +"_share_note_recipient_" = "YOUR MESSAGE"; +"_shareLinksearch_placeholder_" = "Contact name or email address"; +"_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; "_new_comment_" = "New comment …"; "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; -"_share_read_only_" = "View only"; -"_share_editing_" = "Can edit"; +"_share_allow_editing_" = "Allow editing"; "_share_allow_upload_" = "Allow upload and editing"; -"_share_file_drop_" = "File request"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; "_share_hide_download_" = "Hide download"; "_share_allowed_downloads_" = "Allowed downloads"; "_share_limit_download_" = "Limit downloads"; "_remaining_" = "%d remaining"; "_share_password_protect_" = "Password protection"; +//"_share_password_protect_" = "Password protect"; "_share_expiration_date_" = "Set expiration date"; "_share_note_recipient_" = "Note to recipient"; "_share_add_sharelink_" = "Add another link"; @@ -423,6 +512,12 @@ "_remote_" = "Remote"; "_remote_group_" = "Remote group"; "_conversation_" = "Conversation"; +//"_share_can_reshare_" = "Allow resharing"; +//"_share_can_create_" = "Allow creating"; +//"_share_can_change_" = "Allow editing"; +//"_share_can_delete_" = "Allow deleting"; +//"_share_unshare_" = "Unshare"; +//"_share_can_download_" = "Allow download"; "_no_transfer_" = "No transfers yet"; "_no_transfer_sub_" = "Uploads and downloads from this device will show up here"; @@ -464,8 +559,34 @@ "_e2e_remove_folder_encrypted_" = "Decrypt"; "_e2e_file_encrypted_" = "File encrypted"; "_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption"; -"_e2e_error_" = "An internal end-to-end encryption error occurred."; -"_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred."; +"_e2e_error_" = "An internal end-to-end encryption error occurred"; +"_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred"; +"_e2e_delete_folder_not_permitted_" = "Deletion of the directory marked as \"encrypted\" is not allowed"; +"_e2e_error_encode_metadata_" = "Serious internal error in encoding metadata"; +"_e2e_error_decode_metadata_" = "Serious internal error in decoding metadata"; +"_e2e_error_create_encrypted_" = "Could not create encrypted file"; +"_e2e_error_update_metadata_" = "Update metadata error"; +"_e2e_error_store_metadata_" = "Could not save metadata"; +"_e2e_error_send_metadata_" = "Could not send metadata"; +"_e2e_error_delete_metadata_" = "Could not delete metadata"; +"_e2e_error_get_metadata_" = "Could not fetch metadata"; +"_e2e_error_not_enabled_" = "Serious internal error. End-to-end encryption not enabled"; +"_e2e_error_record_not_found_" = "Serious internal error. Records not found"; +"_e2e_error_unlock_" = "Could not unlock folder"; +"_e2e_error_lock_" = "Could not lock folder"; +"_e2e_error_delete_mark_folder_" = "Decrypt marked folder"; +"_e2e_error_mark_folder_" = "Encrypt folder"; +"_e2e_error_directory_not_empty_" = "The directory is not empty"; +"_e2e_error_not_move_" = "It is not possible move files to encrypted directory"; +"_e2e_error_not_versionwriteable_" = "The E2EE version of the server is not compatible with this client"; +"_start_e2e_encryption_1_" = "To set up end-to-end encryption, they must first set up the PIN lock to prevent unauthorised people from accessing your key."; +"_start_e2e_encryption_2_" = "After starting the encryption, a randomly generated word sequence (passphrase) of 12 words is displayed. This remains in this app and can be displayed again. Nevertheless, we recommend that you write down the passphrase."; +"_start_e2e_encryption_3_" = "The passphrase is your personal password with which you can access encrypted data in your MagentaCLOUD or enable access to these files on other devices such as your PC."; +"_read_passphrase_description_" = "Here you can display the passphrase again and also copy it. You need the passphrase if you want to decrypt the data on another device with access to MagentaCLOUD, for example your PC or another smartphone or tablet."; +"_remove_passphrase_desc_1_" = "You can remove the passphrase on this device. This will not affect the encrypted content, but this device will no longer be able to decrypt your data."; +"_remove_passphrase_desc_2_" = "You can re-enter the passphrase here at any time to ensure access to your encrypted content from this device."; +"_e2e_error_incorrect_passphrase_" = "Wrong password?"; +"_e2e_error_passphrase_title" = "Error while decrypting."; "_scans_document_" = "Scan document"; "_scanned_images_" = "Scanned images"; "_scan_document_pdf_page_" = "Page"; @@ -746,3 +867,36 @@ You can stop it at any time, adjust the settings, and enable it again."; "_required_data_collection_help_text_" = "The collection of this data is necessary to be able to use essential functions of the app."; "_analysis_data_acqusition_" = "Analysis-data acqusition for the design"; "_analysis_data_acqusition_help_text_" = "This data helps us to optimize the app usage for you and to identify system crashes and errors more quickly."; + +// MARK: Collabora +"_prefix_upload_path_" = "MagentaCLOUD/"; +"_please_enter_file_name_" = "Please enter the file name"; + +// MARK: Scan +"_location_" = "Location"; +"_save_with_text_recognition_" = "SAVE WITH TEXT RECOGNITION (OCR)"; +"_pdf_with_ocr_" = "PDF (OCR)"; +"_text_file_ocr_" = "Textfile (txt)"; +"_save_without_text_recognition_" = "SAVE WITHOUT TEXT RECOGNITION"; +"_pdf_" = "PDF"; +"_jpg_" = "JPG"; +"_png_" = "PNG"; +"_set_password_" = "Set password"; +"_no_password_warn_" = "Please enter a password for the PDF you want to create or disable the function."; +"_saved_info_alert_" = "Saving will take some time, especially if you have selected several pages and file formats."; +"_no_file_type_selection_error_" = "Please select at least one filetype"; +"_no_internet_alert_message_" = "A data connection is not currently allowed."; +"_no_internet_alert_title_" = "Connection error"; +"_auto_upload_help_text_" = "With this option, you upload your photos or videos to the same folder that you selected for \"Automatic upload.\""; + +// MARK: Dashboard +"_shared_" = "Shared"; +"_recieved_" = "Received"; + +// MARK: App Updater +"update_available" = "Update available"; +"update_description" = "MagentaCLOUD version %@ is now available"; +"update" = "Update"; +"not_now" = "Not Now"; + +"_prompt_insert_file_name" = "Please enter filename"; From 401a1bd37da3c5ab2301756645aad2b005c7d626 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Fri, 19 May 2023 18:08:17 +0530 Subject: [PATCH 30/74] nmc 2023 - scan cluster localisation changes --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 08c2945cf6..30b1e54abe 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -874,6 +874,8 @@ You can stop it at any time, adjust the settings, and enable it again."; // MARK: Scan "_location_" = "Location"; +"_prefix_upload_path_" = "MagentaCLOUD/"; + "_save_with_text_recognition_" = "SAVE WITH TEXT RECOGNITION (OCR)"; "_pdf_with_ocr_" = "PDF (OCR)"; "_text_file_ocr_" = "Textfile (txt)"; From de61a4a058e7ebe76c603ad7892ae6029f009f3b Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Tue, 6 Jun 2023 22:57:35 +0530 Subject: [PATCH 31/74] nmc 2023 - sharing feature localisation strings update --- .../xcschemes/File Provider Extension UI.xcscheme | 1 - .../xcschemes/WidgetDashboardIntentHandler.xcscheme | 1 - iOSClient/Supporting Files/en.lproj/Localizable.strings | 5 +++++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme b/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme index fd16f58a63..6e1cc593fa 100644 --- a/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme +++ b/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme @@ -73,7 +73,6 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" - askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme b/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme index 485c4c3f00..d912dd669b 100644 --- a/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme +++ b/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme @@ -73,7 +73,6 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" - askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 30b1e54abe..f0eea9aa41 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -82,6 +82,10 @@ "_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@."; "_the_account_" = "The account"; "_does_not_exist_" = "does not exist"; +"_purchase_" = "Purchase"; +"_account_not_exists_" = "The account %@ of %@ does not exist"; +"_error_parameter_schema_" = "Wrong parameters, impossible to continue"; +"_comments_" = "Comments"; "_sharing_" = "Sharing"; "_details_" = "Share"; "_sub_details_" = "Subscription Details"; @@ -452,6 +456,7 @@ //"_share_editing_" = "Can edit"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; +"_shareLinksearch_placeholder_" = "Contact name or email address"; "_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; "_create_link_" = "Create Link"; "personal_share_by_mail" = "Personal share by mail"; From 57b99325c60e07951569343c880d5b5d54079d73 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 7 Jun 2023 15:20:10 +0530 Subject: [PATCH 32/74] nmc 2023 - localisation update for share text field --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 1 - 1 file changed, 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index f0eea9aa41..46e848d693 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -456,7 +456,6 @@ //"_share_editing_" = "Can edit"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; -"_shareLinksearch_placeholder_" = "Contact name or email address"; "_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; "_create_link_" = "Create Link"; "personal_share_by_mail" = "Personal share by mail"; From 2e50d18167389fe35e6fe8169956c30bb9701c34 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:48:28 +0530 Subject: [PATCH 33/74] NMC 2023 - update missing localised strings for german languages --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 46e848d693..467d044138 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -84,6 +84,9 @@ "_does_not_exist_" = "does not exist"; "_purchase_" = "Purchase"; "_account_not_exists_" = "The account %@ of %@ does not exist"; +"_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@"; +"_the_account_" = "The account"; +"_does_not_exist_" = "does not exist"; "_error_parameter_schema_" = "Wrong parameters, impossible to continue"; "_comments_" = "Comments"; "_sharing_" = "Sharing"; From 2c0db8c6f5f02c0f693f8e355d53d2ce512cb665 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 10 May 2023 14:22:25 +0530 Subject: [PATCH 34/74] nmc 2023 - privacy policy localization changes added --- .../en.lproj/Localizable.strings | 49 ++++--------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 467d044138..a5860a5441 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -68,7 +68,7 @@ "_experimental_" = "Experimental"; "_select_dir_media_tab_" = "Select as folder \"Media\""; "_error_creation_file_" = "Oops! Could not create the file"; -"_save_path_" = "Storage path"; +"_save_path_" = "Save path"; "_save_settings_" = "Save settings"; "_mode_filename_" = "Filename mode"; "_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; @@ -90,7 +90,7 @@ "_error_parameter_schema_" = "Wrong parameters, impossible to continue"; "_comments_" = "Comments"; "_sharing_" = "Sharing"; -"_details_" = "Share"; +"_details_" = "Details"; "_sub_details_" = "Subscription Details"; "_subscriptions_" = "Subscriptions"; "_dark_mode_" = "Dark mode"; @@ -145,6 +145,7 @@ /* MARK: Files lock */ +"_lock_" = "Lock"; "_unlock_" = "Unlock"; "_lock_file_" = "Lock file"; "_unlock_file_" = "Unlock file"; @@ -167,7 +168,7 @@ "_source_code_" = "Get source code"; "_account_select_" = "Select the account"; "_account_select_to_add_" = "Select the account to add"; -"_host_insert_" = "Insert the host name, for example:"; +"_host_insert_" = "Insert the hostname, for example:"; "_certificate_not_found_" = "File %@ in documents directory not found."; "_copy_failed_" = "Copy failed"; "_certificate_installed_" = "Certificate installed"; @@ -243,9 +244,7 @@ "_notifications_" = "Notifications"; "_quota_space_unlimited_" = "Unlimited"; "_quota_space_unknown_" = "Unknown"; -"_quota_using_" = "%@ "; -"_quota_using_of_" = "of %@"; -"_quota_using_percentage_" = "Memory to %@ occupied"; +"_quota_using_" = "You are using %@ of %@"; "_acknowledgements_" = "Acknowledgements"; "_settings_" = "Settings"; "_enter_password_" = "Enter password …"; @@ -254,13 +253,6 @@ "_lock_not_active_" = "Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; "_enable_touch_face_id_" = "Enable Touch/Face ID"; -"_security_" = "Security"; -"_data_protection_" = "Data protection"; -"_privacy_settings_" = "Privacy Settings"; -"_used_opensource_software_" = "OpenSource software used"; -"_service_" = "Service"; -"_imprint_" = "Imprint"; -"_magentacloud_version_" = "MagentaCLOUD Version"; "_url_" = "URL"; "_username_" = "Username"; "_change_credentials_" = "Change your credentials"; @@ -427,6 +419,7 @@ "_media_viewimage_show_" = "Show only images"; "_media_viewvideo_show_" = "Show only video"; "_media_show_all_" = "Show both"; +"_media_view_options_" = "View options"; "_media_by_created_date_" = "Sort by created date"; "_media_by_upload_date_" = "Sort by upload date"; "_media_by_modified_date_" = "Sort by modified date"; @@ -489,12 +482,16 @@ "_share_hide_download_" = "Prevent download"; "_share_note_recipient_" = "YOUR MESSAGE"; "_shareLinksearch_placeholder_" = "Contact name or email address"; +"_shareLinksearch_placeholder_" = "Type a name and press Search"; "_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; "_new_comment_" = "New comment …"; "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; "_share_allow_editing_" = "Allow editing"; +"_share_read_only_" = "View only"; +"_share_editing_" = "Editing"; "_share_allow_upload_" = "Allow upload and editing"; +"_share_file_drop_" = "File drop (upload only)"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; "_share_hide_download_" = "Hide download"; "_share_allowed_downloads_" = "Allowed downloads"; @@ -568,32 +565,6 @@ "_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption"; "_e2e_error_" = "An internal end-to-end encryption error occurred"; "_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred"; -"_e2e_delete_folder_not_permitted_" = "Deletion of the directory marked as \"encrypted\" is not allowed"; -"_e2e_error_encode_metadata_" = "Serious internal error in encoding metadata"; -"_e2e_error_decode_metadata_" = "Serious internal error in decoding metadata"; -"_e2e_error_create_encrypted_" = "Could not create encrypted file"; -"_e2e_error_update_metadata_" = "Update metadata error"; -"_e2e_error_store_metadata_" = "Could not save metadata"; -"_e2e_error_send_metadata_" = "Could not send metadata"; -"_e2e_error_delete_metadata_" = "Could not delete metadata"; -"_e2e_error_get_metadata_" = "Could not fetch metadata"; -"_e2e_error_not_enabled_" = "Serious internal error. End-to-end encryption not enabled"; -"_e2e_error_record_not_found_" = "Serious internal error. Records not found"; -"_e2e_error_unlock_" = "Could not unlock folder"; -"_e2e_error_lock_" = "Could not lock folder"; -"_e2e_error_delete_mark_folder_" = "Decrypt marked folder"; -"_e2e_error_mark_folder_" = "Encrypt folder"; -"_e2e_error_directory_not_empty_" = "The directory is not empty"; -"_e2e_error_not_move_" = "It is not possible move files to encrypted directory"; -"_e2e_error_not_versionwriteable_" = "The E2EE version of the server is not compatible with this client"; -"_start_e2e_encryption_1_" = "To set up end-to-end encryption, they must first set up the PIN lock to prevent unauthorised people from accessing your key."; -"_start_e2e_encryption_2_" = "After starting the encryption, a randomly generated word sequence (passphrase) of 12 words is displayed. This remains in this app and can be displayed again. Nevertheless, we recommend that you write down the passphrase."; -"_start_e2e_encryption_3_" = "The passphrase is your personal password with which you can access encrypted data in your MagentaCLOUD or enable access to these files on other devices such as your PC."; -"_read_passphrase_description_" = "Here you can display the passphrase again and also copy it. You need the passphrase if you want to decrypt the data on another device with access to MagentaCLOUD, for example your PC or another smartphone or tablet."; -"_remove_passphrase_desc_1_" = "You can remove the passphrase on this device. This will not affect the encrypted content, but this device will no longer be able to decrypt your data."; -"_remove_passphrase_desc_2_" = "You can re-enter the passphrase here at any time to ensure access to your encrypted content from this device."; -"_e2e_error_incorrect_passphrase_" = "Wrong password?"; -"_e2e_error_passphrase_title" = "Error while decrypting."; "_scans_document_" = "Scan document"; "_scanned_images_" = "Scanned images"; "_scan_document_pdf_page_" = "Page"; From db9971607ce75de6a8eaac17e2eceebfd77d7ca2 Mon Sep 17 00:00:00 2001 From: Shweta Waikar Date: Thu, 11 May 2023 16:15:49 +0530 Subject: [PATCH 35/74] NMC 1990 Settings cluster Localization strings added --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index a5860a5441..fea7a5db2e 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -253,6 +253,13 @@ "_lock_not_active_" = "Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; "_enable_touch_face_id_" = "Enable Touch/Face ID"; +"_security_" = "Security"; +"_data_protection_" = "Data protection"; +"_privacy_settings_" = "Privacy Settings"; +"_used_opensource_software_" = "OpenSource software used"; +"_service_" = "Service"; +"_imprint_" = "Imprint"; +"_magentacloud_version_" = "MagentaCLOUD Version"; "_url_" = "URL"; "_username_" = "Username"; "_change_credentials_" = "Change your credentials"; From ab03baf6a29e7796e0e4d8123b99cda01d9c3b88 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 24 May 2023 18:13:44 +0530 Subject: [PATCH 36/74] nmc 2023 - E2e and onboarding Internet not available strings update --- .../en.lproj/Localizable.strings | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index fea7a5db2e..ea9297fcf2 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -572,6 +572,30 @@ "_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption"; "_e2e_error_" = "An internal end-to-end encryption error occurred"; "_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred"; +"_e2e_delete_folder_not_permitted_" = "Deletion of the directory marked as \"encrypted\" is not allowed"; +"_e2e_error_encode_metadata_" = "Serious internal error in encoding metadata"; +"_e2e_error_decode_metadata_" = "Serious internal error in decoding metadata"; +"_e2e_error_create_encrypted_" = "Could not create encrypted file"; +"_e2e_error_update_metadata_" = "Update metadata error"; +"_e2e_error_store_metadata_" = "Could not save metadata"; +"_e2e_error_send_metadata_" = "Could not send metadata"; +"_e2e_error_delete_metadata_" = "Could not delete metadata"; +"_e2e_error_get_metadata_" = "Could not fetch metadata"; +"_e2e_error_not_enabled_" = "Serious internal error. End-to-end encryption not enabled"; +"_e2e_error_record_not_found_" = "Serious internal error. Records not found"; +"_e2e_error_unlock_" = "Could not unlock folder"; +"_e2e_error_lock_" = "Could not lock folder"; +"_e2e_error_delete_mark_folder_" = "Decrypt marked folder"; +"_e2e_error_mark_folder_" = "Encrypt folder"; +"_e2e_error_directory_not_empty_" = "The directory is not empty"; +"_e2e_error_not_move_" = "It is not possible move files to encrypted directory"; +"_e2e_error_not_versionwriteable_" = "The E2EE version of the server is not compatible with this client"; +"_start_e2e_encryption_1_" = "To set up end-to-end encryption, they must first set up the PIN lock to prevent unauthorised people from accessing your key."; +"_start_e2e_encryption_2_" = "After starting the encryption, a randomly generated word sequence (passphrase) of 12 words is displayed. This remains in this app and can be displayed again. Nevertheless, we recommend that you write down the passphrase."; +"_start_e2e_encryption_3_" = "The passphrase is your personal password with which you can access encrypted data in your MagentaCLOUD or enable access to these files on other devices such as your PC."; +"_read_passphrase_description_" = "Here you can display the passphrase again and also copy it. You need the passphrase if you want to decrypt the data on another device with access to MagentaCLOUD, for example your PC or another smartphone or tablet."; +"_remove_passphrase_desc_1_" = "You can remove the passphrase on this device. This will not affect the encrypted content, but this device will no longer be able to decrypt your data."; +"_remove_passphrase_desc_2_" = "You can re-enter the passphrase here at any time to ensure access to your encrypted content from this device."; "_scans_document_" = "Scan document"; "_scanned_images_" = "Scanned images"; "_scan_document_pdf_page_" = "Page"; From 63e532e07ec40709214bf2a7718715621c66984a Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Tue, 6 Jun 2023 22:57:35 +0530 Subject: [PATCH 37/74] nmc 2023 - sharing feature localisation strings update --- .../Supporting Files/en.lproj/Localizable.strings | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index ea9297fcf2..6fad33c05a 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -85,8 +85,7 @@ "_purchase_" = "Purchase"; "_account_not_exists_" = "The account %@ of %@ does not exist"; "_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@"; -"_the_account_" = "The account"; -"_does_not_exist_" = "does not exist"; +"_account_not_exists_" = "The account %@ of %@ does not exist"; "_error_parameter_schema_" = "Wrong parameters, impossible to continue"; "_comments_" = "Comments"; "_sharing_" = "Sharing"; @@ -459,6 +458,7 @@ //"_share_editing_" = "Can edit"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; +"_shareLinksearch_placeholder_" = "Contact name or email address"; "_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; "_create_link_" = "Create Link"; "personal_share_by_mail" = "Personal share by mail"; @@ -490,6 +490,7 @@ "_share_note_recipient_" = "YOUR MESSAGE"; "_shareLinksearch_placeholder_" = "Contact name or email address"; "_shareLinksearch_placeholder_" = "Type a name and press Search"; +//"_shareLinksearch_placeholder_" = "Type a name and press Enter"; "_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; "_new_comment_" = "New comment …"; "_edit_comment_" = "Edit comment"; @@ -516,6 +517,11 @@ "_share_can_delete_" = "Delete"; "_share_can_download_" = "Allow download and sync"; "_share_unshare_" = "Delete share"; +//"_share_can_reshare_" = "Allow resharing"; +//"_share_can_create_" = "Allow creating"; +//"_share_can_change_" = "Allow editing"; +//"_share_can_delete_" = "Allow deleting"; +//"_share_unshare_" = "Unshare"; "_share_internal_link_" = "Internal link"; "_share_internal_link_des_" = "Only works for users with access to this file/folder."; "_share_reshare_disabled_" = "You are not allowed to reshare this file/folder."; From 6f2d9f2d1be801e6e92ff60e3e7e7ccbd404b26e Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 7 Jun 2023 15:20:10 +0530 Subject: [PATCH 38/74] nmc 2023 - localisation update for share text field --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 1 - 1 file changed, 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 6fad33c05a..d9af1ffc87 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -458,7 +458,6 @@ //"_share_editing_" = "Can edit"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; -"_shareLinksearch_placeholder_" = "Contact name or email address"; "_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; "_create_link_" = "Create Link"; "personal_share_by_mail" = "Personal share by mail"; From 8d084078d815761a5f2ecfa6accd81eef332188d Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Fri, 9 Jun 2023 15:27:05 +0530 Subject: [PATCH 39/74] nmc 2023 - more tab localisation changes --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index d9af1ffc87..9e3e071502 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -243,7 +243,9 @@ "_notifications_" = "Notifications"; "_quota_space_unlimited_" = "Unlimited"; "_quota_space_unknown_" = "Unknown"; -"_quota_using_" = "You are using %@ of %@"; +"_quota_using_" = "%@ "; +"_quota_using_of_" = "of %@"; +"_quota_using_percentage_" = "Memory to %@ occupied"; "_acknowledgements_" = "Acknowledgements"; "_settings_" = "Settings"; "_enter_password_" = "Enter password …"; From 526ce2ba6481a44d3385e4b3b15aae41350ecf3b Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Mon, 12 Jun 2023 12:49:53 +0530 Subject: [PATCH 40/74] nmc 2023 - image video upload localisation related changes --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 9e3e071502..29dcaf9550 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -68,7 +68,7 @@ "_experimental_" = "Experimental"; "_select_dir_media_tab_" = "Select as folder \"Media\""; "_error_creation_file_" = "Oops! Could not create the file"; -"_save_path_" = "Save path"; +"_save_path_" = "Storage path"; "_save_settings_" = "Save settings"; "_mode_filename_" = "Filename mode"; "_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; From 5d30027ed5795e248d485641c9810e534b216b72 Mon Sep 17 00:00:00 2001 From: Shweta Waikar Date: Fri, 23 Jun 2023 19:08:01 +0530 Subject: [PATCH 41/74] NMC 2023 "Details" string changed --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 29dcaf9550..223e752d5b 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -89,7 +89,7 @@ "_error_parameter_schema_" = "Wrong parameters, impossible to continue"; "_comments_" = "Comments"; "_sharing_" = "Sharing"; -"_details_" = "Details"; +"_details_" = "Share"; "_sub_details_" = "Subscription Details"; "_subscriptions_" = "Subscriptions"; "_dark_mode_" = "Dark mode"; From fd3c6848c117c0c0b15caf510aaf3ba94511a2f6 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Sat, 24 Jun 2023 18:08:38 +0530 Subject: [PATCH 42/74] NMC 2023 - share strings update --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 223e752d5b..2b2bd50e12 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -497,12 +497,10 @@ "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; "_share_allow_editing_" = "Allow editing"; -"_share_read_only_" = "View only"; "_share_editing_" = "Editing"; "_share_allow_upload_" = "Allow upload and editing"; -"_share_file_drop_" = "File drop (upload only)"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; -"_share_hide_download_" = "Hide download"; +//"_share_hide_download_" = "Hide download"; "_share_allowed_downloads_" = "Allowed downloads"; "_share_limit_download_" = "Limit downloads"; "_remaining_" = "%d remaining"; From e8181a29c3b4b54fb7638350450c22b7b74506bb Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Tue, 11 Jul 2023 17:28:52 +0530 Subject: [PATCH 43/74] NMC 2023 - (nmc 2397) Strings update --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 2b2bd50e12..e8c6f4831b 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -601,6 +601,8 @@ "_read_passphrase_description_" = "Here you can display the passphrase again and also copy it. You need the passphrase if you want to decrypt the data on another device with access to MagentaCLOUD, for example your PC or another smartphone or tablet."; "_remove_passphrase_desc_1_" = "You can remove the passphrase on this device. This will not affect the encrypted content, but this device will no longer be able to decrypt your data."; "_remove_passphrase_desc_2_" = "You can re-enter the passphrase here at any time to ensure access to your encrypted content from this device."; +"_e2e_error_incorrect_passphrase_" = "Wrong password?"; +"_e2e_error_passphrase_title" = "Error while decrypting."; "_scans_document_" = "Scan document"; "_scanned_images_" = "Scanned images"; "_scan_document_pdf_page_" = "Page"; From 3c28a271e9a72700b9c70dde7f2f61ea826a35ec Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Tue, 26 Dec 2023 13:57:32 +0530 Subject: [PATCH 44/74] NMC 2023 - Localisation changes for auto upload description --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 1 - 1 file changed, 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index e8c6f4831b..c7b60b92a8 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -497,7 +497,6 @@ "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; "_share_allow_editing_" = "Allow editing"; -"_share_editing_" = "Editing"; "_share_allow_upload_" = "Allow upload and editing"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; //"_share_hide_download_" = "Hide download"; From 3f7a820482a30cf0ab6ed6ef5974b1cc501d7a15 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:48:28 +0530 Subject: [PATCH 45/74] NMC 2023 - update missing localised strings for german languages --- .../Supporting Files/en.lproj/Localizable.strings | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index c7b60b92a8..456b0f40b0 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -85,7 +85,8 @@ "_purchase_" = "Purchase"; "_account_not_exists_" = "The account %@ of %@ does not exist"; "_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@"; -"_account_not_exists_" = "The account %@ of %@ does not exist"; +"_the_account_" = "The account"; +"_does_not_exist_" = "does not exist"; "_error_parameter_schema_" = "Wrong parameters, impossible to continue"; "_comments_" = "Comments"; "_sharing_" = "Sharing"; @@ -144,7 +145,6 @@ /* MARK: Files lock */ -"_lock_" = "Lock"; "_unlock_" = "Unlock"; "_lock_file_" = "Lock file"; "_unlock_file_" = "Unlock file"; @@ -167,7 +167,7 @@ "_source_code_" = "Get source code"; "_account_select_" = "Select the account"; "_account_select_to_add_" = "Select the account to add"; -"_host_insert_" = "Insert the hostname, for example:"; +"_host_insert_" = "Insert the host name, for example:"; "_certificate_not_found_" = "File %@ in documents directory not found."; "_copy_failed_" = "Copy failed"; "_certificate_installed_" = "Certificate installed"; @@ -427,7 +427,6 @@ "_media_viewimage_show_" = "Show only images"; "_media_viewvideo_show_" = "Show only video"; "_media_show_all_" = "Show both"; -"_media_view_options_" = "View options"; "_media_by_created_date_" = "Sort by created date"; "_media_by_upload_date_" = "Sort by upload date"; "_media_by_modified_date_" = "Sort by modified date"; @@ -515,11 +514,6 @@ "_share_can_delete_" = "Delete"; "_share_can_download_" = "Allow download and sync"; "_share_unshare_" = "Delete share"; -//"_share_can_reshare_" = "Allow resharing"; -//"_share_can_create_" = "Allow creating"; -//"_share_can_change_" = "Allow editing"; -//"_share_can_delete_" = "Allow deleting"; -//"_share_unshare_" = "Unshare"; "_share_internal_link_" = "Internal link"; "_share_internal_link_des_" = "Only works for users with access to this file/folder."; "_share_reshare_disabled_" = "You are not allowed to reshare this file/folder."; From 297e5365b21233b5cb3169b883960a846b4bc349 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Mon, 26 Aug 2024 16:43:24 +0530 Subject: [PATCH 46/74] NMC 2023 german translation update --- .../xcshareddata/xcschemes/File Provider Extension UI.xcscheme | 1 + .../xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme | 1 + 2 files changed, 2 insertions(+) diff --git a/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme b/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme index 6e1cc593fa..fd16f58a63 100644 --- a/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme +++ b/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme @@ -73,6 +73,7 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" + askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme b/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme index d912dd669b..485c4c3f00 100644 --- a/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme +++ b/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme @@ -73,6 +73,7 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" + askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> From ab67ed8955e846b096eee2913e86581403807153 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 7 Apr 2025 15:08:51 +0530 Subject: [PATCH 47/74] NMC 2023 - update missing localised strings for english and german languages --- .../Supporting Files/en.lproj/Localizable.strings | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 456b0f40b0..5605dd0c43 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -280,7 +280,8 @@ "_want_leave_share_" = "You will leave the following shares: "; "_delete_account_" = "Remove account"; "_delete_active_account_" = "Remove active account"; -//"_want_delete_" = "Do you really want to delete?"; +"_want_delete_" = "Do you really want to delete?"; +"_want_leave_share_" = "You will leave the following shares: "; "_no_delete_" = "No, do not delete"; "_yes_delete_" = "Yes, delete"; "_information_" = "Information"; @@ -315,6 +316,8 @@ "_autoupload_subfolder_granularity_" = "Subfolder Granularity"; "_filenamemask_" = "Change filename mask"; "_filenamemask_footer_" = "By default, when a file is uploaded, it automatically gets the following format: yy-mm-dd hh-mm-ss plus a 4-digit counter. If you do not like this format, you can change it here except for the final 4-digit counter, which cannot be omitted."; +"_autoupload_filenamemask_" = "Change filename mask"; +"_autoupload_filenamemask_footer_" = "Change the automatic filename mask"; "_autoupload_current_folder_" = "Currently selected folder"; "_show_hidden_files_" = "Show hidden files"; "_format_compatibility_" = "Most Compatible"; @@ -331,6 +334,7 @@ "_diagnostics_footer_" = "Changing log level requires a restart of the app to take effect"; "_view_log_" = "View log file"; "_clear_log_" = "Clear log file"; +"_level_log_" = "Set Log level (disabled, standard, maximum)"; "_set_log_level_" = "Set Log level"; "_log_file_clear_alert_" = "Log file cleared \n successfully!"; "_connect_server_anyway_" = "Do you want to connect to the server anyway?"; @@ -358,6 +362,7 @@ "_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background"; "_new_photos_starting_" = "Only photos or videos starting %@ will be uploaded."; "_tutorial_photo_view_" = "No photos or videos uploaded yet"; +"_create_full_upload_" = "Creating archive … May take a long time. During this process, keep the application active during the transfer as well."; "_error_createsubfolders_upload_" = "Error creating subfolders"; "_remove_photo_CameraRoll_" = "Remove from camera roll"; "_remove_photo_CameraRoll_desc_" = "\"Remove from camera roll\" after uploads, a confirmation message will be displayed to delete the uploaded photos or videos from the camera roll. The deleted photos or videos will still be available in the iOS Photos Trash for 30 days."; @@ -402,6 +407,7 @@ "_files_no_files_" = "No files in here"; "_files_no_folders_" = "No folders in here"; "_request_in_progress_" = "Request to server in progress …"; +"_personal_files_only_" = "Personal files only"; "audio" = "AUDIO"; "directory" = "FOLDERS"; @@ -613,6 +619,9 @@ "_empty_trash_" = "Empty trash"; "_trash_no_trash_" = "No files deleted"; "_trash_no_trash_description_" = "You can restore deleted files from here"; +"_trash_restore_selected_" = "Restore selected files"; +"_trash_delete_selected_" = "Delete selected files"; +"_recover_" = "Recover"; "_confirm_delete_selected_" = "Are you sure you want to delete the selected items?"; "_manage_file_offline_" = "Manage offline files"; "_set_available_offline_" = "Set as available offline"; @@ -688,6 +697,8 @@ "_show_more_results_" = "Show more results"; "_waiting_for_" = "Waiting for:"; "_reachable_wifi_" = "network reachable via Wi-Fi or cable"; +"_ITMS-90076_" = "Due to a change in the Nextcloud application identifier, the settings and password for accessing your cloud are reset, so please re-enter your account data and check your Settings. We are sorry about that."; +"_password_not_present_" = "Please re-insert your credentials."; "_copy_passphrase_" = "Copy passphrase"; "_ok_copy_passphrase_" = "OK and copy passphrase"; "_select_color_" = "Select the color"; From 0d9c8d34ad7975db0f1dccc733cad18853e36350 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Wed, 16 Apr 2025 17:39:10 +0530 Subject: [PATCH 48/74] NMC 2023 - Updated missing localised strings for English and DE language --- .../en.lproj/Localizable.strings | 477 +++++++++++++++++- 1 file changed, 458 insertions(+), 19 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 5605dd0c43..23d69dad8a 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -20,49 +20,80 @@ // along with this program. If not, see . // +"_itunes_" = "iTunes"; "_cancel_" = "Cancel"; "_edit_" = "Edit"; "_tap_to_cancel_" = "Tap to cancel"; "_cancel_request_" = "Do you want to cancel?"; "_upload_file_" = "Upload file"; "_download_file_" = "Download file"; +"_loading_" = "Loading"; +"_loading_with_points_" = "Loading …"; +"_loading_num_" = "Loading file %i"; +"_loading_autoupload_" = "Auto uploading"; +"_uploading_" = "Uploading"; +"_synchronization_" = "Synchronization"; "_delete_" = "Delete"; +"_delete_file_n_" = "Delete file %i of %i"; "_rename_" = "Rename"; "_rename_file_" = "Rename file"; "_rename_folder_" = "Rename folder"; "_move_" = "Move"; +"_move_file_n_" = "Move file %i of %i"; +"_creating_sharing_" = "Creating share"; +"_updating_sharing_" = "Updating share"; +"_removing_sharing_" = "Removing share"; "_add_" = "Add"; +"_login_" = "Log in"; "_save_" = "Save"; "_warning_" = "Warning"; "_error_" = "Error"; "_no_" = "No"; "_yes_" = "Yes"; "_select_" = "Select"; +"_deselect_" = "Deselect"; "_select_all_" = "Select all"; "_upload_" = "Upload"; "_home_" = "Files"; "_files_" = "Files"; +"_home_dir_" = "Home"; +"_file_to_upload_" = "File to upload"; +"_destination_" = "Destination"; "_ok_" = "OK"; +"_beta_version_" = "Beta version"; +"_function_in_testing_" = "Function in testing, please send information about any problems you run into."; "_done_" = "Done"; "_clear_" = "Clear"; +"_passcode_too_short_" = "Passcode too short, at least 4 characters required"; "_selected_" = "Selected"; +"_scan_fingerprint_" = "Scan fingerprint to authenticate"; "_no_active_account_" = "No account found"; "_info_" = "Info"; "_warning_" = "Warning"; +"_email_" = "Email"; +"_save_exit_" = "Do you want to exit without saving?"; "_video_" = "Video"; "_overwrite_" = "Overwrite"; +"_transfers_in_queue_" = "Transfers in progress, please wait …"; +"_too_errors_upload_" = "Too many errors, please verify the problem"; "_create_" = "Create"; "_create_folder_" = "Create folder"; "_create_folder_e2ee_" = "Create encrypted folder"; +"_create_folder_on_" = "Create folder on"; "_close_" = "Close"; +"_postpone_" = "Postpone"; "_remove_" = "Remove"; "_file_not_found_" = "File not found"; "_continue_" = "Continue"; "_continue_editing_" = "Continue editing"; +"_continue_request_" = "Do you want to continue?"; "_auto_upload_folder_" = "Auto upload"; +"_gallery_" = "Gallery"; "_photo_" = "Photo"; "_audio_" = "Audio"; "_unknown_" = "Unknown"; +"_additional_view_options_" = "Additional view options"; +"_next_" = "Next"; "_success_" = "Success"; "_initialization_" = "Initialization"; "_experimental_" = "Experimental"; @@ -74,10 +105,12 @@ "_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; "_warning_unsupported_" = "You are using an unsupported version of Nextcloud. For the safety of your data we strongly recommend updating to the latest stable Nextcloud."; "_restore_" = "Restore"; +"_camera_roll_" = "Camera roll"; "_tap_here_to_change_" = "Tap here to change"; "_no_albums_" = "No albums"; "_denied_album_" = "This app does not have access to \"Photos\". You can enable access in Privacy Settings."; -"_denied_camera_" = "This app does not have access to the \"Camera\". You can enable access in Privacy Settings."; +"_denied_camera_" = "This app does not have access to \"Camera\". You can enable access in Privacy Settings."; +"_start_" = "Start"; "_force_start_" = "Force the start"; "_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@."; "_the_account_" = "The account"; @@ -107,6 +140,8 @@ "_back_" = "Back"; "_search_" = "Search"; "_of_" = "of"; +"_internal_modify_" = "Edit with internal editor"; +"_database_corrupt_" = "Oops something went wrong, please enter your credentials but don't worry, your files have remained secure"; "_livephoto_save_" = "Save Live Photo to Photo Album"; "_livephoto_save_error_" = "Error during save of Live Photo."; "_livephoto_no_" = "Disable Live Photo"; @@ -122,9 +157,14 @@ "_copy_" = "Copy"; "_now_" = "Now"; "_wait_" = "Please wait …"; +"_attention_" = "Attention"; "_recent_" = "Recent"; "_view_in_folder_" = "View in folder"; "_leave_share_" = "Leave this share"; +"_premium_" = "Premium"; +"_professional_" = "Professional"; +"_current_" = "Current"; +"_buy_" = "Buy"; "_disabled_" = "Disabled"; "_compact_" = "Compact"; "_normal_" = "Normal"; @@ -153,16 +193,35 @@ "_locked_by_" = "Locked by %@"; "_file_locked_no_override_" = "This file is locked. It cannot be overridden."; "_lock_no_permissions_selected_" = "Not allowed for some selected files or folders."; +/* Remove a file from a list, don't delete it entirely */ "_remove_file_" = "Remove file"; + +/* Delete file and put it into the trash */ "_delete_file_" = "Delete file"; "_delete_folder_" = "Delete folder"; +"_delete_photo_" = "Delete photo"; +"_delete_video_" = "Delete video"; +"_automatic_Download_Image_" = "Use images in full resolution"; +"_automatic_Download_Image_footer_" = "When viewing images always download, if not available locally, the images in full resolution"; "_size_" = "Size"; +"_file_size_" = "Exported file size"; +"_dimension_" = "Dimension"; +"_duration_" = "Duration"; +"_model_" = "Model"; "_set_user_status_" = "Set user status"; "_open_settings_" = "Open settings"; +"_rename_ext_title_" = "Change file type?"; +"_rename_ext_message_" = "This file may behave differently if you change it from .%@ to %@"; +"_use_" = "Use"; +"_keep_" = "Keep"; +"_account_request_" = "Request account"; "_settings_account_request_" = "Request account at startup"; +"_print_" = "Print"; "_alias_" = "Alias"; "_alias_placeholder_" = "Write the alias"; "_alias_footer_" = "Give your account names a descriptive name such as Home, Office, School …"; +"_chunk_size_mb_" = "Chunk size in MB"; +"_chunk_footer_title_" = "Chunked file upload (0 is disabled)\nImportant: the chunked upload works only when the app is \"active\"."; "_privacy_legal_" = "Privacy and Legal Policy"; "_source_code_" = "Get source code"; "_account_select_" = "Select the account"; @@ -174,6 +233,8 @@ "_remove_local_account_" = "Remove local account"; "_want_delete_account_" = "Do you want to remove local account?"; "_prevent_http_redirection_"= "The redirection in HTTP is not permitted."; +"_pdf_vertical_" = "PDF vertical display"; +"_pdf_horizontal_" = "PDF horizontal display"; "_single_file_conflict_title_" = "File conflict"; "_multi_file_conflict_title_" = "%@ File conflicts"; "_replace_action_title_" = "Replace"; @@ -191,6 +252,16 @@ "_change_lock_passcode_" = "Change passcode"; "_lock_cannot_disable_mdm_" = "Disabling the passcode lock is not permitted by your configuration profile."; +/* Background of the file listing view */ +"_use_as_background_" = "Use it as a background"; + +/* Background of the file listing view */ +"_background_" = "Background"; + +"_dark_mode_" = "Dark mode"; +"_default_color_" = "Use the default color"; +"_as_default_color_" = "Use as default color"; + // MARK: User Status /* User status */ @@ -235,12 +306,20 @@ "_hours_" = "Hours"; "_minutes_" = "Minutes"; - -"_network_not_available_" = "Network unavailable."; +"_network_available_" = "Network available"; +"_network_not_available_" = "Network unavailable"; +"_file_too_big_" = "File too large to be encrypted/decrypted"; +"_file_too_big_max_100_" = "File too large (max 100 kb.)"; +"_...loading..._" = "Loading …"; +"_download_plist_" = " "; +"_no_reuploadfile_" = "Could not find nor resend file. Delete the upload and reload the file to upload it."; "_file_already_exists_" = "Unable to complete the operation, a file with the same name exists."; +"_read_file_error_" = "Could not read the file"; +"_write_file_error_" = "Could not write the file"; "_files_lock_error_" = "There was an error changing the lock of this file."; "_more_" = "More"; "_notifications_" = "Notifications"; +"_logout_" = "Log out"; "_quota_space_unlimited_" = "Unlimited"; "_quota_space_unknown_" = "Unknown"; "_quota_using_" = "%@ "; @@ -248,11 +327,13 @@ "_quota_using_percentage_" = "Memory to %@ occupied"; "_acknowledgements_" = "Acknowledgements"; "_settings_" = "Settings"; +"_passcode_" = "Password"; "_enter_password_" = "Enter password …"; "_lock_" = "Lock"; "_lock_active_" = "Lock: On"; "_lock_not_active_" = "Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; +"_lock_protection_no_screen_footer_" = "Use \"Do not ask at startup\" for the encryption option."; "_enable_touch_face_id_" = "Enable Touch/Face ID"; "_security_" = "Security"; "_data_protection_" = "Data protection"; @@ -266,15 +347,30 @@ "_change_credentials_" = "Change your credentials"; "_wifi_only_" = "Only use Wi-Fi connection"; "_settings_autoupload_" = "Auto upload"; +"_app_version_" = "Application version"; +"_app_in_use_" = "Application in use"; +"_contact_by_email_" = "Contact us by email"; "_clear_cache_" = "Clear cache"; "_clear_cache_footer_" = "Clear downloaded and offline files from the app"; "_exit_footer_" = "Remove all accounts and local data from the app."; +"_exit_footer_" = "Remove all accounts and local data from the app."; "_exit_" = "Logout"; "_funct_not_enabled_" = "Functionality not enabled"; "_passcode_activate_" = "Password lock on"; "_disabling_passcode_" = "Removing password lock"; "_want_exit_" = "Attention! Will be reset to initial state. Continue?"; -"_want_delete_cache_" = "Do you want to delete the cache (this also removes the transfers in progress)?"; +"_proceed_" = "Proceed"; +"_delete_cache_" = "Delete cache"; +"_want_delete_cache_" = "Do you want to delete the cache (this also removes the transfers in progress)?";"_want_delete_thumbnails_" = "Do you want to delete all thumbnails too?"; +"_mail_deleted_" = "Email deleted"; +"_mail_saved_" = "Email saved"; +"_mail_sent_" = "Email sent"; +"_mail_failure_" = "Could not send email: %@"; +"_information_req_" = "Information request"; +"_write_in_english_" = "Kindly write to us in English"; +"_credentials_" = "Credentials"; +"_manage_account_" = "Manage account"; +"_change_password_" = "Change password"; "_add_account_" = "Add account"; "_want_delete_" = "You will delete the following: "; "_want_leave_share_" = "You will leave the following shares: "; @@ -284,16 +380,51 @@ "_want_leave_share_" = "You will leave the following shares: "; "_no_delete_" = "No, do not delete"; "_yes_delete_" = "Yes, delete"; +"_remove_cache_" = "Deleting cache, please wait …"; +"_optimizations_" = "Optimizations"; +"_synchronizations_" = "Synchronized folders"; +"_version_server_" = "Server version"; +"_help_" = "Help"; +"_change_simply_passcode_" = "Change password type"; +"_quota_" = "Quota"; +"_available_" = "Available"; +"_not_available_" = "Not available"; +"_accounts_" = "Accounts"; "_information_" = "Information"; +"_personal_information_" = "Personal info"; +"_user_full_name_" = "Full name"; +"_user_address_" = "Address"; +"_user_phone_" = "Phone number"; +"_user_email_" = "Email"; +"_user_web_" = "Website"; +"_user_twitter_" = "Twitter"; +"_user_job_" = "Job"; +"_user_businesssize_" = "Business size"; +"_user_businesstype_" = "Business type"; +"_user_city_" = "City"; +"_user_country_" = "Country"; +"_user_company_" = "Company"; +"_user_role_" = "Role"; +"_user_zip_" = "Zip"; +"_user_owner_" = "Owner"; +"_user_employee_" = "Employee"; +"_user_contractor_" = "Contractor"; +"_user_editprofile_" = "Edit profile"; "_select_offline_warning_" = "Making multiple files and folders available offline may take a while and use a lot of memory while doing so."; "_advanced_" = "Advanced"; "_permissions_" = "Permissions"; "_custom_permissions_" = "Custom permissions"; "_disable_files_app_" = "Disable Files App integration"; "_disable_files_app_footer_" = "Do not permit the access of files via the iOS Files application."; +"_trial_" = "Trial"; +"_trial_expired_day_" = "Days remaining"; "_time_remaining_" = "%@ remaining"; +"_disableLocalCacheAfterUpload_footer_" = "After uploading the file, do not keep it in the local cache"; +"_disableLocalCacheAfterUpload_" = "Disable local cache"; "_autoupload_" = "Auto upload photos/videos"; "_autoupload_select_folder_" = "Select the \"Auto upload\" folder"; +"_autoupload_error_select_folder_" = "Select a valid folder for the \"Auto upload\""; +"_autoupload_background_" = "Auto upload in the background"; "_autoupload_photos_" = "Auto upload photos"; "_autoupload_videos_" = "Auto upload videos"; "_autoupload_favorites_" = "Auto upload favorites only"; @@ -319,16 +450,25 @@ "_autoupload_filenamemask_" = "Change filename mask"; "_autoupload_filenamemask_footer_" = "Change the automatic filename mask"; "_autoupload_current_folder_" = "Currently selected folder"; +"_help_tutorial_" = "Tutorial"; +"_help_intro_" = "Introduction to Nextcloud"; +"_help_activity_verbose_" = "Detailed Activity feed"; +"_help_activity_mail_" = "Send activity via email"; +"_help_activity_clear_" = "Clear activity"; "_show_hidden_files_" = "Show hidden files"; "_format_compatibility_" = "Most Compatible"; "_format_compatibility_footer_" = "\"Most compatible\" will save photos as JPEG, if possible."; +"_terms_" = "Terms of Service"; "_privacy_" = "Privacy"; +"_privacy_policy_" = "Privacy Policy"; "_privacy_footer_" = "This app uses a service for the analysis of a crash. Your personal information is not sent with the report. If you want disable it, please change the setting \"Disable crash reporter\" to ON."; "_crashservice_title_" = "Disable crash reporter"; "_crashservice_alert_" = "This option requires a restart of the app to take effect."; "_upload_mov_livephoto_" = "Live Photo"; "_upload_mov_livephoto_footer_" = "\"Live Photo\" will save the selected photo in \"Live Photo\" format, if possible."; +"_view_capabilities_" = "View the capabilities"; "_capabilities_" = "Capabilities"; +"_no_capabilities_found_" = "Capabilities not found"; "_capabilities_footer_" = "Display the packages used by the app if they are installed and available on the server."; "_diagnostics_" = "Diagnostics"; "_diagnostics_footer_" = "Changing log level requires a restart of the app to take effect"; @@ -340,8 +480,16 @@ "_connect_server_anyway_" = "Do you want to connect to the server anyway?"; "_server_is_trusted_" = "Do you consider this server trusted?"; "_connection_error_" = "Connection error"; +"_serverstatus_error_" = "Connection to server failure, verify your server address or network status"; +"_add_your_nextcloud_" = "Add your account"; "_login_url_" = "Server address https:// …"; +"_login_bottom_label_" = "Don't have a server yet?\nChoose one of the providers."; +"_error_multidomain_" = "Address not allowed, only the following domains are valid:"; +"_account_already_exists_" = "The account %@ already exists"; +"_traditional_login_" = "Revert to old login method"; +"_web_login_" = "Revert to web login method"; "_login_url_error_" = "URL error, please verify your server URL"; +"_webflow_not_available_" = "Web login not available, use the old login method"; "_favorites_" = "Favorites"; "_favorite_short_" = "Favorite"; "_favorite_" = "Favorite"; @@ -350,20 +498,31 @@ "_tutorial_favorite_view_" = "Files and folders you mark as favorites will show up here"; "_tutorial_offline_view_" = "Files copied here will be available offline.\n\nThey will be synchronized with your cloud."; "_tutorial_groupfolders_view_" = "No Group folders yet"; +"_tutorial_local_view_" = "You'll find the unpacked files from your cloud.\n\nConnect to iTunes to share these files."; "_more_" = "More"; "_favorite_no_files_" = "No favorites yet"; +"_pull_down_" = "Pull down to refresh"; +"_no_photo_load_" = "No photo or video"; +"_tutorial_autoupload_view_" = "You can enable auto uploads from \"Settings\""; "_no_date_information_" = "No date information"; "_no_camera_information_" = "No camera information"; "_no_lens_information_" = "No lens information"; "_today_" = "Today"; "_yesterday_" = "Yesterday"; +"_time_" = "Time: %@\n\n%@"; +"_location_not_enabled_" = "Location Services not enabled"; +"_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Location Services\""; "_access_photo_not_enabled_" = "Access to Photos is not enabled"; -"_access_photo_not_enabled_msg_" = "Please go to Settings → Apps → Nextcloud → Photos and enable Photo Library Access"; +"_access_photo_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\""; +//"_access_photo_not_enabled_msg_" = "Please go to Settings → Apps → Nextcloud → Photos and enable Photo Library Access"; "_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background"; +"_access_photo_location_not_enabled_" = "Access to Photos and Location not enabled"; +"_access_photo_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\" and \"Location Services\""; "_new_photos_starting_" = "Only photos or videos starting %@ will be uploaded."; "_tutorial_photo_view_" = "No photos or videos uploaded yet"; "_create_full_upload_" = "Creating archive … May take a long time. During this process, keep the application active during the transfer as well."; "_error_createsubfolders_upload_" = "Error creating subfolders"; +"_activate_autoupload_" = "Enable auto upload"; "_remove_photo_CameraRoll_" = "Remove from camera roll"; "_remove_photo_CameraRoll_desc_" = "\"Remove from camera roll\" after uploads, a confirmation message will be displayed to delete the uploaded photos or videos from the camera roll. The deleted photos or videos will still be available in the iOS Photos Trash for 30 days."; "_never_" = "never"; @@ -374,25 +533,88 @@ "_hours_ago_" = "%d hours ago"; "_a_day_ago_" = "a day ago"; "_days_ago_" = "%d days ago"; +"_over_30_days_" = "over 30 days"; +"_connection_internet_offline_" = "The internet connection appears to be offline or Wi-Fi is required"; +"_insert_password_" = "Enter password"; +"_update_in_progress_" = "Version upgrade, please wait …"; "_forbidden_characters_" = "Forbidden characters: %@"; -"_cannot_send_mail_error_" = "No account is set up, or wrong email address entered."; +"_cannot_send_mail_error_" = "No account set up, or wrong email address entered."; "_open_url_error_" = "Cannot open the URL for this action."; +"_photo_camera_" = "Photos"; "_media_" = "Media"; +"_unzip_in_progress_" = "Extraction in progress on local storage …"; +"_file_unpacked_" = "File unpacked on local storage"; +"_file_saved_local_" = "File saved on local storage."; +"_file_not_present_" = "Error: File not present, please reload."; "_order_by_" = "Sort by"; "_name_" = "Name"; "_date_" = "Date"; "_size_" = "Size"; +"_order_by_name_a_z_" = "Sort by name (from A to Z)"; +"_sorted_by_name_a_z_" = "Sorted by name (from A to Z)"; +"_order_by_name_z_a_" = "Sort by name (from Z to A)"; +"_sorted_by_name_z_a_" = "Sorted by name (from Z to A)"; +"_order_by_date_more_recent_" = "Sort by newest"; +"_sorted_by_date_more_recent_" = "Sorted by newest"; +"_order_by_date_less_recent_" = "Sort by oldest"; +"_sorted_by_date_less_recent_" = "Sorted by oldest"; +"_order_by_size_smallest_" = "Sort by smallest"; +"_sorted_by_size_smallest_" = "Sorted by smallest"; +"_order_by_size_largest_" = "Sort by largest"; +"_sorted_by_size_largest_" = "Sorted by largest"; "_delete_selected_files_" = "Delete files"; +"_move_selected_files_" = "Move files"; +"_move_or_copy_selected_files_" = "Move or copy files"; +"_download_selected_files_" = "Download files"; +"_download_selected_files_folders_" = "Download files and folders"; +"_error_operation_canc_" = "Error: Operation canceled."; +"_only_lock_passcode_" = "Available only with Lock password activated. Activate it in the \"Settings\"."; +"_go_to_app_settings_" = "Go to app settings"; +"_passcode_protection_" = "Password protection"; "_remove_favorites_" = "Remove from favorites"; +"_remove_offline_" = "Remove from offline"; "_add_favorites_" = "Add to favorites"; +"_add_offline_" = "Add to offline"; +"_remove_passcode_" = "Remove password protection"; +"_protect_passcode_" = "Protect with password"; "_share_" = "Share"; +"_reload_" = "Reload"; +"_open_in_" = "Open in …"; +"_open_" = "Open …"; "_remove_local_file_" = "Remove locally"; +"_add_local_" = "Add to local storage"; +"_comm_erro_pull_down_" = "Attention: Communication error with the server. Pull down to refresh."; +"_file_not_downloaded_" = "file not downloaded"; +"_file_not_uploaded_" = "file not uploaded"; "_folders_" = "folders"; "_folder_" = "folder"; "_files_" = "files"; "_file_" = "file"; +"_folder_blocked_" = "Folder blocked"; +"_downloading_progress_" = "Initiating download of files …"; "_no_file_pull_down_" = "Upload a file or pull down to refresh"; "_no_file_no_permission_to_create_" = "You don't have permission to create or upload files in this folder."; +"_browse_images_" = "Browse images"; +"_synchronized_folder_" = "Keep the folder synchronized"; +"_remove_synchronized_folder_" = "Turn off the synchronization"; +"_synchronized_confirm_" = "After enabling the synchronization, all files in the folder will be synchronized with the server, continue?"; +"_offline_folder_confirm_" = "After enabling the offline folder, all files in it will be synchronized with the server, continue?"; +"_file_not_found_reload_" = "File not found, pull down to refresh"; +"_title_section_download_" = "DOWNLOAD"; +"_download_" = "Download"; +"_title_section_upload_" = "UPLOAD"; +"_group_alphabetic_yes_" = "✓ Group alphabetically"; +"_group_alphabetic_no_" = "Group alphabetically"; +"_group_typefile_yes_" = "✓ Group by file type"; +"_group_typefile_no_" = "Group by file type"; +"_group_date_yes_" = "✓ Group by date"; +"_group_date_no_" = "Group by date"; +"_element_" = "element"; +"_elements_" = "elements"; +"_tite_footer_upload_wwan_" = " Wi-Fi network required, %lu %@ to upload"; +"_tite_footer_upload_" = "%lu %@ to upload"; +"_tite_footer_download_wwan_" = " Wi-Fi network required, %lu %@ to download"; +"_tite_footer_download_" = "%lu %@ to download"; "_limited_dimension_" = "Maximum size reached"; "_save_selected_files_" = "Save to photo gallery"; "_file_not_saved_cameraroll_" = "Error: File not saved in photo album"; @@ -401,6 +623,12 @@ "_directory_on_top_" = "Sort folders before files"; "_show_description_" = "Show folder description"; "_show_recommended_files_" = "Show recommendations"; +"_directory_on_top_yes_" = "✓ Folders on top"; +"_directory_on_top_no_" = "Folders on top"; +"_show_description_" = "Show description"; +//"_show_recommended_files_" = "Show recommended files"; +"_no_description_available_" = "No description available for this folder"; +"_folder_automatic_upload_" = "Folder for \"Auto upload\""; "_search_no_record_found_" = "No result"; "_search_in_progress_" = "Search in progress …"; "_search_instruction_" = "Search for file (minimum 2 characters)"; @@ -408,17 +636,27 @@ "_files_no_folders_" = "No folders in here"; "_request_in_progress_" = "Request to server in progress …"; "_personal_files_only_" = "Personal files only"; - "audio" = "AUDIO"; "directory" = "FOLDERS"; +"compress" = "COMPRESS"; +"directory" = "FOLDERS"; "document" = "DOCUMENTS"; "image" = "IMAGES"; "template" = "TEMPLATES"; +"unknow" = "UNKNOWN"; "video" = "VIDEO"; +"_file_del_only_local_" = "File not present on server"; +"_copy_file_" = "Copy"; "_paste_file_" = "Paste"; +"_open_quicklook_" = "Open with Quick Look"; +"_search_this_folder_" = "Search in this folder"; +"_search_all_folders_" = "Search in all folders"; +"_search_sub_folder_" = "Search here and in subfolders"; +"_theming_is_light_" = "Server theming too brightly coloured, not applicable"; "_cancel_all_task_" = "Cancel all transfers"; "_status_wait_download_" = "Waiting for download"; +"_status_in_download_" = "In download"; "_status_downloading_" = "Downloading"; "_status_wait_upload_" = "Waiting to upload"; "_status_wait_create_folder_" = "Waiting to create the folder"; @@ -427,6 +665,7 @@ "_status_wait_favorite_" = "Waiting to change favorite"; "_status_wait_copy_" = "Waiting to copy"; "_status_wait_move_" = "Waiting to move"; +"_status_in_upload_" = "In upload"; "_status_uploading_" = "Uploading"; "_status_upload_error_" = "Error, waiting to upload"; "_select_media_folder_" = "Set Media folder"; @@ -450,19 +689,35 @@ // MARK: Share "_share_link_" = "Share link"; +"_share_link_button_" = "Send link to …"; "_share_link_name_" = "Link name"; "_password_" = "Password"; "_share_password_" = "Password protected link"; +"_share_expirationdate_" = "Set expiration date for link"; "_date_" = "Date"; +"_share_title_" = "Share"; +"_add_sharee_" = "Add users or groups"; +"_add_sharee_footer_" = "You can share this resource by adding users or groups. To remove a share, remove all users and groups"; +"_find_sharee_title_" = "Search"; +"_find_sharee_" = "Search for user or group …"; +"_find_sharee_footer_" = "Enter part of the name of the user or group to search for (at least 2 characters) followed by \"Return\", select the users that should be allowed to access the share followed by \"Done\" to confirm"; +"_user_is_group_" = "(Group)"; +"_direct_sharee_title_" = "Share"; +"_direct_sharee_footer_" = "If you already know the name, enter it, then select the share type and press \"Done\" to confirm"; +"_direct_sharee_" = "Enter the username …"; "_user_sharee_footer_" = "Tap to change permissions"; +"_share_type_title_" = "Type of share"; +"_share_type_user_" = "User"; +"_share_type_group_" = "Group"; +"_share_type_remote_" = "Remote"; "_enforce_password_protection_" = "Enforce password protection"; +"_password_obligatory_" = "Enforce password protection enabled, password obligatory"; "_shared_with_you_by_" = "Shared with you by"; -//"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; -//"_new_comment_" = "New comment …"; -//"_edit_comment_" = "Edit comment"; -//"_delete_comment_" = "Delete comment"; -//"_share_read_only_" = "View only"; -//"_share_editing_" = "Can edit"; +"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; +"_new_comment_" = "New comment …"; +"_edit_comment_" = "Edit comment"; +"_delete_comment_" = "Delete comment"; +"_share_read_only_" = "View only"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; "_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; @@ -512,6 +767,7 @@ //"_share_password_protect_" = "Password protect"; "_share_expiration_date_" = "Set expiration date"; "_share_note_recipient_" = "Note to recipient"; +"_share_delete_sharelink_" = "Delete link"; "_share_add_sharelink_" = "Add another link"; "_share_can_read_" = "Read"; "_share_can_reshare_" = "Share"; @@ -536,35 +792,49 @@ "_no_transfer_" = "No transfers yet"; "_no_transfer_sub_" = "Uploads and downloads from this device will show up here"; +"_no_activity_" = "No activity yet"; "_no_activity_footer_" = "No more activities to load"; "_transfers_" = "Transfers"; "_activity_" = "Activity"; +"_activity_file_not_present_" = "File no longer present"; "_trash_file_not_found_" = "It seems that the file is not in the Trash. Go to the Trash to update it and try again."; "_list_shares_" = "Shares"; "_list_shares_no_files_" = "No shares yet"; "_tutorial_list_shares_view_" = "Files and folders you share will show up here"; +"_create_synchronization_" = "Create synchronization"; "_offline_" = "Offline"; +"_local_storage_" = "Local storage"; +"_local_storage_no_record_" = "No files yet"; "_upload_photos_videos_" = "Upload photos or videos"; "_upload_file_" = "Upload file"; +"_upload_file_text_" = "Create text file"; "_create_nextcloudtext_document_" = "Create text document"; +"_save_document_picker_" = "Save here"; +"_destination_folder_" = "Destination folder"; "_use_folder_auto_upload_" = "Use the \"Auto upload\" folder as destination"; +"_rename_filename_" = "Rename"; "_filename_" = "Filename"; "_enter_filename_" = "Enter filename …"; "_default_preview_filename_footer_" = "Example preview of filename: IMG_0001.JPG"; "_filename_header_" = "Enter filename"; -"_preview_filename_" = "Example preview of filename. You can use the mask %@ for date/time."; -"_add_filenametype_" = "Specify type in filename"; +"_preview_filename_" = "Example preview of filename. You can use the mask %@ for date/time.";"_add_filenametype_" = "Specify type in filename"; +"_filenametype_photo_video_" = "Photo/Video"; "_maintain_original_filename_" = "Maintain original filename"; +"_modify_photo_" = "Modify photo"; "_notifications_" = "Notifications"; +"_no_notification_" = "No notifications yet"; +"_autoupload_filename_title_" = "Auto upload filename"; "_untitled_" = "Untitled"; +"_text_upload_title_" = "Upload text file"; "_e2e_settings_title_" = "Encryption"; "_e2e_settings_" = "End-to-end encryption"; "_e2e_settings_start_" = "Start end-to-end encryption"; +"_e2e_settings_not_available_" = "End-to-end encryption not available"; "_e2e_settings_activated_" = "End-to-end encryption activated"; "_e2e_server_disabled_" = "End-to-end encryption app disabled on server"; "_e2e_settings_view_passphrase_" = "All 12 words together make a very strong password, letting only you view and make use of your encrypted files. Please write it down and keep it somewhere safe."; "_e2e_settings_read_passphrase_" = "Read passphrase"; -"_e2e_settings_lock_not_active_" = "Lock not active. Go to \"Settings\" and activate it."; +"_e2e_settings_lock_not_active_" = "Lock not active, go back to \"Settings\" and activate it"; "_e2e_settings_the_passphrase_is_" = "The passphrase is:"; "_e2e_passphrase_request_title_" = "Request passphrase"; "_e2e_passphrase_request_message_" = "Insert the 12 words"; @@ -608,15 +878,37 @@ "_scan_label_document_zone_" = "Tap or drag images down for document creation"; "_filter_document_" = "Document"; "_filter_original_" = "Original"; +"_filter_bn_" = "Black & White"; +"_filter_grayscale_" = "Grayscale"; "_quality_image_title_" = "Preview image quality"; +"_quality_high_" = "Large file size of high quality"; +"_quality_medium_" = "Average file size of medium quality"; +"_quality_low_" = "Small file size of low quality"; +"_file_type_" = "File type"; +"_pdf_password_" = "PDF Password"; "_file_creation_" = "File creation"; "_delete_all_scanned_images_" = "Delete all scanned images"; "_text_recognition_" = "Text recognition"; "_all_files_" = "All files"; "_personal_files_" = "Personal Files"; +/* The title on the navigation bar of the Scanning screen. */ +"wescan.scanning.title" = "Scanning"; +/* The "Next" button on the right side of the navigation bar on the Edit screen. */ +"wescan.edit.button.next" = "Next"; +/* The title on the navigation bar of the Edit screen. */ +"wescan.edit.title" = "Edit Scan"; +/* The "Done" button on the right side of the navigation bar on the Review screen. */ +"wescan.review.button.done" = "Done"; +/* The title on the navigation bar of the Review screen. */ +"wescan.review.title" = "Review"; + "_trash_view_" = "Deleted files"; "_empty_trash_" = "Empty trash"; +"_trash_restore_all_" = "Restore all files"; +"_trash_delete_all_" = "Empty trash"; +"_trash_delete_permanently_" = "Delete permanently"; +"_trash_delete_all_description_" = "Do you want to empty the trash bin?"; "_trash_no_trash_" = "No files deleted"; "_trash_no_trash_description_" = "You can restore deleted files from here"; "_trash_restore_selected_" = "Restore selected files"; @@ -637,7 +929,62 @@ "_log_in_" = "Log in"; "_sign_up_" = "Sign up with provider"; "_host_your_own_server" = "Host your own server"; +"_unauthorized_" = "Unauthorized"; +"_bad_username_password_" = "Wrong username or password"; +"_cancelled_by_user" = "Transfer canceled"; +"_error_folder_destiny_is_the_same_" = "It is not possible to move the folder into itself"; +"_error_not_permission_" = "You don't have permission to complete the operation"; +"_error_path_" = "Unable to open this file or folder. Please make sure it exists"; +"_file_upload_not_exitst_" = "The file that you want to upload does not exist"; +"_forbidden_characters_from_server_" = "The name contains at least one invalid character"; +"_error_not_modified_" = "Resource not modified"; +"_not_connected_internet_" = "Server connection error"; +"_not_possible_connect_to_server_" = "It is not possible to connect to the server at this time"; +"_not_possible_create_folder_" = "Folder could not be created"; +"_server_down_" = "Could not establish contact with server"; +"_time_out_" = "Timeout, try again"; +"_unknow_response_server_" = "Unexpected response from server"; +"_user_authentication_required_" = "User authentication required"; +"_file_directory_locked_" = "File or directory locked"; +"_ssl_certificate_untrusted_" = "The certificate for this server is invalid"; +"_ssl_certificate_changed_" = "The certificate for this server seems to have changed"; +"_internal_server_" = "Internal server error"; +"_file_already_exists_" = "Could not complete the operation, a file with the same name exists"; +"_file_folder_not_exists_" = "The source file wasn't found at the specified path"; +"_folder_contents_nochanged_" = "The folder contents have not changed"; +"_images_invalid_converted_" = "The image is invalid and cannot be converted to a thumbnail"; +"_method_not_expected_" = "Unexpected request method"; +"_reauthenticate_user_" = "Access expired, log in again"; +"_server_error_retry_" = "The server is temporarily unavailable"; +"_too_many_files_" = "Too many files would be involved in this operation"; +"_too_many_request_" = "Sending too many requests caused the rate limit to be reached"; +"_user_over_quota_" = "Storage quota is reached"; +"_ssl_connection_error_" = "Connection SSL error, try again"; +"_bad_request_" = "Bad request"; +"_webdav_locked_" = "WebDAV Locked: Trying to access locked resource"; +"_error_user_not_available_" = "The user is no longer available"; +"_server_response_error_" = "Server response content error"; +"_no_nextcloud_found_" = "Server not found"; +"_error_decompressing_" = "Error during decompressing. Unknown compression method or the file is corrupt"; +"_error_json_decoding_" = "Serious internal error in decoding metadata (The data couldn't be read because it isn't in the correct format.)"; +"_error_check_remote_user_" = "Server responded with an error. Please log in again"; +"_request_entity_too_large_" = "The file is too large"; +"_not_possible_download_" = "It is not possible to download the file"; +"_not_possible_upload_" = "It is not possible to upload the file"; +"_error_files_upload_" = "Error uploading files"; +"_method_not_allowed_" = "The requested method is not supported"; "_invalid_url_" = "Invalid server URL"; +"_invalid_literal_" = "Invalid search string"; +"_invalid_date_format_" = "Invalid date format"; +"_invalid_data_format_" = "Invalid data format"; +"_error_decode_xml_" = "Invalid response, error decode XML"; +"_internal_generic_error_" = "internal error"; +"_editor_unknown_" = "Failed to open file: Editor is unknown"; +"_err_file_not_found_" = "File not found, removed"; +"_err_e2ee_app_version_" = "The app version of end-to-end encryption is not compatible with the server, please update your server"; +"_err_permission_microphone_" = "Please allow Microphone usage from Settings"; +"_err_permission_photolibrary_" = "Please allow Photos from Settings"; +"_err_permission_locationmanager_" = "Please allow Location - Always from Settings"; "_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; "_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; "_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; @@ -650,6 +997,8 @@ "_create_voice_memo_" = "Create voice memo"; "_voice_memo_start_" = "Tap to start"; "_voice_memo_stop_" = "Tap to stop"; +"_voice_memo_filename_" = "Voice memo"; +"_voice_memo_title_" = "Upload voice memo"; "Enter Passcode" = "Enter Passcode"; "Enter a new passcode" = "Enter a new passcode"; "Confirm new passcode" = "Confirm new passcode"; @@ -676,10 +1025,15 @@ "_3_months_" = "3 months"; "_1_month_" = "1 month"; "_1_week_" = "1 week"; +"_1_day_" = "1 day"; "_monthly_" = "Monthly"; "_yearly_" = "Yearly"; +"_weekly_" = "Weekly"; "_daily_" = "Daily"; -"_used_space_" = "Used space:"; +"_day_" = "Day"; +"_used_space_" = "Used space"; +"_open_in_onlyoffice_" = "Open in ONLYOFFICE"; +"_open_in_collabora_" = "Open with Collabora Online"; "_login_address_detail_" = "The link to your %@ web interface when you open it in the browser."; "_go_to_page_" = "Go to page"; "_page_" = "Page"; @@ -687,15 +1041,23 @@ "_invalid_page_" = "Invalid Page"; "_the_entered_page_number_does_not_exist_" = "The entered page number does not exist."; "_error_something_wrong_" = "Something went wrong"; +"_resolution_" = "Resolution"; "_try_download_full_resolution_" = "Download full resolution image"; "_full_resolution_image_info_" = "This may reveal more information about the photo."; "_download_audio_" = "Download the audio locally"; "_copied_path_" = "Copied path"; +"_copy_path_" = "Copy path"; +"_certificates_" = "Certificates"; "_privacy_screen_" = "Splash screen when app inactive"; +"_saving_" = "Saving …"; +"_video_not_streamed_" = "The server does not allow video streaming, do you want to download it?"; +"_video_not_streamed_e2ee_" = "The server does not allow video streaming because it is encrypted, do you want to download it?"; +"_scan_" = "Scan"; "_in_" = "in"; "_enter_passphrase_" = "Enter passphrase (12 words)"; "_show_more_results_" = "Show more results"; "_waiting_for_" = "Waiting for:"; +"_waiting_" = "Waiting …"; "_reachable_wifi_" = "network reachable via Wi-Fi or cable"; "_ITMS-90076_" = "Due to a change in the Nextcloud application identifier, the settings and password for accessing your cloud are reset, so please re-enter your account data and check your Settings. We are sorry about that."; "_password_not_present_" = "Please re-insert your credentials."; @@ -706,9 +1068,15 @@ "_description_dashboardwidget_" = "Having the Dashboard always at your fingertips has never been easier."; "_description_fileswidget_" = "View your recent files and use the toolbar to speed up your operations."; "_description_toolbarwidget_" = "A toolbar to speed up your operations."; +"_no_data_available_" = "No data available"; +"_widget_available_nc25_" = "Widget only available starting with server version 25"; +"_keep_running_" = "Keep the app running for a better user experience"; +"_recent_activity_" = "Recent activity"; "_title_lockscreenwidget_" = "Status"; "_description_lockscreenwidget_" = "Keep an eye on available space and recent activity"; - +"_no_items_" = "No items"; +"_check_back_later_" = "Check back later"; +"_exporting_video_" = "Exporting video … Tap to cancel."; "_keep_running_" = "Keep the app running for a better user experience."; "_apps_nextcloud_detect_" = "Detected %@ apps"; "_add_existing_account_" = "Other %@ Apps has been detected, do you want to add an existing account?"; @@ -724,16 +1092,35 @@ "_mobile_config_" = "Download the configuration profile"; "_calendar_contacts_footer_warning_" = "Configuration profile can only be downloaded if Safari is set as default browser."; "_calendar_contacts_footer_" = "After downloading the profile you can install it from Settings."; +"_preview_" = "Preview"; +"_crop_" = "Crop"; "_modify_image_desc_" = "Tap on a file to modify or rename."; "_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live Photo effect."; +//"_modify_image_desc_" = "Tap the image for modify"; +//"_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live effect"; "_enable_livephoto_" = "Enable Live Photo"; "_disable_livephoto_" = "Disable Live Photo"; "_undo_modify_" = "Undo modifying"; +"_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; +"_disableFilesApp_" = "Files app cannot be used because it is disabled"; +"_reset_application_done_" = "Reset application, done."; "_rename_already_exists_" = "A file with this name already exists."; +"_created_" = "Created"; +"_recipients_" = "Recipients"; +"_are_sure_" = "Are you sure?"; +"_creation_" = "Creation"; +"_modified_" = "Modified"; "_group_folders_" = "Group folders"; "_play_from_files_" = "Play movie from a file"; "_play_from_url_" = "Play movie from URL"; "_valid_video_url_" = "Insert a valid URL"; +"_deletion_progess_" = "Deletion in progress"; +"_copying_progess_" = "Copying in progress"; +"_moving_progess_" = "Moving in progress"; +"_chunk_enough_memory_" = "It seems there is not enough space to send the file"; +"_chunk_create_folder_" = "The file could not be sent, please check the server log"; +"_chunk_files_null_" = "The file for sending could not be created"; +"_chunk_file_null_" = "The file could not be sent"; "_chunk_move_" = "The sent file could not be reassembled, please check the server log."; "_download_image_" = "Download image"; "_download_video_" = "Download video"; @@ -742,6 +1129,9 @@ "_reset_wrong_passcode_desc_" = "Use \"Reset application\" to remove all accounts and local data after %d failed code entry attempts."; "_deviceOwnerAuthentication_" = "The biometric sensor has been temporarily disabled due to multiple failed attempts. Enter the device passcode to re-enable the sensor."; "_virus_detect_" = "Virus detected. Upload cannot be completed!"; +"_zoom_" = "Zoom"; +"_zoom_in_" = "Zoom in"; +"_zoom_out_" = "Zoom out"; "_select_photos_" = "Select photos"; "_selected_photo_" = "selected photo"; "_selected_photos_" = "selected photos"; @@ -757,12 +1147,14 @@ "_account_settings_" = "Account settings"; "_users_" = "Users"; "_users_footer_" = "Every time the app is reactivated, the account will be requested."; +"_additional_view_options_" = "Additional view options"; "_while_charging_" = "While charging"; +"_additional_options_" = "Additional options"; "_keep_screen_awake_" = "Keep screen awake\nwhile transferring files"; "_error_not_found_" = "The requested resource could not be found"; "_error_conflict_" = "The request could not be completed due to a conflict with the current state of the resource"; "_error_precondition_" = "The server does not meet one of the preconditions of the requester"; -"_downloading_" = "Downloading"; + "_additional_options_" = "Additional options"; "_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; "_disableFilesApp_" = "Files app cannot be used because it is disabled"; @@ -773,12 +1165,32 @@ "_recent_activity_" = "Recent activity"; "_maintenance_mode_" = "The server is currently in maintenance mode, which may take a while."; "_account_disabled_" = "Account disabled"; + +//Video +"_select_trace_" = "Select the trace"; +"_video_processing_" = "Video processing"; +"_video_being_processed_" = "Video being processed …"; +"_downloading_" = "Downloading"; +"_download_error_" = "Download error"; +"_subtitle_" = "Subtitle"; +"_dts_to_ac3_" = "The DTS is not supported, it requires a conversion to Dolby Digital"; +"_reuired_conversion_" = "This video takes a long time to convert."; +"_stay_app_foreground_" = "Keep the app in the foreground …"; +"_conversion_available_" = "The conversion is always available on menu"; +"_video_format_not_recognized_" = "This video needs to be processed to be played, do you want to do it now?"; +"_video_must_download_" = "This video needs to be downloaded and processed to be played, do you want to do it now?"; +"_conversion_max_compatibility_" = "Max compatibility, the conversion can take much longer"; +"_video_tap_for_close_" = "A slight pressure to close the processing"; +"_subtitle_not_found_" = "Subtitle not found"; +"_disable_" = "Disable"; +"_subtitle_not_dowloaded_" = "There are subtitles not downloaded locally"; "_user_" = "User"; "_add_subtitle_" = "Add an external subtitle"; "_add_audio_" = "Add an external audio"; "_upload_foreground_msg_" = "Do not close %@ to complete the transfer …"; "_upload_background_msg_" = "Files upload in progress …"; "_create_folder_error_" = "An error has occurred while creating the folder:\n%@.\n\nPlease resolve the issue as soon as possible.\n\nAll uploads are suspended until the problem is resolved.\n"; +"_rename_file_error_" = "An error has occurred while renaming the file:\n%@."; "_creating_dir_progress_" = "Creating directories in progress … keep the application active."; "_creating_db_photo_progress_" = "Creating photo archive in progress … keep the application active."; "_account_unauthorized_" = "Warning, %@, you are not authorized, your account has been deleted, if you have changed your password, re-authenticate."; @@ -813,15 +1225,42 @@ You can stop it at any time, adjust the settings, and enable it again."; "_on_" = "On"; // a11y: On/Off "_off_" = "Off"; +"_grid_view_" = "Show grid view"; +"_list_view_" = "Show list view"; "_list_" = "List"; "_icons_" = "Icons"; +// MARK: Plan customer +"_leave_plan_title" = "We're sorry to see you go"; +"_leave_plan_description" = "You'll no longer have access to:"; +"_current_plan_" = "Current Plan"; +"_billing_plan_" = "Billing Plan"; +"_keep_plan_" = "Keep Plan"; +"_leave_plan_" = "Leave Plan"; +"_change_plan_" = "Change Plan"; +"_manage_plan_" = "Manage Plan"; +"_purchase_plan_" = "Purchase Plan"; +"_restore_plan_" = "Restore Purchased Plan"; +"_purchase_plan_description_" = "Purchases have been restored"; +"_choose_plan_" = "You should choose a plan in order to purchase it."; +"_already_plan_" = "The selected plan has already been bought."; +"_change_billing_" = "Change Billing"; +"_payment_method_" = "Payment Method"; + +// MARK: Mantis library +"Mantis.Done" = "Done"; +"Mantis.Cancel" = "Cancel"; +"Mantis.Reset" = "Reset"; +"Mantis.Original" = "Original"; +"Mantis.Square" = "Square"; + // MARK: Assistant "_assistant_task_unknown_" = "Unknown"; "_assistant_task_scheduled_" = "Scheduled"; "_assistant_task_in_progress_" = "In Progress"; "_assistant_task_completed_" = "Completed"; "_assistant_task_failed_" = "Failed"; +"_all_" = "All"; "_input_" = "Input"; "_output_" = "Output"; "_task_details_" = "Task details"; From cf3642a7a1c1458197faaff8291304c88c364b87 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 30 Sep 2025 16:44:20 +0530 Subject: [PATCH 49/74] NMC-2023 - Localizations for EN and DE updated for New sharing design and as per 9.6.6 version --- .../de.lproj/Localizable.strings | Bin 98000 -> 193572 bytes .../en.lproj/Localizable.strings | 87 ++++++++++++------ 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/iOSClient/Supporting Files/de.lproj/Localizable.strings b/iOSClient/Supporting Files/de.lproj/Localizable.strings index a598aeb4ae03302a4c466abe7c49ecabb71f6ac9..803567d0ebb9af211f10efedebc1c2188203d4a2 100644 GIT binary patch literal 193572 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 delta 4829 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 Y&2r>dBGFgL9kl*82$pxWi!%TJ0fCcAng9R* diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 23d69dad8a..4907063957 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -55,7 +55,7 @@ "_select_all_" = "Select all"; "_upload_" = "Upload"; "_home_" = "Files"; -"_files_" = "Files"; +//"_files_" = "Files"; "_home_dir_" = "Home"; "_file_to_upload_" = "File to upload"; "_destination_" = "Destination"; @@ -313,7 +313,7 @@ "_...loading..._" = "Loading …"; "_download_plist_" = " "; "_no_reuploadfile_" = "Could not find nor resend file. Delete the upload and reload the file to upload it."; -"_file_already_exists_" = "Unable to complete the operation, a file with the same name exists."; +//"_file_already_exists_" = "Unable to complete the operation, a file with the same name exists."; "_read_file_error_" = "Could not read the file"; "_write_file_error_" = "Could not write the file"; "_files_lock_error_" = "There was an error changing the lock of this file."; @@ -335,6 +335,8 @@ "_lock_protection_no_screen_" = "Do not ask at startup"; "_lock_protection_no_screen_footer_" = "Use \"Do not ask at startup\" for the encryption option."; "_enable_touch_face_id_" = "Enable Touch/Face ID"; +"_face_id_" = "Face ID"; +"_touch_id_" = "Touch ID"; "_security_" = "Security"; "_data_protection_" = "Data protection"; "_privacy_settings_" = "Privacy Settings"; @@ -342,6 +344,7 @@ "_service_" = "Service"; "_imprint_" = "Imprint"; "_magentacloud_version_" = "MagentaCLOUD Version"; +"_your_secure_cloud_storage_" = "Your secure cloud storage"; "_url_" = "URL"; "_username_" = "Username"; "_change_credentials_" = "Change your credentials"; @@ -353,7 +356,6 @@ "_clear_cache_" = "Clear cache"; "_clear_cache_footer_" = "Clear downloaded and offline files from the app"; "_exit_footer_" = "Remove all accounts and local data from the app."; -"_exit_footer_" = "Remove all accounts and local data from the app."; "_exit_" = "Logout"; "_funct_not_enabled_" = "Functionality not enabled"; "_passcode_activate_" = "Password lock on"; @@ -372,8 +374,8 @@ "_manage_account_" = "Manage account"; "_change_password_" = "Change password"; "_add_account_" = "Add account"; -"_want_delete_" = "You will delete the following: "; -"_want_leave_share_" = "You will leave the following shares: "; +//"_want_delete_" = "You will delete the following: "; +//"_want_leave_share_" = "You will leave the following shares: "; "_delete_account_" = "Remove account"; "_delete_active_account_" = "Remove active account"; "_want_delete_" = "Do you really want to delete?"; @@ -637,7 +639,7 @@ "_request_in_progress_" = "Request to server in progress …"; "_personal_files_only_" = "Personal files only"; "audio" = "AUDIO"; -"directory" = "FOLDERS"; +//"directory" = "FOLDERS"; "compress" = "COMPRESS"; "directory" = "FOLDERS"; "document" = "DOCUMENTS"; @@ -688,7 +690,8 @@ // MARK: Share -"_share_link_" = "Share link"; +//"_share_link_" = "Share link"; +"_share_link_" = "Link"; "_share_link_button_" = "Send link to …"; "_share_link_name_" = "Link name"; "_password_" = "Password"; @@ -713,10 +716,10 @@ "_enforce_password_protection_" = "Enforce password protection"; "_password_obligatory_" = "Enforce password protection enabled, password obligatory"; "_shared_with_you_by_" = "Shared with you by"; -"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; -"_new_comment_" = "New comment …"; -"_edit_comment_" = "Edit comment"; -"_delete_comment_" = "Delete comment"; +//"_shareLinksearch_placeholder_" = "Name, email";//, or Federated Cloud ID …"; +//"_new_comment_" = "New comment …"; +//"_edit_comment_" = "Edit comment"; +//"_delete_comment_" = "Delete comment"; "_share_read_only_" = "View only"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; @@ -742,17 +745,19 @@ "_share_download_limit_placeholder_" = "Enter download limit"; "_share_download_limit_alert_empty_" = "Download limit cannot be empty."; "_share_download_limit_alert_zero_" = "Download limit should be greater than 0."; +"_share_download_limit_alert_invalid_" = "Please enter a valid number for the download limit."; "_share_remaining_download_" = "Downloads:"; -"_share_read_only_" = "Read only"; +//"_share_read_only_" = "Read only"; +"_share_read_only_" = "View only"; "_share_editing_" = "Can edit"; "_share_file_drop_" = "Filedrop only"; //"_share_file_drop_" = "File request"; "_share_hide_download_" = "Prevent download"; "_share_note_recipient_" = "YOUR MESSAGE"; -"_shareLinksearch_placeholder_" = "Contact name or email address"; -"_shareLinksearch_placeholder_" = "Type a name and press Search"; +//"_shareLinksearch_placeholder_" = "Contact name or email address"; +//"_shareLinksearch_placeholder_" = "Type a name and press Search"; //"_shareLinksearch_placeholder_" = "Type a name and press Enter"; -"_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; +//"_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; "_new_comment_" = "New comment …"; "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; @@ -760,6 +765,7 @@ "_share_allow_upload_" = "Allow upload and editing"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; //"_share_hide_download_" = "Hide download"; +"_share_hide_download_" = "Prevent download"; "_share_allowed_downloads_" = "Allowed downloads"; "_share_limit_download_" = "Limit downloads"; "_remaining_" = "%d remaining"; @@ -770,16 +776,37 @@ "_share_delete_sharelink_" = "Delete link"; "_share_add_sharelink_" = "Add another link"; "_share_can_read_" = "Read"; -"_share_can_reshare_" = "Share"; -"_share_can_create_" = "Create"; -"_share_can_change_" = "Edit"; -"_share_can_delete_" = "Delete"; -"_share_can_download_" = "Allow download and sync"; -"_share_unshare_" = "Delete share"; +"_share_can_reshare_" = "Allow resharing"; +"_share_can_create_" = "Allow creating"; +"_share_can_change_" = "Allow editing"; +"_share_can_delete_" = "Allow deleting"; +"_share_can_download_" = "Allow download"; +"_share_unshare_" = "Unshare"; "_share_internal_link_" = "Internal link"; "_share_internal_link_des_" = "Only works for users with access to this file/folder."; "_share_reshare_disabled_" = "You are not allowed to reshare this file/folder."; "_share_reshare_restricted_" = "Note: You only have limited permission to reshare this file/folder."; +"_create_new_link_" = "Create new link"; +"_share_send_link_by_mail_" = "Send link by mail"; +"_share_or_" = "or"; +"_share_copy_link_" = "Copy link"; +"_share_shared_with_" = "Shared with"; +"_share_quick_permission_everyone_can_just_upload_" = "Everyone can just upload"; +"_share_quick_permission_everyone_can_edit_" = "Everyone can edit"; +"_share_quick_permission_everyone_can_only_view_" = "Everyone can only view"; +"_share_quick_permission_everyone_can_just_upload_short_" = "Just upload"; +"_share_quick_permission_everyone_can_edit_short_" = "Can edit"; +"_share_quick_permission_everyone_can_only_view_short_" = "Only view"; +"_share_received_shares_text_" = "This file / folder was shared with you by"; +"_share_no_shares_text_" = "You have not yet shared your file/folder. Share to give others access"; +"_share_size_" = "Size: "; +"_share_modified_" = "Modified: "; +"_share_created_" = "Created: "; +"_share_uploaded_" = "Uploaded: "; +"_share_details_" = "Details"; +"_share_via_link_menu_password_label_" = "Password protect (%1$s)"; +"_share_link_empty_exp_date_" = "You must select expiration date."; +"_share_link_empty_note_message_" = "Please enter note."; "_remote_" = "Remote"; "_remote_group_" = "Remote group"; "_conversation_" = "Conversation"; @@ -825,6 +852,7 @@ "_no_notification_" = "No notifications yet"; "_autoupload_filename_title_" = "Auto upload filename"; "_untitled_" = "Untitled"; +"_untitled_txt_" = "Untitled.txt"; "_text_upload_title_" = "Upload text file"; "_e2e_settings_title_" = "Encryption"; "_e2e_settings_" = "End-to-end encryption"; @@ -985,13 +1013,13 @@ "_err_permission_microphone_" = "Please allow Microphone usage from Settings"; "_err_permission_photolibrary_" = "Please allow Photos from Settings"; "_err_permission_locationmanager_" = "Please allow Location - Always from Settings"; -"_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; -"_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; -"_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; -"_error_files_upload_" = "Error uploading files."; -"_internal_generic_error_" = "internal error."; -"_err_permission_microphone_" = "Please allow Microphone usage from Settings."; -"_err_permission_photolibrary_" = "Please allow Photos from Settings."; +//"_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; +//"_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; +//"_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; +//"_error_files_upload_" = "Error uploading files."; +//"_internal_generic_error_" = "internal error."; +//"_err_permission_microphone_" = "Please allow Microphone usage from Settings."; +//"_err_permission_photolibrary_" = "Please allow Photos from Settings."; "_qrcode_not_authorized_" = "This app is not authorized to use the Back Camera."; "_qrcode_not_supported_" = "QR code not supported by the current device."; "_create_voice_memo_" = "Create voice memo"; @@ -1077,7 +1105,7 @@ "_no_items_" = "No items"; "_check_back_later_" = "Check back later"; "_exporting_video_" = "Exporting video … Tap to cancel."; -"_keep_running_" = "Keep the app running for a better user experience."; +//"_keep_running_" = "Keep the app running for a better user experience."; "_apps_nextcloud_detect_" = "Detected %@ apps"; "_add_existing_account_" = "Other %@ Apps has been detected, do you want to add an existing account?"; "_status_in_progress_" = "Status reading in progress …"; @@ -1213,6 +1241,7 @@ "_tip_open_mediadetail_" = "Swipe up to show the details"; "_tip_autoupload_button_" = "Configure the options as you prefer (folder, Wi-Fi, new content, subfolders) and tap ‘Turn on auto uploading’. You can stop it at any time, adjust the settings, and enable it again."; +"_tip_autoupload_" = "You can now Auto Upload specific Albums from your Photos. You can enable Auto Upload here"; // MARK: Accessibility From 51d450d6a51a847b53309dd6b48799581e887c97 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 8 Oct 2025 09:15:20 +0200 Subject: [PATCH 50/74] doc Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCTransferStore.swift | 239 +++++++++++++++------ 1 file changed, 168 insertions(+), 71 deletions(-) diff --git a/iOSClient/Networking/NCTransferStore.swift b/iOSClient/Networking/NCTransferStore.swift index 20e323e522..e0f6751929 100644 --- a/iOSClient/Networking/NCTransferStore.swift +++ b/iOSClient/Networking/NCTransferStore.swift @@ -7,6 +7,8 @@ import NextcloudKit // MARK: - Transfer Store (batched persistence) +/// Immutable transfer item snapshot used by the Transfer Store. +/// Fields are optional to allow partial updates/merges during upsert operations. struct TransferItem: Codable { var completed: Bool? var date: Date? @@ -23,24 +25,41 @@ struct TransferItem: Codable { var taskIdentifier: Int? } +/// Centralized, batched persistence of transfer items with low-IO strategy and lifecycle-aware flushes. +/// +/// The store keeps an in-memory cache and periodically persists it to disk (JSON) +/// based on change count and latency thresholds. It also reacts to app lifecycle +/// events to ensure data safety across foreground/background transitions. final class NCTransferStore { static let shared = NCTransferStore() // Shared state + // In-memory cache of transfer items. Access must be performed on `transferStoreIO`. private var transferItemsCache: [TransferItem] = [] + // Serialization queue for disk and cache mutations. private let transferStoreIO = DispatchQueue(label: "TransferStore.IO", qos: .utility) + // Timer queue used for periodic debounce commits. private let debounceQueue = DispatchQueue(label: "TransferStore.Debounce", qos: .utility) + // JSON encoders/decoders configured with ISO8601 dates. private let encoderTransferItem = JSONEncoder() private let decoderTransferItem = JSONDecoder() + // Backing file URL for persisted JSON. private(set) var transferStoreURL: URL? // Batching controls + // Counts in-memory changes since the last persist. private var changeCounter: Int = 0 + // Last successful persist absolute time. private var lastPersist: TimeInterval = 0 + // Max number of changes before forcing a persist. private let batchThreshold: Int = NCBrandOptions.shared.numMaximumProcess / 2 + // Max elapsed time (seconds) between persists. private let maxLatencySec: TimeInterval = 5 // <- or every 5s at most + // Periodic debounce timer. private var debounceTimer: DispatchSourceTimer? + /// Initializes the store, loads any existing snapshot from disk, + /// configures date strategies and installs lifecycle observers and debounce timer. init() { self.encoderTransferItem.dateEncodingStrategy = .iso8601 self.decoderTransferItem.dateDecodingStrategy = .iso8601 @@ -69,25 +88,29 @@ final class NCTransferStore { } deinit { - stopDebounceTimer() + } + /// Installs observers to align flush behavior with app lifecycle: + /// - Stop debounce when resigning active. + /// - Force a flush on backgrounding. + /// - Reload from disk and restart debounce shortly after becoming active. private func setupLifecycleFlush() { NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { _ in self.stopDebounceTimer() } NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in - self.forceFlush() + self.commit(forced: true) } NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in guard let self else { return } - // Force a synchronous reload before anything else self.reloadFromDisk() Task { + // Small delay to avoid racing with other app-activation tasks. try? await Task.sleep(nanoseconds: 1_000_000_000) self.startDebounceTimer() } @@ -96,7 +119,9 @@ final class NCTransferStore { // MARK: Public API - /// Adds or merges an item, then schedules a batched commit. + /// Inserts or updates a transfer item (upsert by `serverUrl + fileName + taskIdentifier`), then schedules a commit. + /// + /// - Parameter item: The item to insert or merge into the cache. func addItem(_ item: TransferItem) { transferStoreIO.sync { // Upsert by (serverUrl + fileName + taskIdentifier) @@ -110,28 +135,17 @@ final class NCTransferStore { } else { transferItemsCache.append(item) } - - changeCounter &+= 1 } - maybeCommit() - } - - /// Updates only the `progress` field of an existing item, then schedules a batched commit. - /// If no matching item is found, nothing happens. - func transferProgress(serverUrl: String, fileName: String, taskIdentifier: Int, progress: Double) { - transferStoreIO.sync { - if let idx = transferItemsCache.firstIndex(where: { - $0.serverUrl == serverUrl && - $0.fileName == fileName && - $0.taskIdentifier == taskIdentifier - }) { - transferItemsCache[idx].progress = progress - } - } + commit() } - /// Removes the first match by (serverUrl + fileName); batched commit. + /// Removes the first item matching `(serverUrl, fileName, taskIdentifier)` and schedules a commit. + /// + /// - Parameters: + /// - serverUrl: Server URL associated with the transfer. + /// - fileName: File name of the transfer. + /// - taskIdentifier: URLSession task identifier. func removeItem(serverUrl: String, fileName: String, taskIdentifier: Int) { transferStoreIO.sync { if let idx = transferItemsCache.firstIndex(where: { @@ -142,9 +156,13 @@ final class NCTransferStore { transferItemsCache.remove(at: idx) } } + + commit() } - /// Removes the first match by (ocIdTransfer); batched commit. + /// Removes the first item matching `ocIdTransfer` and schedules a commit. + /// + /// - Parameter ocIdTransfer: Transfer identifier used to track upload sessions. func removeItem(ocIdTransfer: String) { transferStoreIO.sync { if let idx = transferItemsCache.firstIndex(where: { @@ -153,9 +171,13 @@ final class NCTransferStore { transferItemsCache.remove(at: idx) } } + + commit() } - /// Removes the first match by (ocId); batched commit. + /// Removes the first item matching `ocId` and schedules a commit. + /// + /// - Parameter ocId: Object identifier (Nextcloud file OCID). func removeItem(ocId: String) { transferStoreIO.sync { if let idx = transferItemsCache.firstIndex(where: { @@ -164,39 +186,37 @@ final class NCTransferStore { transferItemsCache.remove(at: idx) } } - } - /// Forces an immediate flush to disk (e.g., app background/terminate). - private func forceFlush() { - guard let url = self.transferStoreURL else { - return - } - var bgTask: UIBackgroundTaskIdentifier = .invalid - bgTask = UIApplication.shared.beginBackgroundTask(withName: "NCTransferStore.flush") { - UIApplication.shared.endBackgroundTask(bgTask); bgTask = .invalid - } - defer { - if bgTask != .invalid { - UIApplication.shared.endBackgroundTask(bgTask); bgTask = .invalid - } - } + commit() + } + /// Updates `progress` for the item identified by `(serverUrl, fileName, taskIdentifier)`. + /// + /// - Parameters: + /// - serverUrl: The server URL associated with the item. + /// - fileName: The file name associated with the item. + /// - taskIdentifier: URLSession task identifier. + /// - progress: New progress value in `[0.0, 1.0]`. + func transferProgress(serverUrl: String, fileName: String, taskIdentifier: Int, progress: Double) { transferStoreIO.sync { - do { - let data = try encoderTransferItem.encode(transferItemsCache) - try data.write(to: url, options: .atomic) - lastPersist = CFAbsoluteTimeGetCurrent() - changeCounter = 0 - nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .info, message: "Force flush to disk") - } catch { - nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Force flush to disk failed: \(error)") + if let idx = transferItemsCache.firstIndex(where: { + $0.serverUrl == serverUrl && + $0.fileName == fileName && + $0.taskIdentifier == taskIdentifier + }) { + transferItemsCache[idx].progress = progress } } } // MARK: - Private - /// Merge: only non-nil fields from `new` overwrite existing values. + /// Field-wise merge of two `TransferItem` values preferring non-nil fields from `new`. + /// + /// - Parameters: + /// - existing: Current cached value. + /// - new: New snapshot to merge in. + /// - Returns: The merged `TransferItem`. private func mergeItem(existing: TransferItem, with new: TransferItem) -> TransferItem { return TransferItem( completed: new.completed ?? existing.completed, @@ -215,51 +235,101 @@ final class NCTransferStore { ) } - /// Persist if threshold reached or max latency exceeded. - private func maybeCommit() { - transferStoreIO.sync { - guard let url = self.transferStoreURL else { - return - } - let now = CFAbsoluteTimeGetCurrent() - let tooManyChanges = changeCounter >= batchThreshold - let tooOld = (now - lastPersist) >= maxLatencySec - guard tooManyChanges || tooOld else { - return - } + /// Commits (flushes) the in-memory transfer items cache to disk, optionally forcing the operation. + /// + /// This method safely serializes the `transferItemsCache` and writes it to the file at `transferStoreURL`. + /// The operation is executed synchronously on the dedicated `transferStoreIO` queue to ensure thread safety. + /// + /// Behavior: + /// - When running inside an extension (`#if EXTENSION`), the cache is always written immediately. + /// - In the main app, the cache is written if either: + /// - The app is currently in background (`UIApplication.shared.applicationState == .background`), or + /// - The call is explicitly forced (`forced == true`). + /// + /// The method uses a **batched commit** strategy to limit disk I/O: + /// - The write is skipped unless one of the following thresholds is reached: + /// - `changeCounter >= batchThreshold` (too many in-memory modifications) + /// - `maxLatencySec` seconds have passed since the last persist (`tooOld`) + /// - After a successful write, both `changeCounter` and `lastPersist` are reset. + /// + /// When a flush occurs, the cache is encoded using `encoderTransferItem` and persisted atomically to prevent corruption. + /// Any error during serialization or write is logged through `nkLog` with detailed context. + /// + /// Parameters: + /// - forced: When `true`, bypasses batching and app-state checks to force an immediate disk flush. + /// + /// Side effects: + /// - Triggers an asynchronous call to `syncUploadRealm()` after each disk commit to synchronize the persisted data with Realm. + /// - Logs successful and failed flushes using the tag `NCGlobal.shared.logTagTransferStore`. + /// + /// This method is optimized for reliability during background transitions and efficient I/O behavior under frequent cache updates. + private func commit(forced: Bool = false) { + guard let url = self.transferStoreURL else { + return + } - do { - let data = try encoderTransferItem.encode(transferItemsCache) - try data.write(to: url, options: .atomic) - lastPersist = now - changeCounter = 0 - } catch { - nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Flush to disk failed: \(error)") + func diskStore() { + transferStoreIO.sync { + do { + let data = try encoderTransferItem.encode(transferItemsCache) + try data.write(to: url, options: .atomic) + lastPersist = CFAbsoluteTimeGetCurrent() + changeCounter = 0 + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .info, message: "Force flush to disk") + } catch { + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Force flush to disk failed: \(error)") + } } + return + } + + #if EXTENSION + diskStore() + #else + if UIApplication.shared.applicationState == .background || forced { + diskStore() + } + #endif + + changeCounter &+= 1 + + let tooManyChanges = changeCounter >= batchThreshold + let tooOld = (CFAbsoluteTimeGetCurrent() - lastPersist) >= maxLatencySec + guard tooManyChanges || tooOld else { + return } + diskStore() + Task { await syncUploadRealm() } } + /// Starts the periodic debounce timer that triggers time-based commits. + /// + /// The timer runs on `debounceQueue` and calls `commit()` every `maxLatencySec` seconds. + /// It is idempotent across start calls if `debounceTimer` is already active. private func startDebounceTimer() { let t = DispatchSource.makeTimerSource(queue: debounceQueue) t.schedule(deadline: .now() + .seconds(Int(maxLatencySec)), repeating: .seconds(Int(maxLatencySec))) t.setEventHandler { [weak self] in - self?.maybeCommit() + self?.commit() } t.resume() debounceTimer = t } + /// Stops and releases the debounce timer if present. private func stopDebounceTimer() { debounceTimer?.cancel() debounceTimer = nil } - /// Reloads the entire JSON store from disk synchronously. - /// When this function returns, `transferItemsCache` is guaranteed to be updated. + /// Reloads the on-disk JSON store into the in-memory cache, replacing the current snapshot. + /// + /// This method executes synchronously on `transferStoreIO` to keep mutation serialized. + /// It logs success/failure and clears the cache if the file is empty. private func reloadFromDisk() { guard let url = self.transferStoreURL else { return @@ -284,7 +354,17 @@ final class NCTransferStore { } } + /// Removes items from the in-memory cache that no longer exist in Realm (no match on `ocIdTransfer` nor `ocId`). + /// + /// Skips execution while the app is in background (main app only). + /// Performs a single Realm query using a composed predicate for efficiency, then prunes unmatched items. private func checkOrphaned() { + #if !EXTENSION + if UIApplication.shared.applicationState == .background { + return + } + #endif + let transfers: Set = Set(transferItemsCache.compactMap { $0.ocIdTransfer }) let ocids: Set = Set(transferItemsCache.compactMap { $0.ocId }) @@ -317,8 +397,25 @@ final class NCTransferStore { } } - /// Performs the actual Realm write using your async APIs. + /// Synchronizes completed transfers from the cache into Realm and notifies delegates to refresh UI state. + /// + /// - Note: No-op while the main app is in background. + /// - Upload path: + /// - Finds completed upload transfers, fetches corresponding metadatas by `ocIdTransfer`, + /// updates fields (`uploadDate`, `etag`, `ocId`, `fileId`, `status`, clears session fields), + /// persists via `replaceMetadataAsync`, and removes processed items from the cache. + /// - Download path: + /// - Finds completed download transfers, fetches metadatas by `ocId`, + /// updates fields (`etag`, `status`, clears session fields), + /// persists via `addMetadatasAsync` and `addLocalFilesAsync`, and removes processed items. + /// - Finally, notifies all transfer delegates per `serverUrl` to reload data. private func syncUploadRealm() async { + #if !EXTENSION + if await UIApplication.shared.applicationState == .background { + return + } + #endif + let snapshotUpload: [TransferItem] = transferStoreIO.sync { transferItemsCache.filter { item in if let completed = item.completed, completed { From 3ffb34eb51f60af7e15eb22a56d160b44c2d0d60 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 8 Oct 2025 09:36:34 +0200 Subject: [PATCH 51/74] cod Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCTransferStore.swift | 108 +++++++++++++++------ 1 file changed, 80 insertions(+), 28 deletions(-) diff --git a/iOSClient/Networking/NCTransferStore.swift b/iOSClient/Networking/NCTransferStore.swift index e0f6751929..4b80febeca 100644 --- a/iOSClient/Networking/NCTransferStore.swift +++ b/iOSClient/Networking/NCTransferStore.swift @@ -52,12 +52,14 @@ final class NCTransferStore { // Last successful persist absolute time. private var lastPersist: TimeInterval = 0 // Max number of changes before forcing a persist. - private let batchThreshold: Int = NCBrandOptions.shared.numMaximumProcess / 2 + private let batchThreshold: Int = max(1, NCBrandOptions.shared.numMaximumProcess / 2) // Max elapsed time (seconds) between persists. private let maxLatencySec: TimeInterval = 5 // <- or every 5s at most // Periodic debounce timer. private var debounceTimer: DispatchSourceTimer? + private var observers: [NSObjectProtocol] = [] + /// Initializes the store, loads any existing snapshot from disk, /// configures date strategies and installs lifecycle observers and debounce timer. init() { @@ -88,7 +90,11 @@ final class NCTransferStore { } deinit { - + stopDebounceTimer() + for observer in observers { + NotificationCenter.default.removeObserver(observer) + } + observers.removeAll() } /// Installs observers to align flush behavior with app lifecycle: @@ -96,15 +102,15 @@ final class NCTransferStore { /// - Force a flush on backgrounding. /// - Reload from disk and restart debounce shortly after becoming active. private func setupLifecycleFlush() { - NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { _ in + let willResignActiveNotification = NotificationCenter.default.addObserver(forName: UIApplication.willResignActiveNotification, object: nil, queue: nil) { _ in self.stopDebounceTimer() } - NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in + let didEnterBackgroundNotification = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { _ in self.commit(forced: true) } - NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in + let didBecomeActiveNotification = NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in guard let self else { return } // Force a synchronous reload before anything else self.reloadFromDisk() @@ -115,6 +121,8 @@ final class NCTransferStore { self.startDebounceTimer() } } + + observers = [willResignActiveNotification, didEnterBackgroundNotification, didBecomeActiveNotification] } // MARK: Public API @@ -190,13 +198,24 @@ final class NCTransferStore { commit() } - /// Updates `progress` for the item identified by `(serverUrl, fileName, taskIdentifier)`. + /// Updates the transfer progress for a specific item and triggers periodic persistence. + /// + /// This method locates the in-memory `TransferItem` matching the provided + /// `(serverUrl, fileName, taskIdentifier)` tuple and updates its `progress` value. + /// The operation is performed synchronously on the `transferStoreIO` queue + /// to maintain thread-safe access to the cache. + /// + /// After updating the value, a conditional commit is triggered to limit + /// disk I/O: + /// - Progress values of **0.0** or **1.0** (start or completion) always trigger a flush. + /// - Intermediate progress triggers a flush only when reaching a multiple of **10%** + /// (e.g. 0.1, 0.2, 0.3, ...), ensuring periodic persistence during long transfers. /// /// - Parameters: - /// - serverUrl: The server URL associated with the item. - /// - fileName: The file name associated with the item. - /// - taskIdentifier: URLSession task identifier. - /// - progress: New progress value in `[0.0, 1.0]`. + /// - serverUrl: The server URL associated with the transfer. + /// - fileName: The file name associated with the transfer. + /// - taskIdentifier: The unique URLSession task identifier. + /// - progress: The new progress value, normalized in the range `[0.0, 1.0]`. func transferProgress(serverUrl: String, fileName: String, taskIdentifier: Int, progress: Double) { transferStoreIO.sync { if let idx = transferItemsCache.firstIndex(where: { @@ -207,6 +226,10 @@ final class NCTransferStore { transferItemsCache[idx].progress = progress } } + + if progress == 0 || progress == 1 || (progress * 100).truncatingRemainder(dividingBy: 10) == 0 { + commit() + } } // MARK: - Private @@ -267,14 +290,19 @@ final class NCTransferStore { guard let url = self.transferStoreURL else { return } + var didWrite = false func diskStore() { transferStoreIO.sync { do { + // Ensure directory exists + try FileManager.default.createDirectory(at: url.deletingLastPathComponent(), withIntermediateDirectories: true) + let data = try encoderTransferItem.encode(transferItemsCache) try data.write(to: url, options: .atomic) lastPersist = CFAbsoluteTimeGetCurrent() changeCounter = 0 + didWrite = true nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .info, message: "Force flush to disk") } catch { nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Force flush to disk failed: \(error)") @@ -284,25 +312,25 @@ final class NCTransferStore { } #if EXTENSION - diskStore() + diskStore() #else - if UIApplication.shared.applicationState == .background || forced { + if appIsInBackground() || forced { diskStore() } #endif - changeCounter &+= 1 + if !didWrite { + changeCounter &+= 1 - let tooManyChanges = changeCounter >= batchThreshold - let tooOld = (CFAbsoluteTimeGetCurrent() - lastPersist) >= maxLatencySec - guard tooManyChanges || tooOld else { - return + let tooManyChanges = changeCounter >= batchThreshold + let tooOld = (CFAbsoluteTimeGetCurrent() - lastPersist) >= maxLatencySec + if tooManyChanges || tooOld { + diskStore() + } } - diskStore() - - Task { - await syncUploadRealm() + if didWrite { + Task { await syncUploadRealm() } } } @@ -311,6 +339,10 @@ final class NCTransferStore { /// The timer runs on `debounceQueue` and calls `commit()` every `maxLatencySec` seconds. /// It is idempotent across start calls if `debounceTimer` is already active. private func startDebounceTimer() { + guard debounceTimer == nil else { + return + } + let t = DispatchSource.makeTimerSource(queue: debounceQueue) t.schedule(deadline: .now() + .seconds(Int(maxLatencySec)), repeating: .seconds(Int(maxLatencySec))) t.setEventHandler { [weak self] in @@ -347,7 +379,7 @@ final class NCTransferStore { self.transferItemsCache = items // check self.checkOrphaned() - nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .info, message: "JSON loaded from disk)", consoleOnly: true) + nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .info, message: "JSON loaded from disk", consoleOnly: true) } catch { nkLog(tag: NCGlobal.shared.logTagTransferStore, emoji: .error, message: "Load JSON from disk failed: \(error)") } @@ -359,11 +391,9 @@ final class NCTransferStore { /// Skips execution while the app is in background (main app only). /// Performs a single Realm query using a composed predicate for efficiency, then prunes unmatched items. private func checkOrphaned() { - #if !EXTENSION - if UIApplication.shared.applicationState == .background { + if appIsInBackground() { return } - #endif let transfers: Set = Set(transferItemsCache.compactMap { $0.ocIdTransfer }) let ocids: Set = Set(transferItemsCache.compactMap { $0.ocId }) @@ -410,11 +440,9 @@ final class NCTransferStore { /// persists via `addMetadatasAsync` and `addLocalFilesAsync`, and removes processed items. /// - Finally, notifies all transfer delegates per `serverUrl` to reload data. private func syncUploadRealm() async { - #if !EXTENSION - if await UIApplication.shared.applicationState == .background { + if appIsInBackground() { return } - #endif let snapshotUpload: [TransferItem] = transferStoreIO.sync { transferItemsCache.filter { item in @@ -511,4 +539,28 @@ final class NCTransferStore { } } } + + // MARK: - Private utility + + #if !EXTENSION + @inline(__always) + private func appIsInBackground() -> Bool { + if Thread.isMainThread { + return UIApplication.shared.applicationState == .background + } else { + var isBg = false + DispatchQueue.main.sync { + isBg = (UIApplication.shared.applicationState == .background) + } + return isBg + } + } + #else + + @inline(__always) + private func appIsInBackground() -> Bool { + // In extensions we treat "background" checks as false by convention + return false + } + #endif } From 08f7044aa7d93bf24e23d2c7d24738bc92a3eba5 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Wed, 8 Oct 2025 09:40:58 +0200 Subject: [PATCH 52/74] cod Signed-off-by: Marino Faggiana --- iOSClient/Networking/NCTransferStore.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/iOSClient/Networking/NCTransferStore.swift b/iOSClient/Networking/NCTransferStore.swift index 4b80febeca..964c009769 100644 --- a/iOSClient/Networking/NCTransferStore.swift +++ b/iOSClient/Networking/NCTransferStore.swift @@ -5,6 +5,13 @@ import UIKit import NextcloudKit +/// A lightweight transactional store based on JSON, implementing batched commits and atomic writes. +/// Acts as an in-memory document-oriented micro-database synchronized to disk. +/// Designed for efficient, low-latency persistence of transient transfer metadata with strong consistency guarantees +/// between in-memory state and its file-backed representation. +/// +/// Version 0.1 - October 2025 by Marino Faggiana + // MARK: - Transfer Store (batched persistence) /// Immutable transfer item snapshot used by the Transfer Store. From c654433c276fe37afd913d61ab13cbf21d29624b Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 10 May 2023 14:22:25 +0530 Subject: [PATCH 53/74] nmc 2023 - privacy policy localization changes added --- .../Supporting Files/en.lproj/Localizable.strings | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 9fb23b1958..56fca54a7a 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -733,3 +733,16 @@ You can stop it at any time, adjust the settings, and enable it again."; "_scanning_files_" = "Scanning files …"; "_moving_items_to_domain_" = "Moving items to correct domain …"; "_finishing_up_" = "Finishing up …"; + +// MARK: Privacy Policy +"_privacy_settings_title_" = "Privacy Settings"; +"_privacy_help_text_after_login_" = "This app uses Cookies and similiar technolgies (tools). By clicking Accept, you accept the processing and also the Transfer of your data to third parties. The data will be used for Analysis, retargeting and to Display personalized Content and Advertising on sites and third-party sites. You can find further informatin, including Information on data processing by third-party Providers, in the settings and in our Privacy Policy.You can reject the use of the Tools or customize them at any time in the Settings."; +"_key_privacy_help_" = "Privacy Policy"; +"_key_reject_help_" = "reject"; +"_key_settings_help_" = "Settings"; +"_accept_button_title_" = "Accept"; +"_privacy_settings_help_text_" = "To optimize your app, we collect anonymous data. For this we use software solutions of different partners. We would like to give you full transparency and decision-making power over the processin and collection of your anonymized usage data. You can also change your settings at any time later in the app settings under data protection. Please note, however, that data collection makes a considerable contribution to the optimization of this app and you prevent this optimization by preventing data transmission."; +"_required_data_collection_" = "Required data collection"; +"_required_data_collection_help_text_" = "The collection of this data is necessary to be able to use essential functions of the app."; +"_analysis_data_acqusition_" = "Analysis-data acqusition for the design"; +"_analysis_data_acqusition_help_text_" = "This data helps us to optimize the app usage for you and to identify system crashes and errors more quickly."; From af87b756072be4375dd108fc42306cf5d443936a Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 10 May 2023 14:25:01 +0530 Subject: [PATCH 54/74] NMC 2023 german translation update (+35 squashed commits) Squashed commits: [353bf009c] NMC 2023 - update missing localised strings for german languages [a754bbf24] NMC 2023 - Localisation change for empty filename alert [3170e8b5b] NMC 2023 - Localisation changes for auto upload description [ab885ab8e] NMC 2023 - NMC 2580 App Updater strings added [bfd0d6653] NMC 2023 - (nmc 2397) Strings update [872a80881] nmc 2023 - re sharing string update [9a3e27542] NMC 2023 - share strings update [b2d3c952f] NMC 2023 "Details" string changed [3450bb2ab] NMC 2023 Two localizable strings added for dashboard [c0f74faf0] nmc 2023 - image video upload localisation related changes [668332a07] nmc 2023 - more tab localisation changes [3489a28ab] nmc 2023 - localisation update for share text field [d7eb28e6b] nmc 2023 - sharing feature localisation strings update [1857be058] nmc 2023 - E2e and onboarding Internet not available strings update [6a6534546] nmc 2023 - scan cluster localisation changes [ed6b28265] NMC 1990 Settings cluster Localization strings added [d951a89ff] nmc 2023 - collabora localization changes added [f9656c196] nmc 2023 - privacy policy localization changes added [39fb24a46] NMC 2023 - update missing localised strings for german languages [3a190b508] NMC 2023 - Localisation change for empty filename alert [1ce0969b6] NMC 2023 - Localisation changes for auto upload description [bc178c063] NMC 2023 - NMC 2580 App Updater strings added [daee67130] NMC 2023 - (nmc 2397) Strings update [0e170564d] nmc 2023 - re sharing string update [fb10cb28b] NMC 2023 - share strings update [2b6a76baf] NMC 2023 "Details" string changed [05e6c22f9] NMC 2023 Two localizable strings added for dashboard [9713606b7] nmc 2023 - image video upload localisation related changes [18332eb52] nmc 2023 - more tab localisation changes [9aeba2a4f] nmc 2023 - localisation update for share text field [92836cf7b] nmc 2023 - sharing feature localisation strings update [0ff7d2b7f] nmc 2023 - E2e and onboarding Internet not available strings update [e2d677b4d] nmc 2023 - scan cluster localisation changes [5d59992d2] NMC 1990 Settings cluster Localization strings added [a3d0a2217] nmc 2023 - collabora localization changes added --- .../en.lproj/Localizable.strings | 186 ++++++++++++++++-- 1 file changed, 170 insertions(+), 16 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 56fca54a7a..08c2945cf6 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -64,6 +64,12 @@ "_audio_" = "Audio"; "_unknown_" = "Unknown"; "_success_" = "Success"; +"_initialization_" = "Initialization"; +"_experimental_" = "Experimental"; +"_select_dir_media_tab_" = "Select as folder \"Media\""; +"_error_creation_file_" = "Oops! Could not create the file"; +"_save_path_" = "Storage path"; +"_save_settings_" = "Save settings"; "_mode_filename_" = "Filename mode"; "_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; "_warning_unsupported_" = "You are using an unsupported version of Nextcloud. For the safety of your data we strongly recommend updating to the latest stable Nextcloud."; @@ -77,11 +83,19 @@ "_the_account_" = "The account"; "_does_not_exist_" = "does not exist"; "_sharing_" = "Sharing"; -"_details_" = "Details"; -"_no_permission_add_file_" = "You don't have permission to add files."; -"_no_permission_delete_file_" = "You don't have permission to delete files."; -"_no_permission_modify_file_" = "You don't have permission to modify files."; -"_no_permission_favorite_file_" = "You don't have permission to add the file to your favorites."; +"_details_" = "Share"; +"_sub_details_" = "Subscription Details"; +"_subscriptions_" = "Subscriptions"; +"_dark_mode_" = "Dark mode"; +"_dark_mode_detect_" = "Detect iOS dark mode"; +"_screen_" = "Screen"; +"_wipe_account_" = "Account is wiped from server"; +"_appconfig_view_title_" = "Account configuration in progress …"; +"_no_permission_add_file_" = "You don't have permission to add files"; +"_no_permission_delete_file_" = "You don't have permission to delete files"; +"_no_permission_modify_file_" = "You don't have permission to modify files"; +"_no_permission_favorite_file_" = "You don't have permission to add the file to your favorites"; +"_request_upload_new_ver_" = "The file has been modified, do you want to upload the new version?"; "_add_folder_info_" = "Add folder info"; "_back_" = "Back"; "_search_" = "Search"; @@ -124,7 +138,6 @@ /* MARK: Files lock */ -"_lock_" = "Lock"; "_unlock_" = "Unlock"; "_lock_file_" = "Lock file"; "_unlock_file_" = "Unlock file"; @@ -147,6 +160,10 @@ "_source_code_" = "Get source code"; "_account_select_" = "Select the account"; "_account_select_to_add_" = "Select the account to add"; +"_host_insert_" = "Insert the host name, for example:"; +"_certificate_not_found_" = "File %@ in documents directory not found."; +"_copy_failed_" = "Copy failed"; +"_certificate_installed_" = "Certificate installed"; "_remove_local_account_" = "Remove local account"; "_want_delete_account_" = "Do you want to remove local account?"; "_prevent_http_redirection_"= "The redirection in HTTP is not permitted."; @@ -219,7 +236,9 @@ "_notifications_" = "Notifications"; "_quota_space_unlimited_" = "Unlimited"; "_quota_space_unknown_" = "Unknown"; -"_quota_using_" = "You are using %@ of %@"; +"_quota_using_" = "%@ "; +"_quota_using_of_" = "of %@"; +"_quota_using_percentage_" = "Memory to %@ occupied"; "_acknowledgements_" = "Acknowledgements"; "_settings_" = "Settings"; "_enter_password_" = "Enter password …"; @@ -228,17 +247,33 @@ "_lock_not_active_" = "Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; "_enable_touch_face_id_" = "Enable Touch/Face ID"; +"_security_" = "Security"; +"_data_protection_" = "Data protection"; +"_privacy_settings_" = "Privacy Settings"; +"_used_opensource_software_" = "OpenSource software used"; +"_service_" = "Service"; +"_imprint_" = "Imprint"; +"_magentacloud_version_" = "MagentaCLOUD Version"; +"_url_" = "URL"; +"_username_" = "Username"; +"_change_credentials_" = "Change your credentials"; "_wifi_only_" = "Only use Wi-Fi connection"; "_settings_autoupload_" = "Auto upload"; "_clear_cache_" = "Clear cache"; "_clear_cache_footer_" = "Clear downloaded and offline files from the app"; -"_exit_" = "Reset application"; "_exit_footer_" = "Remove all accounts and local data from the app."; +"_exit_" = "Logout"; +"_funct_not_enabled_" = "Functionality not enabled"; +"_passcode_activate_" = "Password lock on"; +"_disabling_passcode_" = "Removing password lock"; "_want_exit_" = "Attention! Will be reset to initial state. Continue?"; "_want_delete_cache_" = "Do you want to delete the cache (this also removes the transfers in progress)?"; "_add_account_" = "Add account"; "_want_delete_" = "You will delete the following: "; "_want_leave_share_" = "You will leave the following shares: "; +"_delete_account_" = "Remove account"; +"_delete_active_account_" = "Remove active account"; +//"_want_delete_" = "Do you really want to delete?"; "_no_delete_" = "No, do not delete"; "_yes_delete_" = "Yes, delete"; "_information_" = "Information"; @@ -254,7 +289,16 @@ "_autoupload_photos_" = "Auto upload photos"; "_autoupload_videos_" = "Auto upload videos"; "_autoupload_favorites_" = "Auto upload favorites only"; -"_autoupload_description_" = "New photos/videos will be automatically uploaded to your server."; +"_autoupload_description_" = "New photos/videos will be automatically uploaded to your MagentaCLOUD"; +"_autoupload_description_background_" = "This option requires the use of GPS to trigger the detection of new photos/videos in the camera roll once the location changes significantly"; +"_autoupload_background_title_" = "Limitations"; +"_autoupload_background_msg_" = "Due to iOS restrictions, it is not yet possible to perform background processes, unless GPS services are activated. Once the cell in the cellular network is changed, the system wakes up for a short time and checks for new photos to upload to the cloud."; +"_autoupload_change_location_" = "Change folder"; +"_autoupload_location_now_" = "Folder"; +"_autoupload_location_default_" = "Restore default folder"; +"_autoupload_change_location_footer_" = "Change folder used for \"Automatic upload of camera photos\" (if the option is enabled)"; +"_autoupload_not_select_home_" = "Select a folder"; +"_autoupload_save_album_" = "Copy photo or video into the photo album"; "_autoupload_fullphotos_" = "Upload the whole camera roll"; "_start_autoupload_" = "Turn on auto upload"; "_stop_autoupload_" = "Turn off auto upload"; @@ -289,6 +333,9 @@ "_login_url_error_" = "URL error, please verify your server URL"; "_favorites_" = "Favorites"; "_favorite_short_" = "Favorite"; +"_favorite_" = "Favorite"; +"_unfavorite_" = "Unfavorite"; +"_no_files_uploaded_" = "No files uploaded"; "_tutorial_favorite_view_" = "Files and folders you mark as favorites will show up here"; "_tutorial_offline_view_" = "Files copied here will be available offline.\n\nThey will be synchronized with your cloud."; "_tutorial_groupfolders_view_" = "No Group folders yet"; @@ -348,7 +395,6 @@ "_files_no_files_" = "No files in here"; "_files_no_folders_" = "No folders in here"; "_request_in_progress_" = "Request to server in progress …"; -"_personal_files_only_" = "Personal files only"; "audio" = "AUDIO"; "directory" = "FOLDERS"; @@ -374,6 +420,12 @@ "_media_viewimage_show_" = "Show only images"; "_media_viewvideo_show_" = "Show only video"; "_media_show_all_" = "Show both"; +"_media_by_created_date_" = "Sort by created date"; +"_media_by_upload_date_" = "Sort by upload date"; +"_media_by_modified_date_" = "Sort by modified date"; +"_insert_password_pfd_" = "Secured PDF. Enter password"; +"_password_pdf_error_" = "Wrong password"; +"_error_download_photobrowser_" = "Error: Unable to download photo"; "_good_morning_" = "Good morning"; "_good_day_" = "Good day"; "_good_afternoon_" = "Good afternoon"; @@ -392,20 +444,57 @@ "_user_sharee_footer_" = "Tap to change permissions"; "_enforce_password_protection_" = "Enforce password protection"; "_shared_with_you_by_" = "Shared with you by"; -"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; +//"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; +//"_new_comment_" = "New comment …"; +//"_edit_comment_" = "Edit comment"; +//"_delete_comment_" = "Delete comment"; +//"_share_read_only_" = "View only"; +//"_share_editing_" = "Can edit"; +"_share_reshare_allowed_" = "Resharing is allowed."; +"_share_reshare_not_allowed_" = "Resharing is not allowed."; +"_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; +"_create_link_" = "Create Link"; +"personal_share_by_mail" = "Personal share by mail"; +"_your_shares_" = "Your Shares"; +"_share_linklabel_" = "Link '%@'"; +"_share_link_folder_" = "Link to folder"; +"_share_link_file_" = "Link to file"; +"no_shares_created" = "No shares created yet."; +"_advance_permissions_" = "Advanced permissions"; +"_send_new_email_" = "Send new email"; +"_apply_changes_" = "Apply changes"; +"_send_share_" = "Send share"; +"_PERMISSIONS_" = "PERMISSIONS"; +"share_editing_message" = "There are no editing functions for files unless you share them with MagentaCLOUD users."; +"_file_drop_message_" = "With File drop, only uploading is allowed. Only you can see files and folders that have been uploaded."; +"_custom_link_label" = "Your custom link label"; +"_set_password_" = "Set password"; +"_share_expiration_date_placeholder_"= "Expiration date for this share"; +"_share_download_limit_" = "Download Limit"; +"_share_download_limit_placeholder_" = "Enter download limit"; +"_share_download_limit_alert_empty_" = "Download limit cannot be empty."; +"_share_download_limit_alert_zero_" = "Download limit should be greater than 0."; +"_share_remaining_download_" = "Downloads:"; +"_share_read_only_" = "Read only"; +"_share_editing_" = "Can edit"; +"_share_file_drop_" = "Filedrop only"; +//"_share_file_drop_" = "File request"; +"_share_hide_download_" = "Prevent download"; +"_share_note_recipient_" = "YOUR MESSAGE"; +"_shareLinksearch_placeholder_" = "Contact name or email address"; +"_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; "_new_comment_" = "New comment …"; "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; -"_share_read_only_" = "View only"; -"_share_editing_" = "Can edit"; +"_share_allow_editing_" = "Allow editing"; "_share_allow_upload_" = "Allow upload and editing"; -"_share_file_drop_" = "File request"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; "_share_hide_download_" = "Hide download"; "_share_allowed_downloads_" = "Allowed downloads"; "_share_limit_download_" = "Limit downloads"; "_remaining_" = "%d remaining"; "_share_password_protect_" = "Password protection"; +//"_share_password_protect_" = "Password protect"; "_share_expiration_date_" = "Set expiration date"; "_share_note_recipient_" = "Note to recipient"; "_share_add_sharelink_" = "Add another link"; @@ -423,6 +512,12 @@ "_remote_" = "Remote"; "_remote_group_" = "Remote group"; "_conversation_" = "Conversation"; +//"_share_can_reshare_" = "Allow resharing"; +//"_share_can_create_" = "Allow creating"; +//"_share_can_change_" = "Allow editing"; +//"_share_can_delete_" = "Allow deleting"; +//"_share_unshare_" = "Unshare"; +//"_share_can_download_" = "Allow download"; "_no_transfer_" = "No transfers yet"; "_no_transfer_sub_" = "Uploads and downloads from this device will show up here"; @@ -464,8 +559,34 @@ "_e2e_remove_folder_encrypted_" = "Decrypt"; "_e2e_file_encrypted_" = "File encrypted"; "_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption"; -"_e2e_error_" = "An internal end-to-end encryption error occurred."; -"_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred."; +"_e2e_error_" = "An internal end-to-end encryption error occurred"; +"_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred"; +"_e2e_delete_folder_not_permitted_" = "Deletion of the directory marked as \"encrypted\" is not allowed"; +"_e2e_error_encode_metadata_" = "Serious internal error in encoding metadata"; +"_e2e_error_decode_metadata_" = "Serious internal error in decoding metadata"; +"_e2e_error_create_encrypted_" = "Could not create encrypted file"; +"_e2e_error_update_metadata_" = "Update metadata error"; +"_e2e_error_store_metadata_" = "Could not save metadata"; +"_e2e_error_send_metadata_" = "Could not send metadata"; +"_e2e_error_delete_metadata_" = "Could not delete metadata"; +"_e2e_error_get_metadata_" = "Could not fetch metadata"; +"_e2e_error_not_enabled_" = "Serious internal error. End-to-end encryption not enabled"; +"_e2e_error_record_not_found_" = "Serious internal error. Records not found"; +"_e2e_error_unlock_" = "Could not unlock folder"; +"_e2e_error_lock_" = "Could not lock folder"; +"_e2e_error_delete_mark_folder_" = "Decrypt marked folder"; +"_e2e_error_mark_folder_" = "Encrypt folder"; +"_e2e_error_directory_not_empty_" = "The directory is not empty"; +"_e2e_error_not_move_" = "It is not possible move files to encrypted directory"; +"_e2e_error_not_versionwriteable_" = "The E2EE version of the server is not compatible with this client"; +"_start_e2e_encryption_1_" = "To set up end-to-end encryption, they must first set up the PIN lock to prevent unauthorised people from accessing your key."; +"_start_e2e_encryption_2_" = "After starting the encryption, a randomly generated word sequence (passphrase) of 12 words is displayed. This remains in this app and can be displayed again. Nevertheless, we recommend that you write down the passphrase."; +"_start_e2e_encryption_3_" = "The passphrase is your personal password with which you can access encrypted data in your MagentaCLOUD or enable access to these files on other devices such as your PC."; +"_read_passphrase_description_" = "Here you can display the passphrase again and also copy it. You need the passphrase if you want to decrypt the data on another device with access to MagentaCLOUD, for example your PC or another smartphone or tablet."; +"_remove_passphrase_desc_1_" = "You can remove the passphrase on this device. This will not affect the encrypted content, but this device will no longer be able to decrypt your data."; +"_remove_passphrase_desc_2_" = "You can re-enter the passphrase here at any time to ensure access to your encrypted content from this device."; +"_e2e_error_incorrect_passphrase_" = "Wrong password?"; +"_e2e_error_passphrase_title" = "Error while decrypting."; "_scans_document_" = "Scan document"; "_scanned_images_" = "Scanned images"; "_scan_document_pdf_page_" = "Page"; @@ -746,3 +867,36 @@ You can stop it at any time, adjust the settings, and enable it again."; "_required_data_collection_help_text_" = "The collection of this data is necessary to be able to use essential functions of the app."; "_analysis_data_acqusition_" = "Analysis-data acqusition for the design"; "_analysis_data_acqusition_help_text_" = "This data helps us to optimize the app usage for you and to identify system crashes and errors more quickly."; + +// MARK: Collabora +"_prefix_upload_path_" = "MagentaCLOUD/"; +"_please_enter_file_name_" = "Please enter the file name"; + +// MARK: Scan +"_location_" = "Location"; +"_save_with_text_recognition_" = "SAVE WITH TEXT RECOGNITION (OCR)"; +"_pdf_with_ocr_" = "PDF (OCR)"; +"_text_file_ocr_" = "Textfile (txt)"; +"_save_without_text_recognition_" = "SAVE WITHOUT TEXT RECOGNITION"; +"_pdf_" = "PDF"; +"_jpg_" = "JPG"; +"_png_" = "PNG"; +"_set_password_" = "Set password"; +"_no_password_warn_" = "Please enter a password for the PDF you want to create or disable the function."; +"_saved_info_alert_" = "Saving will take some time, especially if you have selected several pages and file formats."; +"_no_file_type_selection_error_" = "Please select at least one filetype"; +"_no_internet_alert_message_" = "A data connection is not currently allowed."; +"_no_internet_alert_title_" = "Connection error"; +"_auto_upload_help_text_" = "With this option, you upload your photos or videos to the same folder that you selected for \"Automatic upload.\""; + +// MARK: Dashboard +"_shared_" = "Shared"; +"_recieved_" = "Received"; + +// MARK: App Updater +"update_available" = "Update available"; +"update_description" = "MagentaCLOUD version %@ is now available"; +"update" = "Update"; +"not_now" = "Not Now"; + +"_prompt_insert_file_name" = "Please enter filename"; From 6726d99d12d818e59733518e7af7c0008dfe6d78 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Fri, 19 May 2023 18:08:17 +0530 Subject: [PATCH 55/74] nmc 2023 - scan cluster localisation changes --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 08c2945cf6..30b1e54abe 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -874,6 +874,8 @@ You can stop it at any time, adjust the settings, and enable it again."; // MARK: Scan "_location_" = "Location"; +"_prefix_upload_path_" = "MagentaCLOUD/"; + "_save_with_text_recognition_" = "SAVE WITH TEXT RECOGNITION (OCR)"; "_pdf_with_ocr_" = "PDF (OCR)"; "_text_file_ocr_" = "Textfile (txt)"; From f52e432d106149d80e5b9bd03e58011ad4ab3dfc Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Tue, 6 Jun 2023 22:57:35 +0530 Subject: [PATCH 56/74] nmc 2023 - sharing feature localisation strings update --- .../xcschemes/File Provider Extension UI.xcscheme | 1 - .../xcschemes/WidgetDashboardIntentHandler.xcscheme | 1 - iOSClient/Supporting Files/en.lproj/Localizable.strings | 5 +++++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme b/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme index fd16f58a63..6e1cc593fa 100644 --- a/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme +++ b/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme @@ -73,7 +73,6 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" - askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme b/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme index 485c4c3f00..d912dd669b 100644 --- a/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme +++ b/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme @@ -73,7 +73,6 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" - askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 30b1e54abe..f0eea9aa41 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -82,6 +82,10 @@ "_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@."; "_the_account_" = "The account"; "_does_not_exist_" = "does not exist"; +"_purchase_" = "Purchase"; +"_account_not_exists_" = "The account %@ of %@ does not exist"; +"_error_parameter_schema_" = "Wrong parameters, impossible to continue"; +"_comments_" = "Comments"; "_sharing_" = "Sharing"; "_details_" = "Share"; "_sub_details_" = "Subscription Details"; @@ -452,6 +456,7 @@ //"_share_editing_" = "Can edit"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; +"_shareLinksearch_placeholder_" = "Contact name or email address"; "_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; "_create_link_" = "Create Link"; "personal_share_by_mail" = "Personal share by mail"; From 3fe33aa3ab8e1a617d8ca0af7e8a9e4289aeff47 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 7 Jun 2023 15:20:10 +0530 Subject: [PATCH 57/74] nmc 2023 - localisation update for share text field --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 1 - 1 file changed, 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index f0eea9aa41..46e848d693 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -456,7 +456,6 @@ //"_share_editing_" = "Can edit"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; -"_shareLinksearch_placeholder_" = "Contact name or email address"; "_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; "_create_link_" = "Create Link"; "personal_share_by_mail" = "Personal share by mail"; From 87000807c24b56fb04fc3273eb471ca9d8319e61 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:48:28 +0530 Subject: [PATCH 58/74] NMC 2023 - update missing localised strings for german languages --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 46e848d693..467d044138 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -84,6 +84,9 @@ "_does_not_exist_" = "does not exist"; "_purchase_" = "Purchase"; "_account_not_exists_" = "The account %@ of %@ does not exist"; +"_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@"; +"_the_account_" = "The account"; +"_does_not_exist_" = "does not exist"; "_error_parameter_schema_" = "Wrong parameters, impossible to continue"; "_comments_" = "Comments"; "_sharing_" = "Sharing"; From a99070d52f4f0cdd27412d32e19a0d3bc1b9d503 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 10 May 2023 14:22:25 +0530 Subject: [PATCH 59/74] nmc 2023 - privacy policy localization changes added --- .../en.lproj/Localizable.strings | 49 ++++--------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 467d044138..a5860a5441 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -68,7 +68,7 @@ "_experimental_" = "Experimental"; "_select_dir_media_tab_" = "Select as folder \"Media\""; "_error_creation_file_" = "Oops! Could not create the file"; -"_save_path_" = "Storage path"; +"_save_path_" = "Save path"; "_save_settings_" = "Save settings"; "_mode_filename_" = "Filename mode"; "_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; @@ -90,7 +90,7 @@ "_error_parameter_schema_" = "Wrong parameters, impossible to continue"; "_comments_" = "Comments"; "_sharing_" = "Sharing"; -"_details_" = "Share"; +"_details_" = "Details"; "_sub_details_" = "Subscription Details"; "_subscriptions_" = "Subscriptions"; "_dark_mode_" = "Dark mode"; @@ -145,6 +145,7 @@ /* MARK: Files lock */ +"_lock_" = "Lock"; "_unlock_" = "Unlock"; "_lock_file_" = "Lock file"; "_unlock_file_" = "Unlock file"; @@ -167,7 +168,7 @@ "_source_code_" = "Get source code"; "_account_select_" = "Select the account"; "_account_select_to_add_" = "Select the account to add"; -"_host_insert_" = "Insert the host name, for example:"; +"_host_insert_" = "Insert the hostname, for example:"; "_certificate_not_found_" = "File %@ in documents directory not found."; "_copy_failed_" = "Copy failed"; "_certificate_installed_" = "Certificate installed"; @@ -243,9 +244,7 @@ "_notifications_" = "Notifications"; "_quota_space_unlimited_" = "Unlimited"; "_quota_space_unknown_" = "Unknown"; -"_quota_using_" = "%@ "; -"_quota_using_of_" = "of %@"; -"_quota_using_percentage_" = "Memory to %@ occupied"; +"_quota_using_" = "You are using %@ of %@"; "_acknowledgements_" = "Acknowledgements"; "_settings_" = "Settings"; "_enter_password_" = "Enter password …"; @@ -254,13 +253,6 @@ "_lock_not_active_" = "Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; "_enable_touch_face_id_" = "Enable Touch/Face ID"; -"_security_" = "Security"; -"_data_protection_" = "Data protection"; -"_privacy_settings_" = "Privacy Settings"; -"_used_opensource_software_" = "OpenSource software used"; -"_service_" = "Service"; -"_imprint_" = "Imprint"; -"_magentacloud_version_" = "MagentaCLOUD Version"; "_url_" = "URL"; "_username_" = "Username"; "_change_credentials_" = "Change your credentials"; @@ -427,6 +419,7 @@ "_media_viewimage_show_" = "Show only images"; "_media_viewvideo_show_" = "Show only video"; "_media_show_all_" = "Show both"; +"_media_view_options_" = "View options"; "_media_by_created_date_" = "Sort by created date"; "_media_by_upload_date_" = "Sort by upload date"; "_media_by_modified_date_" = "Sort by modified date"; @@ -489,12 +482,16 @@ "_share_hide_download_" = "Prevent download"; "_share_note_recipient_" = "YOUR MESSAGE"; "_shareLinksearch_placeholder_" = "Contact name or email address"; +"_shareLinksearch_placeholder_" = "Type a name and press Search"; "_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; "_new_comment_" = "New comment …"; "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; "_share_allow_editing_" = "Allow editing"; +"_share_read_only_" = "View only"; +"_share_editing_" = "Editing"; "_share_allow_upload_" = "Allow upload and editing"; +"_share_file_drop_" = "File drop (upload only)"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; "_share_hide_download_" = "Hide download"; "_share_allowed_downloads_" = "Allowed downloads"; @@ -568,32 +565,6 @@ "_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption"; "_e2e_error_" = "An internal end-to-end encryption error occurred"; "_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred"; -"_e2e_delete_folder_not_permitted_" = "Deletion of the directory marked as \"encrypted\" is not allowed"; -"_e2e_error_encode_metadata_" = "Serious internal error in encoding metadata"; -"_e2e_error_decode_metadata_" = "Serious internal error in decoding metadata"; -"_e2e_error_create_encrypted_" = "Could not create encrypted file"; -"_e2e_error_update_metadata_" = "Update metadata error"; -"_e2e_error_store_metadata_" = "Could not save metadata"; -"_e2e_error_send_metadata_" = "Could not send metadata"; -"_e2e_error_delete_metadata_" = "Could not delete metadata"; -"_e2e_error_get_metadata_" = "Could not fetch metadata"; -"_e2e_error_not_enabled_" = "Serious internal error. End-to-end encryption not enabled"; -"_e2e_error_record_not_found_" = "Serious internal error. Records not found"; -"_e2e_error_unlock_" = "Could not unlock folder"; -"_e2e_error_lock_" = "Could not lock folder"; -"_e2e_error_delete_mark_folder_" = "Decrypt marked folder"; -"_e2e_error_mark_folder_" = "Encrypt folder"; -"_e2e_error_directory_not_empty_" = "The directory is not empty"; -"_e2e_error_not_move_" = "It is not possible move files to encrypted directory"; -"_e2e_error_not_versionwriteable_" = "The E2EE version of the server is not compatible with this client"; -"_start_e2e_encryption_1_" = "To set up end-to-end encryption, they must first set up the PIN lock to prevent unauthorised people from accessing your key."; -"_start_e2e_encryption_2_" = "After starting the encryption, a randomly generated word sequence (passphrase) of 12 words is displayed. This remains in this app and can be displayed again. Nevertheless, we recommend that you write down the passphrase."; -"_start_e2e_encryption_3_" = "The passphrase is your personal password with which you can access encrypted data in your MagentaCLOUD or enable access to these files on other devices such as your PC."; -"_read_passphrase_description_" = "Here you can display the passphrase again and also copy it. You need the passphrase if you want to decrypt the data on another device with access to MagentaCLOUD, for example your PC or another smartphone or tablet."; -"_remove_passphrase_desc_1_" = "You can remove the passphrase on this device. This will not affect the encrypted content, but this device will no longer be able to decrypt your data."; -"_remove_passphrase_desc_2_" = "You can re-enter the passphrase here at any time to ensure access to your encrypted content from this device."; -"_e2e_error_incorrect_passphrase_" = "Wrong password?"; -"_e2e_error_passphrase_title" = "Error while decrypting."; "_scans_document_" = "Scan document"; "_scanned_images_" = "Scanned images"; "_scan_document_pdf_page_" = "Page"; From e982c3affada9ddf3f9e9fc40653a5afc379c947 Mon Sep 17 00:00:00 2001 From: Shweta Waikar Date: Thu, 11 May 2023 16:15:49 +0530 Subject: [PATCH 60/74] NMC 1990 Settings cluster Localization strings added --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index a5860a5441..fea7a5db2e 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -253,6 +253,13 @@ "_lock_not_active_" = "Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; "_enable_touch_face_id_" = "Enable Touch/Face ID"; +"_security_" = "Security"; +"_data_protection_" = "Data protection"; +"_privacy_settings_" = "Privacy Settings"; +"_used_opensource_software_" = "OpenSource software used"; +"_service_" = "Service"; +"_imprint_" = "Imprint"; +"_magentacloud_version_" = "MagentaCLOUD Version"; "_url_" = "URL"; "_username_" = "Username"; "_change_credentials_" = "Change your credentials"; From c46af24676509102232f4990e566a29fdf09f9b8 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 24 May 2023 18:13:44 +0530 Subject: [PATCH 61/74] nmc 2023 - E2e and onboarding Internet not available strings update --- .../en.lproj/Localizable.strings | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index fea7a5db2e..ea9297fcf2 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -572,6 +572,30 @@ "_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption"; "_e2e_error_" = "An internal end-to-end encryption error occurred"; "_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred"; +"_e2e_delete_folder_not_permitted_" = "Deletion of the directory marked as \"encrypted\" is not allowed"; +"_e2e_error_encode_metadata_" = "Serious internal error in encoding metadata"; +"_e2e_error_decode_metadata_" = "Serious internal error in decoding metadata"; +"_e2e_error_create_encrypted_" = "Could not create encrypted file"; +"_e2e_error_update_metadata_" = "Update metadata error"; +"_e2e_error_store_metadata_" = "Could not save metadata"; +"_e2e_error_send_metadata_" = "Could not send metadata"; +"_e2e_error_delete_metadata_" = "Could not delete metadata"; +"_e2e_error_get_metadata_" = "Could not fetch metadata"; +"_e2e_error_not_enabled_" = "Serious internal error. End-to-end encryption not enabled"; +"_e2e_error_record_not_found_" = "Serious internal error. Records not found"; +"_e2e_error_unlock_" = "Could not unlock folder"; +"_e2e_error_lock_" = "Could not lock folder"; +"_e2e_error_delete_mark_folder_" = "Decrypt marked folder"; +"_e2e_error_mark_folder_" = "Encrypt folder"; +"_e2e_error_directory_not_empty_" = "The directory is not empty"; +"_e2e_error_not_move_" = "It is not possible move files to encrypted directory"; +"_e2e_error_not_versionwriteable_" = "The E2EE version of the server is not compatible with this client"; +"_start_e2e_encryption_1_" = "To set up end-to-end encryption, they must first set up the PIN lock to prevent unauthorised people from accessing your key."; +"_start_e2e_encryption_2_" = "After starting the encryption, a randomly generated word sequence (passphrase) of 12 words is displayed. This remains in this app and can be displayed again. Nevertheless, we recommend that you write down the passphrase."; +"_start_e2e_encryption_3_" = "The passphrase is your personal password with which you can access encrypted data in your MagentaCLOUD or enable access to these files on other devices such as your PC."; +"_read_passphrase_description_" = "Here you can display the passphrase again and also copy it. You need the passphrase if you want to decrypt the data on another device with access to MagentaCLOUD, for example your PC or another smartphone or tablet."; +"_remove_passphrase_desc_1_" = "You can remove the passphrase on this device. This will not affect the encrypted content, but this device will no longer be able to decrypt your data."; +"_remove_passphrase_desc_2_" = "You can re-enter the passphrase here at any time to ensure access to your encrypted content from this device."; "_scans_document_" = "Scan document"; "_scanned_images_" = "Scanned images"; "_scan_document_pdf_page_" = "Page"; From 0c719d3762177fd58e743778aac8d1c5727e4a86 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Tue, 6 Jun 2023 22:57:35 +0530 Subject: [PATCH 62/74] nmc 2023 - sharing feature localisation strings update --- .../Supporting Files/en.lproj/Localizable.strings | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index ea9297fcf2..6fad33c05a 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -85,8 +85,7 @@ "_purchase_" = "Purchase"; "_account_not_exists_" = "The account %@ of %@ does not exist"; "_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@"; -"_the_account_" = "The account"; -"_does_not_exist_" = "does not exist"; +"_account_not_exists_" = "The account %@ of %@ does not exist"; "_error_parameter_schema_" = "Wrong parameters, impossible to continue"; "_comments_" = "Comments"; "_sharing_" = "Sharing"; @@ -459,6 +458,7 @@ //"_share_editing_" = "Can edit"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; +"_shareLinksearch_placeholder_" = "Contact name or email address"; "_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; "_create_link_" = "Create Link"; "personal_share_by_mail" = "Personal share by mail"; @@ -490,6 +490,7 @@ "_share_note_recipient_" = "YOUR MESSAGE"; "_shareLinksearch_placeholder_" = "Contact name or email address"; "_shareLinksearch_placeholder_" = "Type a name and press Search"; +//"_shareLinksearch_placeholder_" = "Type a name and press Enter"; "_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; "_new_comment_" = "New comment …"; "_edit_comment_" = "Edit comment"; @@ -516,6 +517,11 @@ "_share_can_delete_" = "Delete"; "_share_can_download_" = "Allow download and sync"; "_share_unshare_" = "Delete share"; +//"_share_can_reshare_" = "Allow resharing"; +//"_share_can_create_" = "Allow creating"; +//"_share_can_change_" = "Allow editing"; +//"_share_can_delete_" = "Allow deleting"; +//"_share_unshare_" = "Unshare"; "_share_internal_link_" = "Internal link"; "_share_internal_link_des_" = "Only works for users with access to this file/folder."; "_share_reshare_disabled_" = "You are not allowed to reshare this file/folder."; From b40871e086f25a4690971e19a06acd5456d56b56 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Wed, 7 Jun 2023 15:20:10 +0530 Subject: [PATCH 63/74] nmc 2023 - localisation update for share text field --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 1 - 1 file changed, 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 6fad33c05a..d9af1ffc87 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -458,7 +458,6 @@ //"_share_editing_" = "Can edit"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; -"_shareLinksearch_placeholder_" = "Contact name or email address"; "_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; "_create_link_" = "Create Link"; "personal_share_by_mail" = "Personal share by mail"; From 40e9a4f50e2a06cd647d48f06bc509d636e0a92d Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Fri, 9 Jun 2023 15:27:05 +0530 Subject: [PATCH 64/74] nmc 2023 - more tab localisation changes --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index d9af1ffc87..9e3e071502 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -243,7 +243,9 @@ "_notifications_" = "Notifications"; "_quota_space_unlimited_" = "Unlimited"; "_quota_space_unknown_" = "Unknown"; -"_quota_using_" = "You are using %@ of %@"; +"_quota_using_" = "%@ "; +"_quota_using_of_" = "of %@"; +"_quota_using_percentage_" = "Memory to %@ occupied"; "_acknowledgements_" = "Acknowledgements"; "_settings_" = "Settings"; "_enter_password_" = "Enter password …"; From b26299e175b7788c741cf16b46f52964807aa347 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Mon, 12 Jun 2023 12:49:53 +0530 Subject: [PATCH 65/74] nmc 2023 - image video upload localisation related changes --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 9e3e071502..29dcaf9550 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -68,7 +68,7 @@ "_experimental_" = "Experimental"; "_select_dir_media_tab_" = "Select as folder \"Media\""; "_error_creation_file_" = "Oops! Could not create the file"; -"_save_path_" = "Save path"; +"_save_path_" = "Storage path"; "_save_settings_" = "Save settings"; "_mode_filename_" = "Filename mode"; "_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; From 3a196183c12e36a6c00d9da5d1aea591c12fd6e0 Mon Sep 17 00:00:00 2001 From: Shweta Waikar Date: Fri, 23 Jun 2023 19:08:01 +0530 Subject: [PATCH 66/74] NMC 2023 "Details" string changed --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 29dcaf9550..223e752d5b 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -89,7 +89,7 @@ "_error_parameter_schema_" = "Wrong parameters, impossible to continue"; "_comments_" = "Comments"; "_sharing_" = "Sharing"; -"_details_" = "Details"; +"_details_" = "Share"; "_sub_details_" = "Subscription Details"; "_subscriptions_" = "Subscriptions"; "_dark_mode_" = "Dark mode"; From 7d345538a8ba91e2616a3c342dfb8ef5e105f928 Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Sat, 24 Jun 2023 18:08:38 +0530 Subject: [PATCH 67/74] NMC 2023 - share strings update --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 223e752d5b..2b2bd50e12 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -497,12 +497,10 @@ "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; "_share_allow_editing_" = "Allow editing"; -"_share_read_only_" = "View only"; "_share_editing_" = "Editing"; "_share_allow_upload_" = "Allow upload and editing"; -"_share_file_drop_" = "File drop (upload only)"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; -"_share_hide_download_" = "Hide download"; +//"_share_hide_download_" = "Hide download"; "_share_allowed_downloads_" = "Allowed downloads"; "_share_limit_download_" = "Limit downloads"; "_remaining_" = "%d remaining"; From 76081da2bc172037b547747b98eb3205fba64eba Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Tue, 11 Jul 2023 17:28:52 +0530 Subject: [PATCH 68/74] NMC 2023 - (nmc 2397) Strings update --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 2b2bd50e12..e8c6f4831b 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -601,6 +601,8 @@ "_read_passphrase_description_" = "Here you can display the passphrase again and also copy it. You need the passphrase if you want to decrypt the data on another device with access to MagentaCLOUD, for example your PC or another smartphone or tablet."; "_remove_passphrase_desc_1_" = "You can remove the passphrase on this device. This will not affect the encrypted content, but this device will no longer be able to decrypt your data."; "_remove_passphrase_desc_2_" = "You can re-enter the passphrase here at any time to ensure access to your encrypted content from this device."; +"_e2e_error_incorrect_passphrase_" = "Wrong password?"; +"_e2e_error_passphrase_title" = "Error while decrypting."; "_scans_document_" = "Scan document"; "_scanned_images_" = "Scanned images"; "_scan_document_pdf_page_" = "Page"; From 8a97f3aff32d35f0c716cb22b99bb5836090f668 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Tue, 26 Dec 2023 13:57:32 +0530 Subject: [PATCH 69/74] NMC 2023 - Localisation changes for auto upload description --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 1 - 1 file changed, 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index e8c6f4831b..c7b60b92a8 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -497,7 +497,6 @@ "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; "_share_allow_editing_" = "Allow editing"; -"_share_editing_" = "Editing"; "_share_allow_upload_" = "Allow upload and editing"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; //"_share_hide_download_" = "Hide download"; From 29d4c11423548d8b9e24440b2c181ca8ac122fd7 Mon Sep 17 00:00:00 2001 From: TSI-amrutwaghmare <96108296+TSI-amrutwaghmare@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:48:28 +0530 Subject: [PATCH 70/74] NMC 2023 - update missing localised strings for german languages --- .../Supporting Files/en.lproj/Localizable.strings | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index c7b60b92a8..456b0f40b0 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -85,7 +85,8 @@ "_purchase_" = "Purchase"; "_account_not_exists_" = "The account %@ of %@ does not exist"; "_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@"; -"_account_not_exists_" = "The account %@ of %@ does not exist"; +"_the_account_" = "The account"; +"_does_not_exist_" = "does not exist"; "_error_parameter_schema_" = "Wrong parameters, impossible to continue"; "_comments_" = "Comments"; "_sharing_" = "Sharing"; @@ -144,7 +145,6 @@ /* MARK: Files lock */ -"_lock_" = "Lock"; "_unlock_" = "Unlock"; "_lock_file_" = "Lock file"; "_unlock_file_" = "Unlock file"; @@ -167,7 +167,7 @@ "_source_code_" = "Get source code"; "_account_select_" = "Select the account"; "_account_select_to_add_" = "Select the account to add"; -"_host_insert_" = "Insert the hostname, for example:"; +"_host_insert_" = "Insert the host name, for example:"; "_certificate_not_found_" = "File %@ in documents directory not found."; "_copy_failed_" = "Copy failed"; "_certificate_installed_" = "Certificate installed"; @@ -427,7 +427,6 @@ "_media_viewimage_show_" = "Show only images"; "_media_viewvideo_show_" = "Show only video"; "_media_show_all_" = "Show both"; -"_media_view_options_" = "View options"; "_media_by_created_date_" = "Sort by created date"; "_media_by_upload_date_" = "Sort by upload date"; "_media_by_modified_date_" = "Sort by modified date"; @@ -515,11 +514,6 @@ "_share_can_delete_" = "Delete"; "_share_can_download_" = "Allow download and sync"; "_share_unshare_" = "Delete share"; -//"_share_can_reshare_" = "Allow resharing"; -//"_share_can_create_" = "Allow creating"; -//"_share_can_change_" = "Allow editing"; -//"_share_can_delete_" = "Allow deleting"; -//"_share_unshare_" = "Unshare"; "_share_internal_link_" = "Internal link"; "_share_internal_link_des_" = "Only works for users with access to this file/folder."; "_share_reshare_disabled_" = "You are not allowed to reshare this file/folder."; From 768066eb72d01fa734827d79f691ada026c8942e Mon Sep 17 00:00:00 2001 From: Amrut Waghmare Date: Mon, 26 Aug 2024 16:43:24 +0530 Subject: [PATCH 71/74] NMC 2023 german translation update --- .../xcshareddata/xcschemes/File Provider Extension UI.xcscheme | 1 + .../xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme | 1 + 2 files changed, 2 insertions(+) diff --git a/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme b/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme index 6e1cc593fa..fd16f58a63 100644 --- a/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme +++ b/Nextcloud.xcodeproj/xcshareddata/xcschemes/File Provider Extension UI.xcscheme @@ -73,6 +73,7 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" + askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> diff --git a/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme b/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme index d912dd669b..485c4c3f00 100644 --- a/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme +++ b/Nextcloud.xcodeproj/xcshareddata/xcschemes/WidgetDashboardIntentHandler.xcscheme @@ -73,6 +73,7 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES" + askForAppToLaunch = "Yes" launchAutomaticallySubstyle = "2"> From d75edfb18f7c2488fd1ee55cbc434ee98b66249b Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Mon, 7 Apr 2025 15:08:51 +0530 Subject: [PATCH 72/74] NMC 2023 - update missing localised strings for english and german languages --- .../Supporting Files/en.lproj/Localizable.strings | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 456b0f40b0..5605dd0c43 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -280,7 +280,8 @@ "_want_leave_share_" = "You will leave the following shares: "; "_delete_account_" = "Remove account"; "_delete_active_account_" = "Remove active account"; -//"_want_delete_" = "Do you really want to delete?"; +"_want_delete_" = "Do you really want to delete?"; +"_want_leave_share_" = "You will leave the following shares: "; "_no_delete_" = "No, do not delete"; "_yes_delete_" = "Yes, delete"; "_information_" = "Information"; @@ -315,6 +316,8 @@ "_autoupload_subfolder_granularity_" = "Subfolder Granularity"; "_filenamemask_" = "Change filename mask"; "_filenamemask_footer_" = "By default, when a file is uploaded, it automatically gets the following format: yy-mm-dd hh-mm-ss plus a 4-digit counter. If you do not like this format, you can change it here except for the final 4-digit counter, which cannot be omitted."; +"_autoupload_filenamemask_" = "Change filename mask"; +"_autoupload_filenamemask_footer_" = "Change the automatic filename mask"; "_autoupload_current_folder_" = "Currently selected folder"; "_show_hidden_files_" = "Show hidden files"; "_format_compatibility_" = "Most Compatible"; @@ -331,6 +334,7 @@ "_diagnostics_footer_" = "Changing log level requires a restart of the app to take effect"; "_view_log_" = "View log file"; "_clear_log_" = "Clear log file"; +"_level_log_" = "Set Log level (disabled, standard, maximum)"; "_set_log_level_" = "Set Log level"; "_log_file_clear_alert_" = "Log file cleared \n successfully!"; "_connect_server_anyway_" = "Do you want to connect to the server anyway?"; @@ -358,6 +362,7 @@ "_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background"; "_new_photos_starting_" = "Only photos or videos starting %@ will be uploaded."; "_tutorial_photo_view_" = "No photos or videos uploaded yet"; +"_create_full_upload_" = "Creating archive … May take a long time. During this process, keep the application active during the transfer as well."; "_error_createsubfolders_upload_" = "Error creating subfolders"; "_remove_photo_CameraRoll_" = "Remove from camera roll"; "_remove_photo_CameraRoll_desc_" = "\"Remove from camera roll\" after uploads, a confirmation message will be displayed to delete the uploaded photos or videos from the camera roll. The deleted photos or videos will still be available in the iOS Photos Trash for 30 days."; @@ -402,6 +407,7 @@ "_files_no_files_" = "No files in here"; "_files_no_folders_" = "No folders in here"; "_request_in_progress_" = "Request to server in progress …"; +"_personal_files_only_" = "Personal files only"; "audio" = "AUDIO"; "directory" = "FOLDERS"; @@ -613,6 +619,9 @@ "_empty_trash_" = "Empty trash"; "_trash_no_trash_" = "No files deleted"; "_trash_no_trash_description_" = "You can restore deleted files from here"; +"_trash_restore_selected_" = "Restore selected files"; +"_trash_delete_selected_" = "Delete selected files"; +"_recover_" = "Recover"; "_confirm_delete_selected_" = "Are you sure you want to delete the selected items?"; "_manage_file_offline_" = "Manage offline files"; "_set_available_offline_" = "Set as available offline"; @@ -688,6 +697,8 @@ "_show_more_results_" = "Show more results"; "_waiting_for_" = "Waiting for:"; "_reachable_wifi_" = "network reachable via Wi-Fi or cable"; +"_ITMS-90076_" = "Due to a change in the Nextcloud application identifier, the settings and password for accessing your cloud are reset, so please re-enter your account data and check your Settings. We are sorry about that."; +"_password_not_present_" = "Please re-insert your credentials."; "_copy_passphrase_" = "Copy passphrase"; "_ok_copy_passphrase_" = "OK and copy passphrase"; "_select_color_" = "Select the color"; From 904f220f31b207e576dba5054d452fa9637473a9 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Wed, 16 Apr 2025 17:39:10 +0530 Subject: [PATCH 73/74] NMC 2023 - Updated missing localised strings for English and DE language --- .../en.lproj/Localizable.strings | 477 +++++++++++++++++- 1 file changed, 458 insertions(+), 19 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 5605dd0c43..23d69dad8a 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -20,49 +20,80 @@ // along with this program. If not, see . // +"_itunes_" = "iTunes"; "_cancel_" = "Cancel"; "_edit_" = "Edit"; "_tap_to_cancel_" = "Tap to cancel"; "_cancel_request_" = "Do you want to cancel?"; "_upload_file_" = "Upload file"; "_download_file_" = "Download file"; +"_loading_" = "Loading"; +"_loading_with_points_" = "Loading …"; +"_loading_num_" = "Loading file %i"; +"_loading_autoupload_" = "Auto uploading"; +"_uploading_" = "Uploading"; +"_synchronization_" = "Synchronization"; "_delete_" = "Delete"; +"_delete_file_n_" = "Delete file %i of %i"; "_rename_" = "Rename"; "_rename_file_" = "Rename file"; "_rename_folder_" = "Rename folder"; "_move_" = "Move"; +"_move_file_n_" = "Move file %i of %i"; +"_creating_sharing_" = "Creating share"; +"_updating_sharing_" = "Updating share"; +"_removing_sharing_" = "Removing share"; "_add_" = "Add"; +"_login_" = "Log in"; "_save_" = "Save"; "_warning_" = "Warning"; "_error_" = "Error"; "_no_" = "No"; "_yes_" = "Yes"; "_select_" = "Select"; +"_deselect_" = "Deselect"; "_select_all_" = "Select all"; "_upload_" = "Upload"; "_home_" = "Files"; "_files_" = "Files"; +"_home_dir_" = "Home"; +"_file_to_upload_" = "File to upload"; +"_destination_" = "Destination"; "_ok_" = "OK"; +"_beta_version_" = "Beta version"; +"_function_in_testing_" = "Function in testing, please send information about any problems you run into."; "_done_" = "Done"; "_clear_" = "Clear"; +"_passcode_too_short_" = "Passcode too short, at least 4 characters required"; "_selected_" = "Selected"; +"_scan_fingerprint_" = "Scan fingerprint to authenticate"; "_no_active_account_" = "No account found"; "_info_" = "Info"; "_warning_" = "Warning"; +"_email_" = "Email"; +"_save_exit_" = "Do you want to exit without saving?"; "_video_" = "Video"; "_overwrite_" = "Overwrite"; +"_transfers_in_queue_" = "Transfers in progress, please wait …"; +"_too_errors_upload_" = "Too many errors, please verify the problem"; "_create_" = "Create"; "_create_folder_" = "Create folder"; "_create_folder_e2ee_" = "Create encrypted folder"; +"_create_folder_on_" = "Create folder on"; "_close_" = "Close"; +"_postpone_" = "Postpone"; "_remove_" = "Remove"; "_file_not_found_" = "File not found"; "_continue_" = "Continue"; "_continue_editing_" = "Continue editing"; +"_continue_request_" = "Do you want to continue?"; "_auto_upload_folder_" = "Auto upload"; +"_gallery_" = "Gallery"; "_photo_" = "Photo"; "_audio_" = "Audio"; "_unknown_" = "Unknown"; +"_additional_view_options_" = "Additional view options"; +"_next_" = "Next"; "_success_" = "Success"; "_initialization_" = "Initialization"; "_experimental_" = "Experimental"; @@ -74,10 +105,12 @@ "_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; "_warning_unsupported_" = "You are using an unsupported version of Nextcloud. For the safety of your data we strongly recommend updating to the latest stable Nextcloud."; "_restore_" = "Restore"; +"_camera_roll_" = "Camera roll"; "_tap_here_to_change_" = "Tap here to change"; "_no_albums_" = "No albums"; "_denied_album_" = "This app does not have access to \"Photos\". You can enable access in Privacy Settings."; -"_denied_camera_" = "This app does not have access to the \"Camera\". You can enable access in Privacy Settings."; +"_denied_camera_" = "This app does not have access to \"Camera\". You can enable access in Privacy Settings."; +"_start_" = "Start"; "_force_start_" = "Force the start"; "_account_not_available_" = "The account %@ of %@ does not exist, please add it to be able to read the file %@."; "_the_account_" = "The account"; @@ -107,6 +140,8 @@ "_back_" = "Back"; "_search_" = "Search"; "_of_" = "of"; +"_internal_modify_" = "Edit with internal editor"; +"_database_corrupt_" = "Oops something went wrong, please enter your credentials but don't worry, your files have remained secure"; "_livephoto_save_" = "Save Live Photo to Photo Album"; "_livephoto_save_error_" = "Error during save of Live Photo."; "_livephoto_no_" = "Disable Live Photo"; @@ -122,9 +157,14 @@ "_copy_" = "Copy"; "_now_" = "Now"; "_wait_" = "Please wait …"; +"_attention_" = "Attention"; "_recent_" = "Recent"; "_view_in_folder_" = "View in folder"; "_leave_share_" = "Leave this share"; +"_premium_" = "Premium"; +"_professional_" = "Professional"; +"_current_" = "Current"; +"_buy_" = "Buy"; "_disabled_" = "Disabled"; "_compact_" = "Compact"; "_normal_" = "Normal"; @@ -153,16 +193,35 @@ "_locked_by_" = "Locked by %@"; "_file_locked_no_override_" = "This file is locked. It cannot be overridden."; "_lock_no_permissions_selected_" = "Not allowed for some selected files or folders."; +/* Remove a file from a list, don't delete it entirely */ "_remove_file_" = "Remove file"; + +/* Delete file and put it into the trash */ "_delete_file_" = "Delete file"; "_delete_folder_" = "Delete folder"; +"_delete_photo_" = "Delete photo"; +"_delete_video_" = "Delete video"; +"_automatic_Download_Image_" = "Use images in full resolution"; +"_automatic_Download_Image_footer_" = "When viewing images always download, if not available locally, the images in full resolution"; "_size_" = "Size"; +"_file_size_" = "Exported file size"; +"_dimension_" = "Dimension"; +"_duration_" = "Duration"; +"_model_" = "Model"; "_set_user_status_" = "Set user status"; "_open_settings_" = "Open settings"; +"_rename_ext_title_" = "Change file type?"; +"_rename_ext_message_" = "This file may behave differently if you change it from .%@ to %@"; +"_use_" = "Use"; +"_keep_" = "Keep"; +"_account_request_" = "Request account"; "_settings_account_request_" = "Request account at startup"; +"_print_" = "Print"; "_alias_" = "Alias"; "_alias_placeholder_" = "Write the alias"; "_alias_footer_" = "Give your account names a descriptive name such as Home, Office, School …"; +"_chunk_size_mb_" = "Chunk size in MB"; +"_chunk_footer_title_" = "Chunked file upload (0 is disabled)\nImportant: the chunked upload works only when the app is \"active\"."; "_privacy_legal_" = "Privacy and Legal Policy"; "_source_code_" = "Get source code"; "_account_select_" = "Select the account"; @@ -174,6 +233,8 @@ "_remove_local_account_" = "Remove local account"; "_want_delete_account_" = "Do you want to remove local account?"; "_prevent_http_redirection_"= "The redirection in HTTP is not permitted."; +"_pdf_vertical_" = "PDF vertical display"; +"_pdf_horizontal_" = "PDF horizontal display"; "_single_file_conflict_title_" = "File conflict"; "_multi_file_conflict_title_" = "%@ File conflicts"; "_replace_action_title_" = "Replace"; @@ -191,6 +252,16 @@ "_change_lock_passcode_" = "Change passcode"; "_lock_cannot_disable_mdm_" = "Disabling the passcode lock is not permitted by your configuration profile."; +/* Background of the file listing view */ +"_use_as_background_" = "Use it as a background"; + +/* Background of the file listing view */ +"_background_" = "Background"; + +"_dark_mode_" = "Dark mode"; +"_default_color_" = "Use the default color"; +"_as_default_color_" = "Use as default color"; + // MARK: User Status /* User status */ @@ -235,12 +306,20 @@ "_hours_" = "Hours"; "_minutes_" = "Minutes"; - -"_network_not_available_" = "Network unavailable."; +"_network_available_" = "Network available"; +"_network_not_available_" = "Network unavailable"; +"_file_too_big_" = "File too large to be encrypted/decrypted"; +"_file_too_big_max_100_" = "File too large (max 100 kb.)"; +"_...loading..._" = "Loading …"; +"_download_plist_" = " "; +"_no_reuploadfile_" = "Could not find nor resend file. Delete the upload and reload the file to upload it."; "_file_already_exists_" = "Unable to complete the operation, a file with the same name exists."; +"_read_file_error_" = "Could not read the file"; +"_write_file_error_" = "Could not write the file"; "_files_lock_error_" = "There was an error changing the lock of this file."; "_more_" = "More"; "_notifications_" = "Notifications"; +"_logout_" = "Log out"; "_quota_space_unlimited_" = "Unlimited"; "_quota_space_unknown_" = "Unknown"; "_quota_using_" = "%@ "; @@ -248,11 +327,13 @@ "_quota_using_percentage_" = "Memory to %@ occupied"; "_acknowledgements_" = "Acknowledgements"; "_settings_" = "Settings"; +"_passcode_" = "Password"; "_enter_password_" = "Enter password …"; "_lock_" = "Lock"; "_lock_active_" = "Lock: On"; "_lock_not_active_" = "Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; +"_lock_protection_no_screen_footer_" = "Use \"Do not ask at startup\" for the encryption option."; "_enable_touch_face_id_" = "Enable Touch/Face ID"; "_security_" = "Security"; "_data_protection_" = "Data protection"; @@ -266,15 +347,30 @@ "_change_credentials_" = "Change your credentials"; "_wifi_only_" = "Only use Wi-Fi connection"; "_settings_autoupload_" = "Auto upload"; +"_app_version_" = "Application version"; +"_app_in_use_" = "Application in use"; +"_contact_by_email_" = "Contact us by email"; "_clear_cache_" = "Clear cache"; "_clear_cache_footer_" = "Clear downloaded and offline files from the app"; "_exit_footer_" = "Remove all accounts and local data from the app."; +"_exit_footer_" = "Remove all accounts and local data from the app."; "_exit_" = "Logout"; "_funct_not_enabled_" = "Functionality not enabled"; "_passcode_activate_" = "Password lock on"; "_disabling_passcode_" = "Removing password lock"; "_want_exit_" = "Attention! Will be reset to initial state. Continue?"; -"_want_delete_cache_" = "Do you want to delete the cache (this also removes the transfers in progress)?"; +"_proceed_" = "Proceed"; +"_delete_cache_" = "Delete cache"; +"_want_delete_cache_" = "Do you want to delete the cache (this also removes the transfers in progress)?";"_want_delete_thumbnails_" = "Do you want to delete all thumbnails too?"; +"_mail_deleted_" = "Email deleted"; +"_mail_saved_" = "Email saved"; +"_mail_sent_" = "Email sent"; +"_mail_failure_" = "Could not send email: %@"; +"_information_req_" = "Information request"; +"_write_in_english_" = "Kindly write to us in English"; +"_credentials_" = "Credentials"; +"_manage_account_" = "Manage account"; +"_change_password_" = "Change password"; "_add_account_" = "Add account"; "_want_delete_" = "You will delete the following: "; "_want_leave_share_" = "You will leave the following shares: "; @@ -284,16 +380,51 @@ "_want_leave_share_" = "You will leave the following shares: "; "_no_delete_" = "No, do not delete"; "_yes_delete_" = "Yes, delete"; +"_remove_cache_" = "Deleting cache, please wait …"; +"_optimizations_" = "Optimizations"; +"_synchronizations_" = "Synchronized folders"; +"_version_server_" = "Server version"; +"_help_" = "Help"; +"_change_simply_passcode_" = "Change password type"; +"_quota_" = "Quota"; +"_available_" = "Available"; +"_not_available_" = "Not available"; +"_accounts_" = "Accounts"; "_information_" = "Information"; +"_personal_information_" = "Personal info"; +"_user_full_name_" = "Full name"; +"_user_address_" = "Address"; +"_user_phone_" = "Phone number"; +"_user_email_" = "Email"; +"_user_web_" = "Website"; +"_user_twitter_" = "Twitter"; +"_user_job_" = "Job"; +"_user_businesssize_" = "Business size"; +"_user_businesstype_" = "Business type"; +"_user_city_" = "City"; +"_user_country_" = "Country"; +"_user_company_" = "Company"; +"_user_role_" = "Role"; +"_user_zip_" = "Zip"; +"_user_owner_" = "Owner"; +"_user_employee_" = "Employee"; +"_user_contractor_" = "Contractor"; +"_user_editprofile_" = "Edit profile"; "_select_offline_warning_" = "Making multiple files and folders available offline may take a while and use a lot of memory while doing so."; "_advanced_" = "Advanced"; "_permissions_" = "Permissions"; "_custom_permissions_" = "Custom permissions"; "_disable_files_app_" = "Disable Files App integration"; "_disable_files_app_footer_" = "Do not permit the access of files via the iOS Files application."; +"_trial_" = "Trial"; +"_trial_expired_day_" = "Days remaining"; "_time_remaining_" = "%@ remaining"; +"_disableLocalCacheAfterUpload_footer_" = "After uploading the file, do not keep it in the local cache"; +"_disableLocalCacheAfterUpload_" = "Disable local cache"; "_autoupload_" = "Auto upload photos/videos"; "_autoupload_select_folder_" = "Select the \"Auto upload\" folder"; +"_autoupload_error_select_folder_" = "Select a valid folder for the \"Auto upload\""; +"_autoupload_background_" = "Auto upload in the background"; "_autoupload_photos_" = "Auto upload photos"; "_autoupload_videos_" = "Auto upload videos"; "_autoupload_favorites_" = "Auto upload favorites only"; @@ -319,16 +450,25 @@ "_autoupload_filenamemask_" = "Change filename mask"; "_autoupload_filenamemask_footer_" = "Change the automatic filename mask"; "_autoupload_current_folder_" = "Currently selected folder"; +"_help_tutorial_" = "Tutorial"; +"_help_intro_" = "Introduction to Nextcloud"; +"_help_activity_verbose_" = "Detailed Activity feed"; +"_help_activity_mail_" = "Send activity via email"; +"_help_activity_clear_" = "Clear activity"; "_show_hidden_files_" = "Show hidden files"; "_format_compatibility_" = "Most Compatible"; "_format_compatibility_footer_" = "\"Most compatible\" will save photos as JPEG, if possible."; +"_terms_" = "Terms of Service"; "_privacy_" = "Privacy"; +"_privacy_policy_" = "Privacy Policy"; "_privacy_footer_" = "This app uses a service for the analysis of a crash. Your personal information is not sent with the report. If you want disable it, please change the setting \"Disable crash reporter\" to ON."; "_crashservice_title_" = "Disable crash reporter"; "_crashservice_alert_" = "This option requires a restart of the app to take effect."; "_upload_mov_livephoto_" = "Live Photo"; "_upload_mov_livephoto_footer_" = "\"Live Photo\" will save the selected photo in \"Live Photo\" format, if possible."; +"_view_capabilities_" = "View the capabilities"; "_capabilities_" = "Capabilities"; +"_no_capabilities_found_" = "Capabilities not found"; "_capabilities_footer_" = "Display the packages used by the app if they are installed and available on the server."; "_diagnostics_" = "Diagnostics"; "_diagnostics_footer_" = "Changing log level requires a restart of the app to take effect"; @@ -340,8 +480,16 @@ "_connect_server_anyway_" = "Do you want to connect to the server anyway?"; "_server_is_trusted_" = "Do you consider this server trusted?"; "_connection_error_" = "Connection error"; +"_serverstatus_error_" = "Connection to server failure, verify your server address or network status"; +"_add_your_nextcloud_" = "Add your account"; "_login_url_" = "Server address https:// …"; +"_login_bottom_label_" = "Don't have a server yet?\nChoose one of the providers."; +"_error_multidomain_" = "Address not allowed, only the following domains are valid:"; +"_account_already_exists_" = "The account %@ already exists"; +"_traditional_login_" = "Revert to old login method"; +"_web_login_" = "Revert to web login method"; "_login_url_error_" = "URL error, please verify your server URL"; +"_webflow_not_available_" = "Web login not available, use the old login method"; "_favorites_" = "Favorites"; "_favorite_short_" = "Favorite"; "_favorite_" = "Favorite"; @@ -350,20 +498,31 @@ "_tutorial_favorite_view_" = "Files and folders you mark as favorites will show up here"; "_tutorial_offline_view_" = "Files copied here will be available offline.\n\nThey will be synchronized with your cloud."; "_tutorial_groupfolders_view_" = "No Group folders yet"; +"_tutorial_local_view_" = "You'll find the unpacked files from your cloud.\n\nConnect to iTunes to share these files."; "_more_" = "More"; "_favorite_no_files_" = "No favorites yet"; +"_pull_down_" = "Pull down to refresh"; +"_no_photo_load_" = "No photo or video"; +"_tutorial_autoupload_view_" = "You can enable auto uploads from \"Settings\""; "_no_date_information_" = "No date information"; "_no_camera_information_" = "No camera information"; "_no_lens_information_" = "No lens information"; "_today_" = "Today"; "_yesterday_" = "Yesterday"; +"_time_" = "Time: %@\n\n%@"; +"_location_not_enabled_" = "Location Services not enabled"; +"_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Location Services\""; "_access_photo_not_enabled_" = "Access to Photos is not enabled"; -"_access_photo_not_enabled_msg_" = "Please go to Settings → Apps → Nextcloud → Photos and enable Photo Library Access"; +"_access_photo_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\""; +//"_access_photo_not_enabled_msg_" = "Please go to Settings → Apps → Nextcloud → Photos and enable Photo Library Access"; "_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background"; +"_access_photo_location_not_enabled_" = "Access to Photos and Location not enabled"; +"_access_photo_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\" and \"Location Services\""; "_new_photos_starting_" = "Only photos or videos starting %@ will be uploaded."; "_tutorial_photo_view_" = "No photos or videos uploaded yet"; "_create_full_upload_" = "Creating archive … May take a long time. During this process, keep the application active during the transfer as well."; "_error_createsubfolders_upload_" = "Error creating subfolders"; +"_activate_autoupload_" = "Enable auto upload"; "_remove_photo_CameraRoll_" = "Remove from camera roll"; "_remove_photo_CameraRoll_desc_" = "\"Remove from camera roll\" after uploads, a confirmation message will be displayed to delete the uploaded photos or videos from the camera roll. The deleted photos or videos will still be available in the iOS Photos Trash for 30 days."; "_never_" = "never"; @@ -374,25 +533,88 @@ "_hours_ago_" = "%d hours ago"; "_a_day_ago_" = "a day ago"; "_days_ago_" = "%d days ago"; +"_over_30_days_" = "over 30 days"; +"_connection_internet_offline_" = "The internet connection appears to be offline or Wi-Fi is required"; +"_insert_password_" = "Enter password"; +"_update_in_progress_" = "Version upgrade, please wait …"; "_forbidden_characters_" = "Forbidden characters: %@"; -"_cannot_send_mail_error_" = "No account is set up, or wrong email address entered."; +"_cannot_send_mail_error_" = "No account set up, or wrong email address entered."; "_open_url_error_" = "Cannot open the URL for this action."; +"_photo_camera_" = "Photos"; "_media_" = "Media"; +"_unzip_in_progress_" = "Extraction in progress on local storage …"; +"_file_unpacked_" = "File unpacked on local storage"; +"_file_saved_local_" = "File saved on local storage."; +"_file_not_present_" = "Error: File not present, please reload."; "_order_by_" = "Sort by"; "_name_" = "Name"; "_date_" = "Date"; "_size_" = "Size"; +"_order_by_name_a_z_" = "Sort by name (from A to Z)"; +"_sorted_by_name_a_z_" = "Sorted by name (from A to Z)"; +"_order_by_name_z_a_" = "Sort by name (from Z to A)"; +"_sorted_by_name_z_a_" = "Sorted by name (from Z to A)"; +"_order_by_date_more_recent_" = "Sort by newest"; +"_sorted_by_date_more_recent_" = "Sorted by newest"; +"_order_by_date_less_recent_" = "Sort by oldest"; +"_sorted_by_date_less_recent_" = "Sorted by oldest"; +"_order_by_size_smallest_" = "Sort by smallest"; +"_sorted_by_size_smallest_" = "Sorted by smallest"; +"_order_by_size_largest_" = "Sort by largest"; +"_sorted_by_size_largest_" = "Sorted by largest"; "_delete_selected_files_" = "Delete files"; +"_move_selected_files_" = "Move files"; +"_move_or_copy_selected_files_" = "Move or copy files"; +"_download_selected_files_" = "Download files"; +"_download_selected_files_folders_" = "Download files and folders"; +"_error_operation_canc_" = "Error: Operation canceled."; +"_only_lock_passcode_" = "Available only with Lock password activated. Activate it in the \"Settings\"."; +"_go_to_app_settings_" = "Go to app settings"; +"_passcode_protection_" = "Password protection"; "_remove_favorites_" = "Remove from favorites"; +"_remove_offline_" = "Remove from offline"; "_add_favorites_" = "Add to favorites"; +"_add_offline_" = "Add to offline"; +"_remove_passcode_" = "Remove password protection"; +"_protect_passcode_" = "Protect with password"; "_share_" = "Share"; +"_reload_" = "Reload"; +"_open_in_" = "Open in …"; +"_open_" = "Open …"; "_remove_local_file_" = "Remove locally"; +"_add_local_" = "Add to local storage"; +"_comm_erro_pull_down_" = "Attention: Communication error with the server. Pull down to refresh."; +"_file_not_downloaded_" = "file not downloaded"; +"_file_not_uploaded_" = "file not uploaded"; "_folders_" = "folders"; "_folder_" = "folder"; "_files_" = "files"; "_file_" = "file"; +"_folder_blocked_" = "Folder blocked"; +"_downloading_progress_" = "Initiating download of files …"; "_no_file_pull_down_" = "Upload a file or pull down to refresh"; "_no_file_no_permission_to_create_" = "You don't have permission to create or upload files in this folder."; +"_browse_images_" = "Browse images"; +"_synchronized_folder_" = "Keep the folder synchronized"; +"_remove_synchronized_folder_" = "Turn off the synchronization"; +"_synchronized_confirm_" = "After enabling the synchronization, all files in the folder will be synchronized with the server, continue?"; +"_offline_folder_confirm_" = "After enabling the offline folder, all files in it will be synchronized with the server, continue?"; +"_file_not_found_reload_" = "File not found, pull down to refresh"; +"_title_section_download_" = "DOWNLOAD"; +"_download_" = "Download"; +"_title_section_upload_" = "UPLOAD"; +"_group_alphabetic_yes_" = "✓ Group alphabetically"; +"_group_alphabetic_no_" = "Group alphabetically"; +"_group_typefile_yes_" = "✓ Group by file type"; +"_group_typefile_no_" = "Group by file type"; +"_group_date_yes_" = "✓ Group by date"; +"_group_date_no_" = "Group by date"; +"_element_" = "element"; +"_elements_" = "elements"; +"_tite_footer_upload_wwan_" = " Wi-Fi network required, %lu %@ to upload"; +"_tite_footer_upload_" = "%lu %@ to upload"; +"_tite_footer_download_wwan_" = " Wi-Fi network required, %lu %@ to download"; +"_tite_footer_download_" = "%lu %@ to download"; "_limited_dimension_" = "Maximum size reached"; "_save_selected_files_" = "Save to photo gallery"; "_file_not_saved_cameraroll_" = "Error: File not saved in photo album"; @@ -401,6 +623,12 @@ "_directory_on_top_" = "Sort folders before files"; "_show_description_" = "Show folder description"; "_show_recommended_files_" = "Show recommendations"; +"_directory_on_top_yes_" = "✓ Folders on top"; +"_directory_on_top_no_" = "Folders on top"; +"_show_description_" = "Show description"; +//"_show_recommended_files_" = "Show recommended files"; +"_no_description_available_" = "No description available for this folder"; +"_folder_automatic_upload_" = "Folder for \"Auto upload\""; "_search_no_record_found_" = "No result"; "_search_in_progress_" = "Search in progress …"; "_search_instruction_" = "Search for file (minimum 2 characters)"; @@ -408,17 +636,27 @@ "_files_no_folders_" = "No folders in here"; "_request_in_progress_" = "Request to server in progress …"; "_personal_files_only_" = "Personal files only"; - "audio" = "AUDIO"; "directory" = "FOLDERS"; +"compress" = "COMPRESS"; +"directory" = "FOLDERS"; "document" = "DOCUMENTS"; "image" = "IMAGES"; "template" = "TEMPLATES"; +"unknow" = "UNKNOWN"; "video" = "VIDEO"; +"_file_del_only_local_" = "File not present on server"; +"_copy_file_" = "Copy"; "_paste_file_" = "Paste"; +"_open_quicklook_" = "Open with Quick Look"; +"_search_this_folder_" = "Search in this folder"; +"_search_all_folders_" = "Search in all folders"; +"_search_sub_folder_" = "Search here and in subfolders"; +"_theming_is_light_" = "Server theming too brightly coloured, not applicable"; "_cancel_all_task_" = "Cancel all transfers"; "_status_wait_download_" = "Waiting for download"; +"_status_in_download_" = "In download"; "_status_downloading_" = "Downloading"; "_status_wait_upload_" = "Waiting to upload"; "_status_wait_create_folder_" = "Waiting to create the folder"; @@ -427,6 +665,7 @@ "_status_wait_favorite_" = "Waiting to change favorite"; "_status_wait_copy_" = "Waiting to copy"; "_status_wait_move_" = "Waiting to move"; +"_status_in_upload_" = "In upload"; "_status_uploading_" = "Uploading"; "_status_upload_error_" = "Error, waiting to upload"; "_select_media_folder_" = "Set Media folder"; @@ -450,19 +689,35 @@ // MARK: Share "_share_link_" = "Share link"; +"_share_link_button_" = "Send link to …"; "_share_link_name_" = "Link name"; "_password_" = "Password"; "_share_password_" = "Password protected link"; +"_share_expirationdate_" = "Set expiration date for link"; "_date_" = "Date"; +"_share_title_" = "Share"; +"_add_sharee_" = "Add users or groups"; +"_add_sharee_footer_" = "You can share this resource by adding users or groups. To remove a share, remove all users and groups"; +"_find_sharee_title_" = "Search"; +"_find_sharee_" = "Search for user or group …"; +"_find_sharee_footer_" = "Enter part of the name of the user or group to search for (at least 2 characters) followed by \"Return\", select the users that should be allowed to access the share followed by \"Done\" to confirm"; +"_user_is_group_" = "(Group)"; +"_direct_sharee_title_" = "Share"; +"_direct_sharee_footer_" = "If you already know the name, enter it, then select the share type and press \"Done\" to confirm"; +"_direct_sharee_" = "Enter the username …"; "_user_sharee_footer_" = "Tap to change permissions"; +"_share_type_title_" = "Type of share"; +"_share_type_user_" = "User"; +"_share_type_group_" = "Group"; +"_share_type_remote_" = "Remote"; "_enforce_password_protection_" = "Enforce password protection"; +"_password_obligatory_" = "Enforce password protection enabled, password obligatory"; "_shared_with_you_by_" = "Shared with you by"; -//"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; -//"_new_comment_" = "New comment …"; -//"_edit_comment_" = "Edit comment"; -//"_delete_comment_" = "Delete comment"; -//"_share_read_only_" = "View only"; -//"_share_editing_" = "Can edit"; +"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; +"_new_comment_" = "New comment …"; +"_edit_comment_" = "Edit comment"; +"_delete_comment_" = "Delete comment"; +"_share_read_only_" = "View only"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; "_sharing_message_" = "You can create links or send shares by mail. If you invite MagentaCLOUD users, you have more opportunities for collaboration."; @@ -512,6 +767,7 @@ //"_share_password_protect_" = "Password protect"; "_share_expiration_date_" = "Set expiration date"; "_share_note_recipient_" = "Note to recipient"; +"_share_delete_sharelink_" = "Delete link"; "_share_add_sharelink_" = "Add another link"; "_share_can_read_" = "Read"; "_share_can_reshare_" = "Share"; @@ -536,35 +792,49 @@ "_no_transfer_" = "No transfers yet"; "_no_transfer_sub_" = "Uploads and downloads from this device will show up here"; +"_no_activity_" = "No activity yet"; "_no_activity_footer_" = "No more activities to load"; "_transfers_" = "Transfers"; "_activity_" = "Activity"; +"_activity_file_not_present_" = "File no longer present"; "_trash_file_not_found_" = "It seems that the file is not in the Trash. Go to the Trash to update it and try again."; "_list_shares_" = "Shares"; "_list_shares_no_files_" = "No shares yet"; "_tutorial_list_shares_view_" = "Files and folders you share will show up here"; +"_create_synchronization_" = "Create synchronization"; "_offline_" = "Offline"; +"_local_storage_" = "Local storage"; +"_local_storage_no_record_" = "No files yet"; "_upload_photos_videos_" = "Upload photos or videos"; "_upload_file_" = "Upload file"; +"_upload_file_text_" = "Create text file"; "_create_nextcloudtext_document_" = "Create text document"; +"_save_document_picker_" = "Save here"; +"_destination_folder_" = "Destination folder"; "_use_folder_auto_upload_" = "Use the \"Auto upload\" folder as destination"; +"_rename_filename_" = "Rename"; "_filename_" = "Filename"; "_enter_filename_" = "Enter filename …"; "_default_preview_filename_footer_" = "Example preview of filename: IMG_0001.JPG"; "_filename_header_" = "Enter filename"; -"_preview_filename_" = "Example preview of filename. You can use the mask %@ for date/time."; -"_add_filenametype_" = "Specify type in filename"; +"_preview_filename_" = "Example preview of filename. You can use the mask %@ for date/time.";"_add_filenametype_" = "Specify type in filename"; +"_filenametype_photo_video_" = "Photo/Video"; "_maintain_original_filename_" = "Maintain original filename"; +"_modify_photo_" = "Modify photo"; "_notifications_" = "Notifications"; +"_no_notification_" = "No notifications yet"; +"_autoupload_filename_title_" = "Auto upload filename"; "_untitled_" = "Untitled"; +"_text_upload_title_" = "Upload text file"; "_e2e_settings_title_" = "Encryption"; "_e2e_settings_" = "End-to-end encryption"; "_e2e_settings_start_" = "Start end-to-end encryption"; +"_e2e_settings_not_available_" = "End-to-end encryption not available"; "_e2e_settings_activated_" = "End-to-end encryption activated"; "_e2e_server_disabled_" = "End-to-end encryption app disabled on server"; "_e2e_settings_view_passphrase_" = "All 12 words together make a very strong password, letting only you view and make use of your encrypted files. Please write it down and keep it somewhere safe."; "_e2e_settings_read_passphrase_" = "Read passphrase"; -"_e2e_settings_lock_not_active_" = "Lock not active. Go to \"Settings\" and activate it."; +"_e2e_settings_lock_not_active_" = "Lock not active, go back to \"Settings\" and activate it"; "_e2e_settings_the_passphrase_is_" = "The passphrase is:"; "_e2e_passphrase_request_title_" = "Request passphrase"; "_e2e_passphrase_request_message_" = "Insert the 12 words"; @@ -608,15 +878,37 @@ "_scan_label_document_zone_" = "Tap or drag images down for document creation"; "_filter_document_" = "Document"; "_filter_original_" = "Original"; +"_filter_bn_" = "Black & White"; +"_filter_grayscale_" = "Grayscale"; "_quality_image_title_" = "Preview image quality"; +"_quality_high_" = "Large file size of high quality"; +"_quality_medium_" = "Average file size of medium quality"; +"_quality_low_" = "Small file size of low quality"; +"_file_type_" = "File type"; +"_pdf_password_" = "PDF Password"; "_file_creation_" = "File creation"; "_delete_all_scanned_images_" = "Delete all scanned images"; "_text_recognition_" = "Text recognition"; "_all_files_" = "All files"; "_personal_files_" = "Personal Files"; +/* The title on the navigation bar of the Scanning screen. */ +"wescan.scanning.title" = "Scanning"; +/* The "Next" button on the right side of the navigation bar on the Edit screen. */ +"wescan.edit.button.next" = "Next"; +/* The title on the navigation bar of the Edit screen. */ +"wescan.edit.title" = "Edit Scan"; +/* The "Done" button on the right side of the navigation bar on the Review screen. */ +"wescan.review.button.done" = "Done"; +/* The title on the navigation bar of the Review screen. */ +"wescan.review.title" = "Review"; + "_trash_view_" = "Deleted files"; "_empty_trash_" = "Empty trash"; +"_trash_restore_all_" = "Restore all files"; +"_trash_delete_all_" = "Empty trash"; +"_trash_delete_permanently_" = "Delete permanently"; +"_trash_delete_all_description_" = "Do you want to empty the trash bin?"; "_trash_no_trash_" = "No files deleted"; "_trash_no_trash_description_" = "You can restore deleted files from here"; "_trash_restore_selected_" = "Restore selected files"; @@ -637,7 +929,62 @@ "_log_in_" = "Log in"; "_sign_up_" = "Sign up with provider"; "_host_your_own_server" = "Host your own server"; +"_unauthorized_" = "Unauthorized"; +"_bad_username_password_" = "Wrong username or password"; +"_cancelled_by_user" = "Transfer canceled"; +"_error_folder_destiny_is_the_same_" = "It is not possible to move the folder into itself"; +"_error_not_permission_" = "You don't have permission to complete the operation"; +"_error_path_" = "Unable to open this file or folder. Please make sure it exists"; +"_file_upload_not_exitst_" = "The file that you want to upload does not exist"; +"_forbidden_characters_from_server_" = "The name contains at least one invalid character"; +"_error_not_modified_" = "Resource not modified"; +"_not_connected_internet_" = "Server connection error"; +"_not_possible_connect_to_server_" = "It is not possible to connect to the server at this time"; +"_not_possible_create_folder_" = "Folder could not be created"; +"_server_down_" = "Could not establish contact with server"; +"_time_out_" = "Timeout, try again"; +"_unknow_response_server_" = "Unexpected response from server"; +"_user_authentication_required_" = "User authentication required"; +"_file_directory_locked_" = "File or directory locked"; +"_ssl_certificate_untrusted_" = "The certificate for this server is invalid"; +"_ssl_certificate_changed_" = "The certificate for this server seems to have changed"; +"_internal_server_" = "Internal server error"; +"_file_already_exists_" = "Could not complete the operation, a file with the same name exists"; +"_file_folder_not_exists_" = "The source file wasn't found at the specified path"; +"_folder_contents_nochanged_" = "The folder contents have not changed"; +"_images_invalid_converted_" = "The image is invalid and cannot be converted to a thumbnail"; +"_method_not_expected_" = "Unexpected request method"; +"_reauthenticate_user_" = "Access expired, log in again"; +"_server_error_retry_" = "The server is temporarily unavailable"; +"_too_many_files_" = "Too many files would be involved in this operation"; +"_too_many_request_" = "Sending too many requests caused the rate limit to be reached"; +"_user_over_quota_" = "Storage quota is reached"; +"_ssl_connection_error_" = "Connection SSL error, try again"; +"_bad_request_" = "Bad request"; +"_webdav_locked_" = "WebDAV Locked: Trying to access locked resource"; +"_error_user_not_available_" = "The user is no longer available"; +"_server_response_error_" = "Server response content error"; +"_no_nextcloud_found_" = "Server not found"; +"_error_decompressing_" = "Error during decompressing. Unknown compression method or the file is corrupt"; +"_error_json_decoding_" = "Serious internal error in decoding metadata (The data couldn't be read because it isn't in the correct format.)"; +"_error_check_remote_user_" = "Server responded with an error. Please log in again"; +"_request_entity_too_large_" = "The file is too large"; +"_not_possible_download_" = "It is not possible to download the file"; +"_not_possible_upload_" = "It is not possible to upload the file"; +"_error_files_upload_" = "Error uploading files"; +"_method_not_allowed_" = "The requested method is not supported"; "_invalid_url_" = "Invalid server URL"; +"_invalid_literal_" = "Invalid search string"; +"_invalid_date_format_" = "Invalid date format"; +"_invalid_data_format_" = "Invalid data format"; +"_error_decode_xml_" = "Invalid response, error decode XML"; +"_internal_generic_error_" = "internal error"; +"_editor_unknown_" = "Failed to open file: Editor is unknown"; +"_err_file_not_found_" = "File not found, removed"; +"_err_e2ee_app_version_" = "The app version of end-to-end encryption is not compatible with the server, please update your server"; +"_err_permission_microphone_" = "Please allow Microphone usage from Settings"; +"_err_permission_photolibrary_" = "Please allow Photos from Settings"; +"_err_permission_locationmanager_" = "Please allow Location - Always from Settings"; "_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; "_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; "_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; @@ -650,6 +997,8 @@ "_create_voice_memo_" = "Create voice memo"; "_voice_memo_start_" = "Tap to start"; "_voice_memo_stop_" = "Tap to stop"; +"_voice_memo_filename_" = "Voice memo"; +"_voice_memo_title_" = "Upload voice memo"; "Enter Passcode" = "Enter Passcode"; "Enter a new passcode" = "Enter a new passcode"; "Confirm new passcode" = "Confirm new passcode"; @@ -676,10 +1025,15 @@ "_3_months_" = "3 months"; "_1_month_" = "1 month"; "_1_week_" = "1 week"; +"_1_day_" = "1 day"; "_monthly_" = "Monthly"; "_yearly_" = "Yearly"; +"_weekly_" = "Weekly"; "_daily_" = "Daily"; -"_used_space_" = "Used space:"; +"_day_" = "Day"; +"_used_space_" = "Used space"; +"_open_in_onlyoffice_" = "Open in ONLYOFFICE"; +"_open_in_collabora_" = "Open with Collabora Online"; "_login_address_detail_" = "The link to your %@ web interface when you open it in the browser."; "_go_to_page_" = "Go to page"; "_page_" = "Page"; @@ -687,15 +1041,23 @@ "_invalid_page_" = "Invalid Page"; "_the_entered_page_number_does_not_exist_" = "The entered page number does not exist."; "_error_something_wrong_" = "Something went wrong"; +"_resolution_" = "Resolution"; "_try_download_full_resolution_" = "Download full resolution image"; "_full_resolution_image_info_" = "This may reveal more information about the photo."; "_download_audio_" = "Download the audio locally"; "_copied_path_" = "Copied path"; +"_copy_path_" = "Copy path"; +"_certificates_" = "Certificates"; "_privacy_screen_" = "Splash screen when app inactive"; +"_saving_" = "Saving …"; +"_video_not_streamed_" = "The server does not allow video streaming, do you want to download it?"; +"_video_not_streamed_e2ee_" = "The server does not allow video streaming because it is encrypted, do you want to download it?"; +"_scan_" = "Scan"; "_in_" = "in"; "_enter_passphrase_" = "Enter passphrase (12 words)"; "_show_more_results_" = "Show more results"; "_waiting_for_" = "Waiting for:"; +"_waiting_" = "Waiting …"; "_reachable_wifi_" = "network reachable via Wi-Fi or cable"; "_ITMS-90076_" = "Due to a change in the Nextcloud application identifier, the settings and password for accessing your cloud are reset, so please re-enter your account data and check your Settings. We are sorry about that."; "_password_not_present_" = "Please re-insert your credentials."; @@ -706,9 +1068,15 @@ "_description_dashboardwidget_" = "Having the Dashboard always at your fingertips has never been easier."; "_description_fileswidget_" = "View your recent files and use the toolbar to speed up your operations."; "_description_toolbarwidget_" = "A toolbar to speed up your operations."; +"_no_data_available_" = "No data available"; +"_widget_available_nc25_" = "Widget only available starting with server version 25"; +"_keep_running_" = "Keep the app running for a better user experience"; +"_recent_activity_" = "Recent activity"; "_title_lockscreenwidget_" = "Status"; "_description_lockscreenwidget_" = "Keep an eye on available space and recent activity"; - +"_no_items_" = "No items"; +"_check_back_later_" = "Check back later"; +"_exporting_video_" = "Exporting video … Tap to cancel."; "_keep_running_" = "Keep the app running for a better user experience."; "_apps_nextcloud_detect_" = "Detected %@ apps"; "_add_existing_account_" = "Other %@ Apps has been detected, do you want to add an existing account?"; @@ -724,16 +1092,35 @@ "_mobile_config_" = "Download the configuration profile"; "_calendar_contacts_footer_warning_" = "Configuration profile can only be downloaded if Safari is set as default browser."; "_calendar_contacts_footer_" = "After downloading the profile you can install it from Settings."; +"_preview_" = "Preview"; +"_crop_" = "Crop"; "_modify_image_desc_" = "Tap on a file to modify or rename."; "_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live Photo effect."; +//"_modify_image_desc_" = "Tap the image for modify"; +//"_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live effect"; "_enable_livephoto_" = "Enable Live Photo"; "_disable_livephoto_" = "Disable Live Photo"; "_undo_modify_" = "Undo modifying"; +"_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; +"_disableFilesApp_" = "Files app cannot be used because it is disabled"; +"_reset_application_done_" = "Reset application, done."; "_rename_already_exists_" = "A file with this name already exists."; +"_created_" = "Created"; +"_recipients_" = "Recipients"; +"_are_sure_" = "Are you sure?"; +"_creation_" = "Creation"; +"_modified_" = "Modified"; "_group_folders_" = "Group folders"; "_play_from_files_" = "Play movie from a file"; "_play_from_url_" = "Play movie from URL"; "_valid_video_url_" = "Insert a valid URL"; +"_deletion_progess_" = "Deletion in progress"; +"_copying_progess_" = "Copying in progress"; +"_moving_progess_" = "Moving in progress"; +"_chunk_enough_memory_" = "It seems there is not enough space to send the file"; +"_chunk_create_folder_" = "The file could not be sent, please check the server log"; +"_chunk_files_null_" = "The file for sending could not be created"; +"_chunk_file_null_" = "The file could not be sent"; "_chunk_move_" = "The sent file could not be reassembled, please check the server log."; "_download_image_" = "Download image"; "_download_video_" = "Download video"; @@ -742,6 +1129,9 @@ "_reset_wrong_passcode_desc_" = "Use \"Reset application\" to remove all accounts and local data after %d failed code entry attempts."; "_deviceOwnerAuthentication_" = "The biometric sensor has been temporarily disabled due to multiple failed attempts. Enter the device passcode to re-enable the sensor."; "_virus_detect_" = "Virus detected. Upload cannot be completed!"; +"_zoom_" = "Zoom"; +"_zoom_in_" = "Zoom in"; +"_zoom_out_" = "Zoom out"; "_select_photos_" = "Select photos"; "_selected_photo_" = "selected photo"; "_selected_photos_" = "selected photos"; @@ -757,12 +1147,14 @@ "_account_settings_" = "Account settings"; "_users_" = "Users"; "_users_footer_" = "Every time the app is reactivated, the account will be requested."; +"_additional_view_options_" = "Additional view options"; "_while_charging_" = "While charging"; +"_additional_options_" = "Additional options"; "_keep_screen_awake_" = "Keep screen awake\nwhile transferring files"; "_error_not_found_" = "The requested resource could not be found"; "_error_conflict_" = "The request could not be completed due to a conflict with the current state of the resource"; "_error_precondition_" = "The server does not meet one of the preconditions of the requester"; -"_downloading_" = "Downloading"; + "_additional_options_" = "Additional options"; "_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; "_disableFilesApp_" = "Files app cannot be used because it is disabled"; @@ -773,12 +1165,32 @@ "_recent_activity_" = "Recent activity"; "_maintenance_mode_" = "The server is currently in maintenance mode, which may take a while."; "_account_disabled_" = "Account disabled"; + +//Video +"_select_trace_" = "Select the trace"; +"_video_processing_" = "Video processing"; +"_video_being_processed_" = "Video being processed …"; +"_downloading_" = "Downloading"; +"_download_error_" = "Download error"; +"_subtitle_" = "Subtitle"; +"_dts_to_ac3_" = "The DTS is not supported, it requires a conversion to Dolby Digital"; +"_reuired_conversion_" = "This video takes a long time to convert."; +"_stay_app_foreground_" = "Keep the app in the foreground …"; +"_conversion_available_" = "The conversion is always available on menu"; +"_video_format_not_recognized_" = "This video needs to be processed to be played, do you want to do it now?"; +"_video_must_download_" = "This video needs to be downloaded and processed to be played, do you want to do it now?"; +"_conversion_max_compatibility_" = "Max compatibility, the conversion can take much longer"; +"_video_tap_for_close_" = "A slight pressure to close the processing"; +"_subtitle_not_found_" = "Subtitle not found"; +"_disable_" = "Disable"; +"_subtitle_not_dowloaded_" = "There are subtitles not downloaded locally"; "_user_" = "User"; "_add_subtitle_" = "Add an external subtitle"; "_add_audio_" = "Add an external audio"; "_upload_foreground_msg_" = "Do not close %@ to complete the transfer …"; "_upload_background_msg_" = "Files upload in progress …"; "_create_folder_error_" = "An error has occurred while creating the folder:\n%@.\n\nPlease resolve the issue as soon as possible.\n\nAll uploads are suspended until the problem is resolved.\n"; +"_rename_file_error_" = "An error has occurred while renaming the file:\n%@."; "_creating_dir_progress_" = "Creating directories in progress … keep the application active."; "_creating_db_photo_progress_" = "Creating photo archive in progress … keep the application active."; "_account_unauthorized_" = "Warning, %@, you are not authorized, your account has been deleted, if you have changed your password, re-authenticate."; @@ -813,15 +1225,42 @@ You can stop it at any time, adjust the settings, and enable it again."; "_on_" = "On"; // a11y: On/Off "_off_" = "Off"; +"_grid_view_" = "Show grid view"; +"_list_view_" = "Show list view"; "_list_" = "List"; "_icons_" = "Icons"; +// MARK: Plan customer +"_leave_plan_title" = "We're sorry to see you go"; +"_leave_plan_description" = "You'll no longer have access to:"; +"_current_plan_" = "Current Plan"; +"_billing_plan_" = "Billing Plan"; +"_keep_plan_" = "Keep Plan"; +"_leave_plan_" = "Leave Plan"; +"_change_plan_" = "Change Plan"; +"_manage_plan_" = "Manage Plan"; +"_purchase_plan_" = "Purchase Plan"; +"_restore_plan_" = "Restore Purchased Plan"; +"_purchase_plan_description_" = "Purchases have been restored"; +"_choose_plan_" = "You should choose a plan in order to purchase it."; +"_already_plan_" = "The selected plan has already been bought."; +"_change_billing_" = "Change Billing"; +"_payment_method_" = "Payment Method"; + +// MARK: Mantis library +"Mantis.Done" = "Done"; +"Mantis.Cancel" = "Cancel"; +"Mantis.Reset" = "Reset"; +"Mantis.Original" = "Original"; +"Mantis.Square" = "Square"; + // MARK: Assistant "_assistant_task_unknown_" = "Unknown"; "_assistant_task_scheduled_" = "Scheduled"; "_assistant_task_in_progress_" = "In Progress"; "_assistant_task_completed_" = "Completed"; "_assistant_task_failed_" = "Failed"; +"_all_" = "All"; "_input_" = "Input"; "_output_" = "Output"; "_task_details_" = "Task details"; From 1a20ba541792fb6d931b2be79c4447d5e9a1f9f5 Mon Sep 17 00:00:00 2001 From: harshada-15-tsys Date: Tue, 30 Sep 2025 16:44:20 +0530 Subject: [PATCH 74/74] NMC-2023 - Localizations for EN and DE updated for New sharing design and as per 9.6.6 version --- .../de.lproj/Localizable.strings | Bin 98000 -> 193572 bytes .../en.lproj/Localizable.strings | 87 ++++++++++++------ 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/iOSClient/Supporting Files/de.lproj/Localizable.strings b/iOSClient/Supporting Files/de.lproj/Localizable.strings index a598aeb4ae03302a4c466abe7c49ecabb71f6ac9..803567d0ebb9af211f10efedebc1c2188203d4a2 100644 GIT binary patch literal 193572 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 delta 4829 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 Y&2r>dBGFgL9kl*82$pxWi!%TJ0fCcAng9R* diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 23d69dad8a..4907063957 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -55,7 +55,7 @@ "_select_all_" = "Select all"; "_upload_" = "Upload"; "_home_" = "Files"; -"_files_" = "Files"; +//"_files_" = "Files"; "_home_dir_" = "Home"; "_file_to_upload_" = "File to upload"; "_destination_" = "Destination"; @@ -313,7 +313,7 @@ "_...loading..._" = "Loading …"; "_download_plist_" = " "; "_no_reuploadfile_" = "Could not find nor resend file. Delete the upload and reload the file to upload it."; -"_file_already_exists_" = "Unable to complete the operation, a file with the same name exists."; +//"_file_already_exists_" = "Unable to complete the operation, a file with the same name exists."; "_read_file_error_" = "Could not read the file"; "_write_file_error_" = "Could not write the file"; "_files_lock_error_" = "There was an error changing the lock of this file."; @@ -335,6 +335,8 @@ "_lock_protection_no_screen_" = "Do not ask at startup"; "_lock_protection_no_screen_footer_" = "Use \"Do not ask at startup\" for the encryption option."; "_enable_touch_face_id_" = "Enable Touch/Face ID"; +"_face_id_" = "Face ID"; +"_touch_id_" = "Touch ID"; "_security_" = "Security"; "_data_protection_" = "Data protection"; "_privacy_settings_" = "Privacy Settings"; @@ -342,6 +344,7 @@ "_service_" = "Service"; "_imprint_" = "Imprint"; "_magentacloud_version_" = "MagentaCLOUD Version"; +"_your_secure_cloud_storage_" = "Your secure cloud storage"; "_url_" = "URL"; "_username_" = "Username"; "_change_credentials_" = "Change your credentials"; @@ -353,7 +356,6 @@ "_clear_cache_" = "Clear cache"; "_clear_cache_footer_" = "Clear downloaded and offline files from the app"; "_exit_footer_" = "Remove all accounts and local data from the app."; -"_exit_footer_" = "Remove all accounts and local data from the app."; "_exit_" = "Logout"; "_funct_not_enabled_" = "Functionality not enabled"; "_passcode_activate_" = "Password lock on"; @@ -372,8 +374,8 @@ "_manage_account_" = "Manage account"; "_change_password_" = "Change password"; "_add_account_" = "Add account"; -"_want_delete_" = "You will delete the following: "; -"_want_leave_share_" = "You will leave the following shares: "; +//"_want_delete_" = "You will delete the following: "; +//"_want_leave_share_" = "You will leave the following shares: "; "_delete_account_" = "Remove account"; "_delete_active_account_" = "Remove active account"; "_want_delete_" = "Do you really want to delete?"; @@ -637,7 +639,7 @@ "_request_in_progress_" = "Request to server in progress …"; "_personal_files_only_" = "Personal files only"; "audio" = "AUDIO"; -"directory" = "FOLDERS"; +//"directory" = "FOLDERS"; "compress" = "COMPRESS"; "directory" = "FOLDERS"; "document" = "DOCUMENTS"; @@ -688,7 +690,8 @@ // MARK: Share -"_share_link_" = "Share link"; +//"_share_link_" = "Share link"; +"_share_link_" = "Link"; "_share_link_button_" = "Send link to …"; "_share_link_name_" = "Link name"; "_password_" = "Password"; @@ -713,10 +716,10 @@ "_enforce_password_protection_" = "Enforce password protection"; "_password_obligatory_" = "Enforce password protection enabled, password obligatory"; "_shared_with_you_by_" = "Shared with you by"; -"_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; -"_new_comment_" = "New comment …"; -"_edit_comment_" = "Edit comment"; -"_delete_comment_" = "Delete comment"; +//"_shareLinksearch_placeholder_" = "Name, email";//, or Federated Cloud ID …"; +//"_new_comment_" = "New comment …"; +//"_edit_comment_" = "Edit comment"; +//"_delete_comment_" = "Delete comment"; "_share_read_only_" = "View only"; "_share_reshare_allowed_" = "Resharing is allowed."; "_share_reshare_not_allowed_" = "Resharing is not allowed."; @@ -742,17 +745,19 @@ "_share_download_limit_placeholder_" = "Enter download limit"; "_share_download_limit_alert_empty_" = "Download limit cannot be empty."; "_share_download_limit_alert_zero_" = "Download limit should be greater than 0."; +"_share_download_limit_alert_invalid_" = "Please enter a valid number for the download limit."; "_share_remaining_download_" = "Downloads:"; -"_share_read_only_" = "Read only"; +//"_share_read_only_" = "Read only"; +"_share_read_only_" = "View only"; "_share_editing_" = "Can edit"; "_share_file_drop_" = "Filedrop only"; //"_share_file_drop_" = "File request"; "_share_hide_download_" = "Prevent download"; "_share_note_recipient_" = "YOUR MESSAGE"; -"_shareLinksearch_placeholder_" = "Contact name or email address"; -"_shareLinksearch_placeholder_" = "Type a name and press Search"; +//"_shareLinksearch_placeholder_" = "Contact name or email address"; +//"_shareLinksearch_placeholder_" = "Type a name and press Search"; //"_shareLinksearch_placeholder_" = "Type a name and press Enter"; -"_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; +//"_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Enter"; "_new_comment_" = "New comment …"; "_edit_comment_" = "Edit comment"; "_delete_comment_" = "Delete comment"; @@ -760,6 +765,7 @@ "_share_allow_upload_" = "Allow upload and editing"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; //"_share_hide_download_" = "Hide download"; +"_share_hide_download_" = "Prevent download"; "_share_allowed_downloads_" = "Allowed downloads"; "_share_limit_download_" = "Limit downloads"; "_remaining_" = "%d remaining"; @@ -770,16 +776,37 @@ "_share_delete_sharelink_" = "Delete link"; "_share_add_sharelink_" = "Add another link"; "_share_can_read_" = "Read"; -"_share_can_reshare_" = "Share"; -"_share_can_create_" = "Create"; -"_share_can_change_" = "Edit"; -"_share_can_delete_" = "Delete"; -"_share_can_download_" = "Allow download and sync"; -"_share_unshare_" = "Delete share"; +"_share_can_reshare_" = "Allow resharing"; +"_share_can_create_" = "Allow creating"; +"_share_can_change_" = "Allow editing"; +"_share_can_delete_" = "Allow deleting"; +"_share_can_download_" = "Allow download"; +"_share_unshare_" = "Unshare"; "_share_internal_link_" = "Internal link"; "_share_internal_link_des_" = "Only works for users with access to this file/folder."; "_share_reshare_disabled_" = "You are not allowed to reshare this file/folder."; "_share_reshare_restricted_" = "Note: You only have limited permission to reshare this file/folder."; +"_create_new_link_" = "Create new link"; +"_share_send_link_by_mail_" = "Send link by mail"; +"_share_or_" = "or"; +"_share_copy_link_" = "Copy link"; +"_share_shared_with_" = "Shared with"; +"_share_quick_permission_everyone_can_just_upload_" = "Everyone can just upload"; +"_share_quick_permission_everyone_can_edit_" = "Everyone can edit"; +"_share_quick_permission_everyone_can_only_view_" = "Everyone can only view"; +"_share_quick_permission_everyone_can_just_upload_short_" = "Just upload"; +"_share_quick_permission_everyone_can_edit_short_" = "Can edit"; +"_share_quick_permission_everyone_can_only_view_short_" = "Only view"; +"_share_received_shares_text_" = "This file / folder was shared with you by"; +"_share_no_shares_text_" = "You have not yet shared your file/folder. Share to give others access"; +"_share_size_" = "Size: "; +"_share_modified_" = "Modified: "; +"_share_created_" = "Created: "; +"_share_uploaded_" = "Uploaded: "; +"_share_details_" = "Details"; +"_share_via_link_menu_password_label_" = "Password protect (%1$s)"; +"_share_link_empty_exp_date_" = "You must select expiration date."; +"_share_link_empty_note_message_" = "Please enter note."; "_remote_" = "Remote"; "_remote_group_" = "Remote group"; "_conversation_" = "Conversation"; @@ -825,6 +852,7 @@ "_no_notification_" = "No notifications yet"; "_autoupload_filename_title_" = "Auto upload filename"; "_untitled_" = "Untitled"; +"_untitled_txt_" = "Untitled.txt"; "_text_upload_title_" = "Upload text file"; "_e2e_settings_title_" = "Encryption"; "_e2e_settings_" = "End-to-end encryption"; @@ -985,13 +1013,13 @@ "_err_permission_microphone_" = "Please allow Microphone usage from Settings"; "_err_permission_photolibrary_" = "Please allow Photos from Settings"; "_err_permission_locationmanager_" = "Please allow Location - Always from Settings"; -"_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; -"_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; -"_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; -"_error_files_upload_" = "Error uploading files."; -"_internal_generic_error_" = "internal error."; -"_err_permission_microphone_" = "Please allow Microphone usage from Settings."; -"_err_permission_photolibrary_" = "Please allow Photos from Settings."; +//"_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; +//"_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; +//"_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; +//"_error_files_upload_" = "Error uploading files."; +//"_internal_generic_error_" = "internal error."; +//"_err_permission_microphone_" = "Please allow Microphone usage from Settings."; +//"_err_permission_photolibrary_" = "Please allow Photos from Settings."; "_qrcode_not_authorized_" = "This app is not authorized to use the Back Camera."; "_qrcode_not_supported_" = "QR code not supported by the current device."; "_create_voice_memo_" = "Create voice memo"; @@ -1077,7 +1105,7 @@ "_no_items_" = "No items"; "_check_back_later_" = "Check back later"; "_exporting_video_" = "Exporting video … Tap to cancel."; -"_keep_running_" = "Keep the app running for a better user experience."; +//"_keep_running_" = "Keep the app running for a better user experience."; "_apps_nextcloud_detect_" = "Detected %@ apps"; "_add_existing_account_" = "Other %@ Apps has been detected, do you want to add an existing account?"; "_status_in_progress_" = "Status reading in progress …"; @@ -1213,6 +1241,7 @@ "_tip_open_mediadetail_" = "Swipe up to show the details"; "_tip_autoupload_button_" = "Configure the options as you prefer (folder, Wi-Fi, new content, subfolders) and tap ‘Turn on auto uploading’. You can stop it at any time, adjust the settings, and enable it again."; +"_tip_autoupload_" = "You can now Auto Upload specific Albums from your Photos. You can enable Auto Upload here"; // MARK: Accessibility