-
Notifications
You must be signed in to change notification settings - Fork 24
fix: invisible groups - WPB-20123 #3792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/cycle-4.10
Are you sure you want to change the base?
Changes from all commits
f79177e
99138f9
2342ce9
d8db4d1
28b3be7
2524cb0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| // | ||
| // Wire | ||
| // Copyright (C) 2025 Wire Swiss GmbH | ||
| // | ||
| // This program is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
| // | ||
| // This program is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU General Public License for more details. | ||
| // | ||
| // You should have received a copy of the GNU General Public License | ||
| // along with this program. If not, see http://www.gnu.org/licenses/. | ||
| // | ||
| import Foundation | ||
| import WireDataModel | ||
| import WireLogging | ||
|
|
||
| /// sourcery: AutoMockable | ||
| public protocol ConversationUpdatesGeneratorProtocol { | ||
| func start() async | ||
| func stop() | ||
| } | ||
|
|
||
| public final class ConversationUpdatesGenerator: NSObject, ConversationUpdatesGeneratorProtocol { | ||
|
|
||
| private let context: NSManagedObjectContext | ||
| private let fetchedResultsController: NSFetchedResultsController<ZMConversation> | ||
| private let repository: ConversationRepositoryProtocol | ||
| private var onConversationUpdated: (UpdateConversationItem) -> Void | ||
|
|
||
| init( | ||
| repository: ConversationRepositoryProtocol, | ||
| context: NSManagedObjectContext, | ||
| onConversationUpdated: @escaping (UpdateConversationItem) -> Void | ||
| ) { | ||
| let request = NSFetchRequest<ZMConversation>(entityName: ZMConversation.entityName()) | ||
| request.predicate = ZMConversation.predicateForNeedingToBeUpdatedFromBackend() | ||
| request.sortDescriptors = [NSSortDescriptor(key: ZMConversationLastServerTimeStampKey, ascending: true)] | ||
| self.fetchedResultsController = NSFetchedResultsController( | ||
| fetchRequest: request, | ||
| managedObjectContext: context, | ||
| sectionNameKeyPath: nil, | ||
| cacheName: nil | ||
| ) | ||
| self.context = context | ||
| self.onConversationUpdated = onConversationUpdated | ||
| self.repository = repository | ||
| super.init() | ||
| } | ||
|
|
||
| /// Starts monitoring and triggers pulls for any needingToBeUpdatedFromBackend conversations. | ||
| public func start() async { | ||
| fetchedResultsController.delegate = self | ||
| do { | ||
| try fetchedResultsController.performFetch() | ||
| } catch { | ||
| WireLogger.conversation.error("error fetching conversations: \(String(describing: error))") | ||
| } | ||
|
|
||
| let conversations = fetchedResultsController.fetchedObjects ?? [] | ||
| for conversation in conversations { | ||
| await context.perform { | ||
|
Check warning on line 66 in WireDomain/Sources/WireDomain/Synchronization/ConversationUpdatesGenerator.swift
|
||
| if let id = conversation.qualifiedID { | ||
|
Check warning on line 67 in WireDomain/Sources/WireDomain/Synchronization/ConversationUpdatesGenerator.swift
|
||
| self.onConversationUpdated(UpdateConversationItem( | ||
| repository: self.repository, | ||
| conversationID: id.toAPIModel() | ||
| )) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public func stop() { | ||
| fetchedResultsController.delegate = nil | ||
| } | ||
| } | ||
|
|
||
| // MARK: - NSFetchedResultsControllerDelegate | ||
|
|
||
| extension ConversationUpdatesGenerator: NSFetchedResultsControllerDelegate { | ||
|
|
||
| public func controller( | ||
| _ controller: NSFetchedResultsController<NSFetchRequestResult>, | ||
| didChange anObject: Any, | ||
| at indexPath: IndexPath?, | ||
| for type: NSFetchedResultsChangeType, | ||
| newIndexPath: IndexPath? | ||
| ) { | ||
| guard let conversation = anObject as? ZMConversation else { return } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Crash if this fails. The fetched results controller is configured in this file so we know that anObject right? |
||
|
|
||
| switch type { | ||
| case .insert: | ||
| // Insert == flag flipped to true (matches predicate now) | ||
| if let qualifiedID = conversation.qualifiedID { | ||
| onConversationUpdated(UpdateConversationItem( | ||
| repository: repository, | ||
| conversationID: qualifiedID.toAPIModel() | ||
| )) | ||
| } | ||
|
|
||
| case .update: | ||
| // Already in the "true" set; we only act on the transition handled by `.insert`. | ||
| break | ||
|
|
||
| case .move, .delete: | ||
| break | ||
|
|
||
| @unknown default: | ||
| break | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -82,18 +82,18 @@ | |
|
|
||
| let processedEnvelopeIDs: Set<UUID> | ||
| do { | ||
| logger.debug("pulling pending update events", attributes: .incrementalSyncV2) | ||
| logger.info("pulling pending update events", attributes: .incrementalSyncV2, .safePublic) | ||
| syncStateSubject.send(.incrementalSyncing(.pullPendingEvents)) | ||
| try await updateEventsSync.pull() | ||
|
|
||
| logger.debug("processing stored update events", attributes: .incrementalSyncV2) | ||
| logger.info("processing stored update events", attributes: .incrementalSyncV2, .safePublic) | ||
| syncStateSubject.send(.incrementalSyncing(.processPendingEvents)) | ||
| processedEnvelopeIDs = try await processStoredEvents() | ||
| } catch { | ||
| func tearDown() async { | ||
| logger.debug( | ||
| logger.info( | ||
| "incremental sync interrupted, tearing down...", | ||
| attributes: .incrementalSyncV2 | ||
| attributes: .incrementalSyncV2, .safePublic | ||
| ) | ||
| await pushChannel.close() | ||
| } | ||
|
|
@@ -120,14 +120,14 @@ | |
|
|
||
| await mlsGroupRepairAgent.repairConversations() | ||
|
|
||
| let liveEventTask = Task { @Sendable [self] in | ||
|
Check warning on line 123 in WireDomain/Sources/WireDomain/Synchronization/IncrementalSync.swift
|
||
| logger.debug("handling live event stream", attributes: .incrementalSyncV2) | ||
| logger.info("handling live event stream", attributes: .incrementalSyncV2, .safePublic) | ||
| syncStateSubject.send(.liveSyncing(.ongoing)) | ||
|
|
||
| do { | ||
| // because we might be interrupted when in background, we wrap the sync in an expiringActivity that | ||
| // will cancel the task - not keeping any db operation (sqlite file opened) in suspend mode | ||
| try await withExpiringActivity(reason: "processLiveStream IncrementalSync") { | ||
|
Check warning on line 130 in WireDomain/Sources/WireDomain/Synchronization/IncrementalSync.swift
|
||
| await processLiveEvents( | ||
| liveEventStream: liveEventStream, | ||
| processedEnvelopeIDs: processedEnvelopeIDs | ||
|
|
@@ -217,13 +217,13 @@ | |
| do { | ||
| logger.debug( | ||
| "processing live event: \(event.name)", | ||
| attributes: .incrementalSyncV2 + [.eventEnvelopeID: envelope.id] | ||
| attributes: .incrementalSyncV2 + [.eventEnvelopeID: envelope.id, .eventType: event.name] | ||
| ) | ||
| try await processor.processEvent(event) | ||
| } catch { | ||
| logger.error( | ||
| "failed to process live event: \(String(describing: error))", | ||
| attributes: .incrementalSyncV2 + [.eventEnvelopeID: envelope.id] | ||
| attributes: .incrementalSyncV2 + [.eventEnvelopeID: envelope.id, .eventType: event.name] | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| // | ||
| // Wire | ||
| // Copyright (C) 2025 Wire Swiss GmbH | ||
| // | ||
| // This program is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
| // | ||
| // This program is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU General Public License for more details. | ||
| // | ||
| // You should have received a copy of the GNU General Public License | ||
| // along with this program. If not, see http://www.gnu.org/licenses/. | ||
| // | ||
|
|
||
| import Foundation | ||
| import WireDataModel | ||
| import WireLogging | ||
| import WireNetwork | ||
|
|
||
| struct UpdateConversationItem: WorkItem { | ||
| private let repository: ConversationRepositoryProtocol | ||
|
|
||
| var id = UUID() | ||
| var priority: WorkItemPriority { | ||
| .medium | ||
| } | ||
|
|
||
| var conversationID: WireNetwork.QualifiedID | ||
|
Comment on lines
+27
to
+32
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Why are
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no reason, true I'll make it a let |
||
|
|
||
| public init( | ||
| repository: ConversationRepositoryProtocol, | ||
| conversationID: WireNetwork.QualifiedID, | ||
| ) { | ||
| self.repository = repository | ||
| self.conversationID = conversationID | ||
| } | ||
|
|
||
| func start() async throws { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question (out of scope): @netbe @johnxnguyen Am I right that the way a There were a few things that were nice about the old approach:
Anyway this is out of scope for this PR.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes that's basically what change |
||
| do { | ||
| WireLogger.conversation.debug( | ||
| "updating conversation", | ||
| attributes: [.conversationId: conversationID.id.uuidString], | ||
| .init(self) | ||
| ) | ||
| try await repository.pullConversation(id: conversationID.id, domain: conversationID.domain) | ||
|
|
||
| } catch ConversationRepositoryError.conversationNotFound { | ||
| WireLogger.conversation.warn( | ||
| "conversation does not on backend, delete locally", | ||
| attributes: [.conversationId: conversationID.id.uuidString], | ||
| .init(self) | ||
| ) | ||
| try await repository.deleteConversation(id: conversationID.id, domain: conversationID.domain) | ||
|
|
||
| } catch { | ||
| // giving more context to the error | ||
| WireLogger.conversation.error( | ||
| "error updating conversation from the backend: \(String(describing: error))", | ||
| attributes: [.conversationId: conversationID.id.uuidString], | ||
| .init(self) | ||
| ) | ||
| throw error | ||
| } | ||
|
|
||
| } | ||
|
|
||
| func cancel() async { | ||
| // do nothing | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this class monitors the coredata change, and create ticket for scheduler