Modernize CallRecordStore, CallLinkRecordStore

This commit is contained in:
Sasha Weiss 2026-05-18 15:29:07 -07:00 committed by GitHub
parent a9e3580fb4
commit a49428670a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 458 additions and 673 deletions

View File

@ -9,7 +9,7 @@ import SignalServiceKit
final class AdHocCallStateObserver {
private let adHocCallRecordManager: any AdHocCallRecordManager
private let callLinkStore: any CallLinkRecordStore
private let callLinkStore: CallLinkRecordStore
private let db: any DB
private let messageSenderJobQueue: MessageSenderJobQueue
@ -32,7 +32,7 @@ final class AdHocCallStateObserver {
init(
callLinkCall: CallLinkCall,
adHocCallRecordManager: any AdHocCallRecordManager,
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
messageSenderJobQueue: MessageSenderJobQueue,
db: any DB,
) {
@ -62,33 +62,29 @@ final class AdHocCallStateObserver {
}
self.furthestJoinLevel = joinLevel
db.write { tx in
do {
let rootKey = callLinkCall.callLink.rootKey
var (callLink, inserted) = try callLinkStore.fetchOrInsert(rootKey: rootKey, tx: tx)
if inserted {
callLink.updateState(callLinkCall.callLinkState)
try callLinkStore.update(callLink, tx: tx)
}
if callLink.adminPasskey == nil, !callLink.isDeleted {
let updateSender = CallLinkUpdateMessageSender(messageSenderJobQueue: messageSenderJobQueue)
updateSender.sendCallLinkUpdateMessage(rootKey: rootKey, adminPasskey: nil, tx: tx)
}
try adHocCallRecordManager.createOrUpdateRecord(
callId: callIdFromEra(eraId),
callLink: callLink,
status: { () -> CallRecord.CallStatus.CallLinkCallStatus in
switch joinLevel {
case .attempted: return .generic
case .joined: return .joined
}
}(),
timestamp: Date.ows_millisecondTimestamp(),
shouldSendSyncMessge: true,
tx: tx,
)
} catch {
owsFailDebug("Couldn't update CallRecord: \(error)")
let rootKey = callLinkCall.callLink.rootKey
var (callLink, inserted) = callLinkStore.fetchOrInsert(rootKey: rootKey, tx: tx)
if inserted {
callLink.updateState(callLinkCall.callLinkState)
callLinkStore.update(callLink, tx: tx)
}
if callLink.adminPasskey == nil, !callLink.isDeleted {
let updateSender = CallLinkUpdateMessageSender(messageSenderJobQueue: messageSenderJobQueue)
updateSender.sendCallLinkUpdateMessage(rootKey: rootKey, adminPasskey: nil, tx: tx)
}
adHocCallRecordManager.createOrUpdateRecord(
callId: callIdFromEra(eraId),
callLink: callLink,
status: { () -> CallRecord.CallStatus.CallLinkCallStatus in
switch joinLevel {
case .attempted: return .generic
case .joined: return .joined
}
}(),
timestamp: Date.ows_millisecondTimestamp(),
shouldSendSyncMessge: true,
tx: tx,
)
}
}
@ -105,15 +101,11 @@ final class AdHocCallStateObserver {
}
self.activeEraId = .some(peekInfo.eraId)
db.write { tx in
do {
try adHocCallRecordManager.handlePeekResult(
eraId: peekInfo.eraId,
rootKey: self.callLinkCall.callLink.rootKey,
tx: tx,
)
} catch {
owsFailDebug("\(error)")
}
adHocCallRecordManager.handlePeekResult(
eraId: peekInfo.eraId,
rootKey: self.callLinkCall.callLink.rootKey,
tx: tx,
)
}
}
}

View File

@ -8,12 +8,12 @@ import SignalServiceKit
/// Refreshes call links that need to be updated.
actor CallLinkFetchJobRunner: DatabaseChangeDelegate {
private let callLinkStore: any CallLinkRecordStore
private let callLinkStore: CallLinkRecordStore
private let callLinkStateUpdater: CallLinkStateUpdater
private let db: any DB
init(
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
callLinkStateUpdater: CallLinkStateUpdater,
db: any DB,
) {
@ -52,13 +52,8 @@ actor CallLinkFetchJobRunner: DatabaseChangeDelegate {
var sequentialFailureCount = 0
while true {
let callLinkToFetch: CallLinkRecord?
do {
callLinkToFetch = try db.read(block: callLinkStore.fetchAnyPendingRecord(tx:))
} catch {
owsFailDebug("Can't fetch pending record: \(error)")
mightHavePendingFetch = false
return
let callLinkToFetch = db.read { tx in
callLinkStore.fetchAnyPendingRecord(tx: tx)
}
guard let callLinkToFetch else {
// Nothing to fetch.

View File

@ -18,7 +18,7 @@ actor CallLinkStateUpdater {
private let authCredentialManager: any AuthCredentialManager
private let callLinkFetcher: CallLinkFetcherImpl
private let callLinkManager: any CallLinkManager
private let callLinkStore: any CallLinkRecordStore
private let callLinkStore: CallLinkRecordStore
private let callRecordDeleteManager: any CallRecordDeleteManager
private let callRecordStore: any CallRecordStore
private let db: any DB
@ -30,7 +30,7 @@ actor CallLinkStateUpdater {
authCredentialManager: any AuthCredentialManager,
callLinkFetcher: CallLinkFetcherImpl,
callLinkManager: any CallLinkManager,
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
callRecordDeleteManager: any CallRecordDeleteManager,
callRecordStore: any CallRecordStore,
db: any DB,
@ -90,8 +90,8 @@ actor CallLinkStateUpdater {
}
let registeredState = try tsAccountManager.registeredStateWithMaybeSneakyTransaction()
let oldRecord = try db.read { tx -> CallLinkRecord? in
return try callLinkStore.fetch(roomId: roomId, tx: tx)
let oldRecord = db.read { tx -> CallLinkRecord? in
return callLinkStore.fetch(roomId: roomId, tx: tx)
}
let authCredential = try await authCredentialManager.fetchCallLinkAuthCredential(localIdentifiers: registeredState.localIdentifiers)
let updateResult = await Result { try await updateAndFetch(authCredential) }
@ -113,8 +113,8 @@ actor CallLinkStateUpdater {
throw error
}
try await db.awaitableWrite { tx in
if var newRecord = try self.callLinkStore.fetch(roomId: roomId, tx: tx) {
await db.awaitableWrite { tx in
if var newRecord = self.callLinkStore.fetch(roomId: roomId, tx: tx) {
if !newRecord.isDeleted {
switch updateAction {
case .update(let newState):
@ -123,7 +123,7 @@ actor CallLinkStateUpdater {
break
case .delete:
newRecord.markDeleted(atTimestampMs: Date.ows_millisecondTimestamp())
try self.callRecordDeleteManager.deleteCallRecords(
self.callRecordDeleteManager.deleteCallRecords(
self.callRecordStore.fetchExisting(conversationId: .callLink(callLinkRowId: newRecord.id), limit: nil, tx: tx),
sendSyncMessageOnDelete: true,
tx: tx,
@ -133,7 +133,7 @@ actor CallLinkStateUpdater {
if newRecord.pendingFetchCounter == oldRecord?.pendingFetchCounter {
newRecord.clearNeedsFetch()
}
try self.callLinkStore.update(newRecord, tx: tx)
self.callLinkStore.update(newRecord, tx: tx)
}
}

View File

@ -27,7 +27,7 @@ final class CallService: CallServiceStateObserver, CallServiceStateDelegate {
private var adHocCallRecordManager: any AdHocCallRecordManager { DependenciesBridge.shared.adHocCallRecordManager }
private let appReadiness: AppReadiness
private var audioSession: AudioSession { SUIEnvironment.shared.audioSessionRef }
private var callLinkStore: any CallLinkRecordStore { DependenciesBridge.shared.callLinkStore }
private var callLinkStore: CallLinkRecordStore { DependenciesBridge.shared.callLinkStore }
private var chatConnectionManager: any ChatConnectionManager { DependenciesBridge.shared.chatConnectionManager }
let authCredentialManager: any AuthCredentialManager
private var databaseStorage: SDSDatabaseStorage { SSKEnvironment.shared.databaseStorageRef }
@ -91,7 +91,7 @@ final class CallService: CallServiceStateObserver, CallServiceStateDelegate {
appReadiness: AppReadiness,
authCredentialManager: any AuthCredentialManager,
callLinkPublicParams: GenericServerPublicParams,
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
callRecordDeleteManager: any CallRecordDeleteManager,
callRecordStore: any CallRecordStore,
callServiceSettingsStore: CallServiceSettingsStore,
@ -658,8 +658,8 @@ final class CallService: CallServiceStateObserver, CallServiceStateDelegate {
}
let localIdentifiers = DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction!
let authCredential = try await authCredentialManager.fetchCallLinkAuthCredential(localIdentifiers: localIdentifiers)
let (adminPasskey, isDeleted) = try databaseStorage.read { tx -> (Data?, Bool) in
let callLinkRecord = try callLinkStore.fetch(roomId: callLink.rootKey.deriveRoomId(), tx: tx)
let (adminPasskey, isDeleted) = databaseStorage.read { tx -> (Data?, Bool) in
let callLinkRecord = callLinkStore.fetch(roomId: callLink.rootKey.deriveRoomId(), tx: tx)
return (callLinkRecord?.adminPasskey, callLinkRecord?.isDeleted == true)
}
let serverPublicParams = CallService.serverPublicParams()

View File

@ -282,20 +282,16 @@ private extension GroupCallRecordManager {
}
logger.info("Creating or updating record for group call join.")
do {
try createOrUpdateCallRecord(
callId: callId,
groupThread: groupThread,
groupThreadRowId: groupThreadRowId,
callDirection: callDirection,
groupCallStatus: groupCallStatus,
callEventTimestamp: joinTimestamp,
shouldSendSyncMessage: true,
tx: tx,
)
} catch let error {
owsFailBeta("Failed to insert call record: \(error)")
}
createOrUpdateCallRecord(
callId: callId,
groupThread: groupThread,
groupThreadRowId: groupThreadRowId,
callDirection: callDirection,
groupCallStatus: groupCallStatus,
callEventTimestamp: joinTimestamp,
shouldSendSyncMessage: true,
tx: tx,
)
}
/// Create or update a call record in response to the local declining a ring
@ -314,19 +310,15 @@ private extension GroupCallRecordManager {
}
logger.info("Creating or updating record for group ring decline.")
do {
try createOrUpdateCallRecord(
callId: callIdFromRingId(ringId),
groupThread: groupThread,
groupThreadRowId: groupThreadRowId,
callDirection: .incoming,
groupCallStatus: .ringingDeclined,
callEventTimestamp: Date().ows_millisecondsSince1970,
shouldSendSyncMessage: true,
tx: tx,
)
} catch let error {
owsFailBeta("Failed to insert call record: \(error)")
}
createOrUpdateCallRecord(
callId: callIdFromRingId(ringId),
groupThread: groupThread,
groupThreadRowId: groupThreadRowId,
callDirection: .incoming,
groupCallStatus: .ringingDeclined,
callEventTimestamp: Date().ows_millisecondsSince1970,
shouldSendSyncMessage: true,
tx: tx,
)
}
}

View File

@ -14,7 +14,7 @@ final class CallLinkViewController: OWSTableViewController2 {
override var navbarBackgroundColorOverride: UIColor? { tableBackgroundColor }
private var db: any DB { DependenciesBridge.shared.db }
private var callLinkStore: any CallLinkRecordStore { DependenciesBridge.shared.callLinkStore }
private var callLinkStore: CallLinkRecordStore { DependenciesBridge.shared.callLinkStore }
private let callLink: CallLink
@ -259,14 +259,10 @@ final class CallLinkViewController: OWSTableViewController2 {
private func createCallLinkRecord() -> Int64 {
let rowId = SSKEnvironment.shared.databaseStorageRef.write { tx in
var callLinkRecord: CallLinkRecord
do {
(callLinkRecord, _) = try callLinkStore.fetchOrInsert(rootKey: callLink.rootKey, tx: tx)
callLinkRecord.adminPasskey = adminPasskey!
callLinkRecord.updateState(callLinkState!)
try callLinkStore.update(callLinkRecord, tx: tx)
} catch {
owsFail("Couldn't create CallLinkRecord: \(error)")
}
(callLinkRecord, _) = callLinkStore.fetchOrInsert(rootKey: callLink.rootKey, tx: tx)
callLinkRecord.adminPasskey = adminPasskey!
callLinkRecord.updateState(callLinkState!)
callLinkStore.update(callLinkRecord, tx: tx)
CallLinkUpdateMessageSender(
messageSenderJobQueue: SSKEnvironment.shared.messageSenderJobQueueRef,
@ -333,19 +329,14 @@ final class CallLinkViewController: OWSTableViewController2 {
extension CallLinkViewController: DatabaseChangeDelegate {
private func loadStateAndReloadViewIfNeeded(callLinkRowId: Int64) {
let didChangeVisibleProperty: Bool
do {
let oldState = self.callLinkState
let newState = try self.db.read { tx in try callLinkStore.fetch(rowId: callLinkRowId, tx: tx)?.state }
didChangeVisibleProperty = (
(oldState == nil) != (newState == nil)
|| (oldState?.name != newState?.name)
|| (oldState?.requiresAdminApproval != newState?.requiresAdminApproval),
)
self.callLinkState = newState
} catch {
owsFailDebug("Couldn't fetch CallLink: \(error)")
return
}
let oldState = self.callLinkState
let newState = self.db.read { tx in callLinkStore.fetch(rowId: callLinkRowId, tx: tx)?.state }
didChangeVisibleProperty = (
(oldState == nil) != (newState == nil)
|| (oldState?.name != newState?.name)
|| (oldState?.requiresAdminApproval != newState?.requiresAdminApproval),
)
self.callLinkState = newState
if didChangeVisibleProperty, self.isViewLoaded {
updateContents(shouldReload: true)
}

View File

@ -49,7 +49,7 @@ extension CallsListViewController {
case newer
}
private let callLinkStore: any CallLinkRecordStore
private let callLinkStore: CallLinkRecordStore
private let callRecordLoader: CallRecordLoader
private let callViewModelForCallRecords: CallViewModelForCallRecords
private let callViewModelForUpcomingCallLink: CallViewModelForUpcomingCallLink
@ -59,7 +59,7 @@ extension CallsListViewController {
private let maxCoalescedCallsInOneViewModel: Int
init(
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
callRecordLoader: CallRecordLoader,
callViewModelForCallRecords: @escaping CallViewModelForCallRecords,
callViewModelForUpcomingCallLink: @escaping CallViewModelForUpcomingCallLink,
@ -233,13 +233,7 @@ extension CallsListViewController {
guard shouldFetchUpcomingCallLinks else {
return
}
let upcomingCallLinks: [CallLinkRecord]
do {
upcomingCallLinks = try callLinkStore.fetchUpcoming(earlierThan: nil, limit: 2048, tx: tx)
} catch {
Logger.warn("Couldn't fetch call links to show on the calls tab: \(error)")
return
}
let upcomingCallLinks = callLinkStore.fetchUpcoming(earlierThan: nil, limit: 2048, tx: tx)
self.upcomingCallLinkReferences = upcomingCallLinks.map {
return UpcomingCallLinkReference(callLinkRowId: $0.id)
}

View File

@ -36,7 +36,7 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer
let adHocCallRecordManager: any AdHocCallRecordManager
let badgeManager: BadgeManager
let blockingManager: BlockingManager
let callLinkStore: any CallLinkRecordStore
let callLinkStore: CallLinkRecordStore
let callRecordDeleteAllJobQueue: CallRecordDeleteAllJobQueue
let callRecordDeleteManager: any CallRecordDeleteManager
let callRecordMissedCallManager: CallRecordMissedCallManager
@ -416,12 +416,12 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer
// because they must first be deleted on the server. (We delete them
// individually at the end of this method.)
let callLinksToDelete: [(rootKey: CallLinkRootKey, adminPasskey: Data)]
callLinksToDelete = (try? self.deps.callLinkStore.fetchAll(tx: tx).compactMap {
callLinksToDelete = self.deps.callLinkStore.fetchAll(tx: tx).compactMap {
guard let adminPasskey = $0.adminPasskey else {
return nil
}
return ($0.rootKey, adminPasskey)
}) ?? []
}
/// Delete-all should use the timestamp of the most-recent call, at
/// the time the action was initiated, as the timestamp we delete
/// before (and include in the outgoing sync message).
@ -720,7 +720,7 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer
// Query the database separately when starting & ending calls because the
// row will usually be inserted during the call (ie `rowId` may be nil when
// starting the call but nonnil when ending the very same call).
let rowId = deps.db.read { tx in try? deps.callLinkStore.fetch(roomId: call.callLink.rootKey.deriveRoomId(), tx: tx)?.id }
let rowId = deps.db.read { tx in deps.callLinkStore.fetch(roomId: call.callLink.rootKey.deriveRoomId(), tx: tx)?.id }
guard let rowId else {
// If you open the lobby for an ongoing call that you've never joined,
// we'll call this method after the peek succeeds. However, you haven't
@ -1015,13 +1015,8 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer
} else {
return nil
}
do {
return try deps.callLinkStore.fetch(rowId: callLinkRowId, tx: tx) ?? {
owsFail("Couldn't load CallLinkRecord that must exist!")
}()
} catch {
owsFail("Couldn't load CallLinkRecord that must exist: \(error)")
}
return deps.callLinkStore.fetch(rowId: callLinkRowId, tx: tx)
.owsFailUnwrap("FOREIGN KEYs mean this must exist.")
}()
if let callLinkRecord {
@ -1236,8 +1231,8 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer
} catch CallLinkManagerImpl.PeekError.expired, CallLinkManagerImpl.PeekError.invalid {
eraId = nil
}
try await deps.db.awaitableWrite { tx in
try deps.adHocCallRecordManager.handlePeekResult(eraId: eraId, rootKey: rootKey, tx: tx)
await deps.db.awaitableWrite { tx in
deps.adHocCallRecordManager.handlePeekResult(eraId: eraId, rootKey: rootKey, tx: tx)
}
}
@ -2051,15 +2046,9 @@ extension CallsListViewController: CallCellDelegate, NewCallViewControllerDelega
guard let callLinkRowId else {
return false
}
do {
let callLinkRecord = try self.deps.callLinkStore.fetch(rowId: callLinkRowId, tx: tx) ?? {
throw OWSAssertionError("Couldn't fetch CallLink that must exist.")
}()
return callLinkRecord.adminPasskey != nil
} catch {
owsFailDebug("\(error)")
return false
}
let callLinkRecord = self.deps.callLinkStore.fetch(rowId: callLinkRowId, tx: tx)
.owsFailUnwrap("FOREIGN KEYs mean this must exist.")
return callLinkRecord.adminPasskey != nil
}
}
@ -2069,18 +2058,17 @@ extension CallsListViewController: CallCellDelegate, NewCallViewControllerDelega
// First, delete everything that's local only. This includes thread-based
// calls & any call link calls for which we're not the admin. These
// deletions never fail (except for db corruption-level failures).
callLinksToDelete = try await deps.databaseStorage.awaitableWrite { tx in
callLinksToDelete = await deps.databaseStorage.awaitableWrite { tx in
var callLinksToDelete = [(rootKey: CallLinkRootKey, adminPasskey: Data)]()
var callRecordIdsWithInteractions = [CallRecord.ID]()
for modelReferences in modelReferenceses {
if let callLinkRowId = modelReferences.callLinkRowId {
let callLinkRecord = try self.deps.callLinkStore.fetch(rowId: callLinkRowId, tx: tx) ?? {
throw OWSAssertionError("Couldn't fetch CallLink that must exist.")
}()
let callLinkRecord = self.deps.callLinkStore.fetch(rowId: callLinkRowId, tx: tx)
.owsFailUnwrap("FOREIGN KEYs mean this must exist.")
if let adminPasskey = callLinkRecord.adminPasskey {
callLinksToDelete.append((callLinkRecord.rootKey, adminPasskey))
} else {
try self.deleteCallRecords(forCallLinkRowId: callLinkRecord.id, tx: tx)
self.deleteCallRecords(forCallLinkRowId: callLinkRecord.id, tx: tx)
}
} else {
callRecordIdsWithInteractions.append(contentsOf: modelReferences.callRecordRowIds)
@ -2107,8 +2095,8 @@ extension CallsListViewController: CallCellDelegate, NewCallViewControllerDelega
try await deleteCallLinks(callLinksToDelete: callLinksToDelete)
}
private nonisolated func deleteCallRecords(forCallLinkRowId callLinkRowId: Int64, tx: DBWriteTransaction) throws {
let callRecords = try deps.callRecordStore.fetchExisting(conversationId: .callLink(callLinkRowId: callLinkRowId), limit: nil, tx: tx)
private nonisolated func deleteCallRecords(forCallLinkRowId callLinkRowId: Int64, tx: DBWriteTransaction) {
let callRecords = deps.callRecordStore.fetchExisting(conversationId: .callLink(callLinkRowId: callLinkRowId), limit: nil, tx: tx)
deps.callRecordDeleteManager.deleteCallRecords(callRecords, sendSyncMessageOnDelete: true, tx: tx)
}
@ -2202,13 +2190,8 @@ extension CallsListViewController: CallCellDelegate, NewCallViewControllerDelega
private func showCallInfo(forRootKey rootKey: CallLinkRootKey, callRecords: [CallRecord]) {
let callLinkRecord = deps.db.read { tx -> CallLinkRecord in
do {
return try deps.callLinkStore.fetch(roomId: rootKey.deriveRoomId(), tx: tx) ?? {
owsFail("Can't fetch CallLinkRecord that must exist.")
}()
} catch {
owsFail("Can't fetch CallLinkRecord: \(error)")
}
return deps.callLinkStore.fetch(roomId: rootKey.deriveRoomId(), tx: tx)
.owsFailUnwrap("FOREIGN KEYs mean this must exist.")
}
showCallInfo(viewController: CallLinkViewController.forExisting(callLinkRecord: callLinkRecord, callRecords: callRecords))
}

View File

@ -330,7 +330,7 @@ public class NotificationActionHandler {
if let callLinkRoomId {
return SSKEnvironment.shared.databaseStorageRef.read { tx in
let callLinkStore = DependenciesBridge.shared.callLinkStore
if let callLinkRecord = try? callLinkStore.fetch(roomId: callLinkRoomId, tx: tx) {
if let callLinkRecord = callLinkStore.fetch(roomId: callLinkRoomId, tx: tx) {
return .callLink(CallLink(rootKey: callLinkRecord.rootKey))
}
return nil

View File

@ -66,7 +66,7 @@ final class CallsListViewControllerViewModelLoaderTest: XCTestCase {
maxCoalescedCallsInOneViewModel: Int = 100,
) {
viewModelLoader = ViewModelLoader(
callLinkStore: CallLinkRecordStoreImpl(),
callLinkStore: CallLinkRecordStore(),
callRecordLoader: mockCallRecordLoader,
callViewModelForCallRecords: { self.callViewModelForCallRecords($0, $1) },
callViewModelForUpcomingCallLink: { _, _ in owsFail("Not implemented.") },

View File

@ -141,8 +141,6 @@ public class BackupArchiveAdHocCallArchiver: BackupArchiveProtoStreamWriter {
_ adHocCall: BackupProto_AdHocCall,
context: BackupArchive.ChatItemRestoringContext,
) -> RestoreFrameResult {
var partialErrors = [BackupArchive.RestoreFrameError]()
let callId = AdHocCallId(adHocCall: adHocCall)
let state: CallRecord.CallStatus.CallLinkCallStatus
@ -169,30 +167,16 @@ public class BackupArchiveAdHocCallArchiver: BackupArchiveProtoStreamWriter {
)
if let callLinkRecord = context.recipientContext[callLinkRecordId] {
do {
var callLinkRecord = callLinkRecord
callLinkRecord.didInsertCallRecord()
try callLinkRecordStore.update(callLinkRecord, tx: context.tx)
} catch {
partialErrors.append(
.restoreFrameError(.databaseInsertionFailed(error)),
)
}
var callLinkRecord = callLinkRecord
callLinkRecord.didInsertCallRecord()
callLinkRecordStore.update(callLinkRecord, tx: context.tx)
}
do {
try callRecordStore.insert(
callRecord: adHocCallRecord,
tx: context.tx,
)
} catch {
return .failure(partialErrors + [.restoreFrameError(.databaseInsertionFailed(error))])
}
callRecordStore.insert(
callRecord: adHocCallRecord,
tx: context.tx,
)
if partialErrors.isEmpty {
return .success
} else {
return .partialRestore(partialErrors)
}
return .success
}
}

View File

@ -237,31 +237,27 @@ final class BackupArchiveGroupCallArchiver {
}
let callRecord: CallRecord
do {
callRecord = try groupCallRecordManager.createGroupCallRecord(
callId: groupCall.callID,
groupCallInteraction: groupCallInteraction,
groupCallInteractionRowId: groupCallInteraction.sqliteRowId!,
groupThreadRowId: chatThread.threadRowId,
callDirection: callDirection,
groupCallStatus: callStatus,
groupCallRingerAci: groupCallRingerAci,
callEventTimestamp: groupCall.startedCallTimestamp,
shouldSendSyncMessage: false,
callRecord = groupCallRecordManager.createGroupCallRecord(
callId: groupCall.callID,
groupCallInteraction: groupCallInteraction,
groupCallInteractionRowId: groupCallInteraction.sqliteRowId!,
groupThreadRowId: chatThread.threadRowId,
callDirection: callDirection,
groupCallStatus: callStatus,
groupCallRingerAci: groupCallRingerAci,
callEventTimestamp: groupCall.startedCallTimestamp,
shouldSendSyncMessage: false,
tx: context.tx,
)
if groupCall.hasEndedCallTimestamp {
callRecordStore.updateCallEndedTimestamp(
callRecord: callRecord,
callEndedTimestamp: groupCall.endedCallTimestamp,
tx: context.tx,
)
if groupCall.hasEndedCallTimestamp {
try callRecordStore.updateCallEndedTimestamp(
callRecord: callRecord,
callEndedTimestamp: groupCall.endedCallTimestamp,
tx: context.tx,
)
}
if groupCall.read {
try callRecordStore.markAsRead(callRecord: callRecord, tx: context.tx)
}
} catch {
return .messageFailure([.restoreFrameError(.databaseInsertionFailed(error))])
}
if groupCall.read {
callRecordStore.markAsRead(callRecord: callRecord, tx: context.tx)
}
}

View File

@ -248,25 +248,21 @@ final class BackupArchiveIndividualCallArchiver {
if individualCall.hasCallID {
let callRecord: CallRecord
do {
callRecord = try individualCallRecordManager.createRecordForInteraction(
individualCallInteraction: individualCallInteraction,
individualCallInteractionRowId: individualCallInteraction.sqliteRowId!,
contactThread: contactThread,
contactThreadRowId: chatThread.threadRowId,
callId: individualCall.callID,
callType: callRecordType,
callDirection: callRecordDirection,
individualCallStatus: callRecordStatus,
callEventTimestamp: individualCall.startedCallTimestamp,
shouldSendSyncMessage: false,
tx: context.tx,
)
if individualCall.read {
try callRecordStore.markAsRead(callRecord: callRecord, tx: context.tx)
}
} catch {
return .messageFailure([.restoreFrameError(.databaseInsertionFailed(error))])
callRecord = individualCallRecordManager.createRecordForInteraction(
individualCallInteraction: individualCallInteraction,
individualCallInteractionRowId: individualCallInteraction.sqliteRowId!,
contactThread: contactThread,
contactThreadRowId: chatThread.threadRowId,
callId: individualCall.callID,
callType: callRecordType,
callDirection: callRecordDirection,
individualCallStatus: callRecordStatus,
callEventTimestamp: individualCall.startedCallTimestamp,
shouldSendSyncMessage: false,
tx: context.tx,
)
if individualCall.read {
callRecordStore.markAsRead(callRecord: callRecord, tx: context.tx)
}
}

View File

@ -7,49 +7,20 @@ import Foundation
import GRDB
public import SignalRingRTC
public protocol CallLinkRecordStore {
func fetch(rowId: Int64, tx: DBReadTransaction) throws -> CallLinkRecord?
func fetch(roomId: Data, tx: DBReadTransaction) throws -> CallLinkRecord?
func insertFromBackup(
rootKey: CallLinkRootKey,
adminPasskey: Data?,
name: String?,
restrictions: CallLinkRecord.Restrictions?,
revoked: Bool?,
expiration: Int64?,
isUpcoming: Bool?,
tx: DBWriteTransaction,
) throws -> CallLinkRecord
func fetchOrInsert(rootKey: CallLinkRootKey, tx: DBWriteTransaction) throws -> (record: CallLinkRecord, inserted: Bool)
func update(_ callLinkRecord: CallLinkRecord, tx: DBWriteTransaction) throws
func delete(_ callLinkRecord: CallLinkRecord, tx: DBWriteTransaction) throws
func fetchAll(tx: DBReadTransaction) throws -> [CallLinkRecord]
func enumerateAll(tx: DBReadTransaction, block: (CallLinkRecord) throws -> Void) throws
func fetchUpcoming(earlierThan expirationTimestamp: Date?, limit: Int, tx: DBReadTransaction) throws -> [CallLinkRecord]
func fetchWhere(adminDeletedAtTimestampMsIsLessThan thresholdMs: UInt64, tx: DBReadTransaction) throws -> [CallLinkRecord]
func fetchAnyPendingRecord(tx: DBReadTransaction) throws -> CallLinkRecord?
}
public class CallLinkRecordStoreImpl: CallLinkRecordStore {
public struct CallLinkRecordStore {
public init() {}
public func fetch(rowId: Int64, tx: DBReadTransaction) throws -> CallLinkRecord? {
public func fetch(rowId: Int64, tx: DBReadTransaction) -> CallLinkRecord? {
let db = tx.database
do {
return failIfThrows {
return try CallLinkRecord.fetchOne(db, key: rowId)
} catch {
throw error.grdbErrorForLogging
}
}
public func fetch(roomId: Data, tx: DBReadTransaction) throws -> CallLinkRecord? {
public func fetch(roomId: Data, tx: DBReadTransaction) -> CallLinkRecord? {
let db = tx.database
do {
return failIfThrows {
return try CallLinkRecord.filter(Column(CallLinkRecord.CodingKeys.roomId) == roomId).fetchOne(db)
} catch {
throw error.grdbErrorForLogging
}
}
@ -75,37 +46,46 @@ public class CallLinkRecordStoreImpl: CallLinkRecordStore {
)
}
public func fetchOrInsert(rootKey: CallLinkRootKey, tx: DBWriteTransaction) throws -> (record: CallLinkRecord, inserted: Bool) {
if let existingRecord = try fetch(roomId: rootKey.deriveRoomId(), tx: tx) {
public func fetchOrInsert(rootKey: CallLinkRootKey, tx: DBWriteTransaction) -> (record: CallLinkRecord, inserted: Bool) {
if let existingRecord = fetch(roomId: rootKey.deriveRoomId(), tx: tx) {
return (existingRecord, false)
}
return (try CallLinkRecord.insertRecord(rootKey: rootKey, tx: tx), true)
return failIfThrows {
return (
try CallLinkRecord.insertRecord(rootKey: rootKey, tx: tx),
true,
)
}
}
public func update(_ callLinkRecord: CallLinkRecord, tx: DBWriteTransaction) throws {
public func update(_ callLinkRecord: CallLinkRecord, tx: DBWriteTransaction) {
let db = tx.database
do {
failIfThrows {
try callLinkRecord.update(db)
} catch {
throw error.grdbErrorForLogging
}
}
public func delete(_ callLinkRecord: CallLinkRecord, tx: DBWriteTransaction) throws {
/// Delete the given `CallLinkRecord`, unless someone still has a reference
/// to it.
/// - Returns Whether or not a record was deleted.
@discardableResult
public func deleteIfPossible(_ callLinkRecord: CallLinkRecord, tx: DBWriteTransaction) -> Bool {
let db = tx.database
do {
try callLinkRecord.delete(db)
} catch {
throw error.grdbErrorForLogging
return failIfThrows {
do {
try callLinkRecord.delete(db)
return true
} catch DatabaseError.SQLITE_CONSTRAINT {
// We'll delete it later -- something else is still using it.
return false
}
}
}
public func fetchAll(tx: DBReadTransaction) throws -> [CallLinkRecord] {
public func fetchAll(tx: DBReadTransaction) -> [CallLinkRecord] {
let db = tx.database
do {
return failIfThrows {
return try CallLinkRecord.fetchAll(db)
} catch {
throw error.grdbErrorForLogging
}
}
@ -120,9 +100,9 @@ public class CallLinkRecordStoreImpl: CallLinkRecordStore {
}
}
public func fetchUpcoming(earlierThan expirationTimestamp: Date?, limit: Int, tx: DBReadTransaction) throws -> [CallLinkRecord] {
public func fetchUpcoming(earlierThan expirationTimestamp: Date?, limit: Int, tx: DBReadTransaction) -> [CallLinkRecord] {
let db = tx.database
do {
return failIfThrows {
let isUpcomingColumn = Column(CallLinkRecord.CodingKeys.isUpcoming)
let expirationColumn = Column(CallLinkRecord.CodingKeys.expiration)
@ -131,44 +111,20 @@ public class CallLinkRecordStoreImpl: CallLinkRecordStore {
baseQuery = baseQuery.filter(expirationColumn < expirationTimestamp)
}
return try baseQuery.fetchAll(db)
} catch {
throw error.grdbErrorForLogging
}
}
public func fetchWhere(adminDeletedAtTimestampMsIsLessThan thresholdMs: UInt64, tx: DBReadTransaction) throws -> [CallLinkRecord] {
public func fetchWhere(adminDeletedAtTimestampMsIsLessThan thresholdMs: UInt64, tx: DBReadTransaction) -> [CallLinkRecord] {
let db = tx.database
do {
return failIfThrows {
return try CallLinkRecord.filter(Column(CallLinkRecord.CodingKeys.adminDeletedAtTimestampMs) < Int64(bitPattern: thresholdMs)).fetchAll(db)
} catch {
throw error.grdbErrorForLogging
}
}
public func fetchAnyPendingRecord(tx: DBReadTransaction) throws -> CallLinkRecord? {
public func fetchAnyPendingRecord(tx: DBReadTransaction) -> CallLinkRecord? {
let db = tx.database
do {
return failIfThrows {
return try CallLinkRecord.filter(Column(CallLinkRecord.CodingKeys.pendingFetchCounter) > 0).fetchOne(db)
} catch {
throw error.grdbErrorForLogging
}
}
}
#if TESTABLE_BUILD
final class MockCallLinkRecordStore: CallLinkRecordStore {
func fetch(rowId: Int64, tx: DBReadTransaction) throws -> CallLinkRecord? { fatalError() }
func fetch(roomId: Data, tx: DBReadTransaction) throws -> CallLinkRecord? { fatalError() }
func insertFromBackup(rootKey: CallLinkRootKey, adminPasskey: Data?, name: String?, restrictions: CallLinkRecord.Restrictions?, revoked: Bool?, expiration: Int64?, isUpcoming: Bool?, tx: DBWriteTransaction) throws -> CallLinkRecord { fatalError() }
func fetchOrInsert(rootKey: CallLinkRootKey, tx: DBWriteTransaction) throws -> (record: CallLinkRecord, inserted: Bool) { fatalError() }
func update(_ callLinkRecord: CallLinkRecord, tx: DBWriteTransaction) throws { fatalError() }
func delete(_ callLinkRecord: CallLinkRecord, tx: DBWriteTransaction) throws { fatalError() }
func fetchAll(tx: DBReadTransaction) throws -> [CallLinkRecord] { fatalError() }
func enumerateAll(tx: DBReadTransaction, block: (CallLinkRecord) throws -> Void) throws { fatalError() }
func fetchUpcoming(earlierThan expirationTimestamp: Date?, limit: Int, tx: DBReadTransaction) throws -> [CallLinkRecord] { fatalError() }
func fetchWhere(adminDeletedAtTimestampMsIsLessThan thresholdMs: UInt64, tx: DBReadTransaction) throws -> [CallLinkRecord] { fatalError() }
func fetchAnyPendingRecord(tx: DBReadTransaction) throws -> CallLinkRecord? { fatalError() }
}
#endif

View File

@ -14,23 +14,23 @@ public protocol AdHocCallRecordManager {
timestamp: UInt64,
shouldSendSyncMessge: Bool,
tx: DBWriteTransaction,
) throws
)
func handlePeekResult(
eraId: String?,
rootKey: CallLinkRootKey,
tx: DBWriteTransaction,
) throws
)
}
final class AdHocCallRecordManagerImpl: AdHocCallRecordManager {
private let callRecordStore: any CallRecordStore
private let callLinkStore: any CallLinkRecordStore
private let callLinkStore: CallLinkRecordStore
private let outgoingSyncMessageManager: any OutgoingCallEventSyncMessageManager
init(
callRecordStore: any CallRecordStore,
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
outgoingSyncMessageManager: any OutgoingCallEventSyncMessageManager,
) {
self.callRecordStore = callRecordStore
@ -45,7 +45,7 @@ final class AdHocCallRecordManagerImpl: AdHocCallRecordManager {
timestamp: UInt64,
shouldSendSyncMessge: Bool,
tx: DBWriteTransaction,
) throws {
) {
// This shouldn't happen (we block joining earlier), but race conditions
// theoretically allow it, and this is the final point at which we can
// enforce the invariant that deleted links can't have call records.
@ -71,14 +71,10 @@ final class AdHocCallRecordManagerImpl: AdHocCallRecordManager {
callStatus: status,
callBeganTimestamp: timestamp,
)
do {
try callRecordStore.insert(callRecord: callRecord, tx: tx)
} catch let error {
owsFailBeta("Failed to insert call record: \(error)")
}
callRecordStore.insert(callRecord: callRecord, tx: tx)
var callLink = callLink
callLink.didInsertCallRecord()
try callLinkStore.update(callLink, tx: tx)
callLinkStore.update(callLink, tx: tx)
case .matchFound(let callRecord2):
callRecord = callRecord2
@ -106,24 +102,24 @@ final class AdHocCallRecordManagerImpl: AdHocCallRecordManager {
eraId: String?,
rootKey: CallLinkRootKey,
tx: DBWriteTransaction,
) throws {
guard var callLinkRecord = try self.callLinkStore.fetch(roomId: rootKey.deriveRoomId(), tx: tx) else {
) {
guard var callLinkRecord = self.callLinkStore.fetch(roomId: rootKey.deriveRoomId(), tx: tx) else {
return
}
let callId = eraId.map(callIdFromEra(_:))
if callLinkRecord.activeCallId != callId {
callLinkRecord.activeCallId = callId
try self.callLinkStore.update(callLinkRecord, tx: tx)
self.callLinkStore.update(callLinkRecord, tx: tx)
}
if let callId {
// Things that are already in the calls tab get updated timestamps (and
// move to the top) whenever we notice that they're active. "Already in the
// calls tab" means "isUpcoming or hasCallRecord".
let shouldObserveResult = try { () -> Bool in
let shouldObserveResult = { () -> Bool in
if callLinkRecord.isUpcoming == true {
return true
}
let callRecords = try self.callRecordStore.fetchExisting(
let callRecords = self.callRecordStore.fetchExisting(
conversationId: .callLink(callLinkRowId: callLinkRecord.id),
limit: 1,
tx: tx,
@ -131,7 +127,7 @@ final class AdHocCallRecordManagerImpl: AdHocCallRecordManager {
return !callRecords.isEmpty
}()
if shouldObserveResult {
try self.createOrUpdateRecord(
self.createOrUpdateRecord(
callId: callId,
callLink: callLinkRecord,
status: .generic,
@ -147,11 +143,11 @@ final class AdHocCallRecordManagerImpl: AdHocCallRecordManager {
#if TESTABLE_BUILD
final class MockAdHocCallRecordManager: AdHocCallRecordManager {
func createOrUpdateRecord(callId: UInt64, callLink: CallLinkRecord, status: CallRecord.CallStatus.CallLinkCallStatus, timestamp: UInt64, shouldSendSyncMessge: Bool, tx: DBWriteTransaction) throws {
func createOrUpdateRecord(callId: UInt64, callLink: CallLinkRecord, status: CallRecord.CallStatus.CallLinkCallStatus, timestamp: UInt64, shouldSendSyncMessge: Bool, tx: DBWriteTransaction) {
fatalError()
}
func handlePeekResult(eraId: String?, rootKey: CallLinkRootKey, tx: DBWriteTransaction) throws {
func handlePeekResult(eraId: String?, rootKey: CallLinkRootKey, tx: DBWriteTransaction) {
fatalError()
}
}

View File

@ -196,14 +196,10 @@ class CallRecordMissedCallManagerImpl: CallRecordMissedCallManager {
while let unreadCallRecord = try unreadCallCursor.next() {
markedAsReadCount += 1
do {
try callRecordStore.markAsRead(
callRecord: unreadCallRecord,
tx: tx,
)
} catch let error {
owsFailBeta("Failed to update call record: \(error)")
}
callRecordStore.markAsRead(
callRecord: unreadCallRecord,
tx: tx,
)
}
owsAssertDebug(

View File

@ -36,7 +36,7 @@ public protocol CallRecordStore {
/// Insert the given call record.
/// - Important
/// Posts an `.inserted` ``CallRecordStoreNotification``.
func insert(callRecord: CallRecord, tx: DBWriteTransaction) throws
func insert(callRecord: CallRecord, tx: DBWriteTransaction)
/// Deletes the given call records and creates ``DeletedCallRecord``s
/// in their place.
@ -81,7 +81,7 @@ public protocol CallRecordStore {
func markAsRead(
callRecord: CallRecord,
tx: DBWriteTransaction,
) throws
)
/// Update the direction of the given call record.
func updateDirection(
@ -112,7 +112,7 @@ public protocol CallRecordStore {
callRecord: CallRecord,
callEndedTimestamp: UInt64,
tx: DBWriteTransaction,
) throws
)
/// Update all relevant records in response to a thread merge.
/// - Parameter fromThreadRowId
@ -143,7 +143,7 @@ public protocol CallRecordStore {
conversationId: CallRecord.ConversationID,
limit: Int?,
tx: DBReadTransaction,
) throws -> [CallRecord]
) -> [CallRecord]
/// Fetch the record referencing the given ``TSInteraction`` SQLite row ID,
/// if one exists.
@ -168,15 +168,13 @@ class CallRecordStoreImpl: CallRecordStore {
// MARK: - Protocol methods
func insert(callRecord: CallRecord, tx: DBWriteTransaction) throws {
let insertResult = Result<Void, Error>(catching: { try _insert(callRecord: callRecord, tx: tx) })
func insert(callRecord: CallRecord, tx: DBWriteTransaction) {
_insert(callRecord: callRecord, tx: tx)
postNotification(
updateType: .inserted,
tx: tx,
)
try insertResult.get()
}
private var deletedCallRecordIds = [CallRecord.ID]()
@ -211,9 +209,11 @@ class CallRecordStoreImpl: CallRecordStore {
)
}
func markAsRead(callRecord: CallRecord, tx: DBWriteTransaction) throws {
func markAsRead(callRecord: CallRecord, tx: DBWriteTransaction) {
callRecord.unreadStatus = .read
try callRecord.update(tx.database)
failIfThrows {
try callRecord.update(tx.database)
}
}
func updateDirection(
@ -222,10 +222,8 @@ class CallRecordStoreImpl: CallRecordStore {
tx: DBWriteTransaction,
) {
callRecord.callDirection = newCallDirection
do {
failIfThrows {
try callRecord.update(tx.database)
} catch let error {
owsFailBeta("Failed to update call record: \(error)")
}
}
@ -235,10 +233,8 @@ class CallRecordStoreImpl: CallRecordStore {
tx: DBWriteTransaction,
) {
callRecord.setGroupCallRingerAci(newGroupCallRingerAci)
do {
failIfThrows {
try callRecord.update(tx.database)
} catch let error {
owsFailBeta("Failed to update call record: \(error)")
}
}
@ -248,10 +244,8 @@ class CallRecordStoreImpl: CallRecordStore {
tx: DBWriteTransaction,
) {
callRecord.callBeganTimestamp = callBeganTimestamp
do {
failIfThrows {
try callRecord.update(tx.database)
} catch let error {
owsFailBeta("Failed to update call record: \(error)")
}
}
@ -259,9 +253,11 @@ class CallRecordStoreImpl: CallRecordStore {
callRecord: CallRecord,
callEndedTimestamp: UInt64,
tx: DBWriteTransaction,
) throws {
) {
callRecord.callEndedTimestamp = callEndedTimestamp
try callRecord.update(tx.database)
failIfThrows {
try callRecord.update(tx.database)
}
}
func updateWithMergedThread(
@ -299,12 +295,12 @@ class CallRecordStoreImpl: CallRecordStore {
conversationId: CallRecord.ConversationID,
limit: Int?,
tx: DBReadTransaction,
) throws -> [CallRecord] {
) -> [CallRecord] {
switch conversationId {
case .thread(let threadRowId):
return try fetchAll(columnArgs: [(.threadRowId, threadRowId)], limit: limit, tx: tx)
return fetchAll(columnArgs: [(.threadRowId, threadRowId)], limit: limit, tx: tx)
case .callLink(let callLinkRowId):
return try fetchAll(columnArgs: [(.callLinkRowId, callLinkRowId)], limit: limit, tx: tx)
return fetchAll(columnArgs: [(.callLinkRowId, callLinkRowId)], limit: limit, tx: tx)
}
}
@ -330,21 +326,21 @@ class CallRecordStoreImpl: CallRecordStore {
// MARK: - Mutations (impl)
func _insert(callRecord: CallRecord, tx: DBWriteTransaction) throws {
try callRecord.insert(tx.database)
private func _insert(callRecord: CallRecord, tx: DBWriteTransaction) {
failIfThrows {
try callRecord.insert(tx.database)
}
}
func _delete(callRecords: [CallRecord], tx: DBWriteTransaction) {
private func _delete(callRecords: [CallRecord], tx: DBWriteTransaction) {
for callRecord in callRecords {
do {
failIfThrows {
try callRecord.delete(tx.database)
} catch let error {
owsFailBeta("Failed to delete call record: \(error)")
}
}
}
func _updateCallAndUnreadStatus(
private func _updateCallAndUnreadStatus(
callRecord: CallRecord,
newCallStatus: CallRecord.CallStatus,
tx: DBWriteTransaction,
@ -354,16 +350,14 @@ class CallRecordStoreImpl: CallRecordStore {
callRecord.callStatus = newCallStatus
callRecord.unreadStatus = CallRecord.CallUnreadStatus(callStatus: newCallStatus)
do {
failIfThrows {
try callRecord.update(tx.database)
} catch let error {
owsFailBeta("Failed to update call record: \(error)")
}
}
// MARK: - Queries (impl)
func _fetch(
private func _fetch(
callId: UInt64,
conversationId: CallRecord.ConversationID,
tx: DBReadTransaction,
@ -394,31 +388,23 @@ class CallRecordStoreImpl: CallRecordStore {
columnArgs: [(CallRecord.CodingKeys, DatabaseValueConvertible)],
tx: DBReadTransaction,
) -> CallRecord? {
do {
let results = try fetchAll(columnArgs: columnArgs, limit: nil, tx: tx)
owsAssertDebug(results.count <= 1, "columnArgs must identify a unique row")
return results.first
} catch {
let columns = columnArgs.map { column, _ in column }
owsFailBeta("Error fetching CallRecord by \(columns): \(error)")
return nil
}
let results = fetchAll(columnArgs: columnArgs, limit: nil, tx: tx)
owsAssertDebug(results.count <= 1, "columnArgs must identify a unique row")
return results.first
}
fileprivate func fetchAll(
columnArgs: [(CallRecord.CodingKeys, DatabaseValueConvertible)],
limit: Int?,
tx: DBReadTransaction,
) throws -> [CallRecord] {
) -> [CallRecord] {
let (sqlString, sqlArgs) = compileQuery(columnArgs: columnArgs, limit: limit)
do {
return failIfThrows {
return try CallRecord.fetchAll(tx.database, SQLRequest(
sql: sqlString,
arguments: StatementArguments(sqlArgs),
))
} catch {
throw error.grdbErrorForLogging
}
}

View File

@ -12,7 +12,7 @@ public protocol CallRecordSyncMessageConversationIdAdapter {
conversationId: Data,
callId: UInt64,
tx: DBReadTransaction,
) throws -> CallRecord?
) -> CallRecord?
/// Generates a conversation ID for use in call-related sync messages, from
/// a ``CallRecord``.
@ -24,13 +24,13 @@ public protocol CallRecordSyncMessageConversationIdAdapter {
class CallRecordSyncMessageConversationIdAdapterImpl: CallRecordSyncMessageConversationIdAdapter {
private let callLinkStore: any CallLinkRecordStore
private let callLinkStore: CallLinkRecordStore
private let callRecordStore: CallRecordStore
private let recipientDatabaseTable: RecipientDatabaseTable
private let threadStore: ThreadStore
init(
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
callRecordStore: CallRecordStore,
recipientDatabaseTable: RecipientDatabaseTable,
threadStore: ThreadStore,
@ -47,13 +47,13 @@ class CallRecordSyncMessageConversationIdAdapterImpl: CallRecordSyncMessageConve
conversationId: Data,
callId: UInt64,
tx: DBReadTransaction,
) throws -> CallRecord? {
return try parse(conversationId: conversationId, tx: tx).flatMap {
) -> CallRecord? {
return parse(conversationId: conversationId, tx: tx).flatMap {
return callRecordStore.fetch(callId: callId, conversationId: $0, tx: tx).unwrapped
}
}
private func parse(conversationId: Data, tx: DBReadTransaction) throws -> CallRecord.ConversationID? {
private func parse(conversationId: Data, tx: DBReadTransaction) -> CallRecord.ConversationID? {
if let serviceId = try? ServiceId.parseFrom(serviceIdBinary: conversationId) {
guard
let recipient = recipientDatabaseTable.fetchRecipient(serviceId: serviceId, transaction: tx),
@ -69,7 +69,7 @@ class CallRecordSyncMessageConversationIdAdapterImpl: CallRecordSyncMessageConve
{
return .thread(threadRowId: groupThread.sqliteRowId!)
}
if let callLinkRecord = try callLinkStore.fetch(roomId: conversationId, tx: tx) {
if let callLinkRecord = callLinkStore.fetch(roomId: conversationId, tx: tx) {
return .callLink(callLinkRowId: callLinkRecord.id)
}
return nil
@ -93,7 +93,7 @@ class CallRecordSyncMessageConversationIdAdapterImpl: CallRecordSyncMessageConve
throw OWSAssertionError("Unexpected thread type for call record!")
}
case .callLink(let callLinkRowId):
guard let callLinkRecord = try callLinkStore.fetch(rowId: callLinkRowId, tx: tx) else {
guard let callLinkRecord = callLinkStore.fetch(rowId: callLinkRowId, tx: tx) else {
throw OWSAssertionError("Missing CallLinkRecord - how did we get here?")
}
return callLinkRecord.roomId

View File

@ -16,7 +16,7 @@ public protocol GroupCallRecordManager {
callEventTimestamp: UInt64,
shouldSendSyncMessage: Bool,
tx: DBWriteTransaction,
) throws
)
/// Create a group call record with the given parameters.
///
@ -35,7 +35,7 @@ public protocol GroupCallRecordManager {
callEventTimestamp: UInt64,
shouldSendSyncMessage: Bool,
tx: DBWriteTransaction,
) throws -> CallRecord
) -> CallRecord
/// Update an group existing call record with the given parameters.
///
@ -81,8 +81,8 @@ public extension GroupCallRecordManager {
groupCallInteractionRowId: Int64,
groupThreadRowId: Int64,
tx: DBWriteTransaction,
) throws -> CallRecord {
try createGroupCallRecord(
) -> CallRecord {
createGroupCallRecord(
callId: callId,
groupCallInteraction: groupCallInteraction,
groupCallInteractionRowId: groupCallInteractionRowId,
@ -125,7 +125,7 @@ public class GroupCallRecordManagerImpl: GroupCallRecordManager {
callEventTimestamp: UInt64,
shouldSendSyncMessage: Bool,
tx: DBWriteTransaction,
) throws {
) {
// We never have a group call ringer in this flow.
let groupCallRingerAci: Aci? = nil
@ -153,7 +153,7 @@ public class GroupCallRecordManagerImpl: GroupCallRecordManager {
tx: tx,
)
_ = try createGroupCallRecord(
_ = createGroupCallRecord(
callId: callId,
groupCallInteraction: newGroupCallInteraction,
groupCallInteractionRowId: interactionRowId,
@ -179,7 +179,7 @@ public class GroupCallRecordManagerImpl: GroupCallRecordManager {
callEventTimestamp: UInt64,
shouldSendSyncMessage: Bool,
tx: DBWriteTransaction,
) throws -> CallRecord {
) -> CallRecord {
let newCallRecord = CallRecord(
callId: callId,
interactionRowId: groupCallInteractionRowId,
@ -191,7 +191,7 @@ public class GroupCallRecordManagerImpl: GroupCallRecordManager {
callBeganTimestamp: callEventTimestamp,
)
let insertResult = Result(catching: { try callRecordStore.insert(callRecord: newCallRecord, tx: tx) })
callRecordStore.insert(callRecord: newCallRecord, tx: tx)
if shouldSendSyncMessage {
outgoingSyncMessageManager.sendSyncMessage(
@ -202,8 +202,6 @@ public class GroupCallRecordManagerImpl: GroupCallRecordManager {
)
}
try insertResult.get()
return newCallRecord
}

View File

@ -203,22 +203,18 @@ public final class GroupCallRecordRingUpdateHandler: GroupCallRecordRingUpdateDe
)
ringUpdateLogger.info("Creating group call record for ring update.")
do {
_ = try groupCallRecordManager.createGroupCallRecord(
callId: callId,
groupCallInteraction: newGroupCallInteraction,
groupCallInteractionRowId: interactionRowId,
groupThreadRowId: groupThreadRowId,
callDirection: .incoming,
groupCallStatus: groupCallStatus,
groupCallRingerAci: ringerAci,
callEventTimestamp: callEventTimestamp,
shouldSendSyncMessage: false,
tx: tx,
)
} catch let error {
owsFailBeta("Failed to insert call record: \(error)")
}
_ = groupCallRecordManager.createGroupCallRecord(
callId: callId,
groupCallInteraction: newGroupCallInteraction,
groupCallInteractionRowId: interactionRowId,
groupThreadRowId: groupThreadRowId,
callDirection: .incoming,
groupCallStatus: groupCallStatus,
groupCallRingerAci: ringerAci,
callEventTimestamp: callEventTimestamp,
shouldSendSyncMessage: false,
tx: tx,
)
}
}
}

View File

@ -18,7 +18,7 @@ protocol IncomingCallEventSyncMessageManager {
final class IncomingCallEventSyncMessageManagerImpl: IncomingCallEventSyncMessageManager {
private let adHocCallRecordManager: any AdHocCallRecordManager
private let callLinkStore: any CallLinkRecordStore
private let callLinkStore: CallLinkRecordStore
private let callRecordStore: CallRecordStore
private let callRecordDeleteManager: CallRecordDeleteManager
private let groupCallRecordManager: GroupCallRecordManager
@ -31,7 +31,7 @@ final class IncomingCallEventSyncMessageManagerImpl: IncomingCallEventSyncMessag
init(
adHocCallRecordManager: any AdHocCallRecordManager,
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
callRecordStore: CallRecordStore,
callRecordDeleteManager: CallRecordDeleteManager,
groupCallRecordManager: GroupCallRecordManager,
@ -315,7 +315,7 @@ final class IncomingCallEventSyncMessageManagerImpl: IncomingCallEventSyncMessag
}
case .adHoc(let roomId):
guard let callLinkRecord = callLinkRecord(forRoomId: roomId, tx: tx) else {
guard let callLinkRecord = callLinkStore.fetch(roomId: roomId, tx: tx) else {
logger.error("Missing call link record for incoming call event sync message!")
return
}
@ -340,28 +340,14 @@ final class IncomingCallEventSyncMessageManagerImpl: IncomingCallEventSyncMessag
newStatus = .generic
}
do {
try adHocCallRecordManager.createOrUpdateRecord(
callId: callId,
callLink: callLinkRecord,
status: newStatus,
timestamp: callTimestamp,
shouldSendSyncMessge: false,
tx: tx,
)
} catch {
owsFailDebug("\(error)")
return
}
}
}
private func callLinkRecord(forRoomId roomId: Data, tx: DBReadTransaction) -> CallLinkRecord? {
do {
return try callLinkStore.fetch(roomId: roomId, tx: tx)
} catch {
CallRecordLogger.shared.error("Couldn't fetch CallLinkRecord: \(error)")
return nil
adHocCallRecordManager.createOrUpdateRecord(
callId: callId,
callLink: callLinkRecord,
status: newStatus,
timestamp: callTimestamp,
shouldSendSyncMessge: false,
tx: tx,
)
}
}
}
@ -469,24 +455,20 @@ private extension IncomingCallEventSyncMessageManagerImpl {
owsFail("Missing SQLite row ID for just-inserted interaction!")
}
do {
_ = try individualCallRecordManager.createRecordForInteraction(
individualCallInteraction: newIndividualCallInteraction,
individualCallInteractionRowId: interactionRowId,
contactThread: contactThread,
contactThreadRowId: contactThreadRowId,
callId: callId,
callType: callType,
callDirection: callDirection,
individualCallStatus: individualCallStatus,
// The interaction's timestamp is the call event's timestamp.
callEventTimestamp: newIndividualCallInteraction.timestamp,
shouldSendSyncMessage: false,
tx: tx,
)
} catch let error {
owsFailBeta("Failed to insert call record: \(error)")
}
_ = individualCallRecordManager.createRecordForInteraction(
individualCallInteraction: newIndividualCallInteraction,
individualCallInteractionRowId: interactionRowId,
contactThread: contactThread,
contactThreadRowId: contactThreadRowId,
callId: callId,
callType: callType,
callDirection: callDirection,
individualCallStatus: individualCallStatus,
// The interaction's timestamp is the call event's timestamp.
callEventTimestamp: newIndividualCallInteraction.timestamp,
shouldSendSyncMessage: false,
tx: tx,
)
markThingsAsReadForIncomingSyncMessage(
callInteraction: newIndividualCallInteraction,
@ -544,22 +526,18 @@ private extension IncomingCallEventSyncMessageManagerImpl {
tx: tx,
)
do {
_ = try groupCallRecordManager.createGroupCallRecord(
callId: callId,
groupCallInteraction: newGroupCallInteraction,
groupCallInteractionRowId: interactionRowId,
groupThreadRowId: groupThreadRowId,
callDirection: callDirection,
groupCallStatus: groupCallStatus,
groupCallRingerAci: nil,
callEventTimestamp: callEventTimestamp,
shouldSendSyncMessage: false,
tx: tx,
)
} catch let error {
owsFailBeta("Failed to insert call record: \(error)")
}
_ = groupCallRecordManager.createGroupCallRecord(
callId: callId,
groupCallInteraction: newGroupCallInteraction,
groupCallInteractionRowId: interactionRowId,
groupThreadRowId: groupThreadRowId,
callDirection: callDirection,
groupCallStatus: groupCallStatus,
groupCallRingerAci: nil,
callEventTimestamp: callEventTimestamp,
shouldSendSyncMessage: false,
tx: tx,
)
markThingsAsReadForIncomingSyncMessage(
callInteraction: newGroupCallInteraction,

View File

@ -41,15 +41,11 @@ class IncomingCallLogEventSyncMessageManagerImpl: IncomingCallLogEventSyncMessag
/// timestamp embedded in the sync message.
let referencedCallRecord: CallRecord? = {
if let callIdentifiers = incomingSyncMessage.anchorCallIdentifiers {
do {
return try callRecordConversationIdAdapter.hydrate(
conversationId: callIdentifiers.conversationId,
callId: callIdentifiers.callId,
tx: tx,
)
} catch {
owsFailDebug("\(error)")
}
return callRecordConversationIdAdapter.hydrate(
conversationId: callIdentifiers.conversationId,
callId: callIdentifiers.callId,
tx: tx,
)
}
return nil
}()

View File

@ -24,7 +24,7 @@ public protocol IndividualCallRecordManager {
contactThreadRowId: Int64,
callId: UInt64,
tx: DBWriteTransaction,
) throws
)
/// Create a call record for the given interaction's current state.
func createRecordForInteraction(
@ -39,7 +39,7 @@ public protocol IndividualCallRecordManager {
callEventTimestamp: UInt64,
shouldSendSyncMessage: Bool,
tx: DBWriteTransaction,
) throws -> CallRecord
) -> CallRecord
/// Update the given call record.
func updateRecord(
@ -121,7 +121,7 @@ public class IndividualCallRecordManagerImpl: IndividualCallRecordManager {
contactThreadRowId: Int64,
callId: UInt64,
tx: DBWriteTransaction,
) throws {
) {
guard
let callDirection = CallRecord.CallDirection(
individualCallInteractionType: individualCallInteraction.callType,
@ -147,7 +147,7 @@ public class IndividualCallRecordManagerImpl: IndividualCallRecordManager {
tx: tx,
)
case .matchNotFound:
_ = try createRecordForInteraction(
_ = createRecordForInteraction(
individualCallInteraction: individualCallInteraction,
individualCallInteractionRowId: individualCallInteractionRowId,
contactThread: contactThread,
@ -175,7 +175,7 @@ public class IndividualCallRecordManagerImpl: IndividualCallRecordManager {
callEventTimestamp: UInt64,
shouldSendSyncMessage: Bool,
tx: DBWriteTransaction,
) throws -> CallRecord {
) -> CallRecord {
let callRecord = CallRecord(
callId: callId,
interactionRowId: individualCallInteractionRowId,
@ -186,7 +186,7 @@ public class IndividualCallRecordManagerImpl: IndividualCallRecordManager {
callBeganTimestamp: callEventTimestamp,
)
let insertResult = Result(catching: { try callRecordStore.insert(callRecord: callRecord, tx: tx) })
callRecordStore.insert(callRecord: callRecord, tx: tx)
if shouldSendSyncMessage {
outgoingSyncMessageManager.sendSyncMessage(
@ -197,8 +197,6 @@ public class IndividualCallRecordManagerImpl: IndividualCallRecordManager {
)
}
try insertResult.get()
return callRecord
}

View File

@ -19,7 +19,7 @@ import GRDB
/// "multiple records with the same expiration time" is equivalent to "multiple
/// records with the same `deletedAtTimestamp`.
public final class DeletedCallRecordExpirationJob: ExpirationJob<DeletedCallRecord> {
private let callLinkStore: any CallLinkRecordStore
private let callLinkStore: CallLinkRecordStore
private let deletedCallRecordStore: DeletedCallRecordStore
init(
@ -57,15 +57,11 @@ public final class DeletedCallRecordExpirationJob: ExpirationJob<DeletedCallReco
tx: tx,
)
do {
try deleteCallLinkIfNeeded(conversationId: deletedCallRecord.conversationId, tx: tx)
} catch {
owsFailDebug("\(error)")
}
deleteCallLinkIfNeeded(conversationId: deletedCallRecord.conversationId, tx: tx)
}
/// Removes the ``CallLinkRecord`` if there are no more references.
private func deleteCallLinkIfNeeded(conversationId: CallRecord.ConversationID, tx: DBWriteTransaction) throws {
private func deleteCallLinkIfNeeded(conversationId: CallRecord.ConversationID, tx: DBWriteTransaction) {
let callLinkRowId: Int64
switch conversationId {
case .thread:
@ -73,17 +69,12 @@ public final class DeletedCallRecordExpirationJob: ExpirationJob<DeletedCallReco
case .callLink(let callLinkRowId2):
callLinkRowId = callLinkRowId2
}
let callLinkRecord = try callLinkStore.fetch(rowId: callLinkRowId, tx: tx) ?? {
throw OWSAssertionError("Must be able to find call link.")
}()
let callLinkRecord = callLinkStore.fetch(rowId: callLinkRowId, tx: tx)
.owsFailUnwrap("FOREIGN KEYs mean this must exist.")
if callLinkRecord.isDeleted {
// We can't delete this until Storage Service is done with it.
return
}
do {
try callLinkStore.delete(callLinkRecord, tx: tx)
} catch DatabaseError.SQLITE_CONSTRAINT {
// We'll delete it later -- something else is still using it.
}
callLinkStore.deleteIfPossible(callLinkRecord, tx: tx)
}
}

View File

@ -310,17 +310,13 @@ public class GroupCallManager {
)
logger.info("Creating record for group call discovered via peek.")
do {
_ = try groupCallRecordManager.createGroupCallRecordForPeek(
callId: callId.rawValue,
groupCallInteraction: newGroupCallInteraction,
groupCallInteractionRowId: interactionRowId,
groupThreadRowId: groupThreadRowId,
tx: tx,
)
} catch let error {
owsFailBeta("Failed to insert call record: \(error)")
}
_ = groupCallRecordManager.createGroupCallRecordForPeek(
callId: callId.rawValue,
groupCallInteraction: newGroupCallInteraction,
groupCallInteractionRowId: interactionRowId,
groupThreadRowId: groupThreadRowId,
tx: tx,
)
return newGroupCallInteraction
}

View File

@ -235,17 +235,13 @@ public class CallEventInserter {
return
}
do {
try individualCallRecordManager.createOrUpdateRecordForInteraction(
individualCallInteraction: callInteraction,
individualCallInteractionRowId: callInteractionRowId,
contactThread: thread,
contactThreadRowId: threadRowId,
callId: callId,
tx: tx,
)
} catch let error {
owsFailBeta("Failed to insert call record: \(error)")
}
individualCallRecordManager.createOrUpdateRecordForInteraction(
individualCallInteraction: callInteraction,
individualCallInteractionRowId: callInteractionRowId,
contactThread: thread,
contactThreadRowId: threadRowId,
callId: callId,
tx: tx,
)
}
}

View File

@ -794,7 +794,7 @@ extension AppSetup.GlobalsContinuation {
dateProvider: { Date() },
)
let callLinkStore = CallLinkRecordStoreImpl()
let callLinkStore = CallLinkRecordStore()
let deletedCallRecordStore = DeletedCallRecordStoreImpl()
let deletedCallRecordExpirationJob = DeletedCallRecordExpirationJob(
callLinkStore: callLinkStore,

View File

@ -87,7 +87,7 @@ public class DependenciesBridge {
public let backupTestFlightEntitlementManager: BackupTestFlightEntitlementManager
public let badgeCountFetcher: BadgeCountFetcher
let blockedRecipientStore: BlockedRecipientStore
public let callLinkStore: any CallLinkRecordStore
public let callLinkStore: CallLinkRecordStore
public let callRecordDeleteManager: any CallRecordDeleteManager
public let callRecordMissedCallManager: CallRecordMissedCallManager
public let callRecordQuerier: CallRecordQuerier
@ -231,7 +231,7 @@ public class DependenciesBridge {
backupTestFlightEntitlementManager: BackupTestFlightEntitlementManager,
badgeCountFetcher: BadgeCountFetcher,
blockedRecipientStore: BlockedRecipientStore,
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
callRecordDeleteManager: CallRecordDeleteManager,
callRecordMissedCallManager: CallRecordMissedCallManager,
callRecordQuerier: CallRecordQuerier,

View File

@ -29,7 +29,7 @@ public class CallRecordDeleteAllJobQueue {
private let callRecordConversationIdAdapter: CallRecordSyncMessageConversationIdAdapter
public init(
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
callRecordConversationIdAdapter: CallRecordSyncMessageConversationIdAdapter,
callRecordDeleteManager: any CallRecordDeleteManager,
callRecordQuerier: CallRecordQuerier,
@ -119,7 +119,7 @@ private class CallRecordDeleteAllJobRunner: JobRunner {
private var logger: CallRecordLogger { .shared }
private let callLinkStore: any CallLinkRecordStore
private let callLinkStore: CallLinkRecordStore
private let callRecordConversationIdAdapter: CallRecordSyncMessageConversationIdAdapter
private let callRecordDeleteManager: any CallRecordDeleteManager
private let callRecordQuerier: CallRecordQuerier
@ -128,7 +128,7 @@ private class CallRecordDeleteAllJobRunner: JobRunner {
private let messageSenderJobQueue: MessageSenderJobQueue
init(
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
callRecordConversationIdAdapter: CallRecordSyncMessageConversationIdAdapter,
callRecordDeleteManager: any CallRecordDeleteManager,
callRecordQuerier: CallRecordQuerier,
@ -154,7 +154,7 @@ private class CallRecordDeleteAllJobRunner: JobRunner {
jobRecord: jobRecord,
retryLimit: Constants.maxRetries,
db: db,
block: { try await _runJobAttempt(jobRecord) },
block: { await _runJobAttempt(jobRecord) },
)
}
@ -172,7 +172,7 @@ private class CallRecordDeleteAllJobRunner: JobRunner {
private func _runJobAttempt(
_ jobRecord: CallRecordDeleteAllJobRecord,
) async throws {
) async {
var deleteBeforeTimestamp: UInt64 = {
/// We'll prefer the timestamp on the call record if we have it.
/// They should be identical in the 99.999% case, but there's a
@ -184,16 +184,11 @@ private class CallRecordDeleteAllJobRunner: JobRunner {
let callId = jobRecord.deleteAllBeforeCallId,
let conversationId = jobRecord.deleteAllBeforeConversationId,
let referencedCallRecord: CallRecord = db.read(block: { tx -> CallRecord? in
do {
return try callRecordConversationIdAdapter.hydrate(
conversationId: conversationId,
callId: callId,
tx: tx,
)
} catch {
owsFailDebug("\(error)")
return nil
}
return callRecordConversationIdAdapter.hydrate(
conversationId: conversationId,
callId: callId,
tx: tx,
)
})
else {
return jobRecord.deleteAllBeforeTimestamp
@ -272,9 +267,8 @@ private class CallRecordDeleteAllJobRunner: JobRunner {
switch callRecord.conversationId {
case .callLink(let callLinkRowId):
let callLinkRecord = try callLinkStore.fetch(rowId: callLinkRowId, tx: tx) ?? {
throw OWSAssertionError("Can't fetch CallLink that must exist.")
}()
let callLinkRecord = callLinkStore.fetch(rowId: callLinkRowId, tx: tx)
.owsFailUnwrap("FOREIGN KEYs mean this must exist.")
if callLinkRecord.adminPasskey != nil {
// These are deleted via Storage Service syncs.
} else {
@ -345,7 +339,7 @@ private class CallRecordDeleteAllJobRunner: JobRunner {
private class CallRecordDeleteAllJobRunnerFactory: JobRunnerFactory {
typealias JobRunnerType = CallRecordDeleteAllJobRunner
private let callLinkStore: any CallLinkRecordStore
private let callLinkStore: CallLinkRecordStore
private let callRecordConversationIdAdapter: CallRecordSyncMessageConversationIdAdapter
private let callRecordDeleteManager: any CallRecordDeleteManager
private let callRecordQuerier: CallRecordQuerier
@ -354,7 +348,7 @@ private class CallRecordDeleteAllJobRunnerFactory: JobRunnerFactory {
private let messageSenderJobQueue: MessageSenderJobQueue
init(
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
callRecordConversationIdAdapter: CallRecordSyncMessageConversationIdAdapter,
callRecordDeleteManager: any CallRecordDeleteManager,
callRecordQuerier: CallRecordQuerier,

View File

@ -941,10 +941,10 @@ public final class MessageReceiver {
let callLinkStore = DependenciesBridge.shared.callLinkStore
do {
let rootKey = try CallLinkRootKey(callLinkUpdate.rootKey ?? Data())
var (callLink, _) = try callLinkStore.fetchOrInsert(rootKey: rootKey, tx: tx)
var (callLink, _) = callLinkStore.fetchOrInsert(rootKey: rootKey, tx: tx)
callLink.adminPasskey = callLink.adminPasskey ?? callLinkUpdate.adminPasskey
callLink.setNeedsFetch()
try callLinkStore.update(callLink, tx: tx)
callLinkStore.update(callLink, tx: tx)
} catch {
Logger.warn("Ignoring CallLinkUpdate: \(error)")
}

View File

@ -33,7 +33,7 @@ class MockCallRecordStore: CallRecordStore {
return .matchNotFound
}
func fetchExisting(conversationId: CallRecord.ConversationID, limit: Int?, tx: DBReadTransaction) throws -> [CallRecord] {
func fetchExisting(conversationId: CallRecord.ConversationID, limit: Int?, tx: DBReadTransaction) -> [CallRecord] {
fatalError()
}

View File

@ -682,22 +682,21 @@ public class StorageServiceManagerImpl: NSObject, StorageServiceManager {
private func cleanUpDeletedCallLinks() async {
let callLinkStore = DependenciesBridge.shared.callLinkStore
let db = DependenciesBridge.shared.db
let deletionThresholdMs = Date.ows_millisecondTimestamp() - RemoteConfig.current.messageQueueTimeMs
do {
let callLinkRecords = try SSKEnvironment.shared.databaseStorageRef.read { tx in
try callLinkStore.fetchWhere(adminDeletedAtTimestampMsIsLessThan: deletionThresholdMs, tx: tx)
}
if !callLinkRecords.isEmpty {
Logger.info("Cleaning up \(callLinkRecords.count) call links that were deleted a while ago.")
try await SSKEnvironment.shared.databaseStorageRef.awaitableWrite { tx in
for callLinkRecord in callLinkRecords {
try callLinkStore.delete(callLinkRecord, tx: tx)
}
let callLinkRecords = db.read { tx in
callLinkStore.fetchWhere(adminDeletedAtTimestampMsIsLessThan: deletionThresholdMs, tx: tx)
}
if !callLinkRecords.isEmpty {
Logger.info("Cleaning up \(callLinkRecords.count) call links that were deleted a while ago.")
await db.awaitableWrite { tx in
for callLinkRecord in callLinkRecords {
callLinkStore.deleteIfPossible(callLinkRecord, tx: tx)
}
recordPendingUpdates(callLinkRootKeys: callLinkRecords.map(\.rootKey))
}
} catch {
owsFailDebug("Couldn't clean up deleted call links: \(error)")
recordPendingUpdates(callLinkRootKeys: callLinkRecords.map(\.rootKey))
}
}
}
@ -1370,12 +1369,8 @@ class StorageServiceOperation {
let callLinkUpdater = buildCallLinkUpdater()
let callLinkStore = callLinkUpdater.recordUpdater.callLinkStore
do {
try callLinkStore.fetchAll(tx: transaction).forEach {
createRecord(localId: $0.rootKey.bytes, stateUpdater: callLinkUpdater)
}
} catch {
owsFailDebug("Couldn't add CallLinks to manifest: \(error)")
callLinkStore.fetchAll(tx: transaction).forEach {
createRecord(localId: $0.rootKey.bytes, stateUpdater: callLinkUpdater)
}
}
@ -1699,7 +1694,7 @@ class StorageServiceOperation {
let callLinkStore = DependenciesBridge.shared.callLinkStore
guard
let callLinkRootKey = try? CallLinkRootKey(callLinkRootKeyData),
let callLinkRecord = try? callLinkStore.fetch(roomId: callLinkRootKey.deriveRoomId(), tx: transaction),
let callLinkRecord = callLinkStore.fetch(roomId: callLinkRootKey.deriveRoomId(), tx: transaction),
callLinkRecord.adminPasskey != nil
else {
continue

View File

@ -2163,12 +2163,12 @@ class StorageServiceCallLinkRecordUpdater: StorageServiceRecordUpdater {
typealias IdType = Data
typealias RecordType = StorageServiceProtoCallLinkRecord
let callLinkStore: any CallLinkRecordStore
let callLinkStore: CallLinkRecordStore
private let callRecordDeleteManager: any CallRecordDeleteManager
private let callRecordStore: any CallRecordStore
init(
callLinkStore: any CallLinkRecordStore,
callLinkStore: CallLinkRecordStore,
callRecordDeleteManager: any CallRecordDeleteManager,
callRecordStore: any CallRecordStore,
) {
@ -2193,13 +2193,7 @@ class StorageServiceCallLinkRecordUpdater: StorageServiceRecordUpdater {
return nil
}
let roomId = rootKey.deriveRoomId()
let callLink: CallLinkRecord?
do {
callLink = try self.callLinkStore.fetch(roomId: roomId, tx: tx)
} catch {
owsFailDebug("Skipping CallLink that can't be fetched: \(rootKey.description)")
return nil
}
let callLink = self.callLinkStore.fetch(roomId: roomId, tx: tx)
guard let callLink, callLink.adminPasskey != nil || callLink.adminDeletedAtTimestampMs != nil else {
// We're not an admin, so this link doesn't go in Storage Service.
@ -2228,24 +2222,20 @@ class StorageServiceCallLinkRecordUpdater: StorageServiceRecordUpdater {
owsFailDebug("invalid rootKey")
return .invalid
}
do {
var (callLink, _) = try self.callLinkStore.fetchOrInsert(rootKey: rootKey, tx: tx)
// The earliest deletion timestamp takes precendence when merging.
if record.deletedAtTimestampMs > 0 || callLink.adminDeletedAtTimestampMs != nil {
self.callRecordDeleteManager.deleteCallRecords(
try self.callRecordStore.fetchExisting(conversationId: .callLink(callLinkRowId: callLink.id), limit: nil, tx: tx),
sendSyncMessageOnDelete: false,
tx: tx,
)
callLink.markDeleted(atTimestampMs: [record.deletedAtTimestampMs, callLink.adminDeletedAtTimestampMs].compacted().min()!)
} else if let adminPasskey = record.adminPasskey?.nilIfEmpty {
callLink.adminPasskey = adminPasskey
callLink.setNeedsFetch()
}
try self.callLinkStore.update(callLink, tx: tx)
} catch {
owsFailDebug("Couldn't merge CallLink \(rootKey.description): \(error)")
var (callLink, _) = self.callLinkStore.fetchOrInsert(rootKey: rootKey, tx: tx)
// The earliest deletion timestamp takes precendence when merging.
if record.deletedAtTimestampMs > 0 || callLink.adminDeletedAtTimestampMs != nil {
self.callRecordDeleteManager.deleteCallRecords(
self.callRecordStore.fetchExisting(conversationId: .callLink(callLinkRowId: callLink.id), limit: nil, tx: tx),
sendSyncMessageOnDelete: false,
tx: tx,
)
callLink.markDeleted(atTimestampMs: [record.deletedAtTimestampMs, callLink.adminDeletedAtTimestampMs].compacted().min()!)
} else if let adminPasskey = record.adminPasskey?.nilIfEmpty {
callLink.adminPasskey = adminPasskey
callLink.setNeedsFetch()
}
self.callLinkStore.update(callLink, tx: tx)
return .merged(needsUpdate: false, rootKey.bytes)
}
}

View File

@ -31,7 +31,7 @@ final class CallRecordDeleteManagerTest: XCTestCase {
// We never call .start() on this job, so this is a no-op instance.
mockDeletedCallRecordExpirationJob = DeletedCallRecordExpirationJob(
callLinkStore: MockCallLinkRecordStore(),
callLinkStore: CallLinkRecordStore(),
dateProvider: { Date() },
db: mockDB,
deletedCallRecordStore: mockDeletedCallRecordStore,

View File

@ -247,11 +247,11 @@ private extension CallRecord {
}
private class MockConversationIdAdapter: CallRecordSyncMessageConversationIdAdapter {
func hydrate(conversationId: Data, callId: UInt64, tx: DBReadTransaction) throws -> CallRecord? {
func hydrate(conversationId: Data, callId: UInt64, tx: DBReadTransaction) -> CallRecord? {
owsFail("Not implemented!")
}
func getConversationId(callRecord: CallRecord, tx: DBReadTransaction) throws -> Data {
func getConversationId(callRecord: CallRecord, tx: DBReadTransaction) -> Data {
return Aci.randomForTesting().serviceIdBinary
}
}

View File

@ -90,11 +90,11 @@ final class CallRecordStoreTest: XCTestCase {
// MARK: - Insert and fetch
func testInsertAndFetch() throws {
func testInsertAndFetch() {
let callRecord = makeCallRecord()
try inMemoryDB.write { tx in
try callRecordStore._insert(callRecord: callRecord, tx: tx)
inMemoryDB.write { tx in
callRecordStore.insert(callRecord: callRecord, tx: tx)
}
let fetchedByCallId = inMemoryDB.read { tx in
@ -139,17 +139,17 @@ final class CallRecordStoreTest: XCTestCase {
// MARK: - Delete
func testDelete() throws {
func testDelete() {
let callRecord1 = makeCallRecord()
let callRecord2 = makeCallRecord()
try inMemoryDB.write { tx in
try callRecordStore._insert(callRecord: callRecord1, tx: tx)
try callRecordStore._insert(callRecord: callRecord2, tx: tx)
inMemoryDB.write { tx in
callRecordStore.insert(callRecord: callRecord1, tx: tx)
callRecordStore.insert(callRecord: callRecord2, tx: tx)
}
inMemoryDB.write { tx in
callRecordStore._delete(callRecords: [callRecord1, callRecord2], tx: tx)
callRecordStore.delete(callRecords: [callRecord1, callRecord2], tx: tx)
}
inMemoryDB.read { tx in
@ -176,15 +176,15 @@ final class CallRecordStoreTest: XCTestCase {
// MARK: - updateCallStatus
func testUpdateRecordStatus() throws {
func testUpdateRecordStatus() {
let callRecord = makeCallRecord(callStatus: .group(.generic))
try inMemoryDB.write { tx in
try callRecordStore._insert(callRecord: callRecord, tx: tx)
inMemoryDB.write { tx in
callRecordStore.insert(callRecord: callRecord, tx: tx)
}
inMemoryDB.write { tx in
callRecordStore._updateCallAndUnreadStatus(
callRecordStore.updateCallAndUnreadStatus(
callRecord: callRecord,
newCallStatus: .group(.joined),
tx: tx,
@ -203,7 +203,7 @@ final class CallRecordStoreTest: XCTestCase {
XCTAssertTrue(callRecord.matches(fetched))
}
func testUpdateRecordStatusAndUnread() throws {
func testUpdateRecordStatusAndUnread() {
/// Some of these are not updates that can happen in production; for
/// example, a missed individual call cannot move into a pending state.
///
@ -240,10 +240,10 @@ final class CallRecordStoreTest: XCTestCase {
let callRecord = makeCallRecord(callStatus: beforeCallStatus)
XCTAssertEqual(callRecord.unreadStatus, beforeUnreadStatus)
try inMemoryDB.write { tx in
try callRecordStore._insert(callRecord: callRecord, tx: tx)
inMemoryDB.write { tx in
callRecordStore.insert(callRecord: callRecord, tx: tx)
callRecordStore._updateCallAndUnreadStatus(
callRecordStore.updateCallAndUnreadStatus(
callRecord: callRecord,
newCallStatus: afterCallStatus,
tx: tx,
@ -263,13 +263,13 @@ final class CallRecordStoreTest: XCTestCase {
// MARK: - markAsRead
func testMarkAsRead() throws {
func testMarkAsRead() {
let unreadCallRecord = makeCallRecord(callStatus: .group(.ringingMissed))
XCTAssertEqual(unreadCallRecord.unreadStatus, .unread)
try inMemoryDB.write { tx in
try callRecordStore._insert(callRecord: unreadCallRecord, tx: tx)
try callRecordStore.markAsRead(callRecord: unreadCallRecord, tx: tx)
inMemoryDB.write { tx in
callRecordStore.insert(callRecord: unreadCallRecord, tx: tx)
callRecordStore.markAsRead(callRecord: unreadCallRecord, tx: tx)
XCTAssertEqual(unreadCallRecord.unreadStatus, .read)
}
@ -286,12 +286,12 @@ final class CallRecordStoreTest: XCTestCase {
// MARK: - updateWithMergedThread
func testUpdateWithMergedThread() throws {
func testUpdateWithMergedThread() {
let callRecord = makeCallRecord()
let (newThreadRowId, _) = insertThreadAndInteraction()
try inMemoryDB.write { tx in
try callRecordStore._insert(callRecord: callRecord, tx: tx)
inMemoryDB.write { tx in
callRecordStore.insert(callRecord: callRecord, tx: tx)
}
inMemoryDB.write { tx in
@ -321,8 +321,8 @@ final class CallRecordStoreTest: XCTestCase {
func testDeletingInteractionDeletesCallRecord() throws {
let callRecord = makeCallRecord()
try inMemoryDB.write { tx in
try callRecordStore._insert(callRecord: callRecord, tx: tx)
inMemoryDB.write { tx in
callRecordStore.insert(callRecord: callRecord, tx: tx)
}
try inMemoryDB.write { tx in
@ -350,8 +350,8 @@ final class CallRecordStoreTest: XCTestCase {
func testDeletingThreadFailsIfCallRecordExtant() throws {
let callRecord = makeCallRecord()
try inMemoryDB.write { tx in
try callRecordStore._insert(callRecord: callRecord, tx: tx)
inMemoryDB.write { tx in
callRecordStore.insert(callRecord: callRecord, tx: tx)
}
try inMemoryDB.write { tx in

View File

@ -56,11 +56,11 @@ final class GroupCallRecordManagerTest: XCTestCase {
// MARK: - Create or update record
func testCreateOrUpdate_CallsCreateAndInsertsInteraction() throws {
func testCreateOrUpdate_CallsCreateAndInsertsInteraction() {
let (thread, _) = createInteraction()
try mockDB.write { tx in
try snoopingGroupCallRecordManager.createOrUpdateCallRecord(
mockDB.write { tx in
snoopingGroupCallRecordManager.createOrUpdateCallRecord(
callId: .maxRandom,
groupThread: thread,
groupThreadRowId: thread.sqliteRowId!,
@ -76,7 +76,7 @@ final class GroupCallRecordManagerTest: XCTestCase {
XCTAssertTrue(snoopingGroupCallRecordManager.didAskToCreate)
}
func testCreateOrUpdate_CallsUpdate() throws {
func testCreateOrUpdate_CallsUpdate() {
let (thread, interaction) = createInteraction()
let callRecord = CallRecord(
@ -90,8 +90,8 @@ final class GroupCallRecordManagerTest: XCTestCase {
)
mockCallRecordStore.callRecords.append(callRecord)
try mockDB.write { tx in
try snoopingGroupCallRecordManager.createOrUpdateCallRecord(
mockDB.write { tx in
snoopingGroupCallRecordManager.createOrUpdateCallRecord(
callId: callRecord.callId,
groupThread: thread,
groupThreadRowId: thread.sqliteRowId!,
@ -106,13 +106,13 @@ final class GroupCallRecordManagerTest: XCTestCase {
XCTAssertTrue(snoopingGroupCallRecordManager.didAskToUpdate)
}
func testCreateOrUpdate_DoesNothingIfRecentlyDeleted() throws {
func testCreateOrUpdate_DoesNothingIfRecentlyDeleted() {
let (thread, _) = createInteraction()
mockCallRecordStore.fetchMock = { .matchDeleted }
try mockDB.write { tx in
try snoopingGroupCallRecordManager.createOrUpdateCallRecord(
mockDB.write { tx in
snoopingGroupCallRecordManager.createOrUpdateCallRecord(
callId: .maxRandom,
groupThread: thread,
groupThreadRowId: thread.sqliteRowId!,
@ -129,12 +129,12 @@ final class GroupCallRecordManagerTest: XCTestCase {
// MARK: - Create group call record
func testCreateGroupCallRecord() throws {
func testCreateGroupCallRecord() {
let (thread1, interaction1) = createInteraction()
let (thread2, interaction2) = createInteraction()
_ = try mockDB.write { tx in
try groupCallRecordManager.createGroupCallRecord(
_ = mockDB.write { tx in
groupCallRecordManager.createGroupCallRecord(
callId: .maxRandom,
groupCallInteraction: interaction1,
groupCallInteractionRowId: interaction1.sqliteRowId!,
@ -151,8 +151,8 @@ final class GroupCallRecordManagerTest: XCTestCase {
XCTAssertEqual(mockCallRecordStore.callRecords.count, 1)
XCTAssertEqual(mockOutgoingSyncMessageManager.syncMessageSendCount, 0)
_ = try mockDB.write { tx in
try groupCallRecordManager.createGroupCallRecord(
_ = mockDB.write { tx in
groupCallRecordManager.createGroupCallRecord(
callId: .maxRandom,
groupCallInteraction: interaction2,
groupCallInteractionRowId: interaction2.sqliteRowId!,
@ -171,11 +171,11 @@ final class GroupCallRecordManagerTest: XCTestCase {
XCTAssertEqual(mockOutgoingSyncMessageManager.syncMessageSendCount, 1)
}
func testCreateGroupCallRecordForPeek() throws {
func testCreateGroupCallRecordForPeek() {
let (thread, interaction) = createInteraction()
_ = try mockDB.write { tx in
try groupCallRecordManager.createGroupCallRecordForPeek(
mockDB.write { tx in
_ = groupCallRecordManager.createGroupCallRecordForPeek(
callId: .maxRandom,
groupCallInteraction: interaction,
groupCallInteractionRowId: interaction.sqliteRowId!,
@ -197,7 +197,7 @@ final class GroupCallRecordManagerTest: XCTestCase {
// MARK: Update record
func testUpdate_Updates() throws {
func testUpdate_Updates() {
let (thread, interaction) = createInteraction()
let callRecord = CallRecord(
@ -211,8 +211,8 @@ final class GroupCallRecordManagerTest: XCTestCase {
)
mockCallRecordStore.callRecords.append(callRecord)
try mockDB.write { tx in
try groupCallRecordManager.createOrUpdateCallRecord(
mockDB.write { tx in
groupCallRecordManager.createOrUpdateCallRecord(
callId: callRecord.callId,
groupThread: thread,
groupThreadRowId: thread.sqliteRowId!,
@ -229,7 +229,7 @@ final class GroupCallRecordManagerTest: XCTestCase {
XCTAssertEqual(mockOutgoingSyncMessageManager.syncMessageSendCount, 1)
}
func testUpdate_SkipsDirectionAndSyncMessage() throws {
func testUpdate_SkipsDirectionAndSyncMessage() {
let (thread, interaction) = createInteraction()
let callRecord = CallRecord(
@ -243,8 +243,8 @@ final class GroupCallRecordManagerTest: XCTestCase {
)
mockCallRecordStore.callRecords.append(callRecord)
try mockDB.write { tx in
try groupCallRecordManager.createOrUpdateCallRecord(
mockDB.write { tx in
groupCallRecordManager.createOrUpdateCallRecord(
callId: callRecord.callId,
groupThread: thread,
groupThreadRowId: thread.sqliteRowId!,
@ -265,7 +265,7 @@ final class GroupCallRecordManagerTest: XCTestCase {
/// status that's illegal per the record's current state.
///
/// In this test, we try to illegally go from "joined" to "generic".
func testUpdate_SkipsSyncMessageIfStatusTransitionDisallowed() throws {
func testUpdate_SkipsSyncMessageIfStatusTransitionDisallowed() {
let (thread, interaction) = createInteraction()
let callRecord = CallRecord(
@ -279,8 +279,8 @@ final class GroupCallRecordManagerTest: XCTestCase {
)
mockCallRecordStore.callRecords.append(callRecord)
try mockDB.write { tx in
try groupCallRecordManager.createOrUpdateCallRecord(
mockDB.write { tx in
groupCallRecordManager.createOrUpdateCallRecord(
callId: callRecord.callId,
groupThread: thread,
groupThreadRowId: thread.sqliteRowId!,
@ -297,7 +297,7 @@ final class GroupCallRecordManagerTest: XCTestCase {
XCTAssertEqual(mockOutgoingSyncMessageManager.syncMessageSendCount, 0)
}
func testUpdate_UpdatesCallBeganTimestamp() throws {
func testUpdate_UpdatesCallBeganTimestamp() {
let (thread, interaction) = createInteraction()
let callRecord = CallRecord(
@ -311,8 +311,8 @@ final class GroupCallRecordManagerTest: XCTestCase {
)
mockCallRecordStore.callRecords.append(callRecord)
try mockDB.write { tx in
try groupCallRecordManager.createOrUpdateCallRecord(
mockDB.write { tx in
groupCallRecordManager.createOrUpdateCallRecord(
callId: callRecord.callId,
groupThread: thread,
groupThreadRowId: thread.sqliteRowId!,

View File

@ -42,7 +42,7 @@ final class IncomingCallEventSyncMessageManagerTest: XCTestCase {
incomingSyncMessageManager = IncomingCallEventSyncMessageManagerImpl(
adHocCallRecordManager: MockAdHocCallRecordManager(),
callLinkStore: MockCallLinkRecordStore(),
callLinkStore: CallLinkRecordStore(),
callRecordStore: mockCallRecordStore,
callRecordDeleteManager: mockCallRecordDeleteManager,
groupCallRecordManager: mockGroupCallRecordManager,

View File

@ -96,11 +96,11 @@ final class IndividualCallRecordManagerTest: XCTestCase {
// MARK: - createOrUpdateRecordForInteraction
func testCreateOrUpdate_noRecordExists() throws {
func testCreateOrUpdate_noRecordExists() {
let (thread, interaction) = createInteraction()
try mockDB.write { tx in
try individualCallRecordManager.createOrUpdateRecordForInteraction(
mockDB.write { tx in
individualCallRecordManager.createOrUpdateRecordForInteraction(
individualCallInteraction: interaction,
individualCallInteractionRowId: interaction.sqliteRowId!,
contactThread: thread,
@ -114,7 +114,7 @@ final class IndividualCallRecordManagerTest: XCTestCase {
XCTAssertEqual(mockOutgoingSyncMessageManager.syncMessageSendCount, 1)
}
func testCreateOrUpdate_recordExists() throws {
func testCreateOrUpdate_recordExists() {
let (thread, interaction) = createInteraction(callType: .incomingDeclined)
let callId = UInt64.maxRandom
@ -129,8 +129,8 @@ final class IndividualCallRecordManagerTest: XCTestCase {
)
mockCallRecordStore.callRecords.append(callRecord)
try mockDB.write { tx in
try individualCallRecordManager.createOrUpdateRecordForInteraction(
mockDB.write { tx in
individualCallRecordManager.createOrUpdateRecordForInteraction(
individualCallInteraction: interaction,
individualCallInteractionRowId: interaction.sqliteRowId!,
contactThread: thread,
@ -144,14 +144,14 @@ final class IndividualCallRecordManagerTest: XCTestCase {
XCTAssertEqual(mockOutgoingSyncMessageManager.syncMessageSendCount, 1)
}
func testCreateOrUpdate_nothingIfRecordRecentlyDeleted() throws {
func testCreateOrUpdate_nothingIfRecordRecentlyDeleted() {
let (thread, interaction) = createInteraction(callType: .incomingDeclined)
let callId = UInt64.maxRandom
mockCallRecordStore.fetchMock = { .matchDeleted }
try mockDB.write { tx in
try individualCallRecordManager.createOrUpdateRecordForInteraction(
mockDB.write { tx in
individualCallRecordManager.createOrUpdateRecordForInteraction(
individualCallInteraction: interaction,
individualCallInteractionRowId: interaction.sqliteRowId!,
contactThread: thread,
@ -167,11 +167,11 @@ final class IndividualCallRecordManagerTest: XCTestCase {
// MARK: - createRecordForInteraction
func testCreate_noSyncMessage() throws {
func testCreate_noSyncMessage() {
let (thread, interaction) = createInteraction()
try mockDB.write { tx in
_ = try individualCallRecordManager.createRecordForInteraction(
mockDB.write { tx in
_ = individualCallRecordManager.createRecordForInteraction(
individualCallInteraction: interaction,
individualCallInteractionRowId: interaction.sqliteRowId!,
contactThread: thread,
@ -190,11 +190,11 @@ final class IndividualCallRecordManagerTest: XCTestCase {
XCTAssertEqual(mockOutgoingSyncMessageManager.syncMessageSendCount, 0)
}
func testCreate_syncMessage() throws {
func testCreate_syncMessage() {
let (thread, interaction) = createInteraction()
try mockDB.write { tx in
_ = try individualCallRecordManager.createRecordForInteraction(
mockDB.write { tx in
_ = individualCallRecordManager.createRecordForInteraction(
individualCallInteraction: interaction,
individualCallInteractionRowId: interaction.sqliteRowId!,
contactThread: thread,
@ -311,9 +311,9 @@ private class SnoopingIndividualCallRecordManagerImpl: IndividualCallRecordManag
var didAskToCreateRecord: CallRecord.CallStatus.IndividualCallStatus?
var didAskToUpdateRecord: CallRecord.CallStatus.IndividualCallStatus?
override func createRecordForInteraction(individualCallInteraction: TSCall, individualCallInteractionRowId: Int64, contactThread: TSContactThread, contactThreadRowId: Int64, callId: UInt64, callType: CallRecord.CallType, callDirection: CallRecord.CallDirection, individualCallStatus: CallRecord.CallStatus.IndividualCallStatus, callEventTimestamp: UInt64, shouldSendSyncMessage: Bool, tx: DBWriteTransaction) throws -> CallRecord {
override func createRecordForInteraction(individualCallInteraction: TSCall, individualCallInteractionRowId: Int64, contactThread: TSContactThread, contactThreadRowId: Int64, callId: UInt64, callType: CallRecord.CallType, callDirection: CallRecord.CallDirection, individualCallStatus: CallRecord.CallStatus.IndividualCallStatus, callEventTimestamp: UInt64, shouldSendSyncMessage: Bool, tx: DBWriteTransaction) -> CallRecord {
didAskToCreateRecord = individualCallStatus
return try super.createRecordForInteraction(individualCallInteraction: individualCallInteraction, individualCallInteractionRowId: individualCallInteractionRowId, contactThread: contactThread, contactThreadRowId: contactThreadRowId, callId: callId, callType: callType, callDirection: callDirection, individualCallStatus: individualCallStatus, callEventTimestamp: callEventTimestamp, shouldSendSyncMessage: shouldSendSyncMessage, tx: tx)
return super.createRecordForInteraction(individualCallInteraction: individualCallInteraction, individualCallInteractionRowId: individualCallInteractionRowId, contactThread: contactThread, contactThreadRowId: contactThreadRowId, callId: callId, callType: callType, callDirection: callDirection, individualCallStatus: individualCallStatus, callEventTimestamp: callEventTimestamp, shouldSendSyncMessage: shouldSendSyncMessage, tx: tx)
}
override func updateRecord(contactThread: TSContactThread, existingCallRecord: CallRecord, newIndividualCallStatus: CallRecord.CallStatus.IndividualCallStatus, shouldSendSyncMessage: Bool, tx: DBWriteTransaction) {