Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Backups/Archiving/FileStreams/BackupArchiveProtoOutputStream.swift
1 views
//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

extension BackupArchive {
    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)
    }
}

// MARK: -

/**
 * 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.
 */
class BackupArchiveProtoOutputStream {
    private let outputStream: OutputStreamable
    private let exportProgress: BackupArchiveExportProgress?

    init(
        outputStream: OutputStreamable,
        exportProgress: BackupArchiveExportProgress?,
    ) {
        self.outputStream = outputStream
        self.exportProgress = exportProgress
    }

    /// Write a header (``BackupProto_BackupInfo``) to the backup file.
    ///
    /// - Important
    /// 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) -> BackupArchive.ProtoOutputStreamWriteResult {
        let bytes: Data
        do {
            bytes = try header.serializedData()
        } catch {
            return .protoSerializationError(error)
        }
        do {
            try outputStream.write(data: bytes)
        } catch {
            return .fileIOError(error)
        }
        exportProgress?.didExportFrame()
        return .success
    }

    /// Write a frame to the backup file.
    func writeFrame(_ frame: BackupProto_Frame) -> BackupArchive.ProtoOutputStreamWriteResult {
        let bytes: Data
        do {
            bytes = try frame.serializedData()
        } catch {
            return .protoSerializationError(error)
        }
        do {
            try outputStream.write(data: bytes)
        } catch {
            return .fileIOError(error)
        }
        exportProgress?.didExportFrame()
        return .success
    }

    /// Closes the output stream.
    func closeFileStream() throws {
        exportProgress?.didCloseStream()
        try outputStream.close()
    }
}