Signal-iOS/SignalServiceKit/MessageBackup/FileStreams/MessageBackupProtoOutputStream.swift
2024-04-29 19:17:00 -05:00

92 lines
2.9 KiB
Swift

//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Wire
extension MessageBackup {
public enum ProtoOutputStreamWriteResult {
case success
/// Unable to serialize the provided proto object.
/// Should never happen, and catastrophic if it does.
case protoSerializationError(Swift.Error)
/// Failure writing at file I/O level.
case fileIOError(Swift.Error)
}
}
/**
* Output stream for reading and writing a backup file on disk.
*
* The backup file is just a sequence of serialized proto bytes, back to back, delimited by varint
* byte sizes so we know how much to read into memory to deserialize the next proto.
* The output stream abstracts over this, and allows callers to just think in terms of "frames",
* the individual proto objects that we write one at a time.
*/
public protocol MessageBackupProtoOutputStream {
/// Write a header (BakckupInfo) to the backup file.
/// It is the caller's responsibility to ensure this is always written, and is the first thing written,
/// in order to produce a valid backup file.
func writeHeader(_ header: BackupProto.BackupInfo) -> MessageBackup.ProtoOutputStreamWriteResult
/// Write a frame to the backup file.
func writeFrame(_ frame: BackupProto.Frame) -> MessageBackup.ProtoOutputStreamWriteResult
/// Closes the output stream.
func closeFileStream() throws
}
internal class MessageBackupProtoOutputStreamImpl: MessageBackupProtoOutputStream {
private var outputStream: OutputStreamable
private var outputStreamDelegate: StreamDelegate
private var fileUrl: URL
private var uploadMetadata: Upload.BackupUploadMetadata?
internal init(
outputStream: OutputStreamable,
outputStreamDelegate: StreamDelegate,
fileURL: URL
) {
self.outputStream = outputStream
self.outputStreamDelegate = outputStreamDelegate
self.fileUrl = fileURL
}
internal func writeHeader(_ header: BackupProto.BackupInfo) -> MessageBackup.ProtoOutputStreamWriteResult {
let bytes: Data
do {
bytes = try ProtoEncoder().encode(header)
} catch {
return .protoSerializationError(error)
}
do {
try outputStream.write(data: bytes)
} catch {
return .fileIOError(error)
}
return .success
}
internal func writeFrame(_ frame: BackupProto.Frame) -> MessageBackup.ProtoOutputStreamWriteResult {
let bytes: Data
do {
bytes = try ProtoEncoder().encode(frame)
} catch {
return .protoSerializationError(error)
}
do {
try outputStream.write(data: bytes)
} catch {
return .fileIOError(error)
}
return .success
}
public func closeFileStream() throws {
try? outputStream.close()
}
}