From c51f4fda5e58bdde625522b5958a40f7b5333960 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 3 Mar 2025 17:38:13 +1100 Subject: [PATCH] Fixed a number of bugs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Added a 'requireAllRequestsSucceed' flag to the ConfigurationSyncJob (so it'll report a failure if any individual request fails) • Updated the 'unknownMessage' error to include some info about the data contained in the protobuf • Fixed an issue where the logger wasn't correctly respecting the log level settings • Fixed an issue where the path status indicator wouldn't default to unknown • Fixed an issue where the generic database error didn't replace the 'app_name' variable • Fixed an issue where a group could be partially created due to one of it's configs failing to be stored (we now consider that a failure so the user can try again) • Fixed an issue where the background pollers could get released before they finish polling • Fixed an issue where the community poller would only ever fetch the most recent 100 messages (instead of everything since the last poll) • Fixed an issue where we could incorrectly clear the app badge number in some cases • Fixed an issue where processing a config message in the BackgroundPoller could result in attempting to fetch from communities after the process completed • Fixed a crash where a database query could incorrectly be interrupted after it completed if both happened at just the right time --- Session/Meta/AppDelegate.swift | 27 +++++++----- SessionMessagingKit/Messages/Message.swift | 2 +- .../Errors/MessageReceiverError.swift | 43 +++++++++++++++++-- .../Sending & Receiving/MessageReceiver.swift | 4 +- .../NSENotificationPresenter.swift | 28 +++++++++--- .../NotificationServiceExtension.swift | 30 ++++++++++--- .../Models/SnodeRecursiveResponse.swift | 2 +- SessionSnodeKit/Types/BatchResponse.swift | 1 + SessionUtilitiesKit/Database/Storage.swift | 5 ++- 9 files changed, 110 insertions(+), 32 deletions(-) diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 726e5bcd6..25b860eee 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -381,19 +381,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // Ensure we haven't timed out yet guard timer.isCancelled == false else { return } - // Immediately cancel the timer to prevent the timeout being triggered - timer.cancel() - - // Update the unread count badge - let unreadCount: Int = dependencies[singleton: .storage] - .read { db in try Interaction.fetchAppBadgeUnreadCount(db, using: dependencies) } - .defaulting(to: 0) - - DispatchQueue.main.async(using: dependencies) { + /// Update the app badge in case the unread count changed (but only if the database is valid and + /// not suspended) + if + dependencies[singleton: .storage].isValid && + !dependencies[singleton: .storage].isSuspended + { + let unreadCount: Int = dependencies[singleton: .storage] + .read { db in try Interaction.fetchAppBadgeUnreadCount(db, using: dependencies) } + .defaulting(to: 0) UIApplication.shared.applicationIconBadgeNumber = unreadCount } - // If we are still running in the background then suspend the network & database if dependencies[singleton: .appContext].isInBackground { dependencies.mutate(cache: .libSessionNetwork) { $0.suspendNetworkAccess() } dependencies[singleton: .storage].suspendDatabaseAccess() @@ -784,7 +783,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD /// On application startup the `Storage.read` can be slightly slow while GRDB spins up it's database /// read pools (up to a few seconds), since this read is blocking we want to dispatch it to run async to ensure /// we don't block user interaction while it's running + /// + /// **Note:** Only do this if the database is still valid and not suspended (otherwise we will just reset the badge + /// number incorrectly) DispatchQueue.global(qos: .default).async { + guard + dependencies[singleton: .storage].isValid && + !dependencies[singleton: .storage].isSuspended + else { return } + let unreadCount: Int = dependencies[singleton: .storage] .read { db in try Interaction.fetchAppBadgeUnreadCount(db, using: dependencies) } .defaulting(to: 0) diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index cba6af01a..8073ca13e 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -354,7 +354,7 @@ public extension Message { return variant.messageType.fromProto(proto, sender: sender, using: dependencies) } - return try decodedMessage ?? { throw MessageReceiverError.unknownMessage }() + return try decodedMessage ?? { throw MessageReceiverError.unknownMessage(proto) }() } static func requiresExistingConversation(message: Message, threadVariant: SessionThread.Variant) -> Bool { diff --git a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift index aa6376066..f4fbc0708 100644 --- a/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift +++ b/SessionMessagingKit/Sending & Receiving/Errors/MessageReceiverError.swift @@ -4,13 +4,13 @@ import Foundation -public enum MessageReceiverError: LocalizedError { +public enum MessageReceiverError: Error, CustomStringConvertible { case duplicateMessage case duplicateMessageNewSnode case duplicateControlMessage case invalidMessage case invalidSender - case unknownMessage + case unknownMessage(SNProtoContent?) case unknownEnvelopeType case noUserX25519KeyPair case noUserED25519KeyPair @@ -55,14 +55,49 @@ public enum MessageReceiverError: LocalizedError { } } - public var errorDescription: String? { + public var description: String { switch self { case .duplicateMessage: return "Duplicate message." case .duplicateMessageNewSnode: return "Duplicate message from different service node." case .duplicateControlMessage: return "Duplicate control message." case .invalidMessage: return "Invalid message." case .invalidSender: return "Invalid sender." - case .unknownMessage: return "Unknown message type." + case .unknownMessage(let content): + switch content { + case .none: return "Unknown message type (no content)." + case .some(let content): + let protoInfo: [(String, Bool)] = [ + ("hasDataMessage", (content.dataMessage != nil)), + ("hasProfile", (content.dataMessage?.profile != nil)), + ("hasBody", (content.dataMessage?.hasBody == true)), + ("hasAttachments", (content.dataMessage?.attachments.isEmpty == false)), + ("hasReaction", (content.dataMessage?.reaction != nil)), + ("hasQuote", (content.dataMessage?.quote != nil)), + ("hasLinkPreview", (content.dataMessage?.preview != nil)), + ("hasOpenGroupInvitation", (content.dataMessage?.openGroupInvitation != nil)), + ("hasLegacyGroupControlMessage", (content.dataMessage?.closedGroupControlMessage != nil)), + ("hasGroupV2ControlMessage", (content.dataMessage?.groupUpdateMessage != nil)), + ("hasTimestamp", (content.dataMessage?.hasTimestamp == true)), + ("hasSyncTarget", (content.dataMessage?.hasSyncTarget == true)), + ("hasBlocksCommunityMessageRequests", (content.dataMessage?.hasBlocksCommunityMessageRequests == true)), + ("hasCallMessage", (content.callMessage != nil)), + ("hasReceiptMessage", (content.receiptMessage != nil)), + ("hasTypingMessage", (content.typingMessage != nil)), + ("hasDataExtractionMessage", (content.dataExtractionNotification != nil)), + ("hasUnsendRequest", (content.unsendRequest != nil)), + ("hasMessageRequestResponse", (content.messageRequestResponse != nil)), + ("hasExpirationTimer", (content.hasExpirationTimer == true)), + ("hasExpirationType", (content.hasExpirationType == true)), + ("hasSigTimestamp", (content.hasSigTimestamp == true)) + ] + + let protoInfoString: String = protoInfo + .filter { _, val in val } + .map { name, _ in name } + .joined(separator: ", ") + return "Unknown message type (\(protoInfoString))." + } + case .unknownEnvelopeType: return "Unknown envelope type." case .noUserX25519KeyPair: return "Couldn't find user X25519 key pair." case .noUserED25519KeyPair: return "Couldn't find user ED25519 key pair." diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index cf1eef906..a7dd2e1fa 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -234,7 +234,7 @@ public enum MessageReceiver { case .all, .unknown: Log.warn(.messageReceiver, "Couldn't process message due to invalid namespace.") - throw MessageReceiverError.unknownMessage + throw MessageReceiverError.unknownMessage(nil) } } @@ -440,7 +440,7 @@ public enum MessageReceiver { using: dependencies ) - default: throw MessageReceiverError.unknownMessage + default: throw MessageReceiverError.unknownMessage(proto) } // Perform any required post-handling logic diff --git a/SessionNotificationServiceExtension/NSENotificationPresenter.swift b/SessionNotificationServiceExtension/NSENotificationPresenter.swift index 94f820907..cbd389b16 100644 --- a/SessionNotificationServiceExtension/NSENotificationPresenter.swift +++ b/SessionNotificationServiceExtension/NSENotificationPresenter.swift @@ -89,9 +89,17 @@ public class NSENotificationPresenter: NotificationsManagerType { notificationContent.sound = thread.notificationSound .defaulting(to: db[.defaultNotificationSound] ?? Preferences.Sound.defaultNotificationSound) .notificationSound(isQuiet: false) - notificationContent.badge = (try? Interaction.fetchAppBadgeUnreadCount(db, using: dependencies)) - .map { NSNumber(value: $0) } - .defaulting(to: NSNumber(value: 0)) + + /// Update the app badge in case the unread count changed (but only if the database is valid and + /// not suspended) + if + dependencies[singleton: .storage].isValid && + !dependencies[singleton: .storage].isSuspended + { + notificationContent.badge = (try? Interaction.fetchAppBadgeUnreadCount(db, using: dependencies)) + .map { NSNumber(value: $0) } + .defaulting(to: NSNumber(value: 0)) + } // Title & body let previewType: Preferences.NotificationPreviewType = db[.preferencesNotificationPreviewType] @@ -198,9 +206,17 @@ public class NSENotificationPresenter: NotificationsManagerType { notificationContent.sound = thread.notificationSound .defaulting(to: db[.defaultNotificationSound] ?? Preferences.Sound.defaultNotificationSound) .notificationSound(isQuiet: false) - notificationContent.badge = (try? Interaction.fetchAppBadgeUnreadCount(db, using: dependencies)) - .map { NSNumber(value: $0) } - .defaulting(to: NSNumber(value: 0)) + + /// Update the app badge in case the unread count changed (but only if the database is valid and + /// not suspended) + if + dependencies[singleton: .storage].isValid && + !dependencies[singleton: .storage].isSuspended + { + notificationContent.badge = (try? Interaction.fetchAppBadgeUnreadCount(db, using: dependencies)) + .map { NSNumber(value: $0) } + .defaulting(to: NSNumber(value: 0)) + } notificationContent.title = Constants.app_name notificationContent.body = "" diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 759182def..44575f94e 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -478,10 +478,18 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension switch resolution { case .ignoreDueToMainAppRunning: break default: - silentContent.badge = dependencies[singleton: .storage] - .read { [dependencies] db in try Interaction.fetchAppBadgeUnreadCount(db, using: dependencies) } - .map { NSNumber(value: $0) } - .defaulting(to: NSNumber(value: 0)) + /// Update the app badge in case the unread count changed (but only if the database is valid and + /// not suspended) + if + dependencies[singleton: .storage].isValid && + !dependencies[singleton: .storage].isSuspended + { + silentContent.badge = dependencies[singleton: .storage] + .read { [dependencies] db in try Interaction.fetchAppBadgeUnreadCount(db, using: dependencies) } + .map { NSNumber(value: $0) } + .defaulting(to: NSNumber(value: 0)) + } + dependencies[singleton: .storage].suspendDatabaseAccess() } @@ -538,9 +546,17 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension let notificationContent = UNMutableNotificationContent() notificationContent.userInfo = [ NotificationServiceExtension.isFromRemoteKey : true ] notificationContent.title = Constants.app_name - notificationContent.badge = (try? Interaction.fetchAppBadgeUnreadCount(db, using: dependencies)) - .map { NSNumber(value: $0) } - .defaulting(to: NSNumber(value: 0)) + + /// Update the app badge in case the unread count changed (but only if the database is valid and + /// not suspended) + if + dependencies[singleton: .storage].isValid && + !dependencies[singleton: .storage].isSuspended + { + notificationContent.badge = (try? Interaction.fetchAppBadgeUnreadCount(db, using: dependencies)) + .map { NSNumber(value: $0) } + .defaulting(to: NSNumber(value: 0)) + } if let sender: String = callMessage.sender { let senderDisplayName: String = Profile.displayName(db, id: sender, threadVariant: .contact, using: dependencies) diff --git a/SessionSnodeKit/Models/SnodeRecursiveResponse.swift b/SessionSnodeKit/Models/SnodeRecursiveResponse.swift index 91e2d2616..fdb729744 100644 --- a/SessionSnodeKit/Models/SnodeRecursiveResponse.swift +++ b/SessionSnodeKit/Models/SnodeRecursiveResponse.swift @@ -7,7 +7,7 @@ public class SnodeRecursiveResponse: SnodeResponse { case swarm } - internal let swarm: [String: T] + public let swarm: [String: T] // MARK: - Initialization diff --git a/SessionSnodeKit/Types/BatchResponse.swift b/SessionSnodeKit/Types/BatchResponse.swift index cc2919571..e9af2affd 100644 --- a/SessionSnodeKit/Types/BatchResponse.swift +++ b/SessionSnodeKit/Types/BatchResponse.swift @@ -150,6 +150,7 @@ extension Network.BatchSubResponse: Decodable { // MARK: - ErasedBatchSubResponse public protocol ErasedBatchSubResponse: ResponseInfoType { + var code: Int { get } var erasedBody: Any? { get } var failedToParseBody: Bool { get } } diff --git a/SessionUtilitiesKit/Database/Storage.swift b/SessionUtilitiesKit/Database/Storage.swift index 8a2da7c24..f1734bf6f 100644 --- a/SessionUtilitiesKit/Database/Storage.swift +++ b/SessionUtilitiesKit/Database/Storage.swift @@ -578,7 +578,10 @@ open class Storage { switch error { case DatabaseError.SQLITE_ABORT, DatabaseError.SQLITE_INTERRUPT, DatabaseError.SQLITE_ERROR: let message: String = ((error as? DatabaseError)?.message ?? "Unknown") - Log.error(.storage, "Database \(action) failed due to error: \(message) - [ \(info.callInfo) ]") + Log.error(.storage, "Database \(action) failed due to error: \(message)") + + case StorageError.databaseInvalid: + Log.error(.storage, "Database \(action) failed as the database is invalid.") case StorageError.databaseInvalid: Log.error(.storage, "Database \(action) failed as the database is invalid - [ \(info.callInfo) ]")