171 lines
5.5 KiB
Swift
171 lines
5.5 KiB
Swift
//
|
|
// Copyright 2024 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import LibSignalClient
|
|
|
|
public final class BackupArchiveThreadStore {
|
|
|
|
private let threadStore: ThreadStore
|
|
|
|
init(threadStore: ThreadStore) {
|
|
self.threadStore = threadStore
|
|
}
|
|
|
|
// MARK: - Archiving
|
|
|
|
func enumerateNonStoryThreads(
|
|
tx: DBReadTransaction,
|
|
block: (TSThread) throws(CancellationError) -> Bool,
|
|
) throws(CancellationError) {
|
|
try threadStore.enumerateNonStoryThreads(tx: tx, block: block)
|
|
}
|
|
|
|
func enumerateGroupThreads(
|
|
tx: DBReadTransaction,
|
|
block: (TSGroupThread) throws(CancellationError) -> Bool,
|
|
) throws(CancellationError) {
|
|
try threadStore.enumerateGroupThreads(tx: tx, block: block)
|
|
}
|
|
|
|
func enumerateStoryThreads(
|
|
tx: DBReadTransaction,
|
|
block: (TSPrivateStoryThread) throws(CancellationError) -> Bool,
|
|
) throws(CancellationError) {
|
|
try threadStore.enumerateStoryThreads(tx: tx, block: block)
|
|
}
|
|
|
|
func fetchOrDefaultAssociatedData(
|
|
for thread: TSThread,
|
|
tx: DBReadTransaction,
|
|
) -> ThreadAssociatedData {
|
|
return threadStore.fetchOrDefaultAssociatedData(for: thread, tx: tx)
|
|
}
|
|
|
|
func fetchContactThread(
|
|
recipient: SignalRecipient,
|
|
tx: DBReadTransaction,
|
|
) -> TSContactThread? {
|
|
return threadStore.fetchContactThread(recipient: recipient, tx: tx)
|
|
}
|
|
|
|
// MARK: - Restoring
|
|
|
|
func createNoteToSelfThread(
|
|
context: BackupArchive.ChatRestoringContext,
|
|
) throws -> TSContactThread {
|
|
let thread = TSContactThread(contactAddress: context.recipientContext.localIdentifiers.aciAddress)
|
|
try thread.insert(context.tx.database)
|
|
return thread
|
|
}
|
|
|
|
func createContactThread(
|
|
with address: BackupArchive.ContactAddress,
|
|
context: BackupArchive.ChatRestoringContext,
|
|
) throws -> TSContactThread {
|
|
let thread = TSContactThread(contactAddress: address.asInteropAddress())
|
|
try thread.insert(context.tx.database)
|
|
return thread
|
|
}
|
|
|
|
func createGroupThread(
|
|
groupModel: TSGroupModelV2,
|
|
isStorySendEnabled: Bool?,
|
|
context: BackupArchive.RestoringContext,
|
|
) throws -> TSGroupThread {
|
|
let groupThread = TSGroupThread(groupModel: groupModel)
|
|
switch isStorySendEnabled {
|
|
case true:
|
|
groupThread.storyViewMode = .explicit
|
|
case false:
|
|
groupThread.storyViewMode = .disabled
|
|
default:
|
|
groupThread.storyViewMode = .default
|
|
}
|
|
try groupThread.insert(context.tx.database)
|
|
return groupThread
|
|
}
|
|
|
|
func insertFullGroupMemberRecords(
|
|
acis: Set<Aci>,
|
|
groupThread: TSGroupThread,
|
|
context: BackupArchive.RestoringContext,
|
|
) throws {
|
|
for aci in acis {
|
|
let groupMember = TSGroupMember(
|
|
address: NormalizedDatabaseRecordAddress(aci: aci),
|
|
groupThreadId: groupThread.uniqueId,
|
|
// This gets updated in post frame restore actions.
|
|
lastInteractionTimestamp: 0,
|
|
)
|
|
try groupMember.insert(context.tx.database)
|
|
}
|
|
}
|
|
|
|
/// We _have_ to do this in a separate step from group thread creation; we create the group
|
|
/// thread when we process the group's Recipient frame, but only have mention state later
|
|
/// when processing the group's Chat frame.
|
|
func update(
|
|
thread: BackupArchive.ChatThread,
|
|
dontNotifyForMentionsIfMuted: Bool,
|
|
context: BackupArchive.ChatRestoringContext,
|
|
) throws {
|
|
guard dontNotifyForMentionsIfMuted else {
|
|
// We only need to set if its not the default (false)
|
|
return
|
|
}
|
|
|
|
// Technically, this isn't relevant for contact threads (they can't have mentions
|
|
// in them anyway), but the boolean does exist for them and the backup integration
|
|
// tests have contact threads with muted mentions. So we set for all thread types.
|
|
|
|
try context.tx.database.execute(
|
|
sql: """
|
|
UPDATE \(TSThread.databaseTableName)
|
|
SET
|
|
\(threadColumn: .mentionNotificationMode) = ?
|
|
WHERE
|
|
\(threadColumn: .id) = ?;
|
|
""",
|
|
arguments: [TSThreadMentionNotificationMode.never.rawValue, thread.threadRowId],
|
|
)
|
|
}
|
|
|
|
func markVisible(
|
|
thread: BackupArchive.ChatThread,
|
|
lastInteractionRowId: Int64?,
|
|
context: BackupArchive.ChatRestoringContext,
|
|
) throws {
|
|
try context.tx.database.execute(
|
|
sql: """
|
|
UPDATE \(TSThread.databaseTableName)
|
|
SET
|
|
\(threadColumn: .shouldThreadBeVisible) = 1,
|
|
\(threadColumn: .lastInteractionRowId) = ?
|
|
WHERE
|
|
\(threadColumn: .id) = ?;
|
|
""",
|
|
arguments: [lastInteractionRowId ?? 0, thread.threadRowId],
|
|
)
|
|
}
|
|
|
|
func createAssociatedData(
|
|
for thread: TSThread,
|
|
isArchived: Bool,
|
|
isMarkedUnread: Bool,
|
|
mutedUntilTimestamp: UInt64?,
|
|
context: BackupArchive.ChatRestoringContext,
|
|
) throws {
|
|
let threadAssociatedData = ThreadAssociatedData(
|
|
threadUniqueId: thread.uniqueId,
|
|
isArchived: isArchived,
|
|
isMarkedUnread: isMarkedUnread,
|
|
mutedUntilTimestamp: mutedUntilTimestamp ?? 0,
|
|
audioPlaybackRate: 1,
|
|
lastVerifiedGroupNameHash: nil,
|
|
)
|
|
try threadAssociatedData.insert(context.tx.database)
|
|
}
|
|
}
|