From e5984601e8896fa38c9d3db7120f06057414c7df Mon Sep 17 00:00:00 2001 From: Ryan ZHAO <> Date: Wed, 19 Mar 2025 11:21:28 +1100 Subject: [PATCH] remove database access on call creation --- .../Calls/Call Management/SessionCall.swift | 45 ++++++------------- .../Call Management/SessionCallManager.swift | 14 +++++- Session/Calls/CallVC.swift | 41 +++++++++++++---- .../Calls/Views & Modals/MiniCallView.swift | 4 +- .../ConversationVC+Interaction.swift | 2 +- .../PushRegistrationManager.swift | 37 ++++----------- .../NotificationServiceExtension.swift | 9 +++- 7 files changed, 79 insertions(+), 73 deletions(-) diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 64ce23d57..dbffde975 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -14,14 +14,18 @@ import SessionSnodeKit public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { private let dependencies: Dependencies + public let webRTCSession: WebRTCSession + + var currentConnectionStep: ConnectionStep + var connectionStepsRecord: [Bool] // MARK: - Metadata Properties public let uuid: String public let callId: UUID // This is for CallKit public let sessionId: String + let contactName: String let mode: CallMode var audioMode: AudioMode - public let webRTCSession: WebRTCSession let isOutgoing: Bool var remoteSDP: RTCSessionDescription? = nil { didSet { @@ -30,16 +34,8 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { } } } - var callInteractionId: Int64? var answerCallAction: CXAnswerCallAction? = nil - - let contactName: String - let profilePicture: UIImage - let animatedProfilePicture: YYImage? - - var currentConnectionStep: ConnectionStep - var connectionStepsRecord: [Bool] - + // MARK: - Control lazy public var videoCapturer: RTCVideoCapturer = { @@ -161,9 +157,10 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { // MARK: - Initialization - init(_ db: Database, for sessionId: String, uuid: String, mode: CallMode, outgoing: Bool = false, using dependencies: Dependencies) { + init(for sessionId: String, contactName: String, uuid: String, mode: CallMode, outgoing: Bool = false, using dependencies: Dependencies) { self.dependencies = dependencies self.sessionId = sessionId + self.contactName = contactName self.uuid = uuid self.callId = UUID() self.mode = mode @@ -172,20 +169,6 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { self.isOutgoing = outgoing self.currentConnectionStep = (mode == .offer ? OfferStep.initializing : AnswerStep.receivedOffer) self.connectionStepsRecord = [Bool](repeating: false, count: (mode == .answer ? 5 : 6)) - - let avatarData: Data? = dependencies[singleton: .displayPictureManager].displayPicture(db, id: .user(sessionId)) - self.contactName = Profile.displayName(db, id: sessionId, threadVariant: .contact, using: dependencies) - self.profilePicture = avatarData - .map { UIImage(data: $0) } - .defaulting(to: PlaceholderIcon.generate(seed: sessionId, text: self.contactName, size: 300)) - self.animatedProfilePicture = avatarData - .map { data -> YYImage? in - switch data.guessedImageFormat { - case .gif, .webp: return YYImage(data: data) - default: return nil - } - } - WebRTCSession.current = self.webRTCSession self.webRTCSession.delegate = self @@ -262,8 +245,6 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { ) .inserted(db) - self.callInteractionId = interaction?.id - self.updateCurrentConnectionStepIfPossible(OfferStep.initializing) try? webRTCSession @@ -330,14 +311,16 @@ public final class SessionCall: CurrentCallProtocol, WebRTCSessionDelegate { // MARK: - Call Message Handling public func updateCallMessage(mode: EndCallMode, using dependencies: Dependencies) { - guard let callInteractionId: Int64 = callInteractionId else { return } - let duration: TimeInterval = self.duration let hasStartedConnecting: Bool = self.hasStartedConnecting dependencies[singleton: .storage].writeAsync( - updates: { db in - guard let interaction: Interaction = try? Interaction.fetchOne(db, id: callInteractionId) else { + updates: { [sessionId, uuid] db in + guard let interaction: Interaction = try? Interaction + .filter(Interaction.Columns.threadId == sessionId) + .filter(Interaction.Columns.messageUuid == uuid) + .fetchOne(db) + else { return } diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index eee13a644..1c39c7d4b 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -202,11 +202,21 @@ public final class SessionCallManager: NSObject, CallManagerProtocol { public func showCallUIForCall(caller: String, uuid: String, mode: CallMode, interactionId: Int64?) { guard let call: SessionCall = dependencies[singleton: .storage].read({ [dependencies] db in - SessionCall(db, for: caller, uuid: uuid, mode: mode, using: dependencies) + SessionCall( + for: caller, + contactName: Profile.displayName( + db, + id: caller, + threadVariant: .contact, + using: dependencies + ), + uuid: uuid, + mode: mode, + using: dependencies + ) }) else { return } - call.callInteractionId = interactionId call.reportIncomingCallIfNeeded { [dependencies] error in if let error = error { Log.error(.calls, "Failed to report incoming call to CallKit due to error: \(error)") diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index f0ff281ce..78fd184b9 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -130,9 +130,8 @@ final class CallVC: UIViewController, VideoPreviewDelegate, AVRoutePickerViewDel return result }() - private lazy var profilePictureView: UIImageView = { + public lazy var profilePictureView: UIImageView = { let result = UIImageView() - result.image = self.call.profilePicture result.set(.width, to: CallVC.avatarRadius * 2) result.set(.height, to: CallVC.avatarRadius * 2) result.layer.cornerRadius = CallVC.avatarRadius @@ -144,13 +143,11 @@ final class CallVC: UIViewController, VideoPreviewDelegate, AVRoutePickerViewDel private lazy var animatedImageView: YYAnimatedImageView = { let result: YYAnimatedImageView = YYAnimatedImageView() - result.image = self.call.animatedProfilePicture result.set(.width, to: CallVC.avatarRadius * 2) result.set(.height, to: CallVC.avatarRadius * 2) result.layer.cornerRadius = CallVC.avatarRadius result.layer.masksToBounds = true result.contentMode = .scaleAspectFill - result.isHidden = (self.call.animatedProfilePicture == nil) return result }() @@ -382,12 +379,12 @@ final class CallVC: UIViewController, VideoPreviewDelegate, AVRoutePickerViewDel super.init(nibName: nil, bundle: nil) - setupStateChangeCallbacks() + setUpStateChangeCallbacks() self.modalPresentationStyle = .overFullScreen self.modalTransitionStyle = .crossDissolve } - func setupStateChangeCallbacks() { + func setUpStateChangeCallbacks() { self.call.remoteVideoStateDidChange = { isEnabled in DispatchQueue.main.async { UIView.animate(withDuration: 0.25) { @@ -475,6 +472,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate, AVRoutePickerViewDel view.themeBackgroundColor = .backgroundPrimary setUpViewHierarchy() + setUpProfilePictureImage() if shouldRestartCamera { cameraManager.prepare() } @@ -492,7 +490,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate, AVRoutePickerViewDel } } } - setupOrientationMonitoring() + setUpOrientationMonitoring() NotificationCenter.default.addObserver(self, selector: #selector(audioRouteDidChange), name: AVAudioSession.routeChangeNotification, object: nil) } @@ -569,6 +567,33 @@ final class CallVC: UIViewController, VideoPreviewDelegate, AVRoutePickerViewDel callDurationLabel.center(in: callInfoLabelContainer) } + func setUpProfilePictureImage() { + let avatarData: Data? = dependencies[singleton: .storage].read { [call, dependencies] db in + dependencies[singleton: .displayPictureManager].displayPicture(db, id: .user(call.sessionId)) + } + + self.profilePictureView.image = avatarData + .map { UIImage(data: $0) } + .defaulting(to: PlaceholderIcon.generate(seed: call.sessionId, text: call.contactName, size: 300)) + + let maybeAnimatedProfilePicture = avatarData + .map { data -> YYImage? in + switch data.guessedImageFormat { + case .gif, .webp: return YYImage(data: data) + default: return nil + } + } + + if let animatedProfilePicture = maybeAnimatedProfilePicture { + self.animatedImageView.image = animatedProfilePicture + self.animatedImageView.isHidden = false + self.profilePictureView.isHidden = true + } else { + self.animatedImageView.isHidden = true + self.profilePictureView.isHidden = false + } + } + private func addFloatingVideoView() { guard let window: UIWindow = dependencies[singleton: .appContext].mainWindow else { return } @@ -598,7 +623,7 @@ final class CallVC: UIViewController, VideoPreviewDelegate, AVRoutePickerViewDel // MARK: - Orientation - private func setupOrientationMonitoring() { + private func setUpOrientationMonitoring() { UIDevice.current.beginGeneratingDeviceOrientationNotifications() NotificationCenter.default.addObserver(self, selector: #selector(didChangeDeviceOrientation), name: UIDevice.orientationDidChangeNotification, object: UIDevice.current) } diff --git a/Session/Calls/Views & Modals/MiniCallView.swift b/Session/Calls/Views & Modals/MiniCallView.swift index e8f858f1f..7671fe8f8 100644 --- a/Session/Calls/Views & Modals/MiniCallView.swift +++ b/Session/Calls/Views & Modals/MiniCallView.swift @@ -135,7 +135,7 @@ final class MiniCallView: UIView, RTCVideoViewDelegate { imageView.clipsToBounds = true imageView.layer.cornerRadius = 32 imageView.contentMode = .scaleAspectFill - imageView.image = callVC.call.profilePicture + imageView.image = callVC.profilePictureView.image result.addSubview(imageView) imageView.set(.width, to: 64) imageView.set(.height, to: 64) @@ -196,7 +196,7 @@ final class MiniCallView: UIView, RTCVideoViewDelegate { self?.callVC.call.removeRemoteVideoRenderer(remoteVideoView) } - self?.callVC.setupStateChangeCallbacks() + self?.callVC.setUpStateChangeCallbacks() MiniCallView.current = nil self?.removeFromSuperview() }) diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index c9a453034..8ec71e19a 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -182,8 +182,8 @@ extension ConversationVC: let call: SessionCall = viewModel.dependencies[singleton: .storage] .read({ [dependencies = viewModel.dependencies] db in SessionCall( - db, for: threadId, + contactName: self.viewModel.threadData.displayName, uuid: UUID().uuidString.lowercased(), mode: .offer, outgoing: true, diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index 25cc97cf5..8c2dabfd2 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -273,6 +273,7 @@ public class PushRegistrationManager: NSObject, PKPushRegistryDelegate { let uuid: String = payload["uuid"] as? String, let caller: String = payload["caller"] as? String, let timestampMs: UInt64 = payload["timestamp"] as? UInt64, + let contactName: String = payload["contactName"] as? String, TimestampUtils.isWithinOneMinute(timestampMs: timestampMs) else { dependencies[singleton: .callManager].reportFakeCall(info: "Missing payload data") // stringlint:ignore @@ -282,35 +283,15 @@ public class PushRegistrationManager: NSObject, PKPushRegistryDelegate { dependencies[singleton: .storage].resumeDatabaseAccess() dependencies.mutate(cache: .libSessionNetwork) { $0.resumeNetworkAccess() } - let maybeCall: SessionCall? = dependencies[singleton: .storage].write { [dependencies] db -> SessionCall? in - do { - let call: SessionCall = SessionCall( - db, - for: caller, - uuid: uuid, - mode: .answer, - using: dependencies - ) - - let interaction: Interaction? = try Interaction - .filter(Interaction.Columns.threadId == caller) - .filter(Interaction.Columns.messageUuid == uuid) - .fetchOne(db) - - call.callInteractionId = interaction?.id - return call - } - catch { - Log.error(.calls, "Failed to create call due to error: \(error)") - } - - return nil - } + let call: SessionCall = SessionCall( + for: caller, + contactName: contactName, + uuid: uuid, + mode: .answer, + using: dependencies + ) - guard let call: SessionCall = maybeCall else { - dependencies[singleton: .callManager].reportFakeCall(info: "Could not retrieve call from database") // stringlint:ignore - return - } + Log.info(.calls, "Calls created with UUID: \(uuid), caller: \(caller), contactName: \(contactName)") dependencies[singleton: .jobRunner].appDidBecomeActive() diff --git a/SessionNotificationServiceExtension/NotificationServiceExtension.swift b/SessionNotificationServiceExtension/NotificationServiceExtension.swift index 0b7a245fe..9dd5beef4 100644 --- a/SessionNotificationServiceExtension/NotificationServiceExtension.swift +++ b/SessionNotificationServiceExtension/NotificationServiceExtension.swift @@ -487,13 +487,20 @@ public final class NotificationServiceExtension: UNNotificationServiceExtension ) { if Preferences.isCallKitSupported { guard let caller: String = callMessage.sender, let timestamp = callMessage.sentTimestampMs else { return } + let contactName: String = Profile.displayName( + db, + id: caller, + threadVariant: .contact, + using: dependencies + ) let reportCall: () -> () = { [weak self, dependencies] in // stringlint:ignore_start let payload: [String: Any] = [ "uuid": callMessage.uuid, "caller": caller, - "timestamp": timestamp + "timestamp": timestamp, + "contactName": contactName ] // stringlint:ignore_stop