Ignore or use fallback for unrecognized backup enums/oneOfs for forwards compatibility
This commit is contained in:
parent
9e4ec6ac1f
commit
9fd8d3e8be
@ -216,10 +216,9 @@ public class MessageBackupAccountDataArchiverImpl: MessageBackupAccountDataArchi
|
||||
let storiesDisabled = storyManager.areStoriesEnabled(tx: context.tx).negated
|
||||
let hasSeenGroupStoryEducationSheet = systemStoryManager.hasSeenGroupStoryEducationSheet(tx: context.tx)
|
||||
let hasCompletedUsernameOnboarding = usernameEducationManager.shouldShowUsernameEducation(tx: context.tx).negated
|
||||
let phoneNumberSharingMode: BackupProto_AccountData.PhoneNumberSharingMode = switch udManager.phoneNumberSharingMode(tx: context.tx) {
|
||||
let phoneNumberSharingMode: BackupProto_AccountData.PhoneNumberSharingMode = switch udManager.phoneNumberSharingMode(tx: context.tx).orDefault {
|
||||
case .everybody: .everybody
|
||||
case .nobody: .nobody
|
||||
case .none: .unknown
|
||||
}
|
||||
|
||||
// Populate the proto with the settings
|
||||
@ -377,6 +376,8 @@ public class MessageBackupAccountDataArchiverImpl: MessageBackupAccountDataArchi
|
||||
switch customChatColorsResult {
|
||||
case .success:
|
||||
break
|
||||
case .unrecognizedEnum:
|
||||
return customChatColorsResult
|
||||
case .partialRestore(let errors):
|
||||
partialErrors.append(contentsOf: errors)
|
||||
case .failure(let errors):
|
||||
@ -399,6 +400,8 @@ public class MessageBackupAccountDataArchiverImpl: MessageBackupAccountDataArchi
|
||||
switch defaultChatStyleResult {
|
||||
case .success:
|
||||
break
|
||||
case .unrecognizedEnum:
|
||||
return defaultChatStyleResult
|
||||
case .partialRestore(let errors):
|
||||
partialErrors.append(contentsOf: errors)
|
||||
case .failure(let errors):
|
||||
|
||||
@ -170,17 +170,7 @@ public class MessageBackupAdHocCallArchiverImpl: MessageBackupAdHocCallArchiver
|
||||
switch adHocCall.state {
|
||||
case .generic:
|
||||
state = .generic
|
||||
case .unknownState:
|
||||
partialErrors.append(
|
||||
.restoreFrameError(.invalidProtoData(.adHocCallUnknownState),
|
||||
callId
|
||||
))
|
||||
state = .generic
|
||||
case .UNRECOGNIZED:
|
||||
partialErrors.append(
|
||||
.restoreFrameError(.invalidProtoData(.adHocCallUnrecognizedState),
|
||||
callId
|
||||
))
|
||||
case .unknownState, .UNRECOGNIZED:
|
||||
state = .generic
|
||||
}
|
||||
|
||||
|
||||
@ -502,6 +502,8 @@ public class MessageBackupChatArchiverImpl: MessageBackupChatArchiver {
|
||||
switch chatStyleResult {
|
||||
case .success:
|
||||
break
|
||||
case .unrecognizedEnum:
|
||||
return chatStyleResult
|
||||
case .partialRestore(let errors):
|
||||
partialErrors.append(contentsOf: errors)
|
||||
case .failure(let errors):
|
||||
|
||||
@ -119,10 +119,7 @@ public class MessageBackupChatStyleArchiver: MessageBackupProtoArchiver {
|
||||
let colorOrGradientSetting: ColorOrGradientSetting
|
||||
switch chatColorProto.color {
|
||||
case .none:
|
||||
partialErrors.append(.restoreFrameError(
|
||||
.invalidProtoData(.unrecognizedCustomChatStyleColor),
|
||||
.forCustomChatColorError(chatColorId: customChatColorId)
|
||||
))
|
||||
// Fallback to default (skip this chat color)
|
||||
continue
|
||||
case .solid(let colorARGBHex):
|
||||
colorOrGradientSetting = .solidColor(
|
||||
@ -222,9 +219,10 @@ public class MessageBackupChatStyleArchiver: MessageBackupProtoArchiver {
|
||||
if let wallpaper = wallpaperStore.fetchWallpaper(for: thread?.tsThread.uniqueId, tx: context.tx) {
|
||||
var protoWallpaper: BackupProto_ChatStyle.OneOf_Wallpaper?
|
||||
|
||||
if let preset = wallpaper.asBackupProto() {
|
||||
switch wallpaper.asBackupProto() {
|
||||
case .wallpaperPreset(let preset):
|
||||
protoWallpaper = .wallpaperPreset(preset)
|
||||
} else if wallpaper == .photo {
|
||||
case .photo:
|
||||
switch self.archiveWallpaperAttachment(
|
||||
thread: thread,
|
||||
errorId: errorId,
|
||||
@ -238,11 +236,6 @@ public class MessageBackupChatStyleArchiver: MessageBackupProtoArchiver {
|
||||
case .failure(let error):
|
||||
return .failure(error)
|
||||
}
|
||||
} else {
|
||||
return .failure(.archiveFrameError(
|
||||
.unknownWallpaper,
|
||||
errorId
|
||||
))
|
||||
}
|
||||
|
||||
if let protoWallpaper {
|
||||
@ -347,10 +340,8 @@ public class MessageBackupChatStyleArchiver: MessageBackupProtoArchiver {
|
||||
break
|
||||
case .bubbleColorPreset(let bubbleColorPreset):
|
||||
guard let palette = bubbleColorPreset.asPaletteChatColor() else {
|
||||
return .failure([.restoreFrameError(
|
||||
.invalidProtoData(.unrecognizedChatStyleBubbleColorPreset),
|
||||
errorId
|
||||
)])
|
||||
// If we can't recognize the preset, use auto (skip)
|
||||
break
|
||||
}
|
||||
chatColorSettingStore.setChatColorSetting(
|
||||
ChatColorSetting.builtIn(palette),
|
||||
@ -404,10 +395,9 @@ public class MessageBackupChatStyleArchiver: MessageBackupProtoArchiver {
|
||||
break
|
||||
case .wallpaperPreset(let wallpaperPreset):
|
||||
guard let wallpaper = wallpaperPreset.asWallpaper() else {
|
||||
return .failure([.restoreFrameError(
|
||||
.invalidProtoData(.unrecognizedChatStyleWallpaperPreset),
|
||||
errorId
|
||||
)])
|
||||
// If we can't recognize the preset enum,
|
||||
// leave the wallpaper unset.
|
||||
break
|
||||
}
|
||||
wallpaperStore.setWallpaperType(
|
||||
wallpaper,
|
||||
@ -429,6 +419,8 @@ public class MessageBackupChatStyleArchiver: MessageBackupProtoArchiver {
|
||||
switch attachmentResult {
|
||||
case .success:
|
||||
break
|
||||
case .unrecognizedEnum:
|
||||
return attachmentResult
|
||||
case .partialRestore(let errors):
|
||||
partialErrors.append(contentsOf: errors)
|
||||
case .failure(let errors):
|
||||
@ -564,33 +556,38 @@ public class MessageBackupChatStyleArchiver: MessageBackupProtoArchiver {
|
||||
|
||||
fileprivate extension Wallpaper {
|
||||
|
||||
func asBackupProto() -> BackupProto_ChatStyle.WallpaperPreset? {
|
||||
enum BackupRepresentation {
|
||||
case wallpaperPreset(BackupProto_ChatStyle.WallpaperPreset)
|
||||
case photo
|
||||
}
|
||||
|
||||
func asBackupProto() -> BackupRepresentation {
|
||||
// These don't match names exactly because...well nobody knows why
|
||||
// the iOS enum names were defined this way. They're persisted to the
|
||||
// db now, so we just gotta keep the mapping.
|
||||
return switch self {
|
||||
case .blush: .solidBlush
|
||||
case .copper: .solidCopper
|
||||
case .zorba: .solidDust
|
||||
case .envy: .solidCeladon
|
||||
case .sky: .solidPacific
|
||||
case .wildBlueYonder: .solidFrost
|
||||
case .lavender: .solidLilac
|
||||
case .shocking: .solidPink
|
||||
case .gray: .solidSilver
|
||||
case .eden: .solidRainforest
|
||||
case .violet: .solidNavy
|
||||
case .eggplant: .solidEggplant
|
||||
case .starshipGradient: .gradientSunset
|
||||
case .woodsmokeGradient: .gradientNoir
|
||||
case .coralGradient: .gradientHeatmap
|
||||
case .ceruleanGradient: .gradientAqua
|
||||
case .roseGradient: .gradientIridescent
|
||||
case .aquamarineGradient: .gradientMonstera
|
||||
case .tropicalGradient: .gradientBliss
|
||||
case .blueGradient: .gradientSky
|
||||
case .bisqueGradient: .gradientPeach
|
||||
case .photo: nil
|
||||
case .blush: .wallpaperPreset(.solidBlush)
|
||||
case .copper: .wallpaperPreset(.solidCopper)
|
||||
case .zorba: .wallpaperPreset(.solidDust)
|
||||
case .envy: .wallpaperPreset(.solidCeladon)
|
||||
case .sky: .wallpaperPreset(.solidPacific)
|
||||
case .wildBlueYonder: .wallpaperPreset(.solidFrost)
|
||||
case .lavender: .wallpaperPreset(.solidLilac)
|
||||
case .shocking: .wallpaperPreset(.solidPink)
|
||||
case .gray: .wallpaperPreset(.solidSilver)
|
||||
case .eden: .wallpaperPreset(.solidRainforest)
|
||||
case .violet: .wallpaperPreset(.solidNavy)
|
||||
case .eggplant: .wallpaperPreset(.solidEggplant)
|
||||
case .starshipGradient: .wallpaperPreset(.gradientSunset)
|
||||
case .woodsmokeGradient: .wallpaperPreset(.gradientNoir)
|
||||
case .coralGradient: .wallpaperPreset(.gradientHeatmap)
|
||||
case .ceruleanGradient: .wallpaperPreset(.gradientAqua)
|
||||
case .roseGradient: .wallpaperPreset(.gradientIridescent)
|
||||
case .aquamarineGradient: .wallpaperPreset(.gradientMonstera)
|
||||
case .tropicalGradient: .wallpaperPreset(.gradientBliss)
|
||||
case .blueGradient: .wallpaperPreset(.gradientSky)
|
||||
case .bisqueGradient: .wallpaperPreset(.gradientPeach)
|
||||
case .photo: .photo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,7 +200,9 @@ final class MessageBackupGroupCallArchiver {
|
||||
let callStatus: CallRecord.CallStatus.GroupCallStatus
|
||||
switch groupCall.state {
|
||||
case .unknownState, .UNRECOGNIZED:
|
||||
return .messageFailure([.restoreFrameError(.invalidProtoData(.groupCallUnrecognizedState), chatItem.id)])
|
||||
// Fallback to generic
|
||||
callDirection = .incoming
|
||||
callStatus = .generic
|
||||
case .generic:
|
||||
callDirection = .incoming
|
||||
callStatus = .generic
|
||||
|
||||
@ -163,41 +163,40 @@ final class MessageBackupIndividualCallArchiver {
|
||||
)])
|
||||
}
|
||||
|
||||
let callInteractionType: RPRecentCallType
|
||||
let callRecordDirection: CallRecord.CallDirection
|
||||
let callRecordStatus: CallRecord.CallStatus.IndividualCallStatus
|
||||
switch (individualCall.direction, individualCall.state) {
|
||||
case (.unknownDirection, _), (.UNRECOGNIZED, _):
|
||||
return .messageFailure([.restoreFrameError(.invalidProtoData(.individualCallUnrecognizedDirection), chatItem.id)])
|
||||
case (_, .unknownState), (_, .UNRECOGNIZED):
|
||||
return .messageFailure([.restoreFrameError(.invalidProtoData(.individualCallUnrecognizedState), chatItem.id)])
|
||||
case (.incoming, .accepted):
|
||||
callInteractionType = .incoming
|
||||
switch individualCall.direction {
|
||||
case .unknownDirection, .UNRECOGNIZED:
|
||||
// Fallback to incoming
|
||||
callRecordDirection = .incoming
|
||||
case .incoming:
|
||||
callRecordDirection = .incoming
|
||||
case .outgoing:
|
||||
callRecordDirection = .outgoing
|
||||
}
|
||||
|
||||
let callInteractionType: RPRecentCallType
|
||||
let callRecordStatus: CallRecord.CallStatus.IndividualCallStatus
|
||||
switch (callRecordDirection, individualCall.state) {
|
||||
case (.incoming, .accepted), (.incoming, .unknownState), (.incoming, .UNRECOGNIZED):
|
||||
callInteractionType = .incoming
|
||||
callRecordStatus = .accepted
|
||||
case (.incoming, .notAccepted):
|
||||
callInteractionType = .incomingDeclined
|
||||
callRecordDirection = .incoming
|
||||
callRecordStatus = .notAccepted
|
||||
case (.incoming, .missed):
|
||||
callInteractionType = .incomingMissed
|
||||
callRecordDirection = .incoming
|
||||
callRecordStatus = .incomingMissed
|
||||
case (.incoming, .missedNotificationProfile):
|
||||
callInteractionType = .incomingMissedBecauseOfDoNotDisturb
|
||||
callRecordDirection = .incoming
|
||||
callRecordStatus = .incomingMissed
|
||||
case (.outgoing, .accepted):
|
||||
case (.outgoing, .accepted), (.outgoing, .unknownState), (.outgoing, .UNRECOGNIZED):
|
||||
callInteractionType = .outgoing
|
||||
callRecordDirection = .outgoing
|
||||
callRecordStatus = .accepted
|
||||
case (.outgoing, .notAccepted):
|
||||
callInteractionType = .outgoingIncomplete
|
||||
callRecordDirection = .outgoing
|
||||
callRecordStatus = .notAccepted
|
||||
case (.outgoing, .missed), (.outgoing, .missedNotificationProfile):
|
||||
callInteractionType = .outgoingMissed
|
||||
callRecordDirection = .outgoing
|
||||
callRecordStatus = .notAccepted
|
||||
}
|
||||
|
||||
@ -211,7 +210,9 @@ final class MessageBackupIndividualCallArchiver {
|
||||
callInteractionOfferType = .video
|
||||
callRecordType = .videoCall
|
||||
case .unknownType, .UNRECOGNIZED:
|
||||
return .messageFailure([.restoreFrameError(.invalidProtoData(.individualCallUnrecognizedType), chatItem.id)])
|
||||
// Fallback to audio
|
||||
callInteractionOfferType = .audio
|
||||
callRecordType = .audioCall
|
||||
}
|
||||
|
||||
let callerAci: Aci?
|
||||
|
||||
@ -208,10 +208,12 @@ final class MessageBackupGroupUpdateMessageArchiver {
|
||||
partialErrors: &partialErrors,
|
||||
chatItemId: chatItem.id
|
||||
)
|
||||
guard var persistableUpdates =
|
||||
result.unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
var persistableUpdates: [PersistableGroupUpdateItem]
|
||||
switch result.bubbleUp(Void.self, partialErrors: &partialErrors) {
|
||||
case .continue(let component):
|
||||
persistableUpdates = component
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
guard persistableUpdates.isEmpty.negated else {
|
||||
@ -250,10 +252,9 @@ final class MessageBackupGroupUpdateMessageArchiver {
|
||||
)
|
||||
|
||||
guard let directionalDetails = chatItem.directionalDetails else {
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.chatItemMissingDirectionalDetails),
|
||||
chatItem.id
|
||||
)])
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_DirectionalDetails.self
|
||||
))
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
@ -27,10 +27,11 @@ final class MessageBackupGroupUpdateProtoToSwiftConverter {
|
||||
localUserAci: localUserAci,
|
||||
chatItemId: chatItemId
|
||||
)
|
||||
if let persistableItems = result.unwrap(partialErrors: &partialErrors) {
|
||||
persistableUpdates.append(contentsOf: persistableItems)
|
||||
} else {
|
||||
return .messageFailure(partialErrors)
|
||||
switch result.bubbleUp([PersistableGroupUpdateItem].self, partialErrors: &partialErrors) {
|
||||
case .continue(let component):
|
||||
persistableUpdates.append(contentsOf: component)
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
}
|
||||
return .success(persistableUpdates)
|
||||
@ -98,7 +99,8 @@ final class MessageBackupGroupUpdateProtoToSwiftConverter {
|
||||
|
||||
switch groupUpdate.update {
|
||||
case nil:
|
||||
return .messageFailure([.restoreFrameError(.invalidProtoData(.unrecognizedGroupUpdate), chatItemId)])
|
||||
// Fallback to a generic update.
|
||||
return .success([.genericUpdateByUnknownUser])
|
||||
case .genericGroupUpdate(let proto):
|
||||
switch unwrapOptionalAci(proto, \.updaterAci) {
|
||||
case .unknown:
|
||||
|
||||
@ -220,10 +220,9 @@ final class MessageBackupChatUpdateMessageArchiver: MessageBackupProtoArchiver {
|
||||
|
||||
switch chatUpdateMessage.update {
|
||||
case nil:
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.emptyChatUpdateMessage),
|
||||
chatItem.id
|
||||
)])
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatUpdateMessage.OneOf_Update.self
|
||||
))
|
||||
case .groupChange(let groupChangeChatUpdateProto):
|
||||
return groupUpdateMessageArchiver.restoreGroupUpdate(
|
||||
groupChangeChatUpdateProto,
|
||||
|
||||
@ -192,7 +192,9 @@ final class MessageBackupExpirationTimerChatUpdateArchiver {
|
||||
)
|
||||
|
||||
guard let directionalDetails = chatItem.directionalDetails else {
|
||||
return invalidProtoData(.chatItemMissingDirectionalDetails)
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_DirectionalDetails.self
|
||||
))
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
@ -88,16 +88,12 @@ final class MessageBackupLearnedProfileChatUpdateArchiver {
|
||||
)])
|
||||
}
|
||||
|
||||
guard let previousName = learnedProfileUpdateProto.previousName else {
|
||||
return invalidProtoData(.learnedProfileUpdateMissingPreviousName)
|
||||
}
|
||||
|
||||
guard case .contact(let contactThread) = chatThread.threadType else {
|
||||
return invalidProtoData(.learnedProfileUpdateNotFromContact)
|
||||
}
|
||||
|
||||
let displayNameBefore: TSInfoMessage.DisplayNameBeforeLearningProfileName
|
||||
switch previousName {
|
||||
switch learnedProfileUpdateProto.previousName {
|
||||
case .e164(let uintValue):
|
||||
guard let e164 = E164(uintValue) else {
|
||||
return invalidProtoData(.invalidE164(protoClass: BackupProto_LearnedProfileChatUpdate.self))
|
||||
@ -106,6 +102,9 @@ final class MessageBackupLearnedProfileChatUpdateArchiver {
|
||||
displayNameBefore = .phoneNumber(e164.stringValue)
|
||||
case .username(let username):
|
||||
displayNameBefore = .username(username)
|
||||
case nil:
|
||||
// This isn't great, but we just use an empty username.
|
||||
displayNameBefore = .username("")
|
||||
}
|
||||
|
||||
let learnedProfileKeyInfoMessage: TSInfoMessage = .makeForLearnedProfileName(
|
||||
@ -115,10 +114,9 @@ final class MessageBackupLearnedProfileChatUpdateArchiver {
|
||||
)
|
||||
|
||||
guard let directionalDetails = chatItem.directionalDetails else {
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.chatItemMissingDirectionalDetails),
|
||||
chatItem.id
|
||||
)])
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_DirectionalDetails.self
|
||||
))
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
@ -112,10 +112,9 @@ final class MessageBackupProfileChangeChatUpdateArchiver {
|
||||
)
|
||||
|
||||
guard let directionalDetails = chatItem.directionalDetails else {
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.chatItemMissingDirectionalDetails),
|
||||
chatItem.id
|
||||
)])
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_DirectionalDetails.self
|
||||
))
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
@ -106,10 +106,9 @@ final class MessageBackupSessionSwitchoverChatUpdateArchiver {
|
||||
)
|
||||
|
||||
guard let directionalDetails = chatItem.directionalDetails else {
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.chatItemMissingDirectionalDetails),
|
||||
chatItem.id
|
||||
)])
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_DirectionalDetails.self
|
||||
))
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
@ -336,7 +336,9 @@ final class MessageBackupSimpleChatUpdateArchiver {
|
||||
|
||||
switch simpleChatUpdate.type {
|
||||
case .unknown, .UNRECOGNIZED:
|
||||
return invalidProtoData(.unrecognizedSimpleChatUpdate)
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_SimpleChatUpdate.TypeEnum.self
|
||||
))
|
||||
case .joinedSignal:
|
||||
simpleChatUpdateInteraction = .simpleInfoMessage(.userJoinedSignal)
|
||||
case .identityUpdate:
|
||||
@ -527,10 +529,9 @@ final class MessageBackupSimpleChatUpdateArchiver {
|
||||
}
|
||||
|
||||
guard let directionalDetails = chatItem.directionalDetails else {
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.chatItemMissingDirectionalDetails),
|
||||
chatItem.id
|
||||
)])
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_DirectionalDetails.self
|
||||
))
|
||||
}
|
||||
|
||||
switch simpleChatUpdateInteraction {
|
||||
|
||||
@ -106,10 +106,9 @@ final class MessageBackupThreadMergeChatUpdateArchiver {
|
||||
)
|
||||
|
||||
guard let directionalDetails = chatItem.directionalDetails else {
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.chatItemMissingDirectionalDetails),
|
||||
chatItem.id
|
||||
)])
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_DirectionalDetails.self
|
||||
))
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
@ -310,6 +310,9 @@ extension MessageBackup {
|
||||
|
||||
enum RestoreInteractionResult<Component> {
|
||||
case success(Component)
|
||||
/// There was an unrecognized enum (or oneOf) for which we skip restoring this frame
|
||||
/// but we should proceed restoring other frames.
|
||||
case unrecognizedEnum(UnrecognizedEnumError)
|
||||
/// Some portion of the interaction failed to restore, but we can still restore the rest of it.
|
||||
/// e.g. a reaction failed to parse, so we just drop that reaction.
|
||||
case partialRestore(Component, [RestoreFrameError<ChatItemId>])
|
||||
@ -386,9 +389,14 @@ extension MessageBackup.ArchiveInteractionResult {
|
||||
|
||||
extension MessageBackup.RestoreInteractionResult {
|
||||
|
||||
/// Returns nil for ``RestoreInteractionResult.messageFailure``, otherwise
|
||||
/// returns the restored component. Regardless, accumulates any errors so that the caller
|
||||
/// can return the passed in ``partialErrors`` array in the final result.
|
||||
enum BubbleUp<ComponentType, ErrorComponentType> {
|
||||
case `continue`(ComponentType)
|
||||
case bubbleUpError(MessageBackup.RestoreInteractionResult<ErrorComponentType>)
|
||||
}
|
||||
|
||||
/// Make it easier to "bubble up" an error case of ``RestoreInteractionResult`` thrown deeper in the call stack.
|
||||
/// Basically, collapses all the cases that should just be bubbled up to the caller (error cases) into an easily returnable case,
|
||||
/// ditto for the success or partial success cases, and handles updating partialErrors along the way.
|
||||
///
|
||||
/// Concretely, turns this:
|
||||
///
|
||||
@ -398,28 +406,38 @@ extension MessageBackup.RestoreInteractionResult {
|
||||
/// case .partialRestore(let value, let errors):
|
||||
/// myVar = value
|
||||
/// partialErrors.append(contentsOf: errors)
|
||||
/// case messageFailure(let errors)
|
||||
/// partialErrors.append(contentsOf: errors)
|
||||
/// return .messageFailure(partialErrors)
|
||||
/// case someFailureCase(let someErrorOrErrors)
|
||||
/// let coalescedErrorOrErrors = partialErrors.coalesceSomehow(with: someErrorOrErrors)
|
||||
/// // Just bubble up the error after coalescing
|
||||
/// return .someFailureCase(coalescedErrorOrErrors)
|
||||
/// // ...
|
||||
/// // The same for every other error case that should be bubbled up
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// Into this:
|
||||
///
|
||||
/// guard let myVar = someResult.unwrap(&partialErrors) else {
|
||||
/// return .messageFailure(partialErrors)
|
||||
/// switch someResult.bubbleUp(&partialErrors) {
|
||||
/// case .success(let value):
|
||||
/// myVar = value
|
||||
/// case .bubbleUpError(let error):
|
||||
/// return error
|
||||
/// }
|
||||
func unwrap(
|
||||
func bubbleUp<ErrorComponentType>(
|
||||
_ errorComponentType: ErrorComponentType.Type = Component.self,
|
||||
partialErrors: inout [MessageBackup.RestoreFrameError<MessageBackup.ChatItemId>]
|
||||
) -> Component? {
|
||||
) -> BubbleUp<Component, ErrorComponentType> {
|
||||
switch self {
|
||||
case .success(let component):
|
||||
return component
|
||||
return .continue(component)
|
||||
case .unrecognizedEnum(let error):
|
||||
return .bubbleUpError(.unrecognizedEnum(error))
|
||||
case .partialRestore(let component, let errors):
|
||||
partialErrors.append(contentsOf: errors)
|
||||
return component
|
||||
return .continue(component)
|
||||
case .messageFailure(let errors):
|
||||
partialErrors.append(contentsOf: errors)
|
||||
return nil
|
||||
return .bubbleUpError(.messageFailure(partialErrors))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -434,6 +452,8 @@ extension MessageBackup.RestoreInteractionResult where Component == Void {
|
||||
switch (self, other) {
|
||||
case (.success, .success):
|
||||
return .success(())
|
||||
case (.unrecognizedEnum(let error), _), (_, .unrecognizedEnum(let error)):
|
||||
return .unrecognizedEnum(error)
|
||||
case let (.messageFailure(lhs), .messageFailure(rhs)):
|
||||
return .messageFailure(lhs + rhs)
|
||||
case let (.partialRestore(_, lhs), .partialRestore(_, rhs)):
|
||||
@ -453,24 +473,3 @@ extension MessageBackup.RestoreInteractionResult where Component == Void {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MessageBackup.RestoreInteractionResult where Component == Void {
|
||||
|
||||
/// Returns false for ``RestoreInteractionResult.messageFailure``, otherwise
|
||||
/// returns true. Regardless, accumulates any errors so that the caller
|
||||
/// can return the passed in ``partialErrors`` array in the final result.
|
||||
func unwrap(
|
||||
partialErrors: inout [MessageBackup.RestoreFrameError<MessageBackup.ChatItemId>]
|
||||
) -> Bool {
|
||||
switch self {
|
||||
case .success:
|
||||
return true
|
||||
case .partialRestore(_, let errors):
|
||||
partialErrors.append(contentsOf: errors)
|
||||
return true
|
||||
case .messageFailure(let errors):
|
||||
partialErrors.append(contentsOf: errors)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,7 +357,9 @@ public class MessageBackupChatItemArchiverImpl: MessageBackupChatItemArchiver {
|
||||
let restoreInteractionResult: MessageBackup.RestoreInteractionResult<Void>
|
||||
switch chatItem.directionalDetails {
|
||||
case nil:
|
||||
return restoreFrameError(.invalidProtoData(.chatItemMissingDirectionalDetails))
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_DirectionalDetails.self
|
||||
))
|
||||
case .incoming:
|
||||
restoreInteractionResult = incomingMessageArchiver.restoreIncomingChatItem(
|
||||
chatItem,
|
||||
@ -373,7 +375,9 @@ public class MessageBackupChatItemArchiverImpl: MessageBackupChatItemArchiver {
|
||||
case .directionless:
|
||||
switch chatItem.item {
|
||||
case nil:
|
||||
return restoreFrameError(.invalidProtoData(.chatItemMissingItem))
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_Item.self
|
||||
))
|
||||
case
|
||||
.standardMessage,
|
||||
.contactMessage,
|
||||
@ -396,6 +400,8 @@ public class MessageBackupChatItemArchiverImpl: MessageBackupChatItemArchiver {
|
||||
switch restoreInteractionResult {
|
||||
case .success:
|
||||
return .success
|
||||
case .unrecognizedEnum(let error):
|
||||
return .unrecognizedEnum(error)
|
||||
case .partialRestore(_, let errors):
|
||||
return .partialRestore(errors)
|
||||
case .messageFailure(let errors):
|
||||
|
||||
@ -251,35 +251,53 @@ internal class MessageBackupContactAttachmentArchiver: MessageBackupProtoArchive
|
||||
|
||||
let contact = OWSContact(name: contactName)
|
||||
|
||||
contact.phoneNumbers = contactProto.number.compactMap { phoneNumberProto in
|
||||
return self
|
||||
for phoneNumberProto in contactProto.number {
|
||||
switch self
|
||||
.restoreContactPhoneNumber(
|
||||
proto: phoneNumberProto,
|
||||
chatItemId: chatItemId
|
||||
)
|
||||
.unwrap(partialErrors: &partialErrors)
|
||||
// Double unwrap; we will just drops nulls.
|
||||
?? nil
|
||||
.bubbleUp(OWSContact.self, partialErrors: &partialErrors)
|
||||
{
|
||||
case .continue(let phoneNumber):
|
||||
if let phoneNumber {
|
||||
contact.phoneNumbers.append(phoneNumber)
|
||||
}
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
}
|
||||
contact.emails = contactProto.email.compactMap { emailProto in
|
||||
return self
|
||||
for emailProto in contactProto.email {
|
||||
switch self
|
||||
.restoreContactEmail(
|
||||
proto: emailProto,
|
||||
chatItemId: chatItemId
|
||||
)
|
||||
.unwrap(partialErrors: &partialErrors)
|
||||
// Double unwrap; we will just drops nulls.
|
||||
?? nil
|
||||
.bubbleUp(OWSContact.self, partialErrors: &partialErrors)
|
||||
{
|
||||
case .continue(let email):
|
||||
if let email {
|
||||
contact.emails.append(email)
|
||||
}
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
}
|
||||
contact.addresses = contactProto.address.compactMap { addressProto in
|
||||
return self
|
||||
for addressProto in contactProto.address {
|
||||
switch self
|
||||
.restoreContactAddress(
|
||||
proto: addressProto,
|
||||
chatItemId: chatItemId
|
||||
)
|
||||
.unwrap(partialErrors: &partialErrors)
|
||||
// Double unwrap; we will just drops nulls.
|
||||
?? nil
|
||||
.bubbleUp(OWSContact.self, partialErrors: &partialErrors)
|
||||
{
|
||||
case .continue(let address):
|
||||
if let address {
|
||||
contact.addresses.append(address)
|
||||
}
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
}
|
||||
|
||||
// Note: the contact attachment's avatar is restored later (if any is set).
|
||||
@ -313,10 +331,7 @@ internal class MessageBackupContactAttachmentArchiver: MessageBackupProtoArchive
|
||||
case .custom:
|
||||
type = .custom
|
||||
case .unknown, .UNRECOGNIZED(_):
|
||||
return .partialRestore(nil, [.restoreFrameError(
|
||||
.invalidProtoData(.contactAttachmentPhoneNumberUnknownType),
|
||||
chatItemId
|
||||
)])
|
||||
type = .home
|
||||
}
|
||||
|
||||
return .success(OWSContactPhoneNumber(
|
||||
@ -348,10 +363,7 @@ internal class MessageBackupContactAttachmentArchiver: MessageBackupProtoArchive
|
||||
case .custom:
|
||||
type = .custom
|
||||
case .unknown, .UNRECOGNIZED(_):
|
||||
return .partialRestore(nil, [.restoreFrameError(
|
||||
.invalidProtoData(.contactAttachmentEmailUnknownType),
|
||||
chatItemId
|
||||
)])
|
||||
type = .home
|
||||
}
|
||||
|
||||
return .success(OWSContactEmail(
|
||||
@ -374,10 +386,7 @@ internal class MessageBackupContactAttachmentArchiver: MessageBackupProtoArchive
|
||||
case .custom:
|
||||
type = .custom
|
||||
case .unknown, .UNRECOGNIZED(_):
|
||||
return .partialRestore(nil, [.restoreFrameError(
|
||||
.invalidProtoData(.contactAttachmentAddressUnknownType),
|
||||
chatItemId
|
||||
)])
|
||||
type = .home
|
||||
}
|
||||
|
||||
let address = OWSContactAddress(
|
||||
|
||||
@ -67,15 +67,19 @@ class MessageBackupTSIncomingMessageArchiver {
|
||||
) -> MessageBackup.RestoreInteractionResult<Void> {
|
||||
var partialErrors = [RestoreFrameError]()
|
||||
|
||||
guard
|
||||
editHistoryArchiver.restoreMessageAndEditHistory(
|
||||
switch editHistoryArchiver
|
||||
.restoreMessageAndEditHistory(
|
||||
topLevelChatItem,
|
||||
chatThread: chatThread,
|
||||
context: context,
|
||||
builder: self
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
)
|
||||
.bubbleUp(Void.self, partialErrors: &partialErrors)
|
||||
{
|
||||
case .continue:
|
||||
break
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
if partialErrors.isEmpty {
|
||||
@ -222,21 +226,24 @@ extension MessageBackupTSIncomingMessageArchiver: MessageBackupTSMessageEditHist
|
||||
context: MessageBackup.ChatItemRestoringContext
|
||||
) -> MessageBackup.RestoreInteractionResult<EditHistoryMessageType> {
|
||||
guard let chatItemItem = chatItem.item else {
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.chatItemMissingItem),
|
||||
chatItem.id
|
||||
)])
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_Item.self
|
||||
))
|
||||
}
|
||||
|
||||
let incomingDetails: BackupProto_ChatItem.IncomingMessageDetails
|
||||
switch chatItem.directionalDetails {
|
||||
case .incoming(let _incomingDetails):
|
||||
incomingDetails = _incomingDetails
|
||||
case nil, .outgoing, .directionless:
|
||||
case .outgoing, .directionless:
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.revisionOfIncomingMessageMissingIncomingDetails),
|
||||
chatItem.id
|
||||
)])
|
||||
case nil:
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_DirectionalDetails.self
|
||||
))
|
||||
}
|
||||
|
||||
let authorAci: Aci?
|
||||
@ -303,15 +310,20 @@ extension MessageBackupTSIncomingMessageArchiver: MessageBackupTSMessageEditHist
|
||||
|
||||
var partialErrors = [RestoreFrameError]()
|
||||
|
||||
guard
|
||||
let contents = contentsArchiver.restoreContents(
|
||||
let contents: MessageBackup.RestoredMessageContents
|
||||
switch contentsArchiver
|
||||
.restoreContents(
|
||||
chatItemItem,
|
||||
chatItemId: chatItem.id,
|
||||
chatThread: chatThread,
|
||||
context: context
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
)
|
||||
.bubbleUp(EditHistoryMessageType.self, partialErrors: &partialErrors)
|
||||
{
|
||||
case .continue(let component):
|
||||
contents = component
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
let message: TSIncomingMessage = {
|
||||
@ -408,16 +420,20 @@ extension MessageBackupTSIncomingMessageArchiver: MessageBackupTSMessageEditHist
|
||||
return .messageFailure(partialErrors + [.restoreFrameError(.databaseInsertionFailed(error), chatItem.id)])
|
||||
}
|
||||
|
||||
guard
|
||||
contentsArchiver.restoreDownstreamObjects(
|
||||
switch contentsArchiver
|
||||
.restoreDownstreamObjects(
|
||||
message: message,
|
||||
thread: chatThread,
|
||||
chatItemId: chatItem.id,
|
||||
restoredContents: contents,
|
||||
context: context
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
)
|
||||
.bubbleUp(TSIncomingMessage.self, partialErrors: &partialErrors)
|
||||
{
|
||||
case .continue:
|
||||
break
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
if partialErrors.isEmpty {
|
||||
|
||||
@ -1236,6 +1236,12 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
guard let senderOrRecipientAci else {
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.paymentNotificationInGroup),
|
||||
chatItemId
|
||||
)])
|
||||
}
|
||||
|
||||
let direction: ArchivedPayment.Direction
|
||||
switch message {
|
||||
@ -1249,19 +1255,12 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
chatItemId
|
||||
)])
|
||||
}
|
||||
guard
|
||||
let senderOrRecipientAci,
|
||||
let archivedPayment = ArchivedPayment.fromBackup(
|
||||
transaction,
|
||||
senderOrRecipientAci: senderOrRecipientAci,
|
||||
direction: direction,
|
||||
interactionUniqueId: message.uniqueId
|
||||
) else {
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.unrecognizedPaymentTransaction),
|
||||
chatItemId
|
||||
)])
|
||||
}
|
||||
let archivedPayment = ArchivedPayment.fromBackup(
|
||||
transaction,
|
||||
senderOrRecipientAci: senderOrRecipientAci,
|
||||
direction: direction,
|
||||
interactionUniqueId: message.uniqueId
|
||||
)
|
||||
do {
|
||||
try archivedPaymentStore.insert(archivedPayment, tx: context.tx)
|
||||
} catch {
|
||||
@ -1331,17 +1330,24 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
let quotedMessage: TSQuotedMessage?
|
||||
let quotedMessageThumbnail: BackupProto_MessageAttachment?
|
||||
if standardMessage.hasQuote {
|
||||
guard
|
||||
let quoteResult = restoreQuote(
|
||||
switch self
|
||||
.restoreQuote(
|
||||
standardMessage.quote,
|
||||
chatItemId: chatItemId,
|
||||
thread: chatThread,
|
||||
context: context
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
)
|
||||
.bubbleUp(
|
||||
MessageBackup.RestoredMessageContents.self,
|
||||
partialErrors: &partialErrors
|
||||
)
|
||||
{
|
||||
case .continue(let component):
|
||||
quotedMessage = component.0
|
||||
quotedMessageThumbnail = component.1
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
(quotedMessage, quotedMessageThumbnail) = quoteResult
|
||||
} else {
|
||||
quotedMessage = nil
|
||||
quotedMessageThumbnail = nil
|
||||
@ -1350,21 +1356,28 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
let linkPreview: OWSLinkPreview?
|
||||
let linkPreviewAttachment: BackupProto_FilePointer?
|
||||
if let linkPreviewProto = standardMessage.linkPreview.first {
|
||||
guard
|
||||
let linkPreviewResult = restoreLinkPreview(
|
||||
switch self
|
||||
.restoreLinkPreview(
|
||||
linkPreviewProto,
|
||||
standardMessage: standardMessage,
|
||||
chatItemId: chatItemId,
|
||||
context: context
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
}
|
||||
if let linkPreviewResult {
|
||||
(linkPreview, linkPreviewAttachment) = linkPreviewResult
|
||||
} else {
|
||||
linkPreview = nil
|
||||
linkPreviewAttachment = nil
|
||||
)
|
||||
.bubbleUp(
|
||||
MessageBackup.RestoredMessageContents.self,
|
||||
partialErrors: &partialErrors
|
||||
)
|
||||
{
|
||||
case .continue(let component):
|
||||
if let component {
|
||||
linkPreview = component.0
|
||||
linkPreviewAttachment = component.1
|
||||
} else {
|
||||
linkPreview = nil
|
||||
linkPreviewAttachment = nil
|
||||
}
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
} else {
|
||||
linkPreview = nil
|
||||
@ -1437,6 +1450,8 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
)),
|
||||
partialErrors + messageBodyErrors
|
||||
)
|
||||
case .unrecognizedEnum(let error):
|
||||
return .unrecognizedEnum(error)
|
||||
case .messageFailure(let messageBodyErrors):
|
||||
return .messageFailure(partialErrors + messageBodyErrors)
|
||||
}
|
||||
@ -1483,10 +1498,6 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
let swiftStyle: MessageBodyRanges.SingleStyle
|
||||
switch protoBodyRangeStyle {
|
||||
case .none, .UNRECOGNIZED:
|
||||
partialErrors.append(.restoreFrameError(
|
||||
.invalidProtoData(.unrecognizedBodyRangeStyle),
|
||||
chatItemId
|
||||
))
|
||||
continue
|
||||
case .bold:
|
||||
swiftStyle = .bold
|
||||
@ -1573,17 +1584,22 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
|
||||
let quoteBody: MessageBody?
|
||||
if quote.hasText {
|
||||
guard
|
||||
let bodyResult = restoreMessageBody(
|
||||
switch self
|
||||
.restoreMessageBody(
|
||||
text: quote.text.body,
|
||||
bodyRangeProtos: quote.text.bodyRanges,
|
||||
chatItemId: chatItemId
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
)
|
||||
.bubbleUp(
|
||||
(TSQuotedMessage, BackupProto_MessageAttachment?).self,
|
||||
partialErrors: &partialErrors
|
||||
)
|
||||
{
|
||||
case .continue(let component):
|
||||
quoteBody = component
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
quoteBody = bodyResult
|
||||
} else {
|
||||
quoteBody = nil
|
||||
}
|
||||
@ -1724,8 +1740,17 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
contactAttachment,
|
||||
chatItemId: chatItemId
|
||||
)
|
||||
guard let contact = contactResult.unwrap(partialErrors: &partialErrors) else {
|
||||
return .messageFailure(partialErrors)
|
||||
let contact: OWSContact
|
||||
switch contactResult
|
||||
.bubbleUp(
|
||||
MessageBackup.RestoredMessageContents.self,
|
||||
partialErrors: &partialErrors
|
||||
)
|
||||
{
|
||||
case .continue(let component):
|
||||
contact = component
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
let avatar: BackupProto_FilePointer?
|
||||
@ -1781,7 +1806,7 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
) -> RestoreInteractionResult<MessageBackup.RestoredMessageContents> {
|
||||
let giftBadge: OWSGiftBadge
|
||||
switch giftBadgeProto.state {
|
||||
case .unopened:
|
||||
case .unopened, .UNRECOGNIZED:
|
||||
giftBadge = .restoreFromBackup(
|
||||
receiptCredentialPresentation: giftBadgeProto.receiptCredentialPresentation,
|
||||
redemptionState: .pending
|
||||
@ -1805,11 +1830,6 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
receiptCredentialPresentation: nil,
|
||||
redemptionState: .pending
|
||||
)
|
||||
case .UNRECOGNIZED(_):
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.unrecognizedGiftBadgeState),
|
||||
chatItemId
|
||||
)])
|
||||
}
|
||||
|
||||
return .success(.giftBadge(MessageBackup.RestoredMessageContents.GiftBadge(
|
||||
@ -1860,13 +1880,21 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
nil
|
||||
}
|
||||
|
||||
guard
|
||||
let messageBody = restoreMessageBody(
|
||||
let messageBody: MessageBody?
|
||||
switch self
|
||||
.restoreMessageBody(
|
||||
textReply.text,
|
||||
chatItemId: chatItemId
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
)
|
||||
.bubbleUp(
|
||||
MessageBackup.RestoredMessageContents.self,
|
||||
partialErrors: &partialErrors
|
||||
)
|
||||
{
|
||||
case .continue(let component):
|
||||
messageBody = component
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
if let messageBody {
|
||||
@ -1890,10 +1918,9 @@ class MessageBackupTSMessageContentsArchiver: MessageBackupProtoArchiver {
|
||||
case .emoji(let string):
|
||||
replyType = .emoji(string)
|
||||
case .none:
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.directStoryReplyMessageUnknownType),
|
||||
chatItemId
|
||||
)])
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_DirectStoryReplyMessage.OneOf_Reply.self
|
||||
))
|
||||
}
|
||||
|
||||
return .success(.storyReply(.init(
|
||||
@ -1911,8 +1938,8 @@ private extension ArchivedPayment {
|
||||
senderOrRecipientAci: Aci,
|
||||
direction: Direction,
|
||||
interactionUniqueId: String?
|
||||
) -> ArchivedPayment? {
|
||||
var archivedPayment: ArchivedPayment?
|
||||
) -> ArchivedPayment {
|
||||
var archivedPayment: ArchivedPayment
|
||||
switch backup.status {
|
||||
case .failure(let reason):
|
||||
archivedPayment = ArchivedPayment(
|
||||
|
||||
@ -279,16 +279,21 @@ final class MessageBackupTSMessageEditHistoryArchiver<MessageType: TSMessage>
|
||||
) -> MessageBackup.RestoreInteractionResult<Void> {
|
||||
var partialErrors = [RestoreFrameError]()
|
||||
|
||||
guard
|
||||
let latestRevisionMessage = builder.restoreMessage(
|
||||
let latestRevisionMessage: MessageType
|
||||
switch builder
|
||||
.restoreMessage(
|
||||
topLevelChatItem,
|
||||
isPastRevision: false,
|
||||
hasPastRevisions: topLevelChatItem.revisions.count > 0,
|
||||
chatThread: chatThread,
|
||||
context: context
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
)
|
||||
.bubbleUp(Void.self, partialErrors: &partialErrors)
|
||||
{
|
||||
case .continue(let component):
|
||||
latestRevisionMessage = component
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
var earlierRevisionMessages = [MessageType]()
|
||||
@ -297,30 +302,39 @@ final class MessageBackupTSMessageEditHistoryArchiver<MessageType: TSMessage>
|
||||
/// how we want to insert them. Older revisions should be inserted
|
||||
/// before newer ones.
|
||||
for revisionChatItem in topLevelChatItem.revisions {
|
||||
guard
|
||||
let earlierRevisionMessage = builder.restoreMessage(
|
||||
let earlierRevisionMessage: MessageType
|
||||
switch builder
|
||||
.restoreMessage(
|
||||
revisionChatItem,
|
||||
isPastRevision: true,
|
||||
hasPastRevisions: false, // Past revisions can't have their own past revisions!
|
||||
chatThread: chatThread,
|
||||
context: context
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
)
|
||||
.bubbleUp(Void.self, partialErrors: &partialErrors)
|
||||
{
|
||||
case .continue(let component):
|
||||
earlierRevisionMessage = component
|
||||
case .bubbleUpError(let error):
|
||||
/// This means we won't attempt to restore any later revisions,
|
||||
/// but we can't be confident they would have restored
|
||||
/// successfully anyway.
|
||||
return .messageFailure(partialErrors)
|
||||
return error
|
||||
}
|
||||
|
||||
earlierRevisionMessages.append(earlierRevisionMessage)
|
||||
}
|
||||
|
||||
for earlierRevisionMessage in earlierRevisionMessages {
|
||||
guard
|
||||
let wasRead = earlierRevisionMessage.wasRead()
|
||||
.unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
let wasRead: Bool
|
||||
switch earlierRevisionMessage
|
||||
.wasRead()
|
||||
.bubbleUp(Void.self, partialErrors: &partialErrors)
|
||||
{
|
||||
case .continue(let component):
|
||||
wasRead = component
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
let editRecord = EditRecord(
|
||||
|
||||
@ -67,15 +67,19 @@ class MessageBackupTSOutgoingMessageArchiver {
|
||||
) -> MessageBackup.RestoreInteractionResult<Void> {
|
||||
var partialErrors = [RestoreFrameError]()
|
||||
|
||||
guard
|
||||
editHistoryArchiver.restoreMessageAndEditHistory(
|
||||
switch editHistoryArchiver
|
||||
.restoreMessageAndEditHistory(
|
||||
topLevelChatItem,
|
||||
chatThread: chatThread,
|
||||
context: context,
|
||||
builder: self
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
)
|
||||
.bubbleUp(Void.self, partialErrors: &partialErrors)
|
||||
{
|
||||
case .continue:
|
||||
break
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
if partialErrors.isEmpty {
|
||||
@ -317,35 +321,45 @@ extension MessageBackupTSOutgoingMessageArchiver: MessageBackupTSMessageEditHist
|
||||
context: MessageBackup.ChatItemRestoringContext
|
||||
) -> MessageBackup.RestoreInteractionResult<EditHistoryMessageType> {
|
||||
guard let chatItemType = chatItem.item else {
|
||||
// Unrecognized item type!
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.chatItemMissingItem),
|
||||
chatItem.id
|
||||
)])
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_Item.self
|
||||
))
|
||||
}
|
||||
|
||||
let outgoingDetails: BackupProto_ChatItem.OutgoingMessageDetails
|
||||
switch chatItem.directionalDetails {
|
||||
case .outgoing(let _outgoingDetails):
|
||||
outgoingDetails = _outgoingDetails
|
||||
case nil, .incoming, .directionless:
|
||||
case .incoming, .directionless:
|
||||
return .messageFailure([.restoreFrameError(
|
||||
.invalidProtoData(.revisionOfOutgoingMessageMissingOutgoingDetails),
|
||||
chatItem.id
|
||||
)])
|
||||
case nil:
|
||||
return .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_ChatItem.OneOf_DirectionalDetails.self
|
||||
))
|
||||
}
|
||||
|
||||
var partialErrors = [RestoreFrameError]()
|
||||
|
||||
guard
|
||||
let contents = contentsArchiver.restoreContents(
|
||||
let contents: MessageBackup.RestoredMessageContents
|
||||
switch contentsArchiver
|
||||
.restoreContents(
|
||||
chatItemType,
|
||||
chatItemId: chatItem.id,
|
||||
chatThread: chatThread,
|
||||
context: context
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
)
|
||||
.bubbleUp(
|
||||
EditHistoryMessageType.self,
|
||||
partialErrors: &partialErrors
|
||||
)
|
||||
{
|
||||
case .continue(let component):
|
||||
contents = component
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
let editState: TSEditState = {
|
||||
@ -359,29 +373,44 @@ extension MessageBackupTSOutgoingMessageArchiver: MessageBackupTSMessageEditHist
|
||||
}
|
||||
}()
|
||||
|
||||
guard
|
||||
let outgoingMessage = restoreAndInsertOutgoingMessage(
|
||||
let outgoingMessage: TSOutgoingMessage
|
||||
switch self
|
||||
.restoreAndInsertOutgoingMessage(
|
||||
chatItem: chatItem,
|
||||
contents: contents,
|
||||
outgoingDetails: outgoingDetails,
|
||||
editState: editState,
|
||||
context: context,
|
||||
chatThread: chatThread
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
)
|
||||
.bubbleUp(
|
||||
EditHistoryMessageType.self,
|
||||
partialErrors: &partialErrors
|
||||
)
|
||||
{
|
||||
case .continue(let component):
|
||||
outgoingMessage = component
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
guard
|
||||
contentsArchiver.restoreDownstreamObjects(
|
||||
switch contentsArchiver
|
||||
.restoreDownstreamObjects(
|
||||
message: outgoingMessage,
|
||||
thread: chatThread,
|
||||
chatItemId: chatItem.id,
|
||||
restoredContents: contents,
|
||||
context: context
|
||||
).unwrap(partialErrors: &partialErrors)
|
||||
else {
|
||||
return .messageFailure(partialErrors)
|
||||
)
|
||||
.bubbleUp(
|
||||
EditHistoryMessageType.self,
|
||||
partialErrors: &partialErrors
|
||||
)
|
||||
{
|
||||
case .continue:
|
||||
break
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
if partialErrors.isEmpty {
|
||||
@ -578,8 +607,12 @@ extension MessageBackupTSOutgoingMessageArchiver: MessageBackupTSMessageEditHist
|
||||
))
|
||||
}()
|
||||
|
||||
guard let outgoingMessage: TSOutgoingMessage = outgoingMessageResult.unwrap(partialErrors: &partialErrors) else {
|
||||
return .messageFailure(partialErrors)
|
||||
let outgoingMessage: TSOutgoingMessage
|
||||
switch outgoingMessageResult.bubbleUp(TSOutgoingMessage.self, partialErrors: &partialErrors) {
|
||||
case .continue(let component):
|
||||
outgoingMessage = component
|
||||
case .bubbleUpError(let error):
|
||||
return error
|
||||
}
|
||||
|
||||
do {
|
||||
@ -614,18 +647,13 @@ extension MessageBackupTSOutgoingMessageArchiver: MessageBackupTSMessageEditHist
|
||||
partialErrors: inout [RestoreFrameError],
|
||||
chatItemId: MessageBackup.ChatItemId
|
||||
) -> TSOutgoingMessageRecipientState? {
|
||||
guard let deliveryStatus = sendStatus.deliveryStatus else {
|
||||
partialErrors.append(.restoreFrameError(
|
||||
.invalidProtoData(.unrecognizedMessageSendStatus),
|
||||
chatItemId
|
||||
))
|
||||
return nil
|
||||
}
|
||||
|
||||
let recipientStatus: OWSOutgoingMessageRecipientStatus
|
||||
var wasSentByUD: Bool = false
|
||||
var errorCode: Int?
|
||||
switch deliveryStatus {
|
||||
switch sendStatus.deliveryStatus {
|
||||
case nil:
|
||||
// Fallback to pending
|
||||
recipientStatus = .pending
|
||||
case .pending(_):
|
||||
recipientStatus = .pending
|
||||
case .sent(let sent):
|
||||
|
||||
@ -67,9 +67,6 @@ extension MessageBackup {
|
||||
/// Custom chat colors should never have light/dark theme. The UI
|
||||
/// disallows it and the proto cannot represent it.
|
||||
case themedCustomChatColor
|
||||
/// An unknown type of wallpaper was found that we couldn't translate to proto,
|
||||
/// causing the wallpaper to be skipped.
|
||||
case unknownWallpaper
|
||||
|
||||
/// An incoming message has an invalid or missing author address information,
|
||||
/// causing the message to be skipped.
|
||||
@ -218,9 +215,6 @@ extension MessageBackup {
|
||||
/// Associated value provides the number of attachments.
|
||||
case unviewedViewOnceMessageTooManyAttachments(Int)
|
||||
|
||||
/// Restrictions for a call link are unknown.
|
||||
case callLinkRestrictionsUnknown
|
||||
|
||||
/// An ad hoc call's ``CallRecord/conversationId`` is not a
|
||||
/// call link, which is illegal.
|
||||
case adHocCallDoesNotHaveCallLinkAsConversationId
|
||||
@ -291,7 +285,6 @@ extension MessageBackup {
|
||||
.fileIOError,
|
||||
.groupMasterKeyError,
|
||||
.themedCustomChatColor,
|
||||
.unknownWallpaper,
|
||||
.unableToFetchRecipientIdentity,
|
||||
.distributionListMissingDistributionId,
|
||||
.distributionListHasDefaultViewMode,
|
||||
@ -342,7 +335,6 @@ extension MessageBackup {
|
||||
.unableToReadStoryContextAssociatedData,
|
||||
.unviewedViewOnceMessageMissingAttachment,
|
||||
.unviewedViewOnceMessageTooManyAttachments,
|
||||
.callLinkRestrictionsUnknown,
|
||||
.adHocCallDoesNotHaveCallLinkAsConversationId,
|
||||
.invalidAdHocCallTimestamp,
|
||||
.unexpectedRevisionsOnMessage:
|
||||
@ -362,7 +354,6 @@ extension MessageBackup {
|
||||
.fileIOError,
|
||||
.groupMasterKeyError,
|
||||
.themedCustomChatColor,
|
||||
.unknownWallpaper,
|
||||
.distributionListMissingDistributionId,
|
||||
.distributionListHasDefaultViewMode,
|
||||
.customDistributionListBlocklistViewMode,
|
||||
@ -408,7 +399,6 @@ extension MessageBackup {
|
||||
.unableToReadStoryContextAssociatedData,
|
||||
.unviewedViewOnceMessageMissingAttachment,
|
||||
.unviewedViewOnceMessageTooManyAttachments,
|
||||
.callLinkRestrictionsUnknown,
|
||||
.adHocCallDoesNotHaveCallLinkAsConversationId,
|
||||
.invalidAdHocCallTimestamp,
|
||||
.unexpectedRevisionsOnMessage:
|
||||
@ -570,35 +560,16 @@ extension MessageBackup {
|
||||
/// An invalid member (group, distribution list, etc) was specified as a distribution list member. Includes the offending proto
|
||||
case invalidDistributionListMember(protoClass: Any.Type)
|
||||
|
||||
/// A ``BackupProto/Recipient`` with a missing destination.
|
||||
case recipientMissingDestination
|
||||
|
||||
/// A ``BackupProto_Contact`` with unknown identityState.
|
||||
case unknownContactIdentityState
|
||||
|
||||
/// A ``BackupProto/Contact`` with no aci, pni, or e164.
|
||||
case contactWithoutIdentifiers
|
||||
/// A ``BackupProto/Contact`` for the local user. This shouldn't exist.
|
||||
case otherContactWithLocalIdentifiers
|
||||
/// A ``BackupProto/Contact`` missing info as to whether or not
|
||||
/// it is registered.
|
||||
case contactWithoutRegistrationInfo
|
||||
|
||||
/// A ``BackupProto_ChatStyle/BubbleColorPreset`` had an unrecognized case.
|
||||
case unrecognizedChatStyleBubbleColorPreset
|
||||
/// Some custom chat color identifier being referenced was not present earlier in the backup file.
|
||||
case customChatColorNotFound(CustomChatColorId)
|
||||
/// A ``BackupProto_ChatStyle/CustomChatColor`` had an unrecognized color oneof.
|
||||
case unrecognizedCustomChatStyleColor
|
||||
/// A ``BackupProto_ChatStyle/Gradient`` had less than two colors.
|
||||
case chatStyleGradientSingleOrNoColors
|
||||
/// A ``BackupProto_ChatStyle/WallpaperPreset`` had an unrecognized case.
|
||||
case unrecognizedChatStyleWallpaperPreset
|
||||
|
||||
/// A ``BackupProto/ChatItem`` was missing directional details.
|
||||
case chatItemMissingDirectionalDetails
|
||||
/// A ``BackupProto/ChatItem`` was missing its actual item.
|
||||
case chatItemMissingItem
|
||||
/// A directionless chat item was not an update message.
|
||||
case directionlessChatItemNotUpdateMessage
|
||||
/// A ``BackupProto/ChatItem`` has a missing or invalid dateSent.
|
||||
@ -610,8 +581,6 @@ extension MessageBackup {
|
||||
/// Outgoing message's `BackupProto_SendStatus` can only be for `BackupProto_Contacts`.
|
||||
/// One in the backup was to a group, self recipient, or something else.
|
||||
case outgoingNonContactMessageRecipient
|
||||
/// A `BackupProto_SendStatus` had an unregonized `BackupProto_SendStatusStatus`.
|
||||
case unrecognizedMessageSendStatus
|
||||
|
||||
/// `BackupProto_Reaction` must come from either an Aci or an E164.
|
||||
/// One in the backup did not.
|
||||
@ -624,8 +593,6 @@ extension MessageBackup {
|
||||
case directStoryReplyMessageEmpty
|
||||
/// A ``BackupProto_DirectStoryReplyMessage`` had an empty text body, but a long-text attachment was present.
|
||||
case directStoryReplyMessageEmptyWithLongText
|
||||
/// A ``BackupProto_DirectStoryReplyMessage/OneOf_Reply`` has an unknown case.
|
||||
case directStoryReplyMessageUnknownType
|
||||
/// A ``BackupProto_DirectStoryReplyMessage`` author didn't have an aci.
|
||||
case directStoryReplyFromNonAci
|
||||
/// A ``BackupProto_DirectStoryReplyMessage`` was in a group thread.
|
||||
@ -635,9 +602,6 @@ extension MessageBackup {
|
||||
/// message body (the body text must always be a prefix of the long text)
|
||||
case longTextStandardMessageMissingBody
|
||||
|
||||
/// A `BackupProto_BodyRange` with a missing or unrecognized style.
|
||||
case unrecognizedBodyRangeStyle
|
||||
|
||||
/// A quoted message had no body, attachment, gift badge, or other
|
||||
/// content in its representation of the original being quoted.
|
||||
case quotedMessageEmptyContent
|
||||
@ -652,25 +616,16 @@ extension MessageBackup {
|
||||
case contactMessageMissingContactAttachment
|
||||
/// A ``BackupProto_ContactAttachment/Phone/value`` was missing or empty.
|
||||
case contactAttachmentPhoneNumberMissingValue
|
||||
/// A ``BackupProto_ContactAttachment/Phone/type`` was unknown.
|
||||
case contactAttachmentPhoneNumberUnknownType
|
||||
/// A ``BackupProto_ContactAttachment/Email/value`` was missing or empty.
|
||||
case contactAttachmentEmailMissingValue
|
||||
/// A ``BackupProto_ContactAttachment/Email/type`` was unknown.
|
||||
case contactAttachmentEmailUnknownType
|
||||
/// A ``BackupProto_ContactAttachment/PostalAddress`` with all empty fields;
|
||||
/// at least some field has to be nonempty to be a valid address.
|
||||
case contactAttachmentEmptyAddress
|
||||
/// A ``BackupProto_ContactAttachment/PostalAddress/type`` was unknown.
|
||||
case contactAttachmentAddressUnknownType
|
||||
|
||||
/// A `BackupProto_Group's` gv2 master key could not be parsed by libsignal.
|
||||
case invalidGV2MasterKey
|
||||
/// A `BackupProto_Group` was missing its group snapshot.
|
||||
case missingGV2GroupSnapshot
|
||||
/// A ``BackupProtoGroup/BackupProtoFullGroupMember/role`` was
|
||||
/// unrecognized. Includes the class of the offending proto.
|
||||
case unrecognizedGV2MemberRole(protoClass: Any.Type)
|
||||
/// A ``BackupProtoGroup/BackupProtoMemberPendingProfileKey`` was
|
||||
/// missing its member details.
|
||||
case invitedGV2MemberMissingMemberDetails
|
||||
@ -684,11 +639,6 @@ extension MessageBackup {
|
||||
/// A `BackupProto_GroupSequenceOfRequestsAndCancelsUpdate` where
|
||||
/// the requester is the local user, which isn't allowed.
|
||||
case sequenceOfRequestsAndCancelsWithLocalAci
|
||||
/// An unrecognized `BackupProto_GroupChangeChatUpdate`.
|
||||
case unrecognizedGroupUpdate
|
||||
|
||||
/// A frame was entirely missing its enclosed item.
|
||||
case frameMissingItem
|
||||
|
||||
/// A profile key for the local user that could not be parsed into a valid aes256 key
|
||||
case invalidLocalProfileKey
|
||||
@ -698,28 +648,16 @@ extension MessageBackup {
|
||||
/// A `BackupProto_IndividualCall` chat item update was associated
|
||||
/// with a thread that was not a contact thread.
|
||||
case individualCallNotInContactThread
|
||||
/// A `BackupProto_IndividualCall` had an unrecognized type.
|
||||
case individualCallUnrecognizedType
|
||||
/// A `BackupProto_IndividualCall` had an unrecognized direction.
|
||||
case individualCallUnrecognizedDirection
|
||||
/// A `BackupProto_IndividualCall` had an unrecognized state.
|
||||
case individualCallUnrecognizedState
|
||||
|
||||
/// A `BackupProto_GroupCall` chat item update was associated with
|
||||
/// a thread that was not a group thread.
|
||||
case groupCallNotInGroupThread
|
||||
/// A `BackupProto_GroupCall` had an unrecognized state.
|
||||
case groupCallUnrecognizedState
|
||||
/// A `BackupProto_GroupCall` referenced a recipient that was not
|
||||
/// a contact or otherwise did not contain an ACI.
|
||||
case groupCallRecipientIdNotAnAci(RecipientId)
|
||||
|
||||
/// `BackupProto_DistributionListItem` was missing its item
|
||||
case distributionListItemMissingItem
|
||||
/// `BackupProto_DistributionList.distributionId` was not a valid UUID
|
||||
case invalidDistributionListId
|
||||
/// `BackupProto_DistributionList.privacyMode` was missing, or contained an unknown privacy mode
|
||||
case invalidDistributionListPrivacyMode
|
||||
/// A custom (non-MyStory) distribution list had ``BackupProto_DistributionList/PrivacyMode/all``
|
||||
/// or ``BackupProto_DistributionList/PrivacyMode/allExcept``, which are only allowed
|
||||
/// for My Story.
|
||||
@ -734,10 +672,6 @@ extension MessageBackup {
|
||||
/// other than a ``BackupProto_AdHocCall``; this isn't allowed.
|
||||
case callLinkUsedAsChatRecipient
|
||||
|
||||
/// A ``BackupProto/ChatUpdateMessage/update`` was empty.
|
||||
case emptyChatUpdateMessage
|
||||
/// A ``BackupProto/SimpleChatUpdate/type`` was unrecognized.
|
||||
case unrecognizedSimpleChatUpdate
|
||||
/// A "verification state change" simple chat update was
|
||||
/// associated with a non-contact recipient.
|
||||
case verificationStateChangeNotFromContact
|
||||
@ -760,9 +694,9 @@ extension MessageBackup {
|
||||
/// associated with a non-contact recipient.
|
||||
case unsupportedProtocolVersionNotFromContact
|
||||
|
||||
/// An ArchivedPayment was unable to be crated from the
|
||||
/// restored payment information.
|
||||
case unrecognizedPaymentTransaction
|
||||
/// An ``BackupProto_PaymentNotification`` was sent
|
||||
/// in a group chat (not a 1:1 chat).
|
||||
case paymentNotificationInGroup
|
||||
|
||||
/// An "expiration timer update" was in a non-contact thread.
|
||||
/// - Note
|
||||
@ -786,8 +720,6 @@ extension MessageBackup {
|
||||
/// A "session switchover update" was not authored by a contact.
|
||||
case sessionSwitchoverUpdateNotFromContact
|
||||
|
||||
/// A "learned profile update" was missing its previous name.
|
||||
case learnedProfileUpdateMissingPreviousName
|
||||
/// A "learned profile update" was not authored by a contact.
|
||||
case learnedProfileUpdateNotFromContact
|
||||
|
||||
@ -814,18 +746,9 @@ extension MessageBackup {
|
||||
/// A ``BackupProto_MessageAttachment/clientUuid`` contained an invalid UUID.
|
||||
case invalidAttachmentClientUUID
|
||||
|
||||
/// A ``BackupProto_GiftBadge/state`` was unrecognized.
|
||||
case unrecognizedGiftBadgeState
|
||||
|
||||
/// A ``BackupProto_CallLink/rootKey`` was invalid.
|
||||
case callLinkInvalidRootKey
|
||||
/// A ``BackupProto_CallLink/restrictions`` was unrecognized.
|
||||
case callLinkRestrictionsUnrecognizedType
|
||||
|
||||
/// A ``BackupProto_AdHocCall/state`` was unknown.
|
||||
case adHocCallUnknownState
|
||||
/// A ``BackupProto_AdHocCall/state`` was unrecognized.
|
||||
case adHocCallUnrecognizedState
|
||||
/// The recipient on an ad hoc call was not a call link. No other
|
||||
/// recipient types are valid for an ad hoc call.
|
||||
case recipientOfAdHocCallWasNotCallLink
|
||||
@ -919,75 +842,50 @@ extension MessageBackup {
|
||||
.invalidProfileKey,
|
||||
.invalidContactIdentityKey,
|
||||
.invalidDistributionListMember,
|
||||
.recipientMissingDestination,
|
||||
.unknownContactIdentityState,
|
||||
.contactWithoutIdentifiers,
|
||||
.otherContactWithLocalIdentifiers,
|
||||
.contactWithoutRegistrationInfo,
|
||||
.chatItemMissingDirectionalDetails,
|
||||
.chatItemMissingItem,
|
||||
.chatItemInvalidDateSent,
|
||||
.unrecognizedChatStyleBubbleColorPreset,
|
||||
.unrecognizedCustomChatStyleColor,
|
||||
.chatStyleGradientSingleOrNoColors,
|
||||
.unrecognizedChatStyleWallpaperPreset,
|
||||
.directionlessChatItemNotUpdateMessage,
|
||||
.incomingMessageNotFromAciOrE164,
|
||||
.outgoingNonContactMessageRecipient,
|
||||
.unrecognizedMessageSendStatus,
|
||||
.reactionNotFromAciOrE164,
|
||||
.emptyStandardMessage,
|
||||
.directStoryReplyMessageEmpty,
|
||||
.directStoryReplyMessageEmptyWithLongText,
|
||||
.directStoryReplyMessageUnknownType,
|
||||
.directStoryReplyFromNonAci,
|
||||
.directStoryReplyInGroupThread,
|
||||
.longTextStandardMessageMissingBody,
|
||||
.unrecognizedBodyRangeStyle,
|
||||
.quotedMessageEmptyContent,
|
||||
.linkPreviewEmptyUrl,
|
||||
.linkPreviewUrlNotInBody,
|
||||
.contactMessageMissingContactAttachment,
|
||||
.contactAttachmentPhoneNumberMissingValue,
|
||||
.contactAttachmentPhoneNumberUnknownType,
|
||||
.contactAttachmentEmailMissingValue,
|
||||
.contactAttachmentEmailUnknownType,
|
||||
.contactAttachmentEmptyAddress,
|
||||
.contactAttachmentAddressUnknownType,
|
||||
.invalidGV2MasterKey,
|
||||
.missingGV2GroupSnapshot,
|
||||
.unrecognizedGV2MemberRole,
|
||||
.invitedGV2MemberMissingMemberDetails,
|
||||
.failedToBuildGV2GroupModel,
|
||||
.groupUpdateMessageInNonGroupChat,
|
||||
.emptyGroupUpdates,
|
||||
.sequenceOfRequestsAndCancelsWithLocalAci,
|
||||
.unrecognizedGroupUpdate,
|
||||
.frameMissingItem,
|
||||
.invalidLocalProfileKey,
|
||||
.invalidLocalUsernameLink,
|
||||
.individualCallNotInContactThread,
|
||||
.individualCallUnrecognizedType,
|
||||
.individualCallUnrecognizedDirection,
|
||||
.individualCallUnrecognizedState,
|
||||
.groupCallNotInGroupThread,
|
||||
.groupCallUnrecognizedState,
|
||||
.groupCallRecipientIdNotAnAci,
|
||||
.distributionListItemMissingItem,
|
||||
.invalidDistributionListId,
|
||||
.invalidDistributionListPrivacyMode,
|
||||
.customDistributionListPrivacyModeAllOrAllExcept,
|
||||
.invalidDistributionListDeletionTimestamp,
|
||||
.distributionListUsedAsChatRecipient,
|
||||
.emptyChatUpdateMessage,
|
||||
.unrecognizedSimpleChatUpdate,
|
||||
.verificationStateChangeNotFromContact,
|
||||
.phoneNumberChangeNotFromContact,
|
||||
.endSessionNotFromContact,
|
||||
.decryptionErrorNotFromContact,
|
||||
.paymentsActivationRequestNotFromAci,
|
||||
.paymentsActivatedNotFromAci,
|
||||
.unrecognizedPaymentTransaction,
|
||||
.paymentNotificationInGroup,
|
||||
.unsupportedProtocolVersionNotFromContact,
|
||||
.expirationTimerUpdateNotInContactThread,
|
||||
.expirationTimerOverflowedLocalType,
|
||||
@ -995,7 +893,6 @@ extension MessageBackup {
|
||||
.profileChangeUpdateNotFromContact,
|
||||
.threadMergeUpdateNotFromContact,
|
||||
.sessionSwitchoverUpdateNotFromContact,
|
||||
.learnedProfileUpdateMissingPreviousName,
|
||||
.learnedProfileUpdateNotFromContact,
|
||||
.revisionOfIncomingMessageMissingIncomingDetails,
|
||||
.revisionOfOutgoingMessageMissingOutgoingDetails,
|
||||
@ -1004,12 +901,8 @@ extension MessageBackup {
|
||||
.filePointerMissingEncryptionKey,
|
||||
.filePointerMissingDigest,
|
||||
.invalidAttachmentClientUUID,
|
||||
.unrecognizedGiftBadgeState,
|
||||
.callLinkInvalidRootKey,
|
||||
.callLinkRestrictionsUnrecognizedType,
|
||||
.callLinkUsedAsChatRecipient,
|
||||
.adHocCallUnknownState,
|
||||
.adHocCallUnrecognizedState,
|
||||
.recipientOfAdHocCallWasNotCallLink:
|
||||
// Collapse all others by the id of the containing frame.
|
||||
return idLogString
|
||||
@ -1054,74 +947,49 @@ extension MessageBackup {
|
||||
.invalidProfileKey,
|
||||
.invalidContactIdentityKey,
|
||||
.invalidDistributionListMember,
|
||||
.recipientMissingDestination,
|
||||
.unknownContactIdentityState,
|
||||
.contactWithoutIdentifiers,
|
||||
.otherContactWithLocalIdentifiers,
|
||||
.contactWithoutRegistrationInfo,
|
||||
.chatItemMissingDirectionalDetails,
|
||||
.chatItemMissingItem,
|
||||
.chatItemInvalidDateSent,
|
||||
.unrecognizedChatStyleBubbleColorPreset,
|
||||
.unrecognizedCustomChatStyleColor,
|
||||
.chatStyleGradientSingleOrNoColors,
|
||||
.customChatColorNotFound,
|
||||
.unrecognizedChatStyleWallpaperPreset,
|
||||
.directionlessChatItemNotUpdateMessage,
|
||||
.incomingMessageNotFromAciOrE164,
|
||||
.outgoingNonContactMessageRecipient,
|
||||
.unrecognizedMessageSendStatus,
|
||||
.reactionNotFromAciOrE164,
|
||||
.emptyStandardMessage,
|
||||
.directStoryReplyMessageEmpty,
|
||||
.directStoryReplyMessageEmptyWithLongText,
|
||||
.directStoryReplyMessageUnknownType,
|
||||
.directStoryReplyFromNonAci,
|
||||
.directStoryReplyInGroupThread,
|
||||
.longTextStandardMessageMissingBody,
|
||||
.unrecognizedBodyRangeStyle,
|
||||
.linkPreviewEmptyUrl,
|
||||
.contactMessageMissingContactAttachment,
|
||||
.contactAttachmentPhoneNumberMissingValue,
|
||||
.contactAttachmentPhoneNumberUnknownType,
|
||||
.contactAttachmentEmailMissingValue,
|
||||
.contactAttachmentEmailUnknownType,
|
||||
.contactAttachmentEmptyAddress,
|
||||
.contactAttachmentAddressUnknownType,
|
||||
.invalidGV2MasterKey,
|
||||
.missingGV2GroupSnapshot,
|
||||
.unrecognizedGV2MemberRole,
|
||||
.invitedGV2MemberMissingMemberDetails,
|
||||
.failedToBuildGV2GroupModel,
|
||||
.groupUpdateMessageInNonGroupChat,
|
||||
.emptyGroupUpdates,
|
||||
.sequenceOfRequestsAndCancelsWithLocalAci,
|
||||
.unrecognizedGroupUpdate,
|
||||
.frameMissingItem,
|
||||
.invalidLocalProfileKey,
|
||||
.invalidLocalUsernameLink,
|
||||
.individualCallNotInContactThread,
|
||||
.individualCallUnrecognizedType,
|
||||
.individualCallUnrecognizedDirection,
|
||||
.individualCallUnrecognizedState,
|
||||
.groupCallNotInGroupThread,
|
||||
.groupCallUnrecognizedState,
|
||||
.groupCallRecipientIdNotAnAci,
|
||||
.distributionListItemMissingItem,
|
||||
.invalidDistributionListId,
|
||||
.invalidDistributionListPrivacyMode,
|
||||
.customDistributionListPrivacyModeAllOrAllExcept,
|
||||
.invalidDistributionListDeletionTimestamp,
|
||||
.distributionListUsedAsChatRecipient,
|
||||
.emptyChatUpdateMessage,
|
||||
.unrecognizedSimpleChatUpdate,
|
||||
.verificationStateChangeNotFromContact,
|
||||
.phoneNumberChangeNotFromContact,
|
||||
.endSessionNotFromContact,
|
||||
.decryptionErrorNotFromContact,
|
||||
.paymentsActivationRequestNotFromAci,
|
||||
.paymentsActivatedNotFromAci,
|
||||
.unrecognizedPaymentTransaction,
|
||||
.paymentNotificationInGroup,
|
||||
.unsupportedProtocolVersionNotFromContact,
|
||||
.expirationTimerUpdateNotInContactThread,
|
||||
.expirationTimerOverflowedLocalType,
|
||||
@ -1129,7 +997,6 @@ extension MessageBackup {
|
||||
.profileChangeUpdateNotFromContact,
|
||||
.threadMergeUpdateNotFromContact,
|
||||
.sessionSwitchoverUpdateNotFromContact,
|
||||
.learnedProfileUpdateMissingPreviousName,
|
||||
.learnedProfileUpdateNotFromContact,
|
||||
.revisionOfIncomingMessageMissingIncomingDetails,
|
||||
.revisionOfOutgoingMessageMissingOutgoingDetails,
|
||||
@ -1138,12 +1005,8 @@ extension MessageBackup {
|
||||
.filePointerMissingEncryptionKey,
|
||||
.filePointerMissingDigest,
|
||||
.invalidAttachmentClientUUID,
|
||||
.unrecognizedGiftBadgeState,
|
||||
.callLinkInvalidRootKey,
|
||||
.callLinkRestrictionsUnrecognizedType,
|
||||
.callLinkUsedAsChatRecipient,
|
||||
.adHocCallUnknownState,
|
||||
.adHocCallUnrecognizedState,
|
||||
.recipientOfAdHocCallWasNotCallLink:
|
||||
return .error
|
||||
case .quotedMessageEmptyContent:
|
||||
|
||||
@ -45,6 +45,9 @@ extension MessageBackup {
|
||||
/// Frames are always restored individually.
|
||||
public enum RestoreFrameResult<ProtoIdType: MessageBackupLoggableId> {
|
||||
case success
|
||||
/// There was an unrecognized enum (or oneOf) for which we skip restoring this frame
|
||||
/// but we should proceed restoring other frames.
|
||||
case unrecognizedEnum(UnrecognizedEnumError)
|
||||
/// We managed to restore some part of the frame, meaning it is represented in our database.
|
||||
/// For example, we restored a message but dropped some invalid recipients.
|
||||
/// Generally restoration of other frames can proceed, but the caller can determine
|
||||
@ -55,6 +58,21 @@ extension MessageBackup {
|
||||
/// whether to stop or not based on the specific error(s).
|
||||
case failure([RestoreFrameError<ProtoIdType>])
|
||||
}
|
||||
|
||||
public class UnrecognizedEnumError: MessageBackupLoggableError {
|
||||
|
||||
private let enumType: Any.Type
|
||||
|
||||
init(enumType: Any.Type) {
|
||||
self.enumType = enumType
|
||||
}
|
||||
|
||||
var typeLogString: String { String(describing: enumType) }
|
||||
var idLogString: String { "Unrecognized Enum" }
|
||||
var callsiteLogString: String { "" }
|
||||
var collapseKey: String? { typeLogString }
|
||||
var logLevel: MessageBackup.LogLevel { .warning }
|
||||
}
|
||||
}
|
||||
|
||||
public protocol MessageBackupProtoArchiver {
|
||||
|
||||
@ -149,19 +149,14 @@ public class MessageBackupCallLinkRecipientArchiver: MessageBackupProtoArchiver
|
||||
adminKey = nil
|
||||
}
|
||||
|
||||
var partialErrors = [MessageBackup.RestoreFrameError<RecipientId>]()
|
||||
|
||||
let restrictions: CallLinkRecord.Restrictions
|
||||
switch callLinkProto.restrictions {
|
||||
case .adminApproval:
|
||||
restrictions = .adminApproval
|
||||
case .none:
|
||||
restrictions = .none
|
||||
case .unknown:
|
||||
case .unknown, .UNRECOGNIZED:
|
||||
restrictions = .unknown
|
||||
case .UNRECOGNIZED:
|
||||
partialErrors.append(.restoreFrameError(.invalidProtoData(.callLinkRestrictionsUnrecognizedType), recipient.recipientId))
|
||||
restrictions = .adminApproval
|
||||
}
|
||||
|
||||
do {
|
||||
@ -181,11 +176,7 @@ public class MessageBackupCallLinkRecipientArchiver: MessageBackupProtoArchiver
|
||||
return .failure([.restoreFrameError(.databaseInsertionFailed(error), recipient.recipientId)])
|
||||
}
|
||||
|
||||
if partialErrors.isEmpty {
|
||||
return .success
|
||||
} else {
|
||||
return .partialRestore(partialErrors)
|
||||
}
|
||||
return .success
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -537,10 +537,9 @@ public class MessageBackupContactRecipientArchiver: MessageBackupProtoArchiver {
|
||||
let unregisteredTimestamp: UInt64?
|
||||
switch contactProto.registration {
|
||||
case nil:
|
||||
return .failure([.restoreFrameError(
|
||||
.invalidProtoData(.contactWithoutRegistrationInfo),
|
||||
recipient.recipientId
|
||||
)])
|
||||
// We treat unknown values as registered.
|
||||
isRegistered = true
|
||||
unregisteredTimestamp = nil
|
||||
case .notRegistered(let notRegisteredProto):
|
||||
isRegistered = false
|
||||
unregisteredTimestamp = notRegisteredProto.unregisteredTimestamp
|
||||
@ -661,10 +660,7 @@ public class MessageBackupContactRecipientArchiver: MessageBackupProtoArchiver {
|
||||
case .unverified:
|
||||
verificationState = .noLongerVerified
|
||||
case .UNRECOGNIZED:
|
||||
return .failure([.restoreFrameError(
|
||||
.invalidProtoData(.unknownContactIdentityState),
|
||||
recipientProto.recipientId
|
||||
)])
|
||||
verificationState = .default
|
||||
}
|
||||
|
||||
// Write directly to the OWSRecipientIdentity table, bypassing
|
||||
|
||||
@ -274,7 +274,9 @@ public class MessageBackupDistributionListRecipientArchiver: MessageBackupProtoA
|
||||
context: context
|
||||
)
|
||||
case nil:
|
||||
result = restoreFrameError(.invalidProtoData(.distributionListItemMissingItem))
|
||||
result = .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_DistributionListItem.OneOf_Item.self
|
||||
))
|
||||
}
|
||||
|
||||
context[recipient.recipientId] = .distributionList(distributionId)
|
||||
@ -334,10 +336,9 @@ public class MessageBackupDistributionListRecipientArchiver: MessageBackupProtoA
|
||||
partialErrors: &partialErrors
|
||||
)
|
||||
case .unknown, .UNRECOGNIZED:
|
||||
return .failure([.restoreFrameError(
|
||||
.invalidProtoData(.invalidDistributionListPrivacyMode),
|
||||
recipientId
|
||||
)])
|
||||
// Fallback to an empty explicit list.
|
||||
viewMode = .explicit
|
||||
addresses = []
|
||||
}
|
||||
|
||||
// MyStory is created during warmCaches(), so it should be present at this point.
|
||||
|
||||
@ -272,9 +272,7 @@ public class MessageBackupGroupRecipientArchiver: MessageBackupProtoArchiver {
|
||||
guard let aci = try? Aci.parseFrom(serviceIdBinary: fullMember.userID) else {
|
||||
return restoreFrameError(.invalidProtoData(.invalidAci(protoClass: BackupProto_Group.Member.self)))
|
||||
}
|
||||
guard let role = TSGroupMemberRole(backupProtoRole: fullMember.role) else {
|
||||
return restoreFrameError(.invalidProtoData(.unrecognizedGV2MemberRole(protoClass: BackupProto_Group.Member.self)))
|
||||
}
|
||||
let role = TSGroupMemberRole(backupProtoRole: fullMember.role)
|
||||
|
||||
groupMembershipBuilder.addFullMember(aci, role: role)
|
||||
fullGroupMemberAcis.insert(aci)
|
||||
@ -287,9 +285,7 @@ public class MessageBackupGroupRecipientArchiver: MessageBackupProtoArchiver {
|
||||
guard let serviceId = try? ServiceId.parseFrom(serviceIdBinary: memberDetails.userID) else {
|
||||
return restoreFrameError(.invalidProtoData(.invalidServiceId(protoClass: BackupProto_Group.MemberPendingProfileKey.self)))
|
||||
}
|
||||
guard let role = TSGroupMemberRole(backupProtoRole: memberDetails.role) else {
|
||||
return restoreFrameError(.invalidProtoData(.unrecognizedGV2MemberRole(protoClass: BackupProto_Group.MemberPendingProfileKey.self)))
|
||||
}
|
||||
let role = TSGroupMemberRole(backupProtoRole: memberDetails.role)
|
||||
guard let addedByAci = try? Aci.parseFrom(serviceIdBinary: invitedMember.addedByUserID) else {
|
||||
return restoreFrameError(.invalidProtoData(.invalidAci(protoClass: BackupProto_Group.MemberPendingProfileKey.self)))
|
||||
}
|
||||
@ -502,9 +498,11 @@ private extension BackupProto_Group.Member {
|
||||
// MARK: -
|
||||
|
||||
private extension TSGroupMemberRole {
|
||||
init?(backupProtoRole: BackupProto_Group.Member.Role) {
|
||||
init(backupProtoRole: BackupProto_Group.Member.Role) {
|
||||
switch backupProtoRole {
|
||||
case .unknown, .UNRECOGNIZED: return nil
|
||||
case .unknown, .UNRECOGNIZED:
|
||||
// Fallback to normal (default)
|
||||
self = .normal
|
||||
case .default: self = .normal
|
||||
case .administrator: self = .administrator
|
||||
}
|
||||
|
||||
@ -874,10 +874,9 @@ public class MessageBackupManagerImpl: MessageBackupManager {
|
||||
let recipientResult: MessageBackup.RestoreFrameResult<MessageBackup.RecipientId>
|
||||
switch recipient.destination {
|
||||
case nil:
|
||||
recipientResult = .failure([.restoreFrameError(
|
||||
.invalidProtoData(.recipientMissingDestination),
|
||||
recipient.recipientId
|
||||
)])
|
||||
recipientResult = .unrecognizedEnum(MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_Recipient.OneOf_Destination.self
|
||||
))
|
||||
case .self_p(let selfRecipientProto):
|
||||
recipientResult = localRecipientArchiver.restoreSelfRecipient(
|
||||
selfRecipientProto,
|
||||
@ -919,6 +918,9 @@ public class MessageBackupManagerImpl: MessageBackupManager {
|
||||
switch recipientResult {
|
||||
case .success:
|
||||
return
|
||||
case .unrecognizedEnum(let error):
|
||||
frameErrors.append(LoggableErrorAndProto(error: error, wasFatal: false, protoFrame: recipient))
|
||||
return
|
||||
case .partialRestore(let errors):
|
||||
frameErrors.append(contentsOf: errors.map { LoggableErrorAndProto(error: $0, wasFatal: false, protoFrame: recipient) })
|
||||
case .failure(let errors):
|
||||
@ -933,6 +935,9 @@ public class MessageBackupManagerImpl: MessageBackupManager {
|
||||
switch chatResult {
|
||||
case .success:
|
||||
return
|
||||
case .unrecognizedEnum(let error):
|
||||
frameErrors.append(LoggableErrorAndProto(error: error, wasFatal: false, protoFrame: chat))
|
||||
return
|
||||
case .partialRestore(let errors):
|
||||
frameErrors.append(contentsOf: errors.map { LoggableErrorAndProto(error: $0, wasFatal: false, protoFrame: chat) })
|
||||
case .failure(let errors):
|
||||
@ -947,6 +952,9 @@ public class MessageBackupManagerImpl: MessageBackupManager {
|
||||
switch chatItemResult {
|
||||
case .success:
|
||||
return
|
||||
case .unrecognizedEnum(let error):
|
||||
frameErrors.append(LoggableErrorAndProto(error: error, wasFatal: false, protoFrame: chatItem))
|
||||
return
|
||||
case .partialRestore(let errors):
|
||||
frameErrors.append(contentsOf: errors.map { LoggableErrorAndProto(error: $0, wasFatal: false, protoFrame: chatItem) })
|
||||
case .failure(let errors):
|
||||
@ -962,6 +970,9 @@ public class MessageBackupManagerImpl: MessageBackupManager {
|
||||
switch accountDataResult {
|
||||
case .success:
|
||||
return
|
||||
case .unrecognizedEnum(let error):
|
||||
frameErrors.append(LoggableErrorAndProto(error: error, wasFatal: false, protoFrame: backupProtoAccountData))
|
||||
return
|
||||
case .partialRestore(let errors):
|
||||
frameErrors.append(contentsOf: errors.map { LoggableErrorAndProto(error: $0, wasFatal: false, protoFrame: backupProtoAccountData) })
|
||||
case .failure(let errors):
|
||||
@ -976,6 +987,9 @@ public class MessageBackupManagerImpl: MessageBackupManager {
|
||||
switch stickerPackResult {
|
||||
case .success:
|
||||
return
|
||||
case .unrecognizedEnum(let error):
|
||||
frameErrors.append(LoggableErrorAndProto(error: error, wasFatal: false, protoFrame: backupProtoStickerPack))
|
||||
return
|
||||
case .partialRestore(let errors):
|
||||
frameErrors.append(contentsOf: errors.map { LoggableErrorAndProto(error: $0, wasFatal: false, protoFrame: backupProtoStickerPack) })
|
||||
case .failure(let errors):
|
||||
@ -990,6 +1004,9 @@ public class MessageBackupManagerImpl: MessageBackupManager {
|
||||
switch adHocCallResult {
|
||||
case .success:
|
||||
return
|
||||
case .unrecognizedEnum(let error):
|
||||
frameErrors.append(LoggableErrorAndProto(error: error, wasFatal: false, protoFrame: backupProtoAdHocCall))
|
||||
return
|
||||
case .partialRestore(let errors):
|
||||
frameErrors.append(contentsOf: errors.map { LoggableErrorAndProto(error: $0, wasFatal: false, protoFrame: backupProtoAdHocCall) })
|
||||
case .failure(let errors):
|
||||
@ -1006,11 +1023,9 @@ public class MessageBackupManagerImpl: MessageBackupManager {
|
||||
break
|
||||
case nil:
|
||||
if hasMoreFrames {
|
||||
owsFailDebug("Frame missing item!")
|
||||
frameErrors.append(LoggableErrorAndProto(
|
||||
error: MessageBackup.RestoreFrameError.restoreFrameError(
|
||||
.invalidProtoData(.frameMissingItem),
|
||||
MessageBackup.EmptyFrameId.shared
|
||||
error: MessageBackup.UnrecognizedEnumError(
|
||||
enumType: BackupProto_Frame.OneOf_Item.self
|
||||
),
|
||||
wasFatal: false
|
||||
))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user