Signal-iOS/SignalServiceKit/MessageBackup/Archivers/ChatItem/MessageBackupReactionArchiver.swift
2024-06-05 16:55:35 -05:00

141 lines
4.8 KiB
Swift

//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import LibSignalClient
internal class MessageBackupReactionArchiver: MessageBackupProtoArchiver {
private let reactionStore: ReactionStore
init(
reactionStore: ReactionStore
) {
self.reactionStore = reactionStore
}
// MARK: - Archiving
func archiveReactions(
_ message: TSMessage,
context: MessageBackup.RecipientArchivingContext,
tx: DBReadTransaction
) -> MessageBackup.ArchiveInteractionResult<[BackupProto.Reaction]> {
let reactions = reactionStore.allReactions(messageId: message.uniqueId, tx: tx)
var errors = [MessageBackupChatItemArchiver.ArchiveMultiFrameResult.ArchiveFrameError]()
var reactionProtos = [BackupProto.Reaction]()
for reaction in reactions {
guard
let authorAddress = MessageBackup.ContactAddress(
aci: reaction.reactorAci,
e164: E164(reaction.reactorPhoneNumber)
)?.asArchivingAddress()
else {
// Skip this reaction.
errors.append(.archiveFrameError(.invalidReactionAddress, message.uniqueInteractionId))
continue
}
guard let authorId = context[authorAddress] else {
errors.append(.archiveFrameError(
.referencedRecipientIdMissing(authorAddress),
message.uniqueInteractionId
))
continue
}
let reaction = BackupProto.Reaction(
emoji: reaction.emoji,
authorId: authorId.value,
sentTimestamp: reaction.sentAtTimestamp,
sortOrder: reaction.sortOrder
)
reactionProtos.append(reaction)
}
if errors.isEmpty {
return .success(reactionProtos)
} else {
return .partialFailure(reactionProtos, errors)
}
}
// MARK: Restoring
func restoreReactions(
_ reactions: [BackupProto.Reaction],
chatItemId: MessageBackup.ChatItemId,
message: TSMessage,
context: MessageBackup.RecipientRestoringContext,
tx: DBWriteTransaction
) -> MessageBackup.RestoreInteractionResult<Void> {
var reactionErrors = [MessageBackup.RestoreFrameError<MessageBackup.ChatItemId>]()
for reaction in reactions {
let reactorAddress = context[reaction.authorRecipientId]
switch reactorAddress {
case .localAddress:
reactionStore.createReactionFromRestoredBackup(
uniqueMessageId: message.uniqueId,
emoji: reaction.emoji,
reactorAci: context.localIdentifiers.aci,
sentAtTimestamp: reaction.sentTimestamp,
sortOrder: reaction.sortOrder,
tx: tx
)
case .contact(let address):
if let aci = address.aci {
reactionStore.createReactionFromRestoredBackup(
uniqueMessageId: message.uniqueId,
emoji: reaction.emoji,
reactorAci: aci,
sentAtTimestamp: reaction.sentTimestamp,
sortOrder: reaction.sortOrder,
tx: tx
)
} else if let e164 = address.e164 {
reactionStore.createReactionFromRestoredBackup(
uniqueMessageId: message.uniqueId,
emoji: reaction.emoji,
reactorE164: e164,
sentAtTimestamp: reaction.sentTimestamp,
sortOrder: reaction.sortOrder,
tx: tx
)
} else {
reactionErrors.append(.restoreFrameError(
.invalidProtoData(.reactionNotFromAciOrE164),
chatItemId
))
continue
}
case .group, .distributionList:
// Referencing a group or distributionList as the author is invalid.
reactionErrors.append(.restoreFrameError(
.invalidProtoData(.reactionNotFromAciOrE164),
chatItemId
))
continue
case nil:
reactionErrors.append(.restoreFrameError(
.invalidProtoData(.recipientIdNotFound(reaction.authorRecipientId)),
chatItemId
))
continue
}
}
if reactionErrors.isEmpty {
return .success(())
} else {
return .partialRestore((), reactionErrors)
}
}
}