From 1a2026381bc71bae7554ba3266cf1f2553ca4586 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Thu, 15 Feb 2024 15:04:24 +0000 Subject: [PATCH 1/3] Fix Channel back button in DemoApp --- DemoApp/StreamChat/Components/DemoChatChannelVC.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DemoApp/StreamChat/Components/DemoChatChannelVC.swift b/DemoApp/StreamChat/Components/DemoChatChannelVC.swift index 0cf8a831fcf..2d73db1abe6 100644 --- a/DemoApp/StreamChat/Components/DemoChatChannelVC.swift +++ b/DemoApp/StreamChat/Components/DemoChatChannelVC.swift @@ -46,6 +46,6 @@ final class DemoChatChannelVC: ChatChannelVC, UIGestureRecognizerDelegate { } @objc private func goBack() { - dismiss(animated: true) + navigationController?.popViewController(animated: true) } } From 927b89788ac41631f9b7b868f5d096ce31ae6129 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Thu, 15 Feb 2024 17:34:01 +0000 Subject: [PATCH 2/3] Upload attachments in parallel --- .../Background/AttachmentQueueUploader.swift | 31 +++++++-------- .../SpyPattern/Spy/APIClient_Spy.swift | 3 ++ .../AttachmentQueueUploader_Tests.swift | 38 +++++++++++++++++++ 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/Sources/StreamChat/Workers/Background/AttachmentQueueUploader.swift b/Sources/StreamChat/Workers/Background/AttachmentQueueUploader.swift index 0d4c128f308..fad821c7c5b 100644 --- a/Sources/StreamChat/Workers/Background/AttachmentQueueUploader.swift +++ b/Sources/StreamChat/Workers/Background/AttachmentQueueUploader.swift @@ -59,26 +59,24 @@ class AttachmentQueueUploader: Worker { private func handleChanges(changes: [ListChange]) { guard !changes.isEmpty else { return } + // Only start uploading attachment when inserted and it is present in pendingAttachmentIds database.backgroundReadOnlyContext.perform { [weak self] in - var wasEmpty: Bool = false self?._pendingAttachmentIDs.mutate { pendingAttachmentIDs in - wasEmpty = pendingAttachmentIDs.isEmpty - changes.pendingUploadAttachmentIDs.forEach { + let newAttachmentIds = Set(changes.attachmentIDs).subtracting(pendingAttachmentIDs) + newAttachmentIds.forEach { pendingAttachmentIDs.insert($0) } - } - if wasEmpty { - self?.uploadNextAttachment() + newAttachmentIds.forEach { id in + self?.uploadAttachment(with: id) + } } } } - private func uploadNextAttachment() { - guard let attachmentID = pendingAttachmentIDs.first else { return } - - prepareAttachmentForUpload(with: attachmentID) { [weak self] attachment in + private func uploadAttachment(with id: AttachmentId) { + prepareAttachmentForUpload(with: id) { [weak self] attachment in guard let attachment = attachment else { - self?.removeAttachmentIDAndContinue(attachmentID) + self?.removePendingAttachment(with: id) return } @@ -86,7 +84,7 @@ class AttachmentQueueUploader: Worker { attachment, progress: { self?.updateAttachmentIfNeeded( - attachmentId: attachmentID, + attachmentId: id, uploadedAttachment: nil, newState: .uploading(progress: $0), completion: {} @@ -94,11 +92,11 @@ class AttachmentQueueUploader: Worker { }, completion: { result in self?.updateAttachmentIfNeeded( - attachmentId: attachmentID, + attachmentId: id, uploadedAttachment: result.value, newState: result.error == nil ? .uploaded : .uploadingFailed, completion: { - self?.removeAttachmentIDAndContinue(attachmentID) + self?.removePendingAttachment(with: id) } ) } @@ -128,9 +126,8 @@ class AttachmentQueueUploader: Worker { } } - private func removeAttachmentIDAndContinue(_ id: AttachmentId) { + private func removePendingAttachment(with id: AttachmentId) { _pendingAttachmentIDs.mutate { $0.remove(id) } - uploadNextAttachment() } private func updateAttachmentIfNeeded( @@ -229,7 +226,7 @@ class AttachmentQueueUploader: Worker { } private extension Array where Element == ListChange { - var pendingUploadAttachmentIDs: [AttachmentId] { + var attachmentIDs: [AttachmentId] { compactMap { switch $0 { case let .insert(dto, _), let .update(dto, _): diff --git a/TestTools/StreamChatTestTools/SpyPattern/Spy/APIClient_Spy.swift b/TestTools/StreamChatTestTools/SpyPattern/Spy/APIClient_Spy.swift index ded501bd504..3e950f0f3b5 100644 --- a/TestTools/StreamChatTestTools/SpyPattern/Spy/APIClient_Spy.swift +++ b/TestTools/StreamChatTestTools/SpyPattern/Spy/APIClient_Spy.swift @@ -34,6 +34,7 @@ final class APIClient_Spy: APIClient, Spy { @Atomic var uploadFile_attachment: AnyChatMessageAttachment? @Atomic var uploadFile_progress: ((Double) -> Void)? @Atomic var uploadFile_completion: ((Result) -> Void)? + @Atomic var uploadFile_callCount = 0 @Atomic var init_sessionConfiguration: URLSessionConfiguration @Atomic var init_requestEncoder: RequestEncoder @@ -143,9 +144,11 @@ final class APIClient_Spy: APIClient, Spy { progress: ((Double) -> Void)?, completion: @escaping (Result) -> Void ) { + uploadFile_attachment = attachment uploadFile_progress = progress uploadFile_completion = completion + uploadFile_callCount += 1 uploadRequest_expectation.fulfill() } diff --git a/Tests/StreamChatTests/Workers/Background/AttachmentQueueUploader_Tests.swift b/Tests/StreamChatTests/Workers/Background/AttachmentQueueUploader_Tests.swift index 5fd4358e442..4b761b66a21 100644 --- a/Tests/StreamChatTests/Workers/Background/AttachmentQueueUploader_Tests.swift +++ b/Tests/StreamChatTests/Workers/Background/AttachmentQueueUploader_Tests.swift @@ -601,6 +601,44 @@ final class AttachmentQueueUploader_Tests: XCTestCase { "messaging:dummy-fake-0.txt" ) } + + func test_uploadAttachmentsInParallel() throws { + let cid: ChannelId = .unique + let messageId: MessageId = .unique + + // Create channel in the database. + try database.createChannel(cid: cid, withMessages: false) + // Create message in the database. + try database.createMessage(id: messageId, cid: cid, localState: .pendingSend) + + let attachmentPayloads: [AnyAttachmentPayload] = [ + .mockFile, + .mockImage, + .mockVideo, + .mockAudio, + .mockVoiceRecording + ] + + for (index, envelope) in attachmentPayloads.enumerated() { + let attachmentId = AttachmentId(cid: cid, messageId: messageId, index: index) + // Seed attachment in `.pendingUpload` state to the database. + try database.writeSynchronously { session in + try session.createNewAttachment(attachment: envelope, id: attachmentId) + } + + // Load attachment from the database. + let attachment = try XCTUnwrap(database.viewContext.attachment(id: attachmentId)) + + // Assert attachment is in `.pendingUpload` state. + XCTAssertEqual(attachment.localState, .pendingUpload) + } + + // Attachments start all uploading at the same time. + AssertAsync.willBeEqual( + apiClient.uploadFile_callCount, + attachmentPayloads.count + ) + } } private extension URL { From cf4780c2459eca77a3cf529f86a37720b2b373d1 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Fri, 16 Feb 2024 18:27:20 +0000 Subject: [PATCH 3/3] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f70ca553421..00398fb6c21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Upcoming +## StreamChat +### ✅ Added +- Add parallel attachment uploading [#3034](https://github.com/GetStream/stream-chat-swift/pull/3034) + ## StreamChatUI ### 🐞 Fixed - Fix composer link preview overridden by previous enrichment [#3025](https://github.com/GetStream/stream-chat-swift/pull/3025)