From e28b4b453112159d08c6d3045236b1651b775fcd Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 6 Mar 2023 15:20:15 +1100 Subject: [PATCH] Fixed a number of bugs with the config handling Added a number of feature flag checks to config updates Added legacy group disappearing message timer handling Updated the string linter to clean up the build logs a little Split the initial config dump generation into it's own migration so it can run the launch after the feature flag is toggled Fixed a few issues with the initial config dump creation Fixed an issue where "shadow" conversations would be left in the database by opening a thread and never sending a message Fixed a bug where duplicate members could be added to legacy groups Fixed a bug with using animated images for the avatar Fixed a bug where avatar images which were already on disk could be re-downloaded --- Scripts/LintLocalizableStrings.swift | 58 +- Session.xcodeproj/project.pbxproj | 4 + .../ConversationVC+Interaction.swift | 32 +- Session/Conversations/ConversationVC.swift | 19 + ...isappearingMessagesSettingsViewModel.swift | 17 + .../Settings/ThreadSettingsViewModel.swift | 1 + Session/Home/HomeVC.swift | 2 +- Session/Home/HomeViewModel.swift | 20 +- Session/Settings/ImagePickerHandler.swift | 6 +- Session/Settings/SettingsViewModel.swift | 2 - Session/Utilities/MockDataGenerator.swift | 12 +- SessionMessagingKit/Configuration.swift | 10 +- .../Migrations/_012_SharedUtilChanges.swift | 288 +++---- .../_013_GenerateInitialUserConfigDumps.swift | 188 +++++ .../Database/Models/ClosedGroupKeyPair.swift | 9 + .../SessionUtil+Contacts.swift | 228 +++--- .../SessionUtil+ConvoInfoVolatile.swift | 45 +- .../Config Handling/SessionUtil+Shared.swift | 278 ++++--- .../SessionUtil+UserGroups.swift | 156 +++- .../SessionUtil+UserProfile.swift | 64 +- .../QueryInterfaceRequest+Utilities.swift | 35 +- .../LibSessionUtil/SessionUtil.swift | 97 ++- .../libsession-util.xcframework/Info.plist | 24 +- .../ios-arm64/libsession-util.a | Bin 2083072 -> 2083088 bytes .../libsession-util.a | Bin 4588408 -> 4588424 bytes .../Open Groups/OpenGroupManager.swift | 16 +- .../MessageReceiver+ClosedGroups.swift | 765 ++++++++++-------- ...essageReceiver+ConfigurationMessages.swift | 3 +- .../MessageReceiver+ExpirationTimers.swift | 13 + .../MessageReceiver+MessageRequests.swift | 6 +- .../MessageReceiver+VisibleMessages.swift | 17 +- .../MessageSender+ClosedGroups.swift | 25 +- .../Sending & Receiving/MessageReceiver.swift | 11 + .../Utilities/ProfileManager.swift | 20 +- .../Base.lproj/MainInterface.storyboard | 8 +- .../Utilities/Database+Utilities.swift | 4 + 36 files changed, 1518 insertions(+), 965 deletions(-) create mode 100644 SessionMessagingKit/Database/Migrations/_013_GenerateInitialUserConfigDumps.swift diff --git a/Scripts/LintLocalizableStrings.swift b/Scripts/LintLocalizableStrings.swift index 3f0860735..956822df3 100755 --- a/Scripts/LintLocalizableStrings.swift +++ b/Scripts/LintLocalizableStrings.swift @@ -1,11 +1,6 @@ #!/usr/bin/xcrun --sdk macosx swift -// -// ListLocalizableStrings.swift -// Archa -// -// Created by Morgan Pretty on 18/5/20. -// Copyright © 2020 Archa. All rights reserved. +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. // // This script is based on https://github.com/ginowu7/CleanSwiftLocalizableExample the main difference // is canges to the localized usage regex @@ -56,7 +51,6 @@ var executableFiles: [String] = { /// - Parameter path: path of file /// - Returns: content in file func contents(atPath path: String) -> String { - print("Path: \(path)") guard let data = fileManager.contents(atPath: path), let content = String(data: data, encoding: .utf8) else { fatalError("Could not read from path: \(path)") } @@ -109,8 +103,6 @@ func localizedStringsInCode() -> [LocalizationCodeFile] { /// /// - Parameter files: list of localizable files to validate func validateMatchKeys(_ files: [LocalizationStringsFile]) { - print("------------ Validating keys match in all localizable files ------------") - guard let base = files.first, files.count > 1 else { return } let files = Array(files.dropFirst()) @@ -128,8 +120,6 @@ func validateMatchKeys(_ files: [LocalizationStringsFile]) { /// - codeFiles: Array of LocalizationCodeFile /// - localizationFiles: Array of LocalizableStringFiles func validateMissingKeys(_ codeFiles: [LocalizationCodeFile], localizationFiles: [LocalizationStringsFile]) { - print("------------ Checking for missing keys -----------") - guard let baseFile = localizationFiles.first else { fatalError("Could not locate base localization file") } @@ -150,8 +140,6 @@ func validateMissingKeys(_ codeFiles: [LocalizationCodeFile], localizationFiles: /// - codeFiles: Array of LocalizationCodeFile /// - localizationFiles: Array of LocalizableStringFiles func validateDeadKeys(_ codeFiles: [LocalizationCodeFile], localizationFiles: [LocalizationStringsFile]) { - print("------------ Checking for any dead keys in localizable file -----------") - guard let baseFile = localizationFiles.first else { fatalError("Could not locate base localization file") } @@ -174,14 +162,18 @@ protocol Pathable { struct LocalizationStringsFile: Pathable { let path: String let kv: [String: String] + let duplicates: [(key: String, path: String)] var keys: [String] { return Array(kv.keys) } init(path: String) { + let result = ContentParser.parse(path) + self.path = path - self.kv = ContentParser.parse(path) + self.kv = result.kv + self.duplicates = result.duplicates } /// Writes back to localizable file with sorted keys and removed whitespaces and new lines @@ -204,9 +196,7 @@ struct ContentParser { /// /// - Parameter path: Localizable file paths /// - Returns: localizable key and value for content at path - static func parse(_ path: String) -> [String: String] { - print("------------ Checking for duplicate keys: \(path) ------------") - + static func parse(_ path: String) -> (kv: [String: String], duplicates: [(key: String, path: String)]) { let content = contents(atPath: path) let trimmed = content .replacingOccurrences(of: "\n+", with: "", options: .regularExpression, range: nil) @@ -218,13 +208,18 @@ struct ContentParser { fatalError("Error parsing contents: Make sure all keys and values are in correct format (this could be due to extra spaces between keys and values)") } - return zip(keys, values).reduce(into: [String: String]()) { results, keyValue in - if results[keyValue.0] != nil { - printPretty("error: Found duplicate key: \(keyValue.0) in file: \(path)") - abort() + var duplicates: [(key: String, path: String)] = [] + let kv: [String: String] = zip(keys, values) + .reduce(into: [:]) { results, keyValue in + guard results[keyValue.0] == nil else { + duplicates.append((keyValue.0, path)) + return + } + + results[keyValue.0] = keyValue.1 } - results[keyValue.0] = keyValue.1 - } + + return (kv, duplicates) } } @@ -232,20 +227,27 @@ func printPretty(_ string: String) { print(string.replacingOccurrences(of: "\\", with: "")) } -let stringFiles = create() +// MARK: - Processing + +let stringFiles: [LocalizationStringsFile] = create() if !stringFiles.isEmpty { - print("------------ Found \(stringFiles.count) file(s) ------------") + print("------------ Found \(stringFiles.count) file(s) - checking for duplicate, extra, missing and dead keys ------------") + + stringFiles.forEach { file in + file.duplicates.forEach { key, path in + printPretty("error: Found duplicate key: \(key) in file: \(path)") + } + } - stringFiles.forEach { print($0.path) } validateMatchKeys(stringFiles) // Note: Uncomment the below file to clean out all comments from the localizable file (we don't want this because comments make it readable...) // stringFiles.forEach { $0.cleanWrite() } - let codeFiles = localizedStringsInCode() + let codeFiles: [LocalizationCodeFile] = localizedStringsInCode() validateMissingKeys(codeFiles, localizationFiles: stringFiles) validateDeadKeys(codeFiles, localizationFiles: stringFiles) } -print("------------ SUCCESS ------------") +print("------------ Complete ------------") diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 5c13a263f..c7903d7ee 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -708,6 +708,7 @@ FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728972849E8110018502F /* UITableView+ReusableView.swift */; }; FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD772899284AF1BD0018502F /* Sodium+Utilities.swift */; }; FD77289E284EF1C50018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD77289D284EF1C50018502F /* Sodium+Utilities.swift */; }; + FD778B6429B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD778B6329B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift */; }; FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; platformFilter = ios; }; FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */; }; FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; }; @@ -1835,6 +1836,7 @@ FD7728972849E8110018502F /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = ""; }; FD772899284AF1BD0018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = ""; }; FD77289D284EF1C50018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = ""; }; + FD778B6329B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _013_GenerateInitialUserConfigDumps.swift; sourceTree = ""; }; FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionUtilitiesKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionIdSpec.swift; sourceTree = ""; }; FD83B9BD27CF2243005E1583 /* TestConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; @@ -3629,6 +3631,7 @@ FD7115F128C6CB3900B47552 /* _010_AddThreadIdToFTS.swift */, FD432431299C6933008A0213 /* _011_AddPendingReadReceipts.swift */, FD8ECF7C2934293A00C0D1BB /* _012_SharedUtilChanges.swift */, + FD778B6329B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift */, ); path = Migrations; sourceTree = ""; @@ -5791,6 +5794,7 @@ FD09796E27FA6D0000936362 /* Contact.swift in Sources */, C38D5E8D2575011E00B6A65C /* MessageSender+ClosedGroups.swift in Sources */, FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */, + FD778B6429B189FF001BAC6B /* _013_GenerateInitialUserConfigDumps.swift in Sources */, C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */, FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */, FD8ECF7F2934298100C0D1BB /* SharedConfigDump.swift in Sources */, diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index b4a5b213d..054eed615 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -454,10 +454,12 @@ extension ConversationVC: // Let the viewModel know we are about to send a message self?.viewModel.sentMessageBeforeUpdate = true - // Update the thread to be visible - _ = try SessionThread - .filter(id: threadId) - .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + // Update the thread to be visible (if it isn't already) + if !thread.shouldBeVisible { + _ = try SessionThread + .filter(id: threadId) + .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + } let authorId: String = { if let blindedId = self?.viewModel.threadData.currentUserBlindedPublicKey { @@ -585,10 +587,12 @@ extension ConversationVC: // Let the viewModel know we are about to send a message self?.viewModel.sentMessageBeforeUpdate = true - // Update the thread to be visible - _ = try SessionThread - .filter(id: threadId) - .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + // Update the thread to be visible (if it isn't already) + if !thread.shouldBeVisible { + _ = try SessionThread + .filter(id: threadId) + .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + } // Create the interaction let interaction: Interaction = try Interaction( @@ -1301,7 +1305,7 @@ extension ConversationVC: .suffix(19)) .appending(sentTimestamp) } - // TODO: Need to test emoji reacts for both open groups and one-to-one to make sure this isn't broken + // Perform the sending logic Storage.shared .writePublisherFlatMap(receiveOn: DispatchQueue.global(qos: .userInitiated)) { db -> AnyPublisher in @@ -1311,10 +1315,12 @@ extension ConversationVC: .eraseToAnyPublisher() } - // Update the thread to be visible - _ = try SessionThread - .filter(id: thread.id) - .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + // Update the thread to be visible (if it isn't already) + if !thread.shouldBeVisible { + _ = try SessionThread + .filter(id: thread.id) + .updateAllAndConfig(db, SessionThread.Columns.shouldBeVisible.set(to: true)) + } let pendingReaction: Reaction? = { if remove { diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index b7b6707a0..39a0014d9 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -528,6 +528,25 @@ final class ConversationVC: BaseVC, ConversationSearchControllerDelegate, UITabl mediaCache.removeAllObjects() hasReloadedThreadDataAfterDisappearance = false viewIsDisappearing = false + + // If the user just created this thread but didn't send a message then we want to delete the + // "shadow" thread since it's not actually in use (this is to prevent it from taking up database + // space or unintentionally getting synced via libSession in the future) + let threadId: String = viewModel.threadData.threadId + + if + viewModel.threadData.threadShouldBeVisible == false && + !SessionUtil.conversationExistsInConfig( + threadId: threadId, + threadVariant: viewModel.threadData.threadVariant + ) + { + Storage.shared.writeAsync { db in + _ = try SessionThread + .filter(id: threadId) + .deleteAll(db) + } + } } @objc func applicationDidBecomeActive(_ notification: Notification) { diff --git a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift index 1b7682185..69e6b2495 100644 --- a/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift +++ b/Session/Conversations/Settings/ThreadDisappearingMessagesSettingsViewModel.swift @@ -30,6 +30,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel @@ -39,10 +40,12 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel