75 lines
2.4 KiB
Swift
75 lines
2.4 KiB
Swift
//
|
|
// Copyright 2024 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// Simple wrapper around an OutputStream that allows passing data through an
|
|
/// array of `StreamTransform` objects to alter the data before being written
|
|
/// out to the final destination.
|
|
public final class TransformingOutputStream: OutputStreamable {
|
|
|
|
private let transforms: [any StreamTransform]
|
|
private let outputStream: OutputStreamable
|
|
private let runLoop: RunLoop?
|
|
|
|
public init(
|
|
transforms: [any StreamTransform],
|
|
outputStream: OutputStreamable,
|
|
runLoop: RunLoop? = nil,
|
|
) {
|
|
self.transforms = transforms
|
|
self.outputStream = outputStream
|
|
self.runLoop = runLoop
|
|
}
|
|
|
|
public func write(data: Data) throws {
|
|
let data = try transforms.reduce(data) { try $1.transform(data: $0) }
|
|
if data.count > 0 {
|
|
try outputStream.write(data: data)
|
|
}
|
|
}
|
|
|
|
/// Iterates through each transform, allowing it to finalize the output and write any one-time
|
|
/// footers required by the internal transform implementation.
|
|
public func finalizeAndWriteFooter() throws {
|
|
while hasPendingBytes {
|
|
let footerData = try transforms.readNextRemainingBytes()
|
|
if footerData.count > 0 {
|
|
try outputStream.write(data: footerData)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Checks the list of transforms and returns if any data may be available from any in the list,
|
|
/// including buffered data or pending footer date.
|
|
/// Once hasPendingBytes returns `false`, the stream should be considered closed
|
|
/// and should no longer be used.
|
|
public var hasPendingBytes: Bool {
|
|
return
|
|
transforms.contains { $0.hasPendingBytes }
|
|
|| transforms.compactMap { $0 as? FinalizableStreamTransform }.contains { !$0.hasFinalized }
|
|
}
|
|
|
|
public func close() throws {
|
|
try finalizeAndWriteFooter()
|
|
|
|
if let runLoop {
|
|
outputStream.remove(from: runLoop, forMode: .default)
|
|
}
|
|
try outputStream.close()
|
|
}
|
|
|
|
// MARK: - OutputStreamable passthrough
|
|
|
|
public func remove(from runLoop: RunLoop, forMode mode: RunLoop.Mode) {
|
|
self.outputStream.remove(from: runLoop, forMode: mode)
|
|
}
|
|
|
|
public func schedule(in runLoop: RunLoop, forMode mode: RunLoop.Mode) {
|
|
self.outputStream.schedule(in: runLoop, forMode: mode)
|
|
}
|
|
|
|
}
|