From d67f4dcbb6f926155d600375f08d7a4a959c054a Mon Sep 17 00:00:00 2001 From: Iva Horn Date: Fri, 22 Nov 2024 10:48:42 +0100 Subject: [PATCH 1/8] #3184: Download Limit Support - Added tableDownloadLimit entity to app database. - Extended capability query to also consider download limit app. - Extended capabilities list view for display of download limit availability. - Extended share detail user interface to mimic the web user interface for managing download limits. - Every time WebDAV properties of a file are retrieved, its associated download limits are removed and recreated. Housekeeping: - Outsourced NCShareDateCell into dedicated source code file. - Outsourced NCShareToggleCell into dedicated source code file. Notes: - In my first attempt I had a detail view in the download limit row of the advanced share options showing the remaining number of downloads. However, that required to inject and retain the download limit entity object into the complicated share table configuration object. That, in turn, results in inconsistent data state due to invalid and outdated references. To resolve those issues, the assembly of the advanced share options user interface needs some refactoring which appears to expansive at this point and I prefer to leave it as it is for now. Signed-off-by: Iva Horn --- Brand/Database.swift | 2 +- Nextcloud.xcodeproj/project.pbxproj | 46 +++++ .../Data/NCManageDatabase+Capabilities.swift | 9 + .../Data/NCManageDatabase+DownloadLimit.swift | 95 +++++++++++ iOSClient/Data/NCManageDatabase+Share.swift | 46 ++++- iOSClient/Data/NCManageDatabase.swift | 3 +- iOSClient/Menu/NCShare+Menu.swift | 1 + iOSClient/NCCapabilities.swift | 2 + .../Networking/NCNetworking+WebDAV.swift | 18 ++ .../Capabilities/NCCapabilitiesModel.swift | 8 + ...hareDownloadLimitTableViewController.swift | 160 ++++++++++++++++++ .../NCShareDownloadLimitViewController.swift | 55 ++++++ .../Advanced/NCShareAdvancePermission.swift | 9 + iOSClient/Share/Advanced/NCShareCells.swift | 133 +++------------ .../Share/Advanced/NCShareDateCell.swift | 97 +++++++++++ .../Share/Advanced/NCShareToggleCell.swift | 26 +++ iOSClient/Share/NCShare.storyboard | 123 +++++++++++++- .../NCShareDownloadLimitNetworking.swift | 71 ++++++++ .../en.lproj/Localizable.strings | 3 + 19 files changed, 781 insertions(+), 126 deletions(-) create mode 100644 iOSClient/Data/NCManageDatabase+DownloadLimit.swift create mode 100644 iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitTableViewController.swift create mode 100644 iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitViewController.swift create mode 100644 iOSClient/Share/Advanced/NCShareDateCell.swift create mode 100644 iOSClient/Share/Advanced/NCShareToggleCell.swift create mode 100644 iOSClient/Share/NCShareDownloadLimitNetworking.swift diff --git a/Brand/Database.swift b/Brand/Database.swift index fe82386fed..59ec61a758 100644 --- a/Brand/Database.swift +++ b/Brand/Database.swift @@ -26,4 +26,4 @@ import Foundation // Database Realm // let databaseName = "nextcloud.realm" -let databaseSchemaVersion: UInt64 = 367 +let databaseSchemaVersion: UInt64 = 368 diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 96c5cd2219..24eafd981b 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -16,6 +16,18 @@ 371B5A2E23D0B04500FAFAE9 /* NCMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */; }; 3781B9B023DB2B7E006B4B1D /* AppDelegate+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3781B9AF23DB2B7E006B4B1D /* AppDelegate+Menu.swift */; }; 8491B1CD273BBA82001C8C5B /* UIViewController+Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8491B1CC273BBA82001C8C5B /* UIViewController+Menu.swift */; }; + AA3494FE2CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3494FD2CE65EA9005CC075 /* NCManageDatabase+DownloadLimit.swift */; }; + AA3494FF2CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3494FD2CE65EA9005CC075 /* NCManageDatabase+DownloadLimit.swift */; }; + AA3495002CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3494FD2CE65EA9005CC075 /* NCManageDatabase+DownloadLimit.swift */; }; + AA3495012CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3494FD2CE65EA9005CC075 /* NCManageDatabase+DownloadLimit.swift */; }; + AA3495022CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3494FD2CE65EA9005CC075 /* NCManageDatabase+DownloadLimit.swift */; }; + AA3495032CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3494FD2CE65EA9005CC075 /* NCManageDatabase+DownloadLimit.swift */; }; + AA3495042CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3494FD2CE65EA9005CC075 /* NCManageDatabase+DownloadLimit.swift */; }; + AAAC0A122CEE34700001949E /* NCShareDownloadLimitNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAAC0A112CEE346A0001949E /* NCShareDownloadLimitNetworking.swift */; }; + AAF806B22CE25E67009C2D43 /* NCShareDateCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAF806B12CE25E60009C2D43 /* NCShareDateCell.swift */; }; + AAF806B42CE25EFF009C2D43 /* NCShareToggleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAF806B32CE25EFE009C2D43 /* NCShareToggleCell.swift */; }; + AAF806B62CE34C7A009C2D43 /* NCShareDownloadLimitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAF806B52CE34C72009C2D43 /* NCShareDownloadLimitViewController.swift */; }; + AAF806B82CE37C1A009C2D43 /* NCShareDownloadLimitTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAF806B72CE37C15009C2D43 /* NCShareDownloadLimitTableViewController.swift */; }; AF1A9B6427D0CA1E00F17A9E /* UIAlertController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1A9B6327D0CA1E00F17A9E /* UIAlertController+Extension.swift */; }; AF1A9B6527D0CC0500F17A9E /* UIAlertController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF1A9B6327D0CA1E00F17A9E /* UIAlertController+Extension.swift */; }; AF22B206277B4E4C00DAB0CC /* NCCreateFormUploadConflict.swift in Sources */ = {isa = PBXBuildFile; fileRef = F704B5E42430AA8000632F5F /* NCCreateFormUploadConflict.swift */; }; @@ -1154,6 +1166,13 @@ 371B5A2D23D0B04500FAFAE9 /* NCMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCMenu.swift; sourceTree = ""; }; 3781B9AF23DB2B7E006B4B1D /* AppDelegate+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Menu.swift"; sourceTree = ""; }; 8491B1CC273BBA82001C8C5B /* UIViewController+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Menu.swift"; sourceTree = ""; }; + AA3494FD2CE65EA9005CC075 /* NCManageDatabase+DownloadLimit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+DownloadLimit.swift"; sourceTree = ""; }; + AAAC0A112CEE346A0001949E /* NCShareDownloadLimitNetworking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDownloadLimitNetworking.swift; sourceTree = ""; }; + AAF806B12CE25E60009C2D43 /* NCShareDateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDateCell.swift; sourceTree = ""; }; + AAF806B32CE25EFE009C2D43 /* NCShareToggleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareToggleCell.swift; sourceTree = ""; }; + AAF806B52CE34C72009C2D43 /* NCShareDownloadLimitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDownloadLimitViewController.swift; sourceTree = ""; }; + AAF806B72CE37C15009C2D43 /* NCShareDownloadLimitTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDownloadLimitTableViewController.swift; sourceTree = ""; }; + AAF806B92CE38BB2009C2D43 /* NextcloudKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = NextcloudKit; path = ../NextcloudKit; sourceTree = SOURCE_ROOT; }; AACCAB522CFE041F00DA1786 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Intent.strings; sourceTree = ""; }; AACCAB532CFE041F00DA1786 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Localizable.strings; sourceTree = ""; }; AACCAB542CFE041F00DA1786 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1934,6 +1953,15 @@ path = Menu; sourceTree = ""; }; + AA3494FC2CE4FF02005CC075 /* DownloadLimit */ = { + isa = PBXGroup; + children = ( + AAF806B52CE34C72009C2D43 /* NCShareDownloadLimitViewController.swift */, + AAF806B72CE37C15009C2D43 /* NCShareDownloadLimitTableViewController.swift */, + ); + path = DownloadLimit; + sourceTree = ""; + }; AF8ED1FA2757821000B8DBC4 /* NextcloudUnitTests */ = { isa = PBXGroup; children = ( @@ -1945,11 +1973,14 @@ AF93471327E235EB002537EE /* Advanced */ = { isa = PBXGroup; children = ( + AA3494FC2CE4FF02005CC075 /* DownloadLimit */, AF93471627E2361E002537EE /* NCShareAdvancePermission.swift */, AF93471827E2361E002537EE /* NCShareAdvancePermissionFooter.swift */, AF93471427E2361E002537EE /* NCShareAdvancePermissionFooter.xib */, AFCE353627E4ED7B00FEA6C2 /* NCShareCells.swift */, + AAF806B12CE25E60009C2D43 /* NCShareDateCell.swift */, AF93474D27E3F211002537EE /* NCShareNewUserAddComment.swift */, + AAF806B32CE25EFE009C2D43 /* NCShareToggleCell.swift */, ); path = Advanced; sourceTree = ""; @@ -2170,6 +2201,7 @@ F787704E22E7019900F287A9 /* NCShareLinkCell.xib */, AF2D7C7B2742556F00ADF566 /* NCShareLinkCell.swift */, F769454722E9F20D000A798A /* NCShareNetworking.swift */, + AAAC0A112CEE346A0001949E /* NCShareDownloadLimitNetworking.swift */, F769453F22E9F077000A798A /* NCSharePaging.swift */, F774264822EB4D0000B23912 /* NCSearchUserDropDownCell.xib */, F769453B22E9CFFF000A798A /* NCShareUserCell.xib */, @@ -2700,6 +2732,7 @@ F7BAAD951ED5A63D00B7EAD4 /* Data */ = { isa = PBXGroup; children = ( + AA3494FD2CE65EA9005CC075 /* NCManageDatabase+DownloadLimit.swift */, F7BAADB51ED5A87C00B7EAD4 /* NCManageDatabase.swift */, AF4BF613275629E20081CEEF /* NCManageDatabase+Account.swift */, AF4BF61D27562B3F0081CEEF /* NCManageDatabase+Activity.swift */, @@ -3036,6 +3069,7 @@ F7F67B9F1A24D27800EE80DA = { isa = PBXGroup; children = ( + AAF806B92CE38BB2009C2D43 /* NextcloudKit */, F7B8B82F25681C3400967775 /* GoogleService-Info.plist */, F7C1CDD91E6DFC6F005D92BE /* Brand */, F7F67BAA1A24D27800EE80DA /* iOSClient */, @@ -3869,6 +3903,7 @@ F711A4E22AF92CAE00095DD8 /* NCUtility+Date.swift in Sources */, F7401C1B2C75E6F300649E87 /* NCCapabilities.swift in Sources */, AF4BF61C27562A4B0081CEEF /* NCManageDatabase+Metadata.swift in Sources */, + AA3495022CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */, F78E2D6B29AF02DB0024D4F3 /* Database.swift in Sources */, F7817CFF29802D1A00FFBC65 /* NCPushNotificationEncryption.m in Sources */, F798F0EC2588060A000DAFFD /* UIColor+Extension.swift in Sources */, @@ -3941,6 +3976,7 @@ F711A4E12AF92CAE00095DD8 /* NCUtility+Date.swift in Sources */, F76882382C0DD22F001CF441 /* NCKeychain.swift in Sources */, F7C9B9222B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */, + AA3495032CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */, F7490E8529882C8C009DCE94 /* NCManageDatabase+Video.swift in Sources */, F7490E7729882C10009DCE94 /* UIColor+Extension.swift in Sources */, F70716E62987F81500E72C1D /* DocumentActionViewController.swift in Sources */, @@ -3989,6 +4025,7 @@ F73EF7DA2B0226080087E6E9 /* NCManageDatabase+Tip.swift in Sources */, F7817CFB29801A3500FFBC65 /* Data+Extension.swift in Sources */, F72429362AFE39860040AEF3 /* NCLivePhoto.swift in Sources */, + AA3494FF2CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */, AF4BF61F27562B3F0081CEEF /* NCManageDatabase+Activity.swift in Sources */, F7CBC1262BAC8B0000EC1D55 /* NCSectionFirstHeaderEmptyData.swift in Sources */, F7A0D1362591FBC5008F8A13 /* String+Extension.swift in Sources */, @@ -4111,6 +4148,7 @@ F783030328B4C4DD00B84583 /* ThreadSafeDictionary.swift in Sources */, F77ED59128C9CE9D00E24ED0 /* ToolbarData.swift in Sources */, F78302F728B4C3C900B84583 /* NCManageDatabase.swift in Sources */, + AA3495042CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */, F359D8682A7D03420023F405 /* NCUtility+Exif.swift in Sources */, F7346E1628B0EF5C006CE2D2 /* Widget.swift in Sources */, F78302F828B4C3E100B84583 /* NCManageDatabase+Activity.swift in Sources */, @@ -4243,6 +4281,7 @@ F7327E392B73B8D400A462C7 /* Array+Extension.swift in Sources */, F78E2D6929AF02DB0024D4F3 /* Database.swift in Sources */, F749B64E297B0CBB00087535 /* NCManageDatabase+Share.swift in Sources */, + AA3494FE2CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */, F73EF7B32B0224350087E6E9 /* NCManageDatabase+DirectEditing.swift in Sources */, F7401C192C75E6F300649E87 /* NCCapabilities.swift in Sources */, F771E3F320E239A600AFB62D /* FileProviderData.swift in Sources */, @@ -4355,6 +4394,7 @@ F758B460212C56A400515F55 /* NCScan.swift in Sources */, F76882262C0DD1E7001CF441 /* NCSettingsView.swift in Sources */, F78ACD52219046DC0088454D /* NCSectionFirstHeader.swift in Sources */, + AAF806B42CE25EFF009C2D43 /* NCShareToggleCell.swift in Sources */, F72944F52A8424F800246839 /* NCEndToEndMetadataV1.swift in Sources */, F710D2022405826100A6033D /* NCViewer+Menu.swift in Sources */, F765E9CD295C585800A09ED8 /* NCUploadScanDocument.swift in Sources */, @@ -4394,6 +4434,7 @@ F7D4BF472CA2E8D800A5E746 /* TOSettingsKeypadImage.m in Sources */, F7D4BF482CA2E8D800A5E746 /* TOPasscodeSettingsWarningLabel.m in Sources */, F7D4BF492CA2E8D800A5E746 /* TOPasscodeVariableInputView.m in Sources */, + AA3495012CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */, F7D4BF4A2CA2E8D800A5E746 /* TOPasscodeCircleView.m in Sources */, F7D4BF4B2CA2E8D800A5E746 /* TOPasscodeViewContentLayout.m in Sources */, F7D4BF4C2CA2E8D800A5E746 /* TOPasscodeSettingsKeypadButton.m in Sources */, @@ -4435,6 +4476,7 @@ F7EB9B132BBC12F300EDF036 /* UIApplication+Extension.swift in Sources */, F7E98C1627E0D0FC001F9F19 /* NCManageDatabase+Video.swift in Sources */, F7F4F11227ECDC52008676F9 /* UIFont+Extension.swift in Sources */, + AAF806B62CE34C7A009C2D43 /* NCShareDownloadLimitViewController.swift in Sources */, F76882222C0DD1E7001CF441 /* NCCapabilitiesView.swift in Sources */, AF93471A27E2361E002537EE /* NCShareHeader.swift in Sources */, F7F878AE1FB9E3B900599E4F /* NCEndToEndMetadata.swift in Sources */, @@ -4522,6 +4564,7 @@ F77C97392953131000FDDD09 /* NCCameraRoll.swift in Sources */, F343A4B32A1E01FF00DDA874 /* PHAsset+Extension.swift in Sources */, F70968A424212C4E00ED60E5 /* NCLivePhoto.swift in Sources */, + AAF806B22CE25E67009C2D43 /* NCShareDateCell.swift in Sources */, F7C30DFA291BCF790017149B /* NCNetworkingE2EECreateFolder.swift in Sources */, F7BC288026663F85004D46C5 /* NCViewCertificateDetails.swift in Sources */, F78B87E92B62550800C65ADC /* NCMediaDownloadThumbnail.swift in Sources */, @@ -4543,8 +4586,10 @@ F7D68FCC28CB9051009139F3 /* NCManageDatabase+DashboardWidget.swift in Sources */, F76882292C0DD1E7001CF441 /* NCManageE2EEModel.swift in Sources */, F799DF8B2C4B84EB003410B5 /* NCCollectionViewCommon+EndToEndInitialize.swift in Sources */, + AAAC0A122CEE34700001949E /* NCShareDownloadLimitNetworking.swift in Sources */, F78E2D6529AF02DB0024D4F3 /* Database.swift in Sources */, F70CEF5623E9C7E50007035B /* UIColor+Extension.swift in Sources */, + AAF806B82CE37C1A009C2D43 /* NCShareDownloadLimitTableViewController.swift in Sources */, F76882242C0DD1E7001CF441 /* NCSettingsAdvancedView.swift in Sources */, F75CA1472962F13700B01130 /* NCHUDView.swift in Sources */, F77BB748289985270090FC19 /* UITabBarController+Extension.swift in Sources */, @@ -4586,6 +4631,7 @@ F7C9739528F17131002C43E2 /* IntentHandler.swift in Sources */, F7A8D73D28F181D3008BBE1C /* NCUtilityFileSystem.swift in Sources */, F73EF7E12B02266D0087E6E9 /* NCManageDatabase+Trash.swift in Sources */, + AA3495002CE65EB6005CC075 /* NCManageDatabase+DownloadLimit.swift in Sources */, F7C9B91F2B582F550064EA91 /* NCManageDatabase+SecurityGuard.swift in Sources */, F75DD767290ABB25002EB562 /* Intent.intentdefinition in Sources */, F72437812C10B92500C7C68D /* NCPermissions.swift in Sources */, diff --git a/iOSClient/Data/NCManageDatabase+Capabilities.swift b/iOSClient/Data/NCManageDatabase+Capabilities.swift index 059847c1fa..d983e61398 100644 --- a/iOSClient/Data/NCManageDatabase+Capabilities.swift +++ b/iOSClient/Data/NCManageDatabase+Capabilities.swift @@ -88,6 +88,7 @@ extension NCManageDatabase { } struct Capabilities: Codable { + let downloadLimit: DownloadLimit? let filessharing: FilesSharing? let theming: Theming? let endtoendencryption: EndToEndEncryption? @@ -102,6 +103,7 @@ extension NCManageDatabase { let assistant: Assistant? enum CodingKeys: String, CodingKey { + case downloadLimit = "downloadlimit" case filessharing = "files_sharing" case theming case endtoendencryption = "end-to-end-encryption" @@ -112,6 +114,11 @@ extension NCManageDatabase { case assistant } + struct DownloadLimit: Codable { + let enabled: Bool? + let defaultLimit: Int? + } + struct FilesSharing: Codable { let apienabled: Bool? let groupsharing: Bool? @@ -327,6 +334,8 @@ extension NCManageDatabase { capabilities.capabilityFileSharingInternalExpireDateDays = data.capabilities.filessharing?.ncpublic?.expiredateinternal?.days ?? 0 capabilities.capabilityFileSharingRemoteExpireDateEnforced = data.capabilities.filessharing?.ncpublic?.expiredateremote?.enforced ?? false capabilities.capabilityFileSharingRemoteExpireDateDays = data.capabilities.filessharing?.ncpublic?.expiredateremote?.days ?? 0 + capabilities.capabilityFileSharingDownloadLimit = data.capabilities.downloadLimit?.enabled ?? false + capabilities.capabilityFileSharingDownloadLimitDefaultLimit = data.capabilities.downloadLimit?.defaultLimit ?? 1 capabilities.capabilityThemingColor = data.capabilities.theming?.color ?? "" capabilities.capabilityThemingColorElement = data.capabilities.theming?.colorelement ?? "" diff --git a/iOSClient/Data/NCManageDatabase+DownloadLimit.swift b/iOSClient/Data/NCManageDatabase+DownloadLimit.swift new file mode 100644 index 0000000000..300ea10b12 --- /dev/null +++ b/iOSClient/Data/NCManageDatabase+DownloadLimit.swift @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2024 Iva Horn +// SPDX-License-Identifier: GPL-3.0-or-later + +import Foundation +import NextcloudKit +import RealmSwift + +/// +/// Data model for storing information about download limits of shares. +/// +class tableDownloadLimit: Object { + /// + /// The number of downloads which already happened. + /// + @Persisted + @objc dynamic var count: Int = 0 + + /// + /// Total number of allowed downloas. + /// + @Persisted + @objc dynamic var limit: Int = 0 + + /// + /// The token identifying the related share. + /// + @Persisted(primaryKey: true) + @objc dynamic var token: String = "" +} + +extension NCManageDatabase { + /// + /// Create a new download limit object in the database. + /// + @discardableResult + func createDownloadLimit(count: Int, limit: Int, token: String) throws -> tableDownloadLimit? { + let downloadLimit = tableDownloadLimit() + downloadLimit.count = count + downloadLimit.limit = limit + downloadLimit.token = token + + do { + let realm = try Realm() + + try realm.write { + realm.add(downloadLimit, update: .all) + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") + } + + return downloadLimit + } + + /// + /// Delete an existing download limit object identified by the token of its related share. + /// + /// - Parameter token: The `token` of the associated ``Nextcloud/tableShare/token``. + /// + func deleteDownloadLimit(byShareToken token: String) throws { + do { + let realm = try Realm() + + try realm.write { + let result = realm.objects(tableDownloadLimit.self).filter("token == %@", token) + realm.delete(result) + } + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not write to database: \(error)") + } + } + + /// + /// Retrieve a download limit by the token of the associated ``Nextcloud/tableShare/token``. + /// + /// - Parameter token: The `token` of the associated ``tableShare``. + /// + func getDownloadLimit(byShareToken token: String) throws -> tableDownloadLimit? { + do { + let realm = try Realm() + let predicate = NSPredicate(format: "token == %@", token) + + guard let result = realm.objects(tableDownloadLimit.self).filter(predicate).first else { + return nil + } + + return result + } catch let error as NSError { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") + } + + return nil + } +} diff --git a/iOSClient/Data/NCManageDatabase+Share.swift b/iOSClient/Data/NCManageDatabase+Share.swift index c518597d42..86b2f9a921 100644 --- a/iOSClient/Data/NCManageDatabase+Share.swift +++ b/iOSClient/Data/NCManageDatabase+Share.swift @@ -54,7 +54,12 @@ class tableShareV2: Object { @objc dynamic var primaryKey = "" @objc dynamic var sendPasswordByTalk: Bool = false @objc dynamic var serverUrl = "" + + /// + /// See [OCS Share API documentation](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-share-api.html) for semantic definitions of the different possible values. + /// @objc dynamic var shareType: Int = 0 + @objc dynamic var shareWith = "" @objc dynamic var shareWithDisplayname = "" @objc dynamic var storage: Int = 0 @@ -146,22 +151,48 @@ extension NCManageDatabase { return [] } + /// + /// Fetch all available shares of an item identified by the given metadata. + /// + /// - Returns: A tuple consisting of the first public share link and any _additional_ shares that might be there. + /// It is possible that there is no public share link but still shares of other types. + /// In the latter case, all shares are returned as the second tuple value. + /// func getTableShares(metadata: tableMetadata) -> (firstShareLink: tableShare?, share: [tableShare]?) { do { let realm = try Realm() realm.refresh() - let sortProperties = [SortDescriptor(keyPath: "shareType", ascending: false), SortDescriptor(keyPath: "idShare", ascending: false)] - let firstShareLink = realm.objects(tableShare.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@ AND shareType == 3", metadata.account, metadata.serverUrl, metadata.fileName).first + + let sortProperties = [ + SortDescriptor(keyPath: "shareType", ascending: false), + SortDescriptor(keyPath: "idShare", ascending: false) + ] + + let firstShareLink = realm + .objects(tableShare.self) + .filter("account == %@ AND serverUrl == %@ AND fileName == %@ AND shareType == 3", metadata.account, metadata.serverUrl, metadata.fileName) + .first + if let firstShareLink = firstShareLink { - let results = realm.objects(tableShare.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@ AND idShare != %d", metadata.account, metadata.serverUrl, metadata.fileName, firstShareLink.idShare).sorted(by: sortProperties) - return(firstShareLink: tableShare.init(value: firstShareLink), share: Array(results.map { tableShare.init(value: $0) })) + let results = realm + .objects(tableShare.self) + .filter("account == %@ AND serverUrl == %@ AND fileName == %@ AND idShare != %d", metadata.account, metadata.serverUrl, metadata.fileName, firstShareLink.idShare) + .sorted(by: sortProperties) + + return (firstShareLink: tableShare.init(value: firstShareLink), share: Array(results.map { tableShare.init(value: $0) })) } else { - let results = realm.objects(tableShare.self).filter("account == %@ AND serverUrl == %@ AND fileName == %@", metadata.account, metadata.serverUrl, metadata.fileName).sorted(by: sortProperties) - return(firstShareLink: firstShareLink, share: Array(results.map { tableShare.init(value: $0) })) + let results = realm + .objects(tableShare.self) + .filter("account == %@ AND serverUrl == %@ AND fileName == %@", metadata.account, metadata.serverUrl, metadata.fileName) + .sorted(by: sortProperties) + + return (firstShareLink: firstShareLink, share: Array(results.map { tableShare.init(value: $0) })) } + } catch let error as NSError { NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not access database: \(error)") } + return (nil, nil) } @@ -190,6 +221,9 @@ extension NCManageDatabase { return [] } + /// + /// Fetch all shares of a file regardless of type. + /// func getTableShares(account: String, serverUrl: String, fileName: String) -> [tableShare] { do { let realm = try Realm() diff --git a/iOSClient/Data/NCManageDatabase.swift b/iOSClient/Data/NCManageDatabase.swift index 2934948612..6e6008ddf5 100644 --- a/iOSClient/Data/NCManageDatabase.swift +++ b/iOSClient/Data/NCManageDatabase.swift @@ -84,7 +84,8 @@ class NCManageDatabase: NSObject { tableDashboardWidget.self, tableDashboardWidgetButton.self, NCDBLayoutForView.self, - TableSecurityGuardDiagnostics.self] + TableSecurityGuardDiagnostics.self, + tableDownloadLimit.self] // Disable file protection for directory DB // https://docs.mongodb.com/realm/sdk/ios/examples/configure-and-open-a-realm/#std-label-ios-open-a-local-realm diff --git a/iOSClient/Menu/NCShare+Menu.swift b/iOSClient/Menu/NCShare+Menu.swift index 62ab9c8271..2770b5e471 100644 --- a/iOSClient/Menu/NCShare+Menu.swift +++ b/iOSClient/Menu/NCShare+Menu.swift @@ -54,6 +54,7 @@ extension NCShare { advancePermission.share = tableShare(value: share) advancePermission.oldTableShare = tableShare(value: share) advancePermission.metadata = self.metadata + advancePermission.downloadLimit = try? self.database.getDownloadLimit(byShareToken: share.token) navigationController.pushViewController(advancePermission, animated: true) } ) diff --git a/iOSClient/NCCapabilities.swift b/iOSClient/NCCapabilities.swift index 8d56106b48..755665c404 100644 --- a/iOSClient/NCCapabilities.swift +++ b/iOSClient/NCCapabilities.swift @@ -46,6 +46,8 @@ public class NCCapabilities: NSObject { var capabilityFileSharingRemoteExpireDateEnforced: Bool = false var capabilityFileSharingRemoteExpireDateDays: Int = 0 var capabilityFileSharingDefaultPermission: Int = 0 + var capabilityFileSharingDownloadLimit: Bool = false + var capabilityFileSharingDownloadLimitDefaultLimit: Int = 1 var capabilityThemingColor: String = "" var capabilityThemingColorElement: String = "" var capabilityThemingColorText: String = "" diff --git a/iOSClient/Networking/NCNetworking+WebDAV.swift b/iOSClient/Networking/NCNetworking+WebDAV.swift index 2dfa2bc1ed..bb12da5f2b 100644 --- a/iOSClient/Networking/NCNetworking+WebDAV.swift +++ b/iOSClient/Networking/NCNetworking+WebDAV.swift @@ -95,6 +95,24 @@ extension NCNetworking { let isDirectoryE2EE = self.utilityFileSystem.isDirectoryE2EE(file: file) let metadata = self.database.convertFileToMetadata(file, isDirectoryE2EE: isDirectoryE2EE) + // Remove all known download limits from shares related to the given file. + // This avoids obsolete download limit objects to stay around. + // Afterwards create new download limits, should any such be returned for the known shares. + + let shares = self.database.getTableShares(account: metadata.account, serverUrl: metadata.serverUrl, fileName: metadata.fileName) + + do { + try shares.forEach { share in + try self.database.deleteDownloadLimit(byShareToken: share.token) + + if let receivedDownloadLimit = file.downloadLimits.first(where: { $0.token == share.token }) { + try self.database.createDownloadLimit(count: receivedDownloadLimit.count, limit: receivedDownloadLimit.limit, token: receivedDownloadLimit.token) + } + } + } catch { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Could not update download limits: \(error)") + } + completion(account, metadata, error) } } diff --git a/iOSClient/Settings/Advanced/Capabilities/NCCapabilitiesModel.swift b/iOSClient/Settings/Advanced/Capabilities/NCCapabilitiesModel.swift index 86c6f43bcb..ef4c169c18 100644 --- a/iOSClient/Settings/Advanced/Capabilities/NCCapabilitiesModel.swift +++ b/iOSClient/Settings/Advanced/Capabilities/NCCapabilitiesModel.swift @@ -10,6 +10,11 @@ import Foundation import UIKit import SwiftUI +/// +/// Data model for ``NCCapabilitiesView``. +/// +/// Compiles capabilities, their availability and symbol images for display. +/// class NCCapabilitiesModel: ObservableObject, ViewOnAppearHandling { struct Capability: Identifiable, Hashable { let id = UUID() @@ -44,6 +49,9 @@ class NCCapabilitiesModel: ObservableObject, ViewOnAppearHandling { var image = utility.loadImage(named: "person.fill.badge.plus") capabililies.append(Capability(text: "File sharing", image: image, resize: false, available: capability.capabilityFileSharingApiEnabled)) + image = utility.loadImage(named: "gauge.with.dots.needle.bottom.100percent") + capabililies.append(Capability(text: "Download Limit", image: image, resize: false, available: capability.capabilityFileSharingDownloadLimit)) + image = utility.loadImage(named: "network") capabililies.append(Capability(text: "External site", image: image, resize: false, available: capability.capabilityExternalSites)) diff --git a/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitTableViewController.swift b/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitTableViewController.swift new file mode 100644 index 0000000000..a91a675bc2 --- /dev/null +++ b/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitTableViewController.swift @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2024 Iva Horn +// SPDX-License-Identifier: GPL-3.0-or-later + +import NextcloudKit +import UIKit + +/// +/// View controller for the table view managing the input form for download limits. +/// +/// This child view controller is required because table views require a dedicated table view controller. +/// +class NCShareDownloadLimitTableViewController: UITableViewController { + let database = NCManageDatabase.shared + + /// + /// The initial state injected from the parent view controller on appearance. + /// + public var initialDownloadLimit: tableDownloadLimit? + public var metadata: tableMetadata! + public var share: NCTableShareable! + + /// + /// Default value for limits as possibly provided by the server capabilities. + /// + var defaultLimit: Int { + NCCapabilities.shared.getCapabilities(account: metadata.account).capabilityFileSharingDownloadLimitDefaultLimit + } + + /// + /// Share token required to work with download limits. + /// + private var token: String! + + /// + /// The final state to apply once the view is about to disappear. + /// + private var finalDownloadLimit: tableDownloadLimit? + + private var networking: NCShareDownloadLimitNetworking! + + @IBOutlet var limitSwitch: UISwitch! + @IBOutlet var limitTextField: UITextField! + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if let initialDownloadLimit { + limitSwitch.isOn = true + limitTextField.text = "\(initialDownloadLimit.limit)" + + finalDownloadLimit = tableDownloadLimit() + finalDownloadLimit?.count = initialDownloadLimit.count + finalDownloadLimit?.limit = initialDownloadLimit.limit + finalDownloadLimit?.token = initialDownloadLimit.token + } else { + limitSwitch.isOn = false + } + + if let token = self.database.getTableShare(account: metadata.account, idShare: share.idShare)?.token { + self.token = token + } else { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Failed to resolve share token!") + self.token = "" + } + + networking = NCShareDownloadLimitNetworking(account: metadata.account, delegate: self, token: token) + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard indexPath.row == 1 else { + super.tableView(tableView, didSelectRowAt: indexPath) + return + } + + // The accessory text field should become first responder regardless where the user tapped in the table row. + limitTextField.becomeFirstResponder() + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + // Programmatically hide the limit input row depending on limit enablement. + if limitSwitch.isOn == false && indexPath.row == 1 { + return 0 + } + + return super.tableView(tableView, heightForRowAt: indexPath) + } + + override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + if let finalDownloadLimit { + String(format: NSLocalizedString("_remaining_share_downloads_", comment: "Table footer text for form of configuring download limits."), finalDownloadLimit.limit - finalDownloadLimit.count) + } else { + nil + } + } + + @IBAction func switchDownloadLimit(_ sender: UISwitch) { + if sender.isOn { + finalDownloadLimit = tableDownloadLimit() + finalDownloadLimit?.count = 0 + finalDownloadLimit?.limit = defaultLimit + finalDownloadLimit?.token = token + + limitTextField.text = String(defaultLimit) + } else { + finalDownloadLimit = nil + } + + tableView.reloadData() + dispatchShareDownloadLimitUpdate() + } + + @IBAction func editingAllowedDownloadsDidBegin(_ sender: UITextField) { + sender.selectAll(nil) + } + + @IBAction func editingAllowedDownloadsDidEnd(_ sender: UITextField) { + finalDownloadLimit?.limit = Int(sender.text ?? "1") ?? defaultLimit + finalDownloadLimit?.count = 0 + + tableView.reloadData() + dispatchShareDownloadLimitUpdate() + } + + func dispatchShareDownloadLimitUpdate() { + guard let text = limitTextField.text else { + return + } + + guard let limit = Int(text) else { + return + } + + if limitSwitch.isOn { + networking.setShareDownloadLimit(limit: limit) + } else { + networking.removeShareDownloadLimit() + } + } +} + +// MARK: - NCShareDownloadLimitNetworkingDelegate + +extension NCShareDownloadLimitTableViewController: NCShareDownloadLimitNetworkingDelegate { + func downloadLimitRemoved(by token: String, in account: String) { + do { + try self.database.deleteDownloadLimit(byShareToken: token) + } catch { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Failed to delete download limit in database!") + } + } + + func downloadLimitSet(to limit: Int, by token: String, in account: String) { + do { + try self.database.createDownloadLimit(count: 0, limit: limit, token: token) + } catch { + NextcloudKit.shared.nkCommonInstance.writeLog("[ERROR] Failed to create download limit in database!") + } + } +} diff --git a/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitViewController.swift b/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitViewController.swift new file mode 100644 index 0000000000..13e9515353 --- /dev/null +++ b/iOSClient/Share/Advanced/DownloadLimit/NCShareDownloadLimitViewController.swift @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2024 Iva Horn +// SPDX-License-Identifier: GPL-3.0-or-later + +import UIKit +import NextcloudKit + +/// +/// View controller for the download limit detail view in share details. +/// +class NCShareDownloadLimitViewController: UIViewController, NCShareDetail { + public var downloadLimit: tableDownloadLimit? + public var metadata: tableMetadata! + public var onDismiss: (() -> Void)? + public var share: NCTableShareable! + + @IBOutlet var headerContainerView: UIView! + + override func viewDidLoad() { + super.viewDidLoad() + self.setNavigationTitle() + + // Set up header view. + + guard let headerView = (Bundle.main.loadNibNamed("NCShareHeader", owner: self, options: nil)?.first as? NCShareHeader) else { return } + headerContainerView.addSubview(headerView) + headerView.frame = headerContainerView.frame + headerView.translatesAutoresizingMaskIntoConstraints = false + headerView.topAnchor.constraint(equalTo: headerContainerView.topAnchor).isActive = true + headerView.bottomAnchor.constraint(equalTo: headerContainerView.bottomAnchor).isActive = true + headerView.leftAnchor.constraint(equalTo: headerContainerView.leftAnchor).isActive = true + headerView.rightAnchor.constraint(equalTo: headerContainerView.rightAnchor).isActive = true + + headerView.setupUI(with: metadata) + + // End editing of inputs when the user taps anywhere else. + + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) + view.addGestureRecognizer(tapGesture) + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + guard let tableViewController = segue.destination as? NCShareDownloadLimitTableViewController else { + return + } + + tableViewController.initialDownloadLimit = downloadLimit + tableViewController.metadata = metadata + tableViewController.share = share + } + + @objc private func dismissKeyboard() { + view.endEditing(true) + } +} diff --git a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift index 23d395bcd1..8e5df63e92 100644 --- a/iOSClient/Share/Advanced/NCShareAdvancePermission.swift +++ b/iOSClient/Share/Advanced/NCShareAdvancePermission.swift @@ -71,6 +71,7 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg var share: NCTableShareable! var isNewShare: Bool { share is NCTableShareOptions } var metadata: tableMetadata! + var downloadLimit: tableDownloadLimit? var shareConfig: NCShareConfig! var networking: NCShareNetworking? @@ -165,6 +166,14 @@ class NCShareAdvancePermission: UITableViewController, NCShareAdvanceFotterDeleg } switch cellConfig { + case .limitDownload: + let storyboard = UIStoryboard(name: "NCShare", bundle: nil) + guard let viewController = storyboard.instantiateViewController(withIdentifier: "NCShareDownloadLimit") as? NCShareDownloadLimitViewController else { return } + viewController.downloadLimit = self.downloadLimit + viewController.metadata = self.metadata + viewController.share = self.share + viewController.onDismiss = tableView.reloadData + self.navigationController?.pushViewController(viewController, animated: true) case .hideDownload: share.hideDownload.toggle() tableView.reloadData() diff --git a/iOSClient/Share/Advanced/NCShareCells.swift b/iOSClient/Share/Advanced/NCShareCells.swift index 575b57c27d..f7418e8b37 100644 --- a/iOSClient/Share/Advanced/NCShareCells.swift +++ b/iOSClient/Share/Advanced/NCShareCells.swift @@ -192,10 +192,14 @@ enum NCLinkPermission: NCPermission { static let forFile: [NCLinkPermission] = [.allowEdit] } +/// +/// Individual aspects of share. +/// enum NCShareDetails: CaseIterable, NCShareCellConfig { func didSelect(for share: NCTableShareable) { switch self { case .hideDownload: share.hideDownload.toggle() + case .limitDownload: return case .expirationDate: return case .password: return case .note: return @@ -207,6 +211,10 @@ enum NCShareDetails: CaseIterable, NCShareCellConfig { switch self { case .hideDownload: return NCShareToggleCell(isOn: share.hideDownload) + case .limitDownload: + let cell = UITableViewCell(style: .value1, reuseIdentifier: "downloadLimit") + cell.accessoryType = .disclosureIndicator + return cell case .expirationDate: return NCShareDateCell(share: share) case .password: return NCShareToggleCell(isOn: !share.password.isEmpty, customIcons: ("lock", "lock_open")) @@ -225,6 +233,7 @@ enum NCShareDetails: CaseIterable, NCShareCellConfig { var title: String { switch self { case .hideDownload: return NSLocalizedString("_share_hide_download_", comment: "") + case .limitDownload: return NSLocalizedString("_share_limit_download_", comment: "") case .expirationDate: return NSLocalizedString("_share_expiration_date_", comment: "") case .password: return NSLocalizedString("_share_password_protect_", comment: "") case .note: return NSLocalizedString("_share_note_recipient_", comment: "") @@ -232,7 +241,7 @@ enum NCShareDetails: CaseIterable, NCShareCellConfig { } } - case label, hideDownload, expirationDate, password, note + case label, hideDownload, limitDownload, expirationDate, password, note static let forLink: [NCShareDetails] = NCShareDetails.allCases static let forUser: [NCShareDetails] = [.expirationDate, .note] } @@ -248,7 +257,16 @@ struct NCShareConfig { self.resharePermission = parentMetadata.sharePermissionsCollaborationServices let type: NCPermission.Type = share.shareType == NCShareCommon().SHARE_TYPE_LINK ? NCLinkPermission.self : NCUserPermission.self self.permissions = parentMetadata.directory ? (parentMetadata.e2eEncrypted ? type.forDirectoryE2EE(account: parentMetadata.account) : type.forDirectory) : type.forFile - self.advanced = share.shareType == NCShareCommon().SHARE_TYPE_LINK ? NCShareDetails.forLink : NCShareDetails.forUser + + if share.shareType == NCShareCommon().SHARE_TYPE_LINK { + if NCCapabilities.shared.getCapabilities(account: parentMetadata.account).capabilityFileSharingDownloadLimit { + self.advanced = NCShareDetails.forLink + } else { + self.advanced = NCShareDetails.forLink.filter { $0 != .limitDownload } + } + } else { + self.advanced = NCShareDetails.forUser + } } func cellFor(indexPath: IndexPath) -> UITableViewCell? { @@ -275,114 +293,3 @@ struct NCShareConfig { } else { return nil } } } - -class NCShareToggleCell: UITableViewCell { - typealias CustomToggleIcon = (onIconName: String?, offIconName: String?) - init(isOn: Bool, customIcons: CustomToggleIcon? = nil) { - super.init(style: .default, reuseIdentifier: "toggleCell") - self.accessibilityValue = isOn ? NSLocalizedString("_on_", comment: "") : NSLocalizedString("_off_", comment: "") - - guard let customIcons = customIcons, - let iconName = isOn ? customIcons.onIconName : customIcons.offIconName else { - self.accessoryType = isOn ? .checkmark : .none - return - } - let image = NCUtility().loadImage(named: iconName, colors: [NCBrandColor.shared.customer], size: self.frame.height - 26) - self.accessoryView = UIImageView(image: image) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -class NCShareDateCell: UITableViewCell { - let picker = UIDatePicker() - let textField = UITextField() - var shareType: Int - var onReload: (() -> Void)? - let shareCommon = NCShareCommon() - - init(share: NCTableShareable) { - self.shareType = share.shareType - super.init(style: .value1, reuseIdentifier: "shareExpDate") - - picker.datePickerMode = .date - picker.minimumDate = Date() - picker.preferredDatePickerStyle = .wheels - picker.action(for: .valueChanged) { datePicker in - guard let datePicker = datePicker as? UIDatePicker else { return } - self.detailTextLabel?.text = DateFormatter.shareExpDate.string(from: datePicker.date) - } - accessoryView = textField - - let toolbar = UIToolbar.toolbar { - self.resignFirstResponder() - share.expirationDate = nil - self.onReload?() - } onDone: { - self.resignFirstResponder() - share.expirationDate = self.picker.date as NSDate - self.onReload?() - } - - textField.isAccessibilityElement = false - textField.accessibilityElementsHidden = true - textField.inputAccessoryView = toolbar.wrappedSafeAreaContainer - textField.inputView = picker - - if let expDate = share.expirationDate { - detailTextLabel?.text = DateFormatter.shareExpDate.string(from: expDate as Date) - } - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func checkMaximumDate(account: String) { - let defaultExpDays = defaultExpirationDays(account: account) - if defaultExpDays > 0 && isExpireDateEnforced(account: account) { - let enforcedInSecs = TimeInterval(defaultExpDays * 24 * 60 * 60) - self.picker.maximumDate = Date().advanced(by: enforcedInSecs) - } - } - - private func isExpireDateEnforced(account: String) -> Bool { - switch self.shareType { - case shareCommon.SHARE_TYPE_LINK, - shareCommon.SHARE_TYPE_EMAIL, - shareCommon.SHARE_TYPE_GUEST: - return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingPubExpireDateEnforced - case shareCommon.SHARE_TYPE_USER, - shareCommon.SHARE_TYPE_GROUP, - shareCommon.SHARE_TYPE_CIRCLE, - shareCommon.SHARE_TYPE_ROOM: - return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingInternalExpireDateEnforced - case shareCommon.SHARE_TYPE_REMOTE, - shareCommon.SHARE_TYPE_REMOTE_GROUP: - return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingRemoteExpireDateEnforced - default: - return false - } - } - - private func defaultExpirationDays(account: String) -> Int { - switch self.shareType { - case shareCommon.SHARE_TYPE_LINK, - shareCommon.SHARE_TYPE_EMAIL, - shareCommon.SHARE_TYPE_GUEST: - return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingPubExpireDateDays - case shareCommon.SHARE_TYPE_USER, - shareCommon.SHARE_TYPE_GROUP, - shareCommon.SHARE_TYPE_CIRCLE, - shareCommon.SHARE_TYPE_ROOM: - return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingInternalExpireDateDays - case shareCommon.SHARE_TYPE_REMOTE, - shareCommon.SHARE_TYPE_REMOTE_GROUP: - return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingRemoteExpireDateDays - default: - return 0 - } - } -} diff --git a/iOSClient/Share/Advanced/NCShareDateCell.swift b/iOSClient/Share/Advanced/NCShareDateCell.swift new file mode 100644 index 0000000000..fab608f1aa --- /dev/null +++ b/iOSClient/Share/Advanced/NCShareDateCell.swift @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Henrik Storch +// SPDX-License-Identifier: GPL-3.0-or-later + +/// +/// Table view cell to manage the expiration date on a share in its details. +/// +class NCShareDateCell: UITableViewCell { + let picker = UIDatePicker() + let textField = UITextField() + var shareType: Int + var onReload: (() -> Void)? + let shareCommon = NCShareCommon() + + init(share: NCTableShareable) { + self.shareType = share.shareType + super.init(style: .value1, reuseIdentifier: "shareExpDate") + + picker.datePickerMode = .date + picker.minimumDate = Date() + picker.preferredDatePickerStyle = .wheels + picker.action(for: .valueChanged) { datePicker in + guard let datePicker = datePicker as? UIDatePicker else { return } + self.detailTextLabel?.text = DateFormatter.shareExpDate.string(from: datePicker.date) + } + accessoryView = textField + + let toolbar = UIToolbar.toolbar { + self.resignFirstResponder() + share.expirationDate = nil + self.onReload?() + } onDone: { + self.resignFirstResponder() + share.expirationDate = self.picker.date as NSDate + self.onReload?() + } + + textField.isAccessibilityElement = false + textField.accessibilityElementsHidden = true + textField.inputAccessoryView = toolbar.wrappedSafeAreaContainer + textField.inputView = picker + + if let expDate = share.expirationDate { + detailTextLabel?.text = DateFormatter.shareExpDate.string(from: expDate as Date) + } + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func checkMaximumDate(account: String) { + let defaultExpDays = defaultExpirationDays(account: account) + if defaultExpDays > 0 && isExpireDateEnforced(account: account) { + let enforcedInSecs = TimeInterval(defaultExpDays * 24 * 60 * 60) + self.picker.maximumDate = Date().advanced(by: enforcedInSecs) + } + } + + private func isExpireDateEnforced(account: String) -> Bool { + switch self.shareType { + case shareCommon.SHARE_TYPE_LINK, + shareCommon.SHARE_TYPE_EMAIL, + shareCommon.SHARE_TYPE_GUEST: + return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingPubExpireDateEnforced + case shareCommon.SHARE_TYPE_USER, + shareCommon.SHARE_TYPE_GROUP, + shareCommon.SHARE_TYPE_CIRCLE, + shareCommon.SHARE_TYPE_ROOM: + return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingInternalExpireDateEnforced + case shareCommon.SHARE_TYPE_REMOTE, + shareCommon.SHARE_TYPE_REMOTE_GROUP: + return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingRemoteExpireDateEnforced + default: + return false + } + } + + private func defaultExpirationDays(account: String) -> Int { + switch self.shareType { + case shareCommon.SHARE_TYPE_LINK, + shareCommon.SHARE_TYPE_EMAIL, + shareCommon.SHARE_TYPE_GUEST: + return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingPubExpireDateDays + case shareCommon.SHARE_TYPE_USER, + shareCommon.SHARE_TYPE_GROUP, + shareCommon.SHARE_TYPE_CIRCLE, + shareCommon.SHARE_TYPE_ROOM: + return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingInternalExpireDateDays + case shareCommon.SHARE_TYPE_REMOTE, + shareCommon.SHARE_TYPE_REMOTE_GROUP: + return NCCapabilities.shared.getCapabilities(account: account).capabilityFileSharingRemoteExpireDateDays + default: + return 0 + } + } +} diff --git a/iOSClient/Share/Advanced/NCShareToggleCell.swift b/iOSClient/Share/Advanced/NCShareToggleCell.swift new file mode 100644 index 0000000000..b3ff60457a --- /dev/null +++ b/iOSClient/Share/Advanced/NCShareToggleCell.swift @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2022 Henrik Storch +// SPDX-License-Identifier: GPL-3.0-or-later + +/// +/// A table view cell for logical switches in the detaills of a share configuration. +/// +class NCShareToggleCell: UITableViewCell { + typealias CustomToggleIcon = (onIconName: String?, offIconName: String?) + init(isOn: Bool, customIcons: CustomToggleIcon? = nil) { + super.init(style: .default, reuseIdentifier: "toggleCell") + self.accessibilityValue = isOn ? NSLocalizedString("_on_", comment: "") : NSLocalizedString("_off_", comment: "") + + guard let customIcons = customIcons, + let iconName = isOn ? customIcons.onIconName : customIcons.offIconName else { + self.accessoryType = isOn ? .checkmark : .none + return + } + let image = NCUtility().loadImage(named: iconName, colors: [NCBrandColor.shared.customer], size: self.frame.height - 26) + self.accessoryView = UIImageView(image: image) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/iOSClient/Share/NCShare.storyboard b/iOSClient/Share/NCShare.storyboard index d06257f31f..e3e3bbd089 100644 --- a/iOSClient/Share/NCShare.storyboard +++ b/iOSClient/Share/NCShare.storyboard @@ -1,9 +1,9 @@ - + - + @@ -247,6 +247,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -282,16 +395,16 @@ - + - + - + diff --git a/iOSClient/Share/NCShareDownloadLimitNetworking.swift b/iOSClient/Share/NCShareDownloadLimitNetworking.swift new file mode 100644 index 0000000000..325e81c5b7 --- /dev/null +++ b/iOSClient/Share/NCShareDownloadLimitNetworking.swift @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: Nextcloud GmbH +// SPDX-FileCopyrightText: 2024 Iva Horn +// SPDX-License-Identifier: GPL-3.0-or-later + +import UIKit +import NextcloudKit + +/// +/// Delegate requirements for ``NCShareDownloadLimitNetworking`` to handle results. +/// +protocol NCShareDownloadLimitNetworkingDelegate: AnyObject { + /// + /// The download limit was successfully removed from the share on the server. + /// + func downloadLimitRemoved(by token: String, in account: String) + + /// + /// The download limit was successfully removed from the share on the server. + /// + func downloadLimitSet(to limit: Int, by token: String, in account: String) +} + +/// +/// Share-bound network abstraction for download limits. +/// +class NCShareDownloadLimitNetworking: NSObject { + let account: String + weak var delegate: (any NCShareDownloadLimitNetworkingDelegate)? + weak var view: UIView? + let token: String + + init(account: String, delegate: (any NCShareDownloadLimitNetworkingDelegate)?, token: String) { + self.account = account + self.delegate = delegate + self.token = token + } + + /// + /// Remove the download limit on the share, if existent. + /// + func removeShareDownloadLimit() { + NCActivityIndicator.shared.start(backgroundView: view) + NextcloudKit.shared.removeShareDownloadLimit(account: account, token: token) { error in + NCActivityIndicator.shared.stop() + + if error == .success { + self.delegate?.downloadLimitRemoved(by: self.token, in: self.account) + } else { + NCContentPresenter().showError(error: error) + } + } + } + + /// + /// Set the download limit for the share. + /// + /// - Parameter limit: The new download limit to set. + /// + func setShareDownloadLimit(limit: Int) { + NCActivityIndicator.shared.start(backgroundView: view) + NextcloudKit.shared.setShareDownloadLimit(account: account, token: token, limit: limit) { error in + NCActivityIndicator.shared.stop() + + if error == .success { + self.delegate?.downloadLimitSet(to: limit, by: self.token, in: self.account) + } else { + NCContentPresenter().showError(error: error) + } + } + } +} diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index d180c2e608..51725e200f 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -677,6 +677,9 @@ "_share_file_drop_" = "File drop (upload only)"; "_share_secure_file_drop_" = "Secure file drop (upload only)"; "_share_hide_download_" = "Hide download"; +"_share_limit_download_" = "Limit downloads"; +"_remaining_share_downloads_" = "%d remaining downloads allowed"; +"_remaining_" = "%d remaining"; "_share_password_protect_" = "Password protect"; "_share_expiration_date_" = "Set expiration date"; "_share_note_recipient_" = "Note to recipient"; From 1965c6bce8d18b7e349b7650ed9cb6fb498e01f0 Mon Sep 17 00:00:00 2001 From: Iva Horn Date: Wed, 27 Nov 2024 17:01:43 +0100 Subject: [PATCH 2/8] Updated NextcloudKit reference to files_downloadlimit branch. Signed-off-by: Iva Horn --- Nextcloud.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 24eafd981b..fd6456b0b8 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -5913,8 +5913,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nextcloud/NextcloudKit"; requirement = { - kind = exactVersion; - version = 5.0.2; + branch = files_downloadlimit; + kind = branch; }; }; F788ECC5263AAAF900ADC67F /* XCRemoteSwiftPackageReference "MarkdownKit" */ = { From 79127cfc1e206b49b6b517b84aea3e2078175d84 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 19 Dec 2024 13:29:06 +0100 Subject: [PATCH 3/8] Update iOSClient/Data/NCManageDatabase+DownloadLimit.swift Signed-off-by: Milen Pivchev --- iOSClient/Data/NCManageDatabase+DownloadLimit.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOSClient/Data/NCManageDatabase+DownloadLimit.swift b/iOSClient/Data/NCManageDatabase+DownloadLimit.swift index 300ea10b12..f00e92c80d 100644 --- a/iOSClient/Data/NCManageDatabase+DownloadLimit.swift +++ b/iOSClient/Data/NCManageDatabase+DownloadLimit.swift @@ -17,7 +17,7 @@ class tableDownloadLimit: Object { @objc dynamic var count: Int = 0 /// - /// Total number of allowed downloas. + /// Total number of allowed downloads. /// @Persisted @objc dynamic var limit: Int = 0 From aed54cbe74edb87bc063e071d5fc31ae40897779 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Dec 2024 11:19:15 +0100 Subject: [PATCH 4/8] fix Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 4 ++-- iOSClient/Files/NCFiles.swift | 22 ++++++++++++++-------- iOSClient/Media/NCMedia.swift | 19 +++++++++++++++++-- iOSClient/Media/NCMediaDataSource.swift | 7 +++++-- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 0679464d5b..2a7bc40420 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -5530,7 +5530,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.2.1; + MARKETING_VERSION = 6.2.2; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-v"; OTHER_LDFLAGS = ""; @@ -5593,7 +5593,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.2.1; + MARKETING_VERSION = 6.2.2; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-v"; OTHER_LDFLAGS = ""; diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index dcd604cf8d..796178a128 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -31,7 +31,7 @@ class NCFiles: NCCollectionViewCommon { internal var fileNameBlink: String? internal var fileNameOpen: String? internal var matadatasHash: String = "" - internal var reloadDataSourceInProgress: Bool = false + internal var semaphoreReloadDataSource = DispatchSemaphore(value: 1) required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) @@ -122,12 +122,16 @@ class NCFiles: NCCollectionViewCommon { // MARK: - DataSource override func reloadDataSource() { - guard !isSearchingMode, - !reloadDataSourceInProgress + guard !isSearchingMode else { return super.reloadDataSource() } - reloadDataSourceInProgress = true + + // This is only a fail safe "dead lock", I don't think the timeout will ever be called but at least nothing gets stuck, if after 5 sec. (which is a long time in this routine), the semaphore is still locked + // + if self.semaphoreReloadDataSource.wait(timeout: .now() + 5) == .timedOut { + self.semaphoreReloadDataSource.signal() + } var predicate = self.defaultPredicate let predicateDirectory = NSPredicate(format: "account == %@ AND serverUrl == %@", session.account, self.serverUrl) @@ -145,14 +149,16 @@ class NCFiles: NCCollectionViewCommon { self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: layoutForView) if metadatas.isEmpty { - reloadDataSourceInProgress = false + self.semaphoreReloadDataSource.signal() return super.reloadDataSource() } self.dataSource.caching(metadatas: metadatas, dataSourceMetadatas: dataSourceMetadatas) { updated in - self.reloadDataSourceInProgress = false - if updated || self.isNumberOfItemsInAllSectionsNull || self.numberOfItemsInAllSections != metadatas.count { - super.reloadDataSource() + self.semaphoreReloadDataSource.signal() + DispatchQueue.main.async { + if updated || self.isNumberOfItemsInAllSectionsNull || self.numberOfItemsInAllSections != metadatas.count { + super.reloadDataSource() + } } } } diff --git a/iOSClient/Media/NCMedia.swift b/iOSClient/Media/NCMedia.swift index 222591a080..ea14552be2 100644 --- a/iOSClient/Media/NCMedia.swift +++ b/iOSClient/Media/NCMedia.swift @@ -36,6 +36,9 @@ class NCMedia: UIViewController { @IBOutlet weak var menuButton: UIButton! @IBOutlet weak var gradientView: UIView! + let semaphoreSearchMedia = DispatchSemaphore(value: 1) + let semaphoreNotificationCenter = DispatchSemaphore(value: 1) + let layout = NCMediaLayout() var layoutType = NCGlobal.shared.mediaLayoutRatio var documentPickerViewController: NCDocumentPickerViewController? @@ -257,13 +260,25 @@ class NCMedia: UIViewController { return } + // This is only a fail safe "dead lock", I don't think the timeout will ever be called but at least nothing gets stuck, if after 5 sec. (which is a long time in this routine), the semaphore is still locked + // + if self.semaphoreNotificationCenter.wait(timeout: .now() + 5) == .timedOut { + self.semaphoreNotificationCenter.signal() + } + if error.errorCode == self.global.errorResourceNotFound, let ocId = userInfo["ocId"] as? String { self.database.deleteMetadataOcId(ocId) - self.loadDataSource() + self.loadDataSource { + self.semaphoreNotificationCenter.signal() + } } else if error != .success { NCContentPresenter().showError(error: error) - self.loadDataSource() + self.loadDataSource { + self.semaphoreNotificationCenter.signal() + } + } else { + semaphoreNotificationCenter.signal() } } diff --git a/iOSClient/Media/NCMediaDataSource.swift b/iOSClient/Media/NCMediaDataSource.swift index 4a62a534c3..19740441f8 100644 --- a/iOSClient/Media/NCMediaDataSource.swift +++ b/iOSClient/Media/NCMediaDataSource.swift @@ -58,12 +58,13 @@ extension NCMedia { NCNetworking.shared.downloadThumbnailQueue.operationCount == 0, let tableAccount = database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) else { return } - self.searchMediaInProgress = true - let limit = max(self.collectionView.visibleCells.count * 3, 300) let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) DispatchQueue.global(qos: .background).async { + self.semaphoreSearchMedia.wait() + self.searchMediaInProgress = true + var lessDate = Date.distantFuture var greaterDate = Date.distantPast let countMetadatas = self.dataSource.metadatas.count @@ -156,6 +157,8 @@ extension NCMedia { self.collectionViewReloadData() } + self.semaphoreSearchMedia.signal() + DispatchQueue.main.async { self.activityIndicator.stopAnimating() self.searchMediaInProgress = false From 341c030f03be660864612ce4ce524cb71ce6dd30 Mon Sep 17 00:00:00 2001 From: Iva Horn Date: Fri, 20 Dec 2024 16:08:37 +0100 Subject: [PATCH 5/8] Updated NextcloudKit reference to develop branch. Signed-off-by: Iva Horn --- Nextcloud.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index fd6456b0b8..c1d8ac25d9 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -1168,11 +1168,6 @@ 8491B1CC273BBA82001C8C5B /* UIViewController+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Menu.swift"; sourceTree = ""; }; AA3494FD2CE65EA9005CC075 /* NCManageDatabase+DownloadLimit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCManageDatabase+DownloadLimit.swift"; sourceTree = ""; }; AAAC0A112CEE346A0001949E /* NCShareDownloadLimitNetworking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDownloadLimitNetworking.swift; sourceTree = ""; }; - AAF806B12CE25E60009C2D43 /* NCShareDateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDateCell.swift; sourceTree = ""; }; - AAF806B32CE25EFE009C2D43 /* NCShareToggleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareToggleCell.swift; sourceTree = ""; }; - AAF806B52CE34C72009C2D43 /* NCShareDownloadLimitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDownloadLimitViewController.swift; sourceTree = ""; }; - AAF806B72CE37C15009C2D43 /* NCShareDownloadLimitTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDownloadLimitTableViewController.swift; sourceTree = ""; }; - AAF806B92CE38BB2009C2D43 /* NextcloudKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = NextcloudKit; path = ../NextcloudKit; sourceTree = SOURCE_ROOT; }; AACCAB522CFE041F00DA1786 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Intent.strings; sourceTree = ""; }; AACCAB532CFE041F00DA1786 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Localizable.strings; sourceTree = ""; }; AACCAB542CFE041F00DA1786 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -1182,6 +1177,11 @@ AACCAB622CFE04F700DA1786 /* lo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lo; path = lo.lproj/Intent.strings; sourceTree = ""; }; AACCAB632CFE04F700DA1786 /* lo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lo; path = lo.lproj/Localizable.strings; sourceTree = ""; }; AACCAB642CFE04F700DA1786 /* lo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lo; path = lo.lproj/InfoPlist.strings; sourceTree = ""; }; + AAF806B12CE25E60009C2D43 /* NCShareDateCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDateCell.swift; sourceTree = ""; }; + AAF806B32CE25EFE009C2D43 /* NCShareToggleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareToggleCell.swift; sourceTree = ""; }; + AAF806B52CE34C72009C2D43 /* NCShareDownloadLimitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDownloadLimitViewController.swift; sourceTree = ""; }; + AAF806B72CE37C15009C2D43 /* NCShareDownloadLimitTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareDownloadLimitTableViewController.swift; sourceTree = ""; }; + AAF806B92CE38BB2009C2D43 /* NextcloudKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = NextcloudKit; path = ../NextcloudKit; sourceTree = SOURCE_ROOT; }; AF1A9B6327D0CA1E00F17A9E /* UIAlertController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Extension.swift"; sourceTree = ""; }; AF22B20B277C6F4D00DAB0CC /* NCShareCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCShareCell.swift; sourceTree = ""; }; AF22B215277D196700DAB0CC /* NCShareExtension+DataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NCShareExtension+DataSource.swift"; sourceTree = ""; }; @@ -5913,7 +5913,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/nextcloud/NextcloudKit"; requirement = { - branch = files_downloadlimit; + branch = develop; kind = branch; }; }; From bcfb4031eb0b116128378dc1a36b8234613b716e Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Sat, 21 Dec 2024 02:51:38 +0000 Subject: [PATCH 6/8] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot --- .../af.lproj/Localizable.strings | Bin 138352 -> 138694 bytes .../an.lproj/Localizable.strings | Bin 137710 -> 138052 bytes .../ar.lproj/Localizable.strings | Bin 135906 -> 136248 bytes .../ast.lproj/Localizable.strings | Bin 139388 -> 139730 bytes .../az.lproj/Localizable.strings | Bin 138034 -> 138376 bytes .../be.lproj/Localizable.strings | Bin 137790 -> 138132 bytes .../bg_BG.lproj/Localizable.strings | Bin 144720 -> 145062 bytes .../bn_BD.lproj/Localizable.strings | Bin 137964 -> 138306 bytes .../br.lproj/Localizable.strings | Bin 141730 -> 142072 bytes .../bs.lproj/Localizable.strings | Bin 138026 -> 138368 bytes .../ca.lproj/Localizable.strings | Bin 144626 -> 144968 bytes .../cs-CZ.lproj/Localizable.strings | Bin 141660 -> 142002 bytes .../cy_GB.lproj/Localizable.strings | Bin 137960 -> 138302 bytes .../da.lproj/Localizable.strings | Bin 138954 -> 139296 bytes .../de.lproj/Localizable.strings | Bin 150644 -> 150986 bytes .../el.lproj/Localizable.strings | Bin 151012 -> 151354 bytes .../en-GB.lproj/Localizable.strings | Bin 137812 -> 138154 bytes .../eo.lproj/Localizable.strings | Bin 138416 -> 138758 bytes .../es-419.lproj/Localizable.strings | Bin 141836 -> 142178 bytes .../es-AR.lproj/Localizable.strings | Bin 140902 -> 141244 bytes .../es-CL.lproj/Localizable.strings | Bin 142554 -> 142896 bytes .../es-CO.lproj/Localizable.strings | Bin 142196 -> 142538 bytes .../es-CR.lproj/Localizable.strings | Bin 142198 -> 142540 bytes .../es-DO.lproj/Localizable.strings | Bin 142194 -> 142536 bytes .../es-EC.lproj/Localizable.strings | Bin 146996 -> 147338 bytes .../es-GT.lproj/Localizable.strings | Bin 142198 -> 142540 bytes .../es-HN.lproj/Localizable.strings | Bin 141820 -> 142162 bytes .../es-MX.lproj/Localizable.strings | Bin 142538 -> 142880 bytes .../es-NI.lproj/Localizable.strings | Bin 141810 -> 142152 bytes .../es-PA.lproj/Localizable.strings | Bin 141810 -> 142152 bytes .../es-PE.lproj/Localizable.strings | Bin 141800 -> 142142 bytes .../es-PR.lproj/Localizable.strings | Bin 141812 -> 142154 bytes .../es-PY.lproj/Localizable.strings | Bin 141836 -> 142178 bytes .../es-SV.lproj/Localizable.strings | Bin 142186 -> 142528 bytes .../es-UY.lproj/Localizable.strings | Bin 141834 -> 142176 bytes .../es.lproj/Localizable.strings | Bin 147232 -> 147574 bytes .../et_EE.lproj/Localizable.strings | Bin 138236 -> 138578 bytes .../eu.lproj/Localizable.strings | Bin 146046 -> 146388 bytes .../fa.lproj/Localizable.strings | Bin 138488 -> 138830 bytes .../fi-FI.lproj/Localizable.strings | Bin 140664 -> 141006 bytes .../fo.lproj/Localizable.strings | Bin 137720 -> 138062 bytes .../fr.lproj/Localizable.strings | Bin 153056 -> 153398 bytes .../ga.lproj/Localizable.strings | Bin 149270 -> 149612 bytes .../gd.lproj/Localizable.strings | Bin 139196 -> 139538 bytes .../gl.lproj/Localizable.strings | Bin 148024 -> 148366 bytes .../he.lproj/Localizable.strings | Bin 135844 -> 136186 bytes .../hi_IN.lproj/Localizable.strings | Bin 137694 -> 138036 bytes .../hr.lproj/Localizable.strings | Bin 142348 -> 142690 bytes .../hsb.lproj/Localizable.strings | Bin 137700 -> 138042 bytes .../hu.lproj/Localizable.strings | Bin 143622 -> 143964 bytes .../hy.lproj/Localizable.strings | Bin 137968 -> 138310 bytes .../ia.lproj/Localizable.strings | Bin 138440 -> 138782 bytes .../id.lproj/Localizable.strings | Bin 138586 -> 138928 bytes .../ig.lproj/Localizable.strings | Bin 137670 -> 138012 bytes .../is.lproj/Localizable.strings | Bin 139892 -> 140234 bytes .../it.lproj/Localizable.strings | Bin 147334 -> 147676 bytes .../ja-JP.lproj/Localizable.strings | Bin 113948 -> 113694 bytes .../ka-GE.lproj/Localizable.strings | Bin 140396 -> 140738 bytes .../ka.lproj/Localizable.strings | Bin 137698 -> 138040 bytes .../kab.lproj/Localizable.strings | Bin 137740 -> 138082 bytes .../km.lproj/Localizable.strings | Bin 137952 -> 138294 bytes .../kn.lproj/Localizable.strings | Bin 138180 -> 138522 bytes .../ko.lproj/Localizable.strings | Bin 116602 -> 116944 bytes .../la.lproj/Localizable.strings | Bin 137684 -> 138026 bytes .../lb.lproj/Localizable.strings | Bin 138162 -> 138504 bytes .../lo.lproj/Localizable.strings | Bin 136614 -> 136956 bytes .../lt_LT.lproj/Localizable.strings | Bin 140852 -> 141194 bytes .../lv.lproj/Localizable.strings | Bin 138888 -> 139230 bytes .../mk.lproj/Localizable.strings | Bin 138976 -> 139318 bytes .../mn.lproj/Localizable.strings | Bin 138460 -> 138802 bytes .../mr.lproj/Localizable.strings | Bin 137662 -> 138004 bytes .../ms_MY.lproj/Localizable.strings | Bin 137808 -> 138150 bytes .../my.lproj/Localizable.strings | Bin 137880 -> 138222 bytes .../nb-NO.lproj/Localizable.strings | Bin 140382 -> 140724 bytes .../ne.lproj/Localizable.strings | Bin 137714 -> 138056 bytes .../nl.lproj/Localizable.strings | Bin 142980 -> 143322 bytes .../nn_NO.lproj/Localizable.strings | Bin 137832 -> 138174 bytes .../oc.lproj/Localizable.strings | Bin 138838 -> 139180 bytes .../pl.lproj/Localizable.strings | Bin 142358 -> 142700 bytes .../ps.lproj/Localizable.strings | Bin 137708 -> 138050 bytes .../pt-BR.lproj/Localizable.strings | Bin 145268 -> 145610 bytes .../pt-PT.lproj/Localizable.strings | Bin 141468 -> 141810 bytes .../ro.lproj/Localizable.strings | Bin 140792 -> 141134 bytes .../ru.lproj/Localizable.strings | Bin 145134 -> 145476 bytes .../sc.lproj/Localizable.strings | Bin 146500 -> 146842 bytes .../si.lproj/Localizable.strings | Bin 138574 -> 138916 bytes .../sk-SK.lproj/Localizable.strings | Bin 142454 -> 142796 bytes .../sl.lproj/Localizable.strings | Bin 143406 -> 143748 bytes .../sq.lproj/Localizable.strings | Bin 139426 -> 139768 bytes .../sr.lproj/Localizable.strings | Bin 142248 -> 142590 bytes .../sr@latin.lproj/Localizable.strings | Bin 138002 -> 138344 bytes .../sv.lproj/Localizable.strings | Bin 140018 -> 140360 bytes .../sw.lproj/Localizable.strings | Bin 137694 -> 138036 bytes .../ta.lproj/Localizable.strings | Bin 138032 -> 138374 bytes .../th_TH.lproj/Localizable.strings | Bin 137604 -> 137946 bytes .../tk.lproj/Localizable.strings | Bin 138376 -> 138718 bytes .../tr.lproj/Localizable.strings | Bin 142922 -> 143264 bytes .../ug.lproj/Localizable.strings | Bin 142118 -> 142460 bytes .../uk.lproj/Localizable.strings | Bin 140194 -> 140536 bytes .../ur_PK.lproj/Localizable.strings | Bin 137742 -> 138084 bytes .../uz.lproj/Localizable.strings | Bin 137694 -> 138036 bytes .../vi.lproj/Localizable.strings | Bin 139072 -> 139414 bytes .../zh-Hans.lproj/Localizable.strings | Bin 101276 -> 101088 bytes .../zh-Hant-TW.lproj/Localizable.strings | Bin 107640 -> 107982 bytes .../zh_HK.lproj/Localizable.strings | Bin 101456 -> 101798 bytes .../zu_ZA.lproj/Localizable.strings | Bin 137694 -> 138036 bytes 106 files changed, 0 insertions(+), 0 deletions(-) diff --git a/iOSClient/Supporting Files/af.lproj/Localizable.strings b/iOSClient/Supporting Files/af.lproj/Localizable.strings index bf7af0d72cd33bdb18789655f9a65c69243283d9..bd653ed8937584cb1179ee8cd0c407dc59b7842e 100644 GIT binary patch delta 128 zcmeycgX7q4j)pCaat5q944DkM(|H9L4LN;4Odu|qdXQ0=vzS4N!J2`KL22^E8R9@* z216o45ku^bm}2Sq3H@1j4Ygq3^_oU&rm-3eVFLvTQhW~ de^6%>m@IIMZL-iznduLV8F{wL888OD0RUD*ChY(K delta 36 ucmV+<0Nek@y9n^O2!ON!9x#{Q9sv@U>?i>alc*37mk>w+3b(E?0T}EE;|&b} diff --git a/iOSClient/Supporting Files/an.lproj/Localizable.strings b/iOSClient/Supporting Files/an.lproj/Localizable.strings index 24d93ae700fbcf7943212a2ddf1d5951486b8afb..15bcd6a402a189a95734d5e59833822a5b8d1809 100644 GIT binary patch delta 152 zcmaF2nd8Vdj)pCaEqc>GNHemq<}ze5luS?TU{s&nz{$a$$p8@u+eyb4?e}W#rl3qQ{u^8UV|kDwF^K delta 17 ZcmX@IjpN;Bj)pCaEqdE;=`&`%1^`TD2q^#n diff --git a/iOSClient/Supporting Files/ar.lproj/Localizable.strings b/iOSClient/Supporting Files/ar.lproj/Localizable.strings index 371b03de1856ec868db306c066d04d2bd5f85535..c57a09fef24fd486f0de4af5a611eea65399ac97 100644 GIT binary patch delta 215 zcmaE~lw-#Vj)pCa)AXjvFtV`bGGsE8Okc>ys6P1vADgNVkP8-KP+&-5$Y&^L$YaO> z(uqJ`F@q9=H3Jue(&UBH#W{-^GJv8*45^b7zX*pGGsFXB6~NFINGdU?0(B^W|Ir)Pt2YV(1kjqdqU6F}VShJWxiNTtI z3rNN@6fvX%r4oUXd0?Cl6f0)P0Ez%*CI{vVixvY#lo%Ai&=yE4F{m=6Ox`$4bh;7~ zqY!T~P$-ch2MF^S$|pYz7i9(-HhtkSM$yT6`&F1BYUQVYFks}F&ZfvHw7txTk?kD- DdY3I0 delta 17 Zcmca~nB&g@j)pCaDMs6O88b?}0{~5X2iO1r diff --git a/iOSClient/Supporting Files/az.lproj/Localizable.strings b/iOSClient/Supporting Files/az.lproj/Localizable.strings index 53bdc363ae2b933c78cc24705b7c0825142bab3a..7fa7ff869807802b7492f97f9e973426569a446e 100644 GIT binary patch delta 162 zcmdnAjiX~HN5dAz6n)MdhD?TBATF7_@R$1J3p{MoFUT^outJ0azY22}Gbk}wGjK5| zO>Uea&R)!r!H~#MG<{(wqbO@JLp)Hp@TTzOLo@O!t#x6q+8O!N@mxn}EP1F}}&`>bNE+yy4rv JOP{ggH2|dhHg^C3 delta 27 jcmeC!$+2l0N5dAz6#eOba*RUL12h=Z(ky0B zVz6f50+R6zMGUD7xeSRw)p=l?4iqb9$N-7}WhM)L5}wZVnNdhkl_7;e0a@|nk6$Du zi-BT^3^_oU&rl9DC625SZ`AQ{h4 z#E=S4j?;^(Qy*a!lTElWj7~OquC*MvOeu6XY0$w)f~W&U+02 Dz2GYM delta 17 ZcmZqJ$+2o1N5dAzD*f#j3>Y`O1^`5r2ipJu diff --git a/iOSClient/Supporting Files/ca.lproj/Localizable.strings b/iOSClient/Supporting Files/ca.lproj/Localizable.strings index a1bf1a08217dac17a30dae2f5514c96b1043fcc4..84afb3b693aa855bde84f0c2b5c4403e4b73b9c4 100644 GIT binary patch delta 189 zcmezLlH0D)YcN9Vk}JkO33{%1m~ADXd%!6j5SO07F|Ksl=elkOHKUbWXPYDk)hE z6iZ~t0m6KSa-gXxFk2?ioMAe7Lxcb`P~YV1Gvuc~&}HPAZl=MgF!`6hz~qJ?w&?<# NOf1{`^ckaG0{|mrG3)>U delta 17 ZcmdnDgX6_kj)pCaA^O`_88A+H4FE~h2kig= diff --git a/iOSClient/Supporting Files/da.lproj/Localizable.strings b/iOSClient/Supporting Files/da.lproj/Localizable.strings index 7d70fc9f680ebaab9b11bdb58f751c0f474c11ad..588b0a9d5d508258bc15975c0c79388a1962b622 100644 GIT binary patch delta 148 zcmX@Lmt(;Jj)pCaT}JFV44DkM44Kp4zh%^%yg;6V9n57YnZA&TQJAxsL5abdfr~+D zviwYOATNU?^D2MF^S%7N+_ muL)1qn5i>)1HS;0(p|?Gxuz>Y6=-}S(1JWL pP6vu%8mj48$r{ti0_O$9ahTP<(UWoeMo*^0OaS&BFOvWO delta 21 dcmX>#oAb*I&W0_FCp_EbycxI4c{8nH0sv=42nzrJ diff --git a/iOSClient/Supporting Files/el.lproj/Localizable.strings b/iOSClient/Supporting Files/el.lproj/Localizable.strings index 0349bd0ed6c0b9206ed390f92095b28d263e74bc..a3cfe743e0d43f876a44912fd9308865a24e4601 100644 GIT binary patch delta 208 zcmaDdn{(GZ&W0_F0e4sk!g(oX4*PB+yC;&1-5W|S+j=qfI+XMUMB_ delta 51 zcmV-30L=foo(bfe34pW#P)?WLIsp=wZbktPmudw95SOsL0SJ?z4+fXa2LV2pcsl_U Jw>D4#1pnx@5sm-= diff --git a/iOSClient/Supporting Files/en-GB.lproj/Localizable.strings b/iOSClient/Supporting Files/en-GB.lproj/Localizable.strings index df22923d5f11d76fdac112fabece8feba341897e..5a45b551c17592c5e6e82d387a152c8059e0a80c 100644 GIT binary patch delta 144 zcmcbzg=5urj)pCa-}KmX7%~}h88WA@f61shxq*{|9n57Ynf$R@n6sEciNTtIi$Q7f z`|0BB#S9q?i3~;4FLp4BvKBMM1BDH53QzttU1;)`FG9S<3<^N$93aeRD4)LZ7^Cpy qZ`1WA2M7vGy2S=ob#FS?^gKgGp6MTC8HJ|5Q)lGcE~d|D_Zk4N@-bon delta 22 ecmZ3ro#Vg1(X;Rz|a;*Dlw=6 zjZna%5t~H{K!b9?7MBApN`YBCS$n4G$L9$>=AGd)0_QD}OCCL`bW J6$XqPZve@HGI;<1 delta 22 ecmZqM!?9r}N5dAz4uk0d@{B^;KNvEKy#WAd6$qUG diff --git a/iOSClient/Supporting Files/es-419.lproj/Localizable.strings b/iOSClient/Supporting Files/es-419.lproj/Localizable.strings index 6054bcfc7327a8da813f1911ffe0dadfbafdb9c0..6bc2954af91066da6aaabee492e586f0a98bb63d 100644 GIT binary patch delta 175 zcmeCV!tv-DN5dAz6;|vy44DkM44IQJ{t})3ftgWcazQNzJ6MRJWctJljKbW-3`z{v z3|v4mezM{mVdHp)VulQcM1~@UREBtl6o!0;a)vyH93Y(t6t+D9%I}79 B5widQ diff --git a/iOSClient/Supporting Files/es-CR.lproj/Localizable.strings b/iOSClient/Supporting Files/es-CR.lproj/Localizable.strings index 9c2ca038f90c628212c17e780a6c2c3f6b253310..e85cd5fc535a218de61a5b8c8485b33f5b9cb429 100644 GIT binary patch delta 158 zcmex%jpNKsj)pCa3f8PS44DkM)8nNW4LN;4Odu|q&Nz!vSiP7*iNTtI3rNN@6fvX% zr4oUXd0?Cl6f0)P0E!e%-*3ezDq0K_P-0L3Lt7xJ#GuNMGC6UM=yax8j6%G{K%qp2 m93aeRD4)DALUi(kIXctcF$yq4l*vyQuxI4iu3*g=_5lFRR3@7M delta 39 xcmV+?0NDS`*$DR52!ON!AUl^_DFG6f{4)U#lb8_@li)fAlh8l}x3D|`9PmM+4{QJc diff --git a/iOSClient/Supporting Files/es-DO.lproj/Localizable.strings b/iOSClient/Supporting Files/es-DO.lproj/Localizable.strings index b3e61dc1a40b0babdbb30743e5e388529ca11c28..d53ef3f05b88d604adb15c86439a785393298c9f 100644 GIT binary patch delta 150 zcmex#jpM{kj)pCaV%F?A44DkM44Kmx{$kXetRTiVX(`uqk$;Rrla0O#P41Y($63sf z!H~#M#E?4OQH4=hyqF=LL5V>D3~hm=5`!v33WLJr#yP^1GyVxl76Zi+8FGLypP`%~ o6)2~~V9mhApftIDj_G8B1_4%t`n)+@)6Zx!@@yBgW{mj&0LgkOQ2+n{ delta 17 ZcmX?cljGAhj)pCaV%FPpY#3uc002w62dn@9 diff --git a/iOSClient/Supporting Files/es-EC.lproj/Localizable.strings b/iOSClient/Supporting Files/es-EC.lproj/Localizable.strings index 48c918564142db613ea421ff863584f229ec6fb7..94d590daa1b5ef9c24c0cb134b3c9e6a2d95625a 100644 GIT binary patch delta 224 zcmdn;hokF1N5dAz3og?I3>jINa~U$HPh@5koo=v233X>AdR6D uY`A(cP%4oj2MF^S%7KQbV7F{~;b}(w$qphM;{R9AYUI{(` diff --git a/iOSClient/Supporting Files/es-GT.lproj/Localizable.strings b/iOSClient/Supporting Files/es-GT.lproj/Localizable.strings index 27cd6a2d0808c9b2dcfbf27f47e2f240ccd583ef..c278d00fd33aa8765194f9c9280dea0b5dc33e23 100644 GIT binary patch delta 187 zcmex%jpNKsj)pCaa@MRl44DkM(_^I=4X01gWMpy71@cQ66c|z%@)^n*@)&Y}bRv*f z%%H?z&A5Kae*6*FW2MSwEXFLE*pixvZglz_&7pe+z9F{m=6 vOir95Dv!-*1)$a(uvO)g7eJGenR4bUu4Vp6znhj1eCI?rkeV delta 39 xcmV+?0NDS`*$DR52!ON!9y^y>DFG6f`ZECzlb8_@li)fAlh8l}x2`+^9`Hc^4`u)W diff --git a/iOSClient/Supporting Files/es-HN.lproj/Localizable.strings b/iOSClient/Supporting Files/es-HN.lproj/Localizable.strings index 9e05f6651c0236a6161dfa110ec1bd9e53739efc..0f9bf25febc8d653249e5debf6e2cafda672be4e 100644 GIT binary patch delta 149 zcmex!nd8zmj)pCav#dCC7%~}hfw*My$9&Q00$hv|lXorQn#|-QG^-BHlq-4F;FOxAqNQa l8OkTiM~Z?BnVdCOVe$h70cMC&`RRA;7uj)pCa3O3uzY#CR5002m02gU#Z diff --git a/iOSClient/Supporting Files/es-NI.lproj/Localizable.strings b/iOSClient/Supporting Files/es-NI.lproj/Localizable.strings index df041a856906808f9cb0fd84b5c321fc1880e671..b57ebd1ed4e7f85e8e20ed6f5a63f5aae38bb46c 100644 GIT binary patch delta 153 zcmex#nd8JYj)pCa6Rg;C7%~}h88W9U{$$jg9B_(*6~dkPhf#F0(QTp07IXMGiy1N) z5*dmZQm239WE2)JW{77{Vo(4>TOg^#pvsWKpfK5Rj_~A1bwZNGK(R!I93aeRC}&6o n$|*5eGjK5|P1c`dI$1$PfEA%$ZVuP<20KQc?GvmRlimXWW}PWo delta 22 ecmX?cjpNg0j)pCa6Rf76&|nnW{==Fv;5`6+MG7|n diff --git a/iOSClient/Supporting Files/es-PA.lproj/Localizable.strings b/iOSClient/Supporting Files/es-PA.lproj/Localizable.strings index dad7cb3da4e5da736ff3ca14f0f2298b7dbc2cd5..03457a9b40fc2ee796572746a2488c1d443beee0 100644 GIT binary patch delta 165 zcmex#nd8JYj)pCa6Rg;C7%~}h88W9U{$$jgrpd^~4B|}u!zij=%%H?z&AI2|Zf%#Z;TDVnZt#V9IT3=~jePyj<)AgRQl%8)WyaE|EYXMcqxi-97E o3^_oU&rl9DDg|crWb-+ulNCe+m?5g=r#IL!@@$`A#hCOS0Fl@zIRF3v delta 39 xcmV+?0NDS?)(G;`2!ON!kUE#pC;<|eY%>84lb8_@ldw+)mQn!(w;((LDDOe*4psmF diff --git a/iOSClient/Supporting Files/es-PE.lproj/Localizable.strings b/iOSClient/Supporting Files/es-PE.lproj/Localizable.strings index 731dd412094724c21f7e8f311b43882fe45d69ac..6a9641252bd539463004e59a5bbf9ee1e4ac81e4 100644 GIT binary patch delta 169 zcmaEHnPcBIj)pCaZC2A4s57##<}ze5luU1AW>lUmu%Bo0h9w-+i=-HZIExvS7_1q% z7?dXK&JhRlG8hsWiWpKsDup$R8RCJ`3SejpB$XIc8B%~WPys_OP+=xR9vG)jFO*^w tmMjKJB{JjyVLn4SP)Q1s*`|{fL#MFVaR7FXUJp70n&*K zDU&yT5f)7ZD$E1pbcT3_VulQ$Own{hD@IY}VxWK$g8~@Z0!bwXRiF_H3`Gp7K=Z*G zCqMiv$y*E*O9UDUGPZoW;x$I$$qsY$CM$>tFatGAE}tVmeVr{M&-5coOhVK1j2ZbR W&kz)tyuqJsvYaE^_H$N@YVQGi$urde delta 17 ZcmX?gjpNH@j)pCaldQJCux3np4**f^2&Di3 diff --git a/iOSClient/Supporting Files/es-PY.lproj/Localizable.strings b/iOSClient/Supporting Files/es-PY.lproj/Localizable.strings index 5615c06361c230d11756871dfe0cb72348bdcd39..449a0d2d1e55a7b753682efca104202352300a0d 100644 GIT binary patch delta 191 zcmeCV!tv-DN5dAzMOK_S44DkMKwL7Ju|RaX0vDsi~lwQ#9SticwU#7$~5`pa6!pKvIc86=;M4LlHwN(0s7Q z$shkp@)iTd5`l(-j4hvTc#Tnba=;wD$qFI@%s>s3+vmtn-)GCnGu=dqNoabVF(cpP W8G-_n5BRf9=K1cjUB;U6!#e=jLn1@b^!}NQqSKvjFbWB(GNdplFcdMQ0!>K->dKoO|4&k~7$}y= zkOPGI4CO$%6eLqjCo6~uFaz~X-aSWtx`7=d&-4IIMxp5+j2ZbR&kz)t{J@HB@&yN$ K?S9sbG9Li9YcA0M delta 27 jcmX?bljGGjj)pCaBG%IbG#Q1ae=ug`+umivX!ZdBpPdSR diff --git a/iOSClient/Supporting Files/es-UY.lproj/Localizable.strings b/iOSClient/Supporting Files/es-UY.lproj/Localizable.strings index 09c1ac01f4bda0b7849887972f554794eea320f4..b9e8ac736a806a4a70db6d3291a50e937ace5218 100644 GIT binary patch delta 172 zcmeCW!tvl5N5dAzX;#x6)EQYAbEjXFWHg-oVJ8b~E<+|m$#lUzjKbW-3`z{v3|v4m zezM>kVeWW_VulQcM1~@U)X5h=2v2`>jZuhMl_6y^{~XcDPyPx?7c(dTrE`EVpP`%~ zl_3SyfXQBS6ebHu2|)G8PoJaC$TR(%2BXmQEMrE#$uk56CJXelO_p5htwhMYbiCJ>iQU-*bon6sEciNTtIi$Q6!{z7r~ zVulQcM24d2j7u3s#fur@fszVfXbU8j7*rWj7!)QOE)<@==nKr)`8 zh#?iIC=sYM4~)}+V#N#@KoO8kJVOdYK0`TBE(b^_0(r$iF(n2CFti1dN(`z%Q3XtM zrcV@L6y_}kN+kj{gS3}V4*V`UnPsNV^aXm10?a^tlPzb;PnVHrkmC1^`Jt2j2hy diff --git a/iOSClient/Supporting Files/eu.lproj/Localizable.strings b/iOSClient/Supporting Files/eu.lproj/Localizable.strings index d62dd0cf194d3cb3a9f01d0a7750f34c05fd3fec..f99eb3b3c5d5ac7c56be36dcad558e629c9eddc5 100644 GIT binary patch delta 225 zcmezOh2zS1j)pCaYaCf~7%~}hr~j8{G<5X=F@d;*L4hHKA)ld~A&(&kNGAe$#SBUe z)(l)gGM=G`Ar+`D5vVc`jMIT)#S9rh5unWEjo*bsi-96aKvO`_7KoJ?RDotFV9|-q pCIz5LIbfU1fi|VUY@Tenz;tp!y?{6lv!-v3`&#t&JYLk zG8hsWiWpL-USt%Wp2W)}G+FKz8#hpZAr~l-$&feM|C=OlF@pk-mji_P4CRv>!$l__ ho1rs#!d?N8>dF6R$V|5Sz%xBXo>6GKh#}*X*8tsmD;@v< delta 17 ZcmX@NhvUaij)pCaOorPdj2OSY1^`It2iE`q diff --git a/iOSClient/Supporting Files/fi-FI.lproj/Localizable.strings b/iOSClient/Supporting Files/fi-FI.lproj/Localizable.strings index 2a70dfb0c25f77f1e73b88aa2d5c6c0fae05851b..1458c0da0705a941d20a42762ede6730ae2da36a 100644 GIT binary patch delta 165 zcmexyisRf_j)pCa8_d{q7%~}h88WAD{KBX?dBHa>Rv?F=Wb(sb!konnN(|NvTntK+ zb?1l!c^M3e3`Gp7(?5za3M&^g!~>-jz|a;*Dlw=sqyTB40-$+`Ktu8--~J^jSqv0Q uWXJ)+e1>wMTndt*rjs9N3orxqO=h1XKV3(aQEqyUJ|o}u3ucTf-T?qda4cT{ delta 40 ycmV+@0N4M{%?S9)2!ON!ur`-CB>@tbY%c*0lgcm}lP`S~QY~gs0Lte8VLn4SLn=cGgA#)^ r0~e5t2k8P@1T=5DqZFg=0kz6ZFEC`}**-y!G2=A=ZKN&F delta 22 ecmX@NjpN5=j)pCa6ZEFn$ubIU|Dn$q_8I_nTnU%} diff --git a/iOSClient/Supporting Files/fr.lproj/Localizable.strings b/iOSClient/Supporting Files/fr.lproj/Localizable.strings index 644536c6359345d8af2af922e04a87c5a64d9deb..9b8181487e9b489b68fbe0efa4799270d8ef6386 100644 GIT binary patch delta 206 zcmaE`nseJa&W0_F83C+044DkM)8A_|8cxpJz-6t#kiwA9P|lFYkOQO>fxKb{B?fB- zE+84tP{feRkP8&e1j>VOI#8^bApw2 g4m3RlX4~ZG<))Jxlmx_anAIK_z_>jyfT^Dq0O~p}nE(I) delta 26 icmdnCj`P84&W0_F83EH9Y#4>wF9b4fzYxfj#tHzKq6$L* diff --git a/iOSClient/Supporting Files/ga.lproj/Localizable.strings b/iOSClient/Supporting Files/ga.lproj/Localizable.strings index 78c253473506ee79700c6439aae5f9c0a6b1228c..106b5353d27d60a5c9c6b683a70ffdfbaae83966 100644 GIT binary patch delta 214 zcmbQ%$N6RgXTui8C+@5{44DkM)A>~x4OM+WOdu{{P+&-5$Y&^L$YaO>(uqJ`F@q9= zH3Jue(&U8;#W{-^GJv8*45`y6sxS((7Bj>HB^qA}+X97vMkE3a$phnbtVSsSRpfw; zE(fYlK{DEO@&+w|$$c-`1TjpSzR;9We6m@e(DXa@jC_+N1O+As*sx81pvNe(UC)E@ G#XkT5%{HO{ delta 17 ZcmaDefpc0PXTui8C+^#=JQ`%(U`S!eXDDaLW5@y0i9lX4 zgA#)^0~e5tXDDJw1!_tJYRd!Tbf8! D)iFAJ delta 17 YcmeBc=iJf4*|3E%#bx_0SH>%U0YKXa$p8QV diff --git a/iOSClient/Supporting Files/he.lproj/Localizable.strings b/iOSClient/Supporting Files/he.lproj/Localizable.strings index 106c87aae965e1c538d6385c5e082ebc7e8d0b4a..f151b5eba710fde18fe0ef07c51a0f9d1acd278d 100644 GIT binary patch delta 160 zcmZ3ol;hWOj)pCa9NMfo44DkM(8cueY%O$PAkiwA9P|lFYkOQO>fxKb{B?fB- zE(WE^`cuU@iy1P2qD2g;lMiMKix)G*17#J!&=yE4F{lD{C`>k-DlCu9Bn6<}9I(md i(;4?O3QsnhsyA8Ss@PGNHemq<}ze5luS?TU{s&nz{$a$$p8@uCj)pCaEqdE;=`%*X1^`M`2l)U1 diff --git a/iOSClient/Supporting Files/hr.lproj/Localizable.strings b/iOSClient/Supporting Files/hr.lproj/Localizable.strings index af0a46ece161ee30774676d57cccbd9fa2416b58..dc5f2347adc6eaf4b0bdb1b589e675b8155ec55e 100644 GIT binary patch delta 232 zcmeCV!SU!8N5dAzP1e&dXfU!c=Q3nY7W^YR{lN@Ik;wuFIh->YfI4EYS@ z40#MWKsph~D`rq)ux8)_lJN{h45>hMi9nTkV4MyVD`v<5iU4IM2XY8cFOp#rGFJs^ zQ^2Aao8byT%{gGp%BLINU=*JGVXoff0DA#updpjh=gCi>W6a1i{f{Q2(DVWmM!v}v gh62+k1T%6?7cga%nSQ_o$kMT7QU$z@-Jc#DA|i3~YF un9oo?-SHTsFgw(^>47?onv*ZQ;hQXQgKe_P44LTAD}cj+-ky#@d_LoDO~ delta 17 ZcmdnBjpNB?j)pCaU3%Le=rcyW1^`Q42oV4P diff --git a/iOSClient/Supporting Files/hu.lproj/Localizable.strings b/iOSClient/Supporting Files/hu.lproj/Localizable.strings index b836b3b41232db1389fa7abffab2b3ee557776f2..b0b433443174ce71addfd37f3b4eb418396a20ef 100644 GIT binary patch delta 236 zcmZpB#Bt{dN5dAzUpDMH44DkM44Kmf|1)Y%evr=L2<9@BFeosjFyu3oGvqPk0O>>^ zub4rJ!J2^!NX9c1F{A>uB?2|)fpI!ete7DKC{i@N--b~%v=}I$1T+K$ZGl*cK^16( z0v3(fEK&d(lmoW79B5Gr%;L$~b4@2-h!B{Z@Q6(i!wJ(JH5kRG_h>Q-O&2g_==2z002&T2kHO- diff --git a/iOSClient/Supporting Files/hy.lproj/Localizable.strings b/iOSClient/Supporting Files/hy.lproj/Localizable.strings index 0f8e66282929984dcc0b9bce6ee5a2c52116e1f9..e7003060bb34ab9b880e617db816c4c854a2a36e 100644 GIT binary patch delta 135 zcmeycmE+hBj)pCaQTnVo44DkM(+l|-4W|p-V`O2?1@cR#8|pI(a~3lwF<3KjF(^%* zI71xB%V0=kC}K#ReDRAgYcWGSP~7mA@Z?J~gr*zmGYat*GbjKR^b7YHxmY3mjTVfe>PigO3|tIK4Dk#_ z45>hci9n@!V4MyVD`v<5iU4ImnuS@5fkH}?8*d6vHkm0jS?rq-Z!v=cP(cn5<};K} kPdvpaJUMQr-sA=afysS0*?`JqrWcqn@@$`Gz-aUa0CH6;U;qFB delta 17 ZcmbQYhvURfj)pCa(+sx1F=RA)0{}@12igDt diff --git a/iOSClient/Supporting Files/id.lproj/Localizable.strings b/iOSClient/Supporting Files/id.lproj/Localizable.strings index 0700e8928ac2b88a0faee62eb1e438215a5a750d..ed4941ff2fb8b6d0c2d2ca7a3e5bbc287efce3d9 100644 GIT binary patch delta 157 zcmcb$i(|uHj)pCa?+n;;7%~}h88RmqeiofRp_@@;asU_GWP^oV(}Nfpg*b~Dlo+fT zxEPcs%g+>NFJ{PKNMtCQ-e|xmDqhSG50q2@Lt7xJ#GuNM!k{o&ai;L(v~NO^#Xzw{ uh8!TwXDDY#1J0!6^(fc? delta 22 ecmdn6m*dthj)pCa?+m6l$TJFU4>MwndIJD+jtMjX diff --git a/iOSClient/Supporting Files/ig.lproj/Localizable.strings b/iOSClient/Supporting Files/ig.lproj/Localizable.strings index a76e22b03cd8807bd05bc4bc1387489f6f8bbdc5..376e86bb69709e3b6e85e636100411aacf087e90 100644 GIT binary patch delta 155 zcmX@MnPbj2j)pCa6?*JB44DkM44KmvUovV=Zs6o#2Xh%prU#laimEFySTk@jC^5t{ z6fvX%)g=N|=YerLP^_3C11JKNnY=Mqn6(%vqBQyAb>Yb(GlV9md=cU=W>5gi=Kx_o pLpey*WnpHZ3DX%R8FeQsNb^kocbyHWSZ4YM14f?h6?%+*uK~RqEA#*W delta 17 ZcmbQUjpNv6j)pCa6?)sx=`;Gh1^`C62c-Z2 diff --git a/iOSClient/Supporting Files/is.lproj/Localizable.strings b/iOSClient/Supporting Files/is.lproj/Localizable.strings index 56b481bc0405961f7bd3aec2c8b01c89fc8b6e21..f3a2637d10e7a4e90bcb03fa9480277b6cabb292 100644 GIT binary patch delta 180 zcmexzgyYn4j)pCaB4(^P44DkM)7OeJ8cy25!kP=@l}t|b7FI82P-3uV-~y8I3`Gp7 z47m)6K)F0HP6vt=Gh_foil#4|z$iN1=scqkvnoT%V4_n8%>NkiwA9P|lFYkOQO>fxKb{ zB?fB-E+84tP{fc5)RhR-mj}k_K(S(m44?>5W_qI{qcCeRP)KQV<1=ATOvTs?R{(0x q0b5oMG&}{HWz!9H7sDba-Z_VW)(JRCjL()rmXR2k*Tlqfr2}aiO-jqkhO3=fhZ`Y? zi&0rO!t5J82^)3ni*0UQ6%`wofH5%`;}=db@eeWKhu=TY#Nb5Fy<6c2CjNNuec$&v z&vVZEoHKAm&-|(9yz|9zZLjvUtai2KpL^A|oArnNg&WPQHEOL|hnCRd@{h#asIF8-!H#U$>v~&JSkp`4l7g2*daW(Qs+F2}Af07w`+_@iFS8hE|w-;23S- zTpzg@{j_x5|4~escm&+mPbTLcB$t|0<$ST9>U?giB9*{9BK{TfxUZpV-_dT@!8E2* zhd@}mqcjpk7~9c|G>)XY@Ek(mu>mrbx5HIp$F?|8MTtXT{WzUhgYUZQ(kKr7z9)np zJSr-=SWxm0;SG%8GR}$8-$w@f;Wk&TPGnGow`NSqPPm9Hia0XHN2~qqK2Z0r~#5Ui7HDo?XGa#>jV# zP-p>PTu&AJ(m44_3yc#7^mfrwN7k<9s*{9kz|0mx-#{m+$1N3EHeY9vAn$K>CUW zxK*5Ol98|#?Qs&Pokz`0kgvox=K56eno07zABa63HIa{f9d|lCHc4-j>${!jyX_-C zKSfWbS9|!_6s_W0r)k~tJl@26xFqxR6N(aIKEE0v@6rE8BrNk~ujiHqCC~B=p5h*29C~AXP`dULj5TRgS+L#8@yqGVItNsEv z51dWEgnn4GrRbtiinwwmBI45D(1nPID0@1dn1p*`!T zH+|IV^?qx4_2k#$N^DBxMOL`N5p%*48Ii*`hg}Klib!F17v6$c(6#~WtFRV@8d#F? z{{N580ZrwXWkek;$zE#`U<5A$*hMM}D_SZ@KZ_NQcByuJ(!+2%Fx~?{S3Cgg6ga2x zwZL{>^Rom`ac#I4;5C&wh+>5fB4{;HZY`EOMi)R8DfaHYaVS9<4gri9oG zR7dYWY|Hlj*Q@f=@sl=e%-AI^i)MW9U_a~qp6OAGPi3TeSX0RPK`OK^lb3D*t-Gd9 z5t$13RqL{w?o{C^!yT(vDOhpviEhq8oy?>xG7J2dp%8U zHjemX=qK>3N>o)gWnW~dVH|wFI@pwUWK@$ejjBuW_)G_5&wE!!nxZVgj1)&TIHd|( z(!MYb8=FFDjkobUhr!S35% zW+9r3pJB<7%-6*B9~*Mycm)?A{}&IQEQwyB_K-&nzLen`y!*2-nz;@jNtd)8Bw^{i zOPs8LJnwiJeVsRu*0!`e8k^DBfAxqb5n zL>)et(=Ahw%zv>kVVyhjoQl7YXIm$ssUNwwwSjvNbJ#V#wj)pCaZsyZBC^NFK<}ze5luT`8RG+-Sje|Xt0nDF#(OX!vm_dobnt=;Q z#xoQ#qyl9Ufok)>I2|Zf%#Z;T0m=Y%gH*=@RVsj?Es#`VP-RE~(il2HX7d&UWfB>3 ufH0q-e0t&qM&Zdqv-Kt)a1>w$YMbmiTYkEa86(ehD^*6J?GfgTB5wi0do4Zy delta 17 ZcmX?fn&Zt0j)pCaZsyzPSTL%+1prGw2crN0 diff --git a/iOSClient/Supporting Files/ka.lproj/Localizable.strings b/iOSClient/Supporting Files/ka.lproj/Localizable.strings index 30b68ceaae621a31d54149f54a0dd35fc1dfa443..5d5250e01c21e68ac987d232b9f4c338003fe519 100644 GIT binary patch delta 146 zcmaE~nPbN`j)pCa9eUFb$S|@n=1$+p$7smu!;s043&bVU6U`V!)s+~m8Mqjf7~&a< z7*c@}i9o45Fir=G6*FW2MSwEX8~GW9S&M-}N|PCH2v1g-AvC$-ix6)yg91=N4iM%u lluw^{oKbl4tLb`^KL`j+=DWcLR3(0fpja_O22iAE`guJ@(dkT|8HJct8B!)Y&Jdj}@l}Yom_Y$3 nn*)UT4CT`&o?sN7oHs*nvVfxiRGIvA0b@pffOc6us()?$Wu2Bpc2H-#r}njti~Dmpni}E oF2=2A4i40S32#F#($EU6BvC diff --git a/iOSClient/Supporting Files/ko.lproj/Localizable.strings b/iOSClient/Supporting Files/ko.lproj/Localizable.strings index c6efb99dcd903012bbc91f5097b6e664aa94a561..65707dd33b66b5e88b689c2dd9df70a152540b82 100644 GIT binary patch delta 236 zcmey>$9`cV`-YbPlRtc5Va%PJ`^3=Ihar<87l=z36c|z%@)^n*@)&Y}bRv*f%%H?z z&AqtNDQ|DPNO0LcA1!2kdN delta 18 acmcaGk^NU6`-YbPlmC4b+WLm^$#DQ>Qwt0L diff --git a/iOSClient/Supporting Files/la.lproj/Localizable.strings b/iOSClient/Supporting Files/la.lproj/Localizable.strings index f3972d4f031f5fa45cfcff4eb855842f1fb5d943..91c0c1b5597e713c6a83ff5526734e40755c919b 100644 GIT binary patch delta 140 zcmcbznPb&9j)pCaReG#B44DkM)93Rs8czN&pKE%M9-|OvF@q9=H3Jue(&YQo#euvG zhD3%UhSbR$bA`o=8RCJm3SejpB$XIc8B!P&CO@1mJUQ))5N|P1ERi7x2=f`rryCw) m6rTKSy58gu0s@n6uuXn6U1;*T>0Hw3>h!H1^`Aj2i^bx diff --git a/iOSClient/Supporting Files/lo.lproj/Localizable.strings b/iOSClient/Supporting Files/lo.lproj/Localizable.strings index 5be68fdb25bf48fc6bd665c06524bb99fce3f82f..c089cce3155f26fda13735e066b2d219500b3670 100644 GIT binary patch delta 215 zcmZ3sn&Zz}j)pCaGCJ%z44DkM44IPy--}L;`77n>1K~52FeosjFyu3oGvqPk0O>>^ zub4rJ!J2^!NX9c1F{A?ZB?49FfpI!ete7DKC<2t3Y?v!NebOODAwgB3HU(tGlMO#f zsulyq5`o5o3@!&6l!DFR>4FlBx|0i5@l1Yjl|>lE5QvS_1+^K)rw2$g3T@ZZVXSxw E0Aac_n*aa+ delta 22 ecmeyfmSfp!j)pCaGCI=(q#1>_x9BofyaWJf2?%EZ diff --git a/iOSClient/Supporting Files/lt_LT.lproj/Localizable.strings b/iOSClient/Supporting Files/lt_LT.lproj/Localizable.strings index cb327227e8da5aa2825364ece5236996a1452c14..25445b64cb55ccc812ba7c9f039389d52a3aa2df 100644 GIT binary patch delta 161 zcmdmThNJ5|N5dAz3iIg?lo?rAa~U!jN~SY1GOAB*;9}$S0dgTi57`)nCtr#X5>EsQ z<$-ZJLp(z665mgqT$sQYIgqBRc&N8>5hFF@pk7J_iW%8Oj+_ s8B!RO7_1q%fMh&K7tnB^d6OUV>rWO4=7MUKpPr-2D7U@KoN?7V0K6qC*Z=?k delta 17 ZcmeA=&#~nUN5dAz3iIvfEExB^0{}>(2sQu! diff --git a/iOSClient/Supporting Files/lv.lproj/Localizable.strings b/iOSClient/Supporting Files/lv.lproj/Localizable.strings index 973b3768a8292ccce868bd5d1bc5f368968027c8..9a6fd4006dc524e20703242644fefe32daa64992 100644 GIT binary patch delta 160 zcmeC!%W-c%N5dAzJBHIA$T6}o=Q3nY&i^7h{oGqdk;xCrIVLA8;+V`-C&XFIpu}L! zz{Q|6*>{#Wke9)b$WX+PI=S$Rux2qsJWy5v3~hm=5`!v33XlfMGvor*WisS}ar$J& xI$_>opiCk|4iM%uluu@i5S<)0OJ{O|p#U>b*W~V5^3!7!80EIVGi03g1_0kaETjMc delta 22 ecmcb&pQB?hN5dAzJBHIU6c~lJyBIUhdIJD(F$o+1 diff --git a/iOSClient/Supporting Files/mk.lproj/Localizable.strings b/iOSClient/Supporting Files/mk.lproj/Localizable.strings index fc159eeeed3c62881f3d1c89e177cbc0b06fa843..bfe77516684274f0e6e6c604540f926ee91a096f 100644 GIT binary patch delta 152 zcmaE`mt)%jj)pCaT1Mrj9D4BfFTUfK0L5abdfeT2+ zGZZnT0+l8L<@3Ne9Vk}JkO33{%1od5l2Mqo7$~GPdE*`7$t|;lCLi(^;w@%S04m4< o!hDAE$%#KjCvTXgGkJl&z~p^**np~JrstS3@@&^KV%+uy0KgS3-v9sr delta 17 ZcmdmXfaAelj)pCaT1ML&j2XAR0RTt{2f_dV diff --git a/iOSClient/Supporting Files/mn.lproj/Localizable.strings b/iOSClient/Supporting Files/mn.lproj/Localizable.strings index 2ee3a4d8ac0a8f25ea3300fbeff23aabb35d5165..49249a6fd61973bb9bcbdceeb67e624ee8d7ef19 100644 GIT binary patch delta 163 zcmcb!lVj5!j)pCaI}A8;7%~}hfw*M4VmG7u^ZFbOcdA9E`U{rYn083ygEC2ui delta 17 ZcmdnAhvUvpj)pCaI}En-88NE70RTuC2RQ%$ diff --git a/iOSClient/Supporting Files/mr.lproj/Localizable.strings b/iOSClient/Supporting Files/mr.lproj/Localizable.strings index 77e9f771b9a0c904c1df0c5dd4b25b5cfee948a0..9a72de3cf3da24c201b154f489bee1af92b3c8f4 100644 GIT binary patch delta 157 zcmdnDnPbW}j)pCaF?!P{$S|@n=1yFz6Yd+WXMIDSnn#BxC4Au->Kr)`8h#{3B zmm!fMlOYd`(}7~e3>iQXpv?3@en#QxN?c4rlk;w}aU-dn?D$1evY0^us4fQx^BKy4 j=A^*PoqTe->EsUr0w7(Jxn{^rmyu!Q*&d_EnDiO|@2e@{ delta 22 ecmbQTjbq0D)YcN9Vk}JkO34antrf@QIxe9D4;a?;Z0#~B&Czjf02|dW>5es%K^fC ohH{`WDKKLvYtJy9{J>UV^1GXClXGUsOg~`A$g}-{9%I340K!`@UH||9 delta 17 ZcmZ3so#Vn5j)pCa5A?R1888;S1^`H$2d)4B diff --git a/iOSClient/Supporting Files/my.lproj/Localizable.strings b/iOSClient/Supporting Files/my.lproj/Localizable.strings index eb01f5cea22d9e5a2f33e523bc36a005878366e0..9714b757d9dd0babc9b9a42bed672851f77e1f30 100644 GIT binary patch delta 164 zcmbQSmE+xZj)pCa2lUu;7%~}h88Rm?{3tr@6{E=H22KuEu*melw~V5i#SBUe)(l)g zGM=G`Ar+`J5vU;#jMIT)#S9rh5unWEi7$kwZ~DY2#H`AYGWp?jQF#T16o!0;a-iZI sAe{*06*DLR=^QY>eDcCD(a8ccbS4`p2tf77ProC_$g}-`9^;MI09GF_!T4FE}z2j>6) diff --git a/iOSClient/Supporting Files/nb-NO.lproj/Localizable.strings b/iOSClient/Supporting Files/nb-NO.lproj/Localizable.strings index a8498df64f7aa86fb52b324d789fd87f84fb450c..57800a3c485cf8699efe8897a6bc0c80ffead340 100644 GIT binary patch delta 197 zcmcb2f@8~Rj)pCaJf@sE44DkMKwL6?AuFTqbOSCX7I842L4hHKA)ld~A&(&kNGCF+ zOm^fG7EJ`I%>(0fhIoczh76!g(e%KHjH1&gi7*LGUU!Ghwum7WXeP2s46}eb6@V&o zz-E^N)u%8hF<3KjF(^&mKFf5nf~3IYfO~A*NG466sKY2by+N5#XuF&#qr-au&lE5& delta 49 zcmV-10M7rk%Lv}c2!ON!3^kX4BLNbZ&Mg5Blg3pLm*6)629sVE4wGKL HHUZA=+_n>a diff --git a/iOSClient/Supporting Files/ne.lproj/Localizable.strings b/iOSClient/Supporting Files/ne.lproj/Localizable.strings index 36e104e2111387915fe5a8aa62d5832aa7229f36..2502362214dc203c3d4ab16b6830fce2c2a1786d 100644 GIT binary patch delta 141 zcmeygnd8JZj)pCaReG#B44DkM)93Rs8glx8m_S@IJy4HPn6sEciNTtIi$Q7f!|CEc zUIs%VLlHyjAD}SLrcUyaoVg)h6Nq delta 17 ZcmX@HjpNg1j)pCaReIYm=rdNl1^`TZ2rB>p diff --git a/iOSClient/Supporting Files/nl.lproj/Localizable.strings b/iOSClient/Supporting Files/nl.lproj/Localizable.strings index c9046dc3cfb02033a4a4b089a402626b6b868661..ed6647263328ca8b957c040858645934bbb2a60e 100644 GIT binary patch delta 190 zcmZp<%W>;IN5d9I9~;&jhD?Us>Acd6hMYbiCJ>iQKj^|Js;b0b&A`Q=#1PL=#E=S< zNCZmdfpI!ete7DKC{i^2p*5qZW-(Aei9rDjZGof`gDOJ`kjBsmGFe46Ty|lZ23`&z7 zXNa>GGh{F%G89d(*JBiAEoO)Z3M<|ep4>D;X!<2HMlsGrh8!TwXDFXs_)T=O*9@J> a3HAb$Kip&kDv_B!M~#tZ`vX13j@JO6peUyR delta 22 ecmdnDo#Vw8j)pCa5A>#=l4TUy?qR@K_8I_o&k2VB diff --git a/iOSClient/Supporting Files/oc.lproj/Localizable.strings b/iOSClient/Supporting Files/oc.lproj/Localizable.strings index da36ddf30cf385cf566c29ed7fd3e28827237e84..ef1b97bc3d05e20f2a97882d86bcd80da9165b47 100644 GIT binary patch delta 171 zcmcb%hhxotj)pCa9}HP@7%~}hr>_=dG@Sfk8w+bLkXthOW34b}F@q9=H3Jue(&X4# z;`YT184QUGMGUD7@eC;p`3&U@c?>x~IuXb#28t;$D1f0YkW^w&1&S(wRp$a#WHRJU v_Wv#^Sqv0Q1nL6mD+ijCf@Fi~WP=<5W}v>wvuDXqpJ&3zv;Bi1W7-=4FiR_l delta 22 ecmZ3ppX1sdj)pCa9}K4#C@>0bk1%Eoc>@4)$q69< diff --git a/iOSClient/Supporting Files/pl.lproj/Localizable.strings b/iOSClient/Supporting Files/pl.lproj/Localizable.strings index 5482a222975471568b4908488836580d9a31deea..dc91d1acfd52858c04b544cd3ea19b7a40d06735 100644 GIT binary patch delta 168 zcmbPsgX7IDj)pCauPmpjF|sh`PIr`GG@SmgpOI(!mWzx6oW%@E4Au->3`&!I=ZXV) z84QUGMGUEvHx>#{SGvI{B(BPk!k_>WV#ozbWisS}ar*R)7a4^mi-A&!3^_oU&rl9j fl7eKK>EsLC0ywolG-MQ?-lNVawEd4Iqtqt=n!YUZ delta 17 ZcmaEJi(}dij)pCauPnDaSTpK;0su~w2krm> diff --git a/iOSClient/Supporting Files/ps.lproj/Localizable.strings b/iOSClient/Supporting Files/ps.lproj/Localizable.strings index 4ef23dff4172c71934cfa901cc88fe37d4370279..ca64468f974550ff8b5c5ecad9c2e4c9df88db4e 100644 GIT binary patch delta 158 zcmaE}nd8tlj)pCaC3>7W44DkMKwL6i@hPL|3hYZ#viXcLt0++e`EqD_#Qt6htgx delta 17 ZcmX@KjpNN`j)pCaC3@RW=`&Wm1^`QQ2onGR diff --git a/iOSClient/Supporting Files/pt-BR.lproj/Localizable.strings b/iOSClient/Supporting Files/pt-BR.lproj/Localizable.strings index f9cb6b1bd6b0374a228fc656dad2d6f81fddc4ec..01e109738410bbbf5f48405da3a7c961bf7af93c 100644 GIT binary patch delta 236 zcmezJj^orvj)pCaA$FWO44DkMKwL82k&RJ#@&b9T=>^V=ER*w=vrT_=fKf=jm_dob znt=;Q#xoQ#qyiNu0+r{1aXL_}m>~lwQZ!w09;2vcF;GB>K>-YHfus_HDnkm8#?UzZ z;Q>Zr$zq^XB0~-k<};K7%}#;YHCb}L>12gU0dZW~8?_lVrynq5krdj)pCaA$HqW*)uBq1OQQp2rmEt diff --git a/iOSClient/Supporting Files/pt-PT.lproj/Localizable.strings b/iOSClient/Supporting Files/pt-PT.lproj/Localizable.strings index 89ae56b6890f69ddb443081b55808edf0722fd6c..87a51f674864c4549e1824706e12ee3b774719d7 100644 GIT binary patch delta 153 zcmbPpk>k^4j)pCaJ1kgp7%~}hrwd9j8cw(CXXKJrU`S!eXDDaLW5@y0i9lX4gA#)^ z0~dqRWXCz;?8OWjK+&S<2d6QLvKBMM1BChSH4X9aW`x*4EYS@40#MWKsph~ zD`rq)ux8+5P@24OfjDO|Lk3W^h#?iESva(qAs#5L0EV_eQi(wos6_#!0BA=d(AYdM uP6vu%8ms_Rkps4<9H>48$s*Iq28IHY3!bqFVVX33f*qsS_6?4Vc3%Ozsxl}5 delta 39 xcmV+?0NDS;@Cfeh2!ON!utAsXE&&pjIsp!sTJ8Z5lkf=!li&yhw?IPy1oc@o51s%3 diff --git a/iOSClient/Supporting Files/sc.lproj/Localizable.strings b/iOSClient/Supporting Files/sc.lproj/Localizable.strings index e469390f0f307c0e55b660f0e5ceb2ec853cca95..2c315aa3b48275d739127537e46c5be678bedd45 100644 GIT binary patch delta 161 zcmX@|gJafjj)pCaR&Ja*44DkMKwL8YqAR2N^af)_E>;L%@f@QtXEB2kgEa#egVN-+ zi^PGv42DF8B8JrIj=YS*ti=rRKncT_!rVY1pecz!bMmI2XJ?d@EM`yua&v$%pP?Km snSx}F>EsCl0+a1tvQ2)sNM`yR7e=1x0mh6%(-+t=@@iWPw3`3&Vi%{f3i5y&eBs!Ig& nK>YH_h7qEZRc7f-cE}cB2AVNBa+dsb9z#Z+?RAEX7hVGZJf|!c delta 17 ZcmZ3om*d8T{KxqXqv;~q%45|z%3<{Gk&Jmu@bcs<&vKT0s$dChs z`3&U@sX$4DX{M7OXb7;vHB6rLU2OUs4Mw5qHO7p5lO+TNCKsG!oBm-kBg=LfYsRRL E0Lg1H2mk;8 delta 27 jcmX?eo8#LJj)pCakF2K8(O?vsUSrJ2w>`p!vE(BFurCWs diff --git a/iOSClient/Supporting Files/sl.lproj/Localizable.strings b/iOSClient/Supporting Files/sl.lproj/Localizable.strings index 2af55630156190c90f4605d906e1006d9df62e98..2215fb3ff98076f49250dc7c997ed6977adbb6fd 100644 GIT binary patch delta 193 zcmZ4YfTQIxN5dAzBev5YXfm=e=1#YlVKn6QVaR021>%zFiq{#1)r%RF7_1q%fMh&F z5ko3aDiJ7|2gd0@v0{b{ph(el#yO0ln#Di?B?bjBv;~q%45|z%KpI0M*lfvSpj0A5 z4iM%ulmpF9f!Q^A=6uu14=xHY1GP`SK3{(N85>5P>2=zSLepoMG4gGHV$10D2>@3U BFJJ%w delta 22 ecmZp9%(3nPN5dAzBev7)v>AoA>)10!d;$P)f(ZNo diff --git a/iOSClient/Supporting Files/sq.lproj/Localizable.strings b/iOSClient/Supporting Files/sq.lproj/Localizable.strings index f64cc8bc78dea2f07cfdf4dff1b45f6d7a626635..f2e895cb65bc66b75bfde7d7349e21ae6c31ab98 100644 GIT binary patch delta 172 zcmZ2wm diff --git a/iOSClient/Supporting Files/sr@latin.lproj/Localizable.strings b/iOSClient/Supporting Files/sr@latin.lproj/Localizable.strings index 0f743833e483516594787c62feddcb3112add691..322a14d71899ecb3b94a8afd90b3ea31e545a1cf 100644 GIT binary patch delta 183 zcmbQVjpM}*j)pCaDf+BA44DkM(+l|-4LN;4Odu|qzVQ{Kux2rX5`#4Z7m$o+C}Kzj zN+kj%^T0SAC|1mn0TcnsOm`Gu6c#N83MnxtfT1mrRANwNNSVBGhUoN7uNZ}Ri-AIk z3^_oU&rm-3ewZjT(6q_-#q}o(2y#uHaFcEFmKieB&locDOt+I`6q@d#!N@l~>I$R4 PWC3}$?X&b5D_#QtX?QRW delta 22 ecmaE{gJaS*j)pCaDf-jxs3PV0aIYS;p4vGNHemq<}ze5luS?TU{s&nz{$a$$p8@uCj)pCaEqdE;=`%*X1^`M`2l)U1 diff --git a/iOSClient/Supporting Files/ta.lproj/Localizable.strings b/iOSClient/Supporting Files/ta.lproj/Localizable.strings index e29bd7e988a96db4cbd3be9dbb61dd2cb15763ec..03a84dfee704a5763598ba42b465270738d02782 100644 GIT binary patch delta 155 zcmdn6jiYTRN5dAzEPd7-hD?Us=@a=G4JQk1Wns+)a!V#p^b*!AW>8|VX5a#n@eD-_ zsSLRci9oqLFir=G6*FW2MSwEX69pKBS&M-}N|PVn5T5*HhS20mUP8RZ3<^L6IY5}t uP(GRQyC^fzfXVz4`jbDTa7})9gAJ%sX8HjgMxN<0a*RUTYxEhnyaoW-uPuxK delta 17 ZcmZqM$+2M@N5dAzEdA{V3>c5S1^`5>2i*Vw diff --git a/iOSClient/Supporting Files/th_TH.lproj/Localizable.strings b/iOSClient/Supporting Files/th_TH.lproj/Localizable.strings index 4bb97353902571830abed0a6e8f2631a3d14e9ff..5a081614c351783bb33ae0656b28f83df7e799b7 100644 GIT binary patch delta 206 zcmZqK%yDZgN5dAz1ik43GK?&&xeS>MCDR%87}X~?aIj4-*bEh#%=lkevzS4N!J2^! zNX9c1F{Co&G9&_(=YerLP^_3C11JKNnLIHEq&gm`QUMHYfus_HDnkm8#?T2eTe27^ plgN+*g!v5RK(kX|c1?ag&2;htI{|SVx~B`OF^X?b&|_424FFnlE@J=y delta 17 Zcmcb$m7`@dN5dAz1ikIs^chuN0{}&z2Sxw@ diff --git a/iOSClient/Supporting Files/tk.lproj/Localizable.strings b/iOSClient/Supporting Files/tk.lproj/Localizable.strings index abd86b353bfe452d85453403b107552aa4248a72..946e3c2e278675425c9499bf4778f49028df68b9 100644 GIT binary patch delta 138 zcmeC!$#HKtN5d9I4TEWNj4aH#44IQReiEH}k5Ov!y#-v;KOJTi;w)xRVz6f5Vo;iV zc7`~6F+&DJB16&igZhl3ti=rRK;ebAgt?0tQW}<}ze5luUR0!6-W2PMT3-`hjbVEQ(-31_g!`hJ1!{ zhCGHGAe{*06*DL?STk@jC{12CN1U^mAptn8MkxSQEy-A0>HXuF0jqtZtJO=vW$ delta 17 ZcmZ2*pX1aWj)pCaVz%3J>=;cx0su!>2Soq? diff --git a/iOSClient/Supporting Files/ug.lproj/Localizable.strings b/iOSClient/Supporting Files/ug.lproj/Localizable.strings index bf887b7211b3bc72eb3919191533b58f07f199da..d529c5242040a71c545c187c139f364a7652bd8d 100644 GIT binary patch delta 214 zcmZ2>jpNS^j)pCaa#rj)44DkM44IP`{uG^lU>c*yWCISi$>)}ESt~H4Fyu3oGvqPk z0O>>^ub4rJ!J2^!NX9c1F{A>uB?2|)fpI!ete7DKC<2t3-YCH+%vuZ-QkooiU)UB? vF*d^$fSPl_mX!kyPk~uBxpR)`j)pCaa#q_*tQk2z0su;^2TlM0 diff --git a/iOSClient/Supporting Files/uk.lproj/Localizable.strings b/iOSClient/Supporting Files/uk.lproj/Localizable.strings index 44142d39d7b212d6467f7e5363ab8455a97aad47..7e8f85a21a7c5b5d4b444e50b2391b3aaadda91b 100644 GIT binary patch delta 182 zcmZ2fxKb{B?fB- zE(WE^{4>Nkiy1P2qD2g;lQ(`8o?gVoBsBTo4YtWbGlb-^X;lC!&H)=$K0Wamqwr+0 z8G4f^$O}w9a1*R)^1ta^(+dn4d8SX0WfYp;r_RVXd6&MxGNHemq<}ze5luS?TU{s&nz{$a$$p8@uCj)pCaEqdE;=`%*X1^`M`2l)U1 diff --git a/iOSClient/Supporting Files/vi.lproj/Localizable.strings b/iOSClient/Supporting Files/vi.lproj/Localizable.strings index 3e36245b4081314e41821a5ef948a8757240f450..10404542b52cde98058128902c24bc42c42fe4e8 100644 GIT binary patch delta 126 zcmX@Gk7L?Fj)pCa9Y(A<44DkM(`|(q4LN;4Odu|q-e|=r%C5v<&A`Q=G+A-BIA<|K z216o45ku-^!8gL9#SHNbN(>5MXbU8j7*rWjCdbbfo!(@{D8`w{kOPGI4CRv-{uG_8 WHCtzL!)>w2Nwa0P*BCJ#c>@6J^dgD? delta 17 ZcmbPskmJBUj)pCa9Y)*l88hy90{}@62toh= diff --git a/iOSClient/Supporting Files/zh-Hans.lproj/Localizable.strings b/iOSClient/Supporting Files/zh-Hans.lproj/Localizable.strings index 1da9cb48cab662851b1efb94adeab7b6da10d6d2..773b71e13704ce22f7a1bab87b9414b0fa969bfa 100644 GIT binary patch delta 513 zcmbO;o$bL?whb&oOtq1lIfUxWIliP>rXGuCteD&oD>8Y9r-90fgqAYjiFx(slRxyg zr+)L}^>Yl|(H#?@Q3_JVICWMulfmXXFFz5MzVg@A!YX&X`rO9*4#o3D) zGJv8*lLMcKPFKof6mnJtDp6o4Vn_uVnFur{4~)~X8l(VJkpnik9H>48$zapTAEX2( zZ&<`4g3YYS5AKR>UhpKrU~3gYgIBhXO7vhZb<_FGu^V2amNl_~>KdF9}IOAUpzhF!$mbAaJmRK<_ zibvFhoIFLgRHLhdo@_LGWBwJ%nvvNzd5wZ7qF2vXeTm^~-QJ^H>ar$&JgkGw`3&U@c?>x~ zIuXb#W>8|VX5eB_njBau&R)!r0TeBo?EhMHdQuFdkh3aKi2_3rLn_e3M4%CQV4RNC jAO)a`9I(OVK=mm|2AfVQ6p+AW)Z_pumvAkk3%gkjIb%q!WR> zVg@A!YX&X`rOCDB;+(|{89>n@hSbT79KwvnlOHMzPj*<$Vha=j8kq<*Cl8F%v6`d+ qRFMNVxg4lI1<7R7$p)?hlkFC>31ONv+2N+fGNHemq<}ze5luS?TU{s&nz{$a$$p8@uCj)pCaEqdE;=`%*X1^`M`2l)U1 From f152fb3b80c9eb256b74f001cd50e1b388c03809 Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Sun, 22 Dec 2024 03:06:56 +0000 Subject: [PATCH 7/8] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot --- .../ar.lproj/Localizable.strings | Bin 136248 -> 136250 bytes .../cs-CZ.lproj/Localizable.strings | Bin 142002 -> 142050 bytes .../de.lproj/Localizable.strings | Bin 150986 -> 151002 bytes .../zh_HK.lproj/Localizable.strings | Bin 101798 -> 101720 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/iOSClient/Supporting Files/ar.lproj/Localizable.strings b/iOSClient/Supporting Files/ar.lproj/Localizable.strings index c57a09fef24fd486f0de4af5a611eea65399ac97..8c9c3f32385066a8a9f0ce81a919cb0988e16c59 100644 GIT binary patch delta 136 zcmdn7f@9YTj)pCaXY^#X*qqqBfKZ=Jfx!|;f@pO%7d9<6H#Vc`7cCiuCntEbO#h+B z=pqjn2dZ~rQwOSnsC8vCW^-lpn6AH#QJh_g!J2`KL22^B>86txs0mEpr_ad7?Fv+@ U0k%nV`axAj@$GN)8GW7r002!MhX4Qo delta 151 zcmdnBf@8-Dj)pCaXY^Ek7%~}hfw+W0fgyz Hj8;zox)~!4 diff --git a/iOSClient/Supporting Files/cs-CZ.lproj/Localizable.strings b/iOSClient/Supporting Files/cs-CZ.lproj/Localizable.strings index 6ac1297715c029f98778bb971767daf8bd04d1f0..b0b116e50da344196fc1efc2afef19fb138bb58d 100644 GIT binary patch delta 424 zcmXw#y-R{o6vod{F%^rl%)|s8jEV@N!7zwuE@>)iDBjfY`xQTm9IT~3AntGn$8vGf zSI9Al#s&*)3PFd4z<(eRdMq5y*Q=dFkq%Nf> zK?QL=YnL|_wuy)5Ik$?Z3sOkoA(WvksdP>;N>YSUq8_x%QM;VbgBMR_hkW(1wb|O>_k`%2F6c3fgIys4(2pKZysqJUtsPM>KN{kvZ=Zn}LWBziy63Jg ziAJ=E>J+(io|2EbC79zr*llhcycjocAks<-v(omOV diff --git a/iOSClient/Supporting Files/de.lproj/Localizable.strings b/iOSClient/Supporting Files/de.lproj/Localizable.strings index d0969e2a0b1881c055f78fb40f5362ba0fe58e41..9ab11a010a64e74ca98905b743abbfffedb2608b 100644 GIT binary patch delta 92 zcmV-i0HgoPn+e*R34pW#{z;dPNdXX-@KXT<1!4eY0B4syj{z5#poswnmn=#FLX$G1 y6a!=cWtZJk0T+|BUKp1qN&ys;{GJY%&`JR&lQN?om#|F%42K>|0klIs3ZlXC0Gzc_4JAEjN$35Vi{7X$>k|Wz@j^_c(02bFGoB#j- From f4f2f64781391b1cdbb0137d332278486ba0aef1 Mon Sep 17 00:00:00 2001 From: Marino Faggiana Date: Fri, 20 Dec 2024 11:19:15 +0100 Subject: [PATCH 8/8] fix Signed-off-by: Marino Faggiana --- Nextcloud.xcodeproj/project.pbxproj | 4 ++-- iOSClient/Files/NCFiles.swift | 22 ++++++++++++++-------- iOSClient/Media/NCMedia.swift | 19 +++++++++++++++++-- iOSClient/Media/NCMediaDataSource.swift | 7 +++++-- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 08f62fb9ad..b0c9548050 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -5576,7 +5576,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.2.1; + MARKETING_VERSION = 6.2.2; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-v"; OTHER_LDFLAGS = ""; @@ -5639,7 +5639,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 6.2.1; + MARKETING_VERSION = 6.2.2; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-v"; OTHER_LDFLAGS = ""; diff --git a/iOSClient/Files/NCFiles.swift b/iOSClient/Files/NCFiles.swift index dcd604cf8d..796178a128 100644 --- a/iOSClient/Files/NCFiles.swift +++ b/iOSClient/Files/NCFiles.swift @@ -31,7 +31,7 @@ class NCFiles: NCCollectionViewCommon { internal var fileNameBlink: String? internal var fileNameOpen: String? internal var matadatasHash: String = "" - internal var reloadDataSourceInProgress: Bool = false + internal var semaphoreReloadDataSource = DispatchSemaphore(value: 1) required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) @@ -122,12 +122,16 @@ class NCFiles: NCCollectionViewCommon { // MARK: - DataSource override func reloadDataSource() { - guard !isSearchingMode, - !reloadDataSourceInProgress + guard !isSearchingMode else { return super.reloadDataSource() } - reloadDataSourceInProgress = true + + // This is only a fail safe "dead lock", I don't think the timeout will ever be called but at least nothing gets stuck, if after 5 sec. (which is a long time in this routine), the semaphore is still locked + // + if self.semaphoreReloadDataSource.wait(timeout: .now() + 5) == .timedOut { + self.semaphoreReloadDataSource.signal() + } var predicate = self.defaultPredicate let predicateDirectory = NSPredicate(format: "account == %@ AND serverUrl == %@", session.account, self.serverUrl) @@ -145,14 +149,16 @@ class NCFiles: NCCollectionViewCommon { self.dataSource = NCCollectionViewDataSource(metadatas: metadatas, layoutForView: layoutForView) if metadatas.isEmpty { - reloadDataSourceInProgress = false + self.semaphoreReloadDataSource.signal() return super.reloadDataSource() } self.dataSource.caching(metadatas: metadatas, dataSourceMetadatas: dataSourceMetadatas) { updated in - self.reloadDataSourceInProgress = false - if updated || self.isNumberOfItemsInAllSectionsNull || self.numberOfItemsInAllSections != metadatas.count { - super.reloadDataSource() + self.semaphoreReloadDataSource.signal() + DispatchQueue.main.async { + if updated || self.isNumberOfItemsInAllSectionsNull || self.numberOfItemsInAllSections != metadatas.count { + super.reloadDataSource() + } } } } diff --git a/iOSClient/Media/NCMedia.swift b/iOSClient/Media/NCMedia.swift index 222591a080..ea14552be2 100644 --- a/iOSClient/Media/NCMedia.swift +++ b/iOSClient/Media/NCMedia.swift @@ -36,6 +36,9 @@ class NCMedia: UIViewController { @IBOutlet weak var menuButton: UIButton! @IBOutlet weak var gradientView: UIView! + let semaphoreSearchMedia = DispatchSemaphore(value: 1) + let semaphoreNotificationCenter = DispatchSemaphore(value: 1) + let layout = NCMediaLayout() var layoutType = NCGlobal.shared.mediaLayoutRatio var documentPickerViewController: NCDocumentPickerViewController? @@ -257,13 +260,25 @@ class NCMedia: UIViewController { return } + // This is only a fail safe "dead lock", I don't think the timeout will ever be called but at least nothing gets stuck, if after 5 sec. (which is a long time in this routine), the semaphore is still locked + // + if self.semaphoreNotificationCenter.wait(timeout: .now() + 5) == .timedOut { + self.semaphoreNotificationCenter.signal() + } + if error.errorCode == self.global.errorResourceNotFound, let ocId = userInfo["ocId"] as? String { self.database.deleteMetadataOcId(ocId) - self.loadDataSource() + self.loadDataSource { + self.semaphoreNotificationCenter.signal() + } } else if error != .success { NCContentPresenter().showError(error: error) - self.loadDataSource() + self.loadDataSource { + self.semaphoreNotificationCenter.signal() + } + } else { + semaphoreNotificationCenter.signal() } } diff --git a/iOSClient/Media/NCMediaDataSource.swift b/iOSClient/Media/NCMediaDataSource.swift index 4a62a534c3..19740441f8 100644 --- a/iOSClient/Media/NCMediaDataSource.swift +++ b/iOSClient/Media/NCMediaDataSource.swift @@ -58,12 +58,13 @@ extension NCMedia { NCNetworking.shared.downloadThumbnailQueue.operationCount == 0, let tableAccount = database.getTableAccount(predicate: NSPredicate(format: "account == %@", session.account)) else { return } - self.searchMediaInProgress = true - let limit = max(self.collectionView.visibleCells.count * 3, 300) let visibleCells = self.collectionView?.indexPathsForVisibleItems.sorted(by: { $0.row < $1.row }).compactMap({ self.collectionView?.cellForItem(at: $0) }) DispatchQueue.global(qos: .background).async { + self.semaphoreSearchMedia.wait() + self.searchMediaInProgress = true + var lessDate = Date.distantFuture var greaterDate = Date.distantPast let countMetadatas = self.dataSource.metadatas.count @@ -156,6 +157,8 @@ extension NCMedia { self.collectionViewReloadData() } + self.semaphoreSearchMedia.signal() + DispatchQueue.main.async { self.activityIndicator.stopAnimating() self.searchMediaInProgress = false