From 892582df6715473ac68c1383a9de085bddcc362f Mon Sep 17 00:00:00 2001 From: Mikhail Kilin Date: Thu, 21 May 2026 00:29:47 +0300 Subject: [PATCH] Wire iOS media downloads through session bridge --- .../TeleTuiIOS/Sources/TeleTuiIOSCore/Bridge.swift | 10 ++++++++++ .../TeleTuiIOS/Sources/TeleTuiIOSCore/Models.swift | 10 ++++++++++ .../Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift | 12 ++++++++++++ .../Sources/TeleTuiIOSSmokeTests/main.swift | 5 +++++ crates/tele-core/src/session.rs | 5 ++++- .../tele-core/src/test_support/fake_tdclient_impl.rs | 2 +- crates/tele-ios-ffi/src/lib.rs | 2 ++ scripts/smoke-ios-ffi-swift.sh | 6 ++++++ 8 files changed, 50 insertions(+), 2 deletions(-) diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Bridge.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Bridge.swift index 6aeeb15..6c12733 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Bridge.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Bridge.swift @@ -18,6 +18,8 @@ public protocol SessionBridge: Sendable { func react(chatId: Int64, messageId: Int64, reaction: String) async throws -> [Reaction] func pinnedMessages(chatId: Int64) async throws -> [Message] func copyPayload(chatId: Int64, messageId: Int64) async throws -> String + func downloadPhoto(fileId: Int32) async throws -> DownloadedFile + func downloadVoice(fileId: Int32) async throws -> DownloadedFile } public actor FakeSessionBridge: SessionBridge { @@ -201,6 +203,14 @@ public actor FakeSessionBridge: SessionBridge { } return message.text } + + public func downloadPhoto(fileId: Int32) async throws -> DownloadedFile { + DownloadedFile(fileId: fileId, path: "/tmp/fake-photo-\(fileId).jpg") + } + + public func downloadVoice(fileId: Int32) async throws -> DownloadedFile { + DownloadedFile(fileId: fileId, path: "/tmp/fake-voice-\(fileId).ogg") + } } public enum FakeBridgeError: LocalizedError { diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Models.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Models.swift index 25ae7d2..a0fa18a 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Models.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/Models.swift @@ -153,6 +153,16 @@ public struct Profile: Equatable, Sendable { } } +public struct DownloadedFile: Equatable, Sendable { + public var fileId: Int32 + public var path: String + + public init(fileId: Int32, path: String) { + self.fileId = fileId + self.path = path + } +} + public enum SessionEvent: Equatable, Sendable { case authChanged(AuthState) case chatListChanged([ChatSummary]) diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift index 3508c29..437efd9 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSCore/UniFfiSessionBridge.swift @@ -105,6 +105,14 @@ public actor UniFfiSessionBridge: SessionBridge { try handle.copyPayload(chatId: chatId, messageId: messageId) } + public func downloadPhoto(fileId: Int32) async throws -> DownloadedFile { + try Self.mapDownloadedFile(handle.downloadPhoto(fileId: fileId)) + } + + public func downloadVoice(fileId: Int32) async throws -> DownloadedFile { + try Self.mapDownloadedFile(handle.downloadVoice(fileId: fileId)) + } + private static func mapAuthState(_ state: IosAuthState) -> AuthState { switch state { case .waitTdlibParameters: @@ -192,6 +200,10 @@ public actor UniFfiSessionBridge: SessionBridge { ) } + private static func mapDownloadedFile(_ file: IosDownloadedFile) -> DownloadedFile { + DownloadedFile(fileId: file.fileId, path: file.path) + } + private static func mapEvent(_ event: IosEvent) -> SessionEvent { switch event { case let .authChanged(state): diff --git a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift index ca39573..53fc820 100644 --- a/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift +++ b/apps/ios/TeleTuiIOS/Sources/TeleTuiIOSSmokeTests/main.swift @@ -100,6 +100,11 @@ struct TeleTuiIOSSmokeTests { await viewModel.copyPayload(for: viewModel.messages[0]) precondition(viewModel.copiedPayload == "Edited text") + let photo = try await bridge.downloadPhoto(fileId: 100) + let voice = try await bridge.downloadVoice(fileId: 200) + precondition(photo.path == "/tmp/fake-photo-100.jpg") + precondition(voice.path == "/tmp/fake-voice-200.ogg") + await viewModel.forward(message: viewModel.messages[0], to: 2) let forwarded = try await bridge.loadHistory(chatId: 2) precondition(forwarded.contains { $0.forwardSenderName == "Alice" && $0.text == "Edited text" }) diff --git a/crates/tele-core/src/session.rs b/crates/tele-core/src/session.rs index 03dae3d..775e400 100644 --- a/crates/tele-core/src/session.rs +++ b/crates/tele-core/src/session.rs @@ -942,7 +942,8 @@ mod tests { .build(); let client = FakeTdClient::new() .with_message(chat_id.as_i64(), message) - .with_downloaded_file(77, "/tmp/photo.jpg"); + .with_downloaded_file(77, "/tmp/photo.jpg") + .with_downloaded_file(88, "/tmp/voice.ogg"); let mut session = CoreSession::new(client); session @@ -960,8 +961,10 @@ mod tests { .await .unwrap(); let downloaded = session.download_photo(77).await.unwrap(); + let downloaded_voice = session.download_voice(88).await.unwrap(); assert_eq!(downloaded.path, "/tmp/photo.jpg"); + assert_eq!(downloaded_voice.path, "/tmp/voice.ogg"); assert_eq!(session.client().get_forwarded_messages().len(), 1); assert_eq!( reactions, diff --git a/crates/tele-core/src/test_support/fake_tdclient_impl.rs b/crates/tele-core/src/test_support/fake_tdclient_impl.rs index a0a4d6e..a8de0a9 100644 --- a/crates/tele-core/src/test_support/fake_tdclient_impl.rs +++ b/crates/tele-core/src/test_support/fake_tdclient_impl.rs @@ -308,7 +308,7 @@ impl FileClient for FakeTdClient { } async fn download_voice_note(&self, file_id: i32) -> Result { - Ok(format!("/tmp/fake_voice_{}.ogg", file_id)) + FakeTdClient::download_file(self, file_id).await } } diff --git a/crates/tele-ios-ffi/src/lib.rs b/crates/tele-ios-ffi/src/lib.rs index c67d2ec..a9246f1 100644 --- a/crates/tele-ios-ffi/src/lib.rs +++ b/crates/tele-ios-ffi/src/lib.rs @@ -1301,6 +1301,8 @@ mod tests { assert_eq!(history[0].text, "Hello from fake TDLib"); let pinned = session.pinned_messages(chats[0].id).unwrap(); assert_eq!(pinned[0].text, "Hello from fake TDLib"); + assert_eq!(session.download_photo(100).unwrap().path, "/tmp/fake-photo.jpg"); + assert_eq!(session.download_voice(200).unwrap().path, "/tmp/fake-voice.ogg"); let sent = session .send_message(chats[0].id, "Hi from Swift".to_string(), None) diff --git a/scripts/smoke-ios-ffi-swift.sh b/scripts/smoke-ios-ffi-swift.sh index caa0bfe..422c11b 100755 --- a/scripts/smoke-ios-ffi-swift.sh +++ b/scripts/smoke-ios-ffi-swift.sh @@ -63,6 +63,12 @@ struct Smoke { let pinned = try session.pinnedMessages(chatId: chat.id) require(pinned.first?.text == "Hello from fake TDLib", "expected pinned message") + let photo = try session.downloadPhoto(fileId: 100) + require(photo.path == "/tmp/fake-photo.jpg", "expected downloaded photo path") + + let voice = try session.downloadVoice(fileId: 200) + require(voice.path == "/tmp/fake-voice.ogg", "expected downloaded voice path") + let sent = try session.sendMessage(chatId: chat.id, text: "Hi from Swift FFI", replyToMessageId: nil) require(sent.text == "Hi from Swift FFI", "expected sent message text")