The following code is working for me. Hopefully, Apple has a better example posted somewhere and someone will post it here.
It is based on Apple's string compression code with some code at the end to write the source file as a sequence of blobs to the encodeStream.
func compressFile(at sourceURL: URL, to destURL: URL, deleteSource: Bool = true) async throws {
let sourceLastComponent = sourceURL.lastPathComponent
let destPath = FilePath(destURL.path)
guard let sourceFileSize = getFileSize(url: sourceURL) else {
return
}
// writeFileStream
guard let writeFileStream = ArchiveByteStream.fileStream(
path: destPath,
mode: .writeOnly,
options: [.create],
permissions: FilePermissions(rawValue: 0o644)
) else {
return
}
defer { try? writeFileStream.close() }
// compressStream
guard let compressStream = ArchiveByteStream.compressionStream(
using: .lzfse,
writingTo: writeFileStream
) else {
return
}
defer { try? compressStream.close() }
// encodeStream
guard let encodeStream = ArchiveStream.encodeStream(writingTo: compressStream) else {
return
}
defer {
try? encodeStream.close()
}
// Create header and send it to the encodeStream
let header = ArchiveHeader()
header.append(.string(key: ArchiveHeader.FieldKey("PAT"),
value: sourceLastComponent))
header.append(.uint(key: ArchiveHeader.FieldKey("TYP"),
value: UInt64(ArchiveHeader.EntryType.regularFile.rawValue)))
header.append(.blob(key: ArchiveHeader.FieldKey("DAT"),
size: UInt64(sourceFileSize)))
do {
try encodeStream.writeHeader(header)
} catch {
print("Failed to write header.")
return
}
// Open source file, reading in chunks up to 64000 bytes and writing
// them as blobs to the encodeStream
let fileHandle = try FileHandle(forReadingFrom: sourceURL)
defer { try? fileHandle.close() }
let chunkSize = 64000
while true {
guard let data = try fileHandle.read(upToCount: chunkSize) else { break }
if data.isEmpty { break }
try data.withUnsafeBytes { rawBufferPointer in
try encodeStream.writeBlob(key: ArchiveHeader.FieldKey("DAT"), from: rawBufferPointer)
}
}
try fileHandle.close()
if deleteSource {
do {
try FileManager.default.removeItem(at: sourceURL)
} catch {
print("ERROR: Could not remove source file at \(sourceURL.path)")
}
}
}
func getFileSize(url: URL) -> Int64? {
do {
let resourceValues = try url.resourceValues(forKeys: [.fileSizeKey])
return Int64(resourceValues.fileSize ?? 0)
} catch {
print("Error getting file size: \(error.localizedDescription)")
return nil
}
}
Topic:
App & System Services
SubTopic:
Core OS
Tags: