Consolidate update+reader & remove some dead code

This commit is contained in:
2024-11-22 21:01:01 +11:00
parent cc703ad0c5
commit be51934334
7 changed files with 108 additions and 170 deletions

View File

@ -6,28 +6,6 @@
import Foundation
public struct ApkIndexDownloader {
@available(*, deprecated, message: "This is stinky, use ApkIndexDownloader.fetch instead")
internal func downloadFile(remote remoteURL: URL, destination destLocalURL: URL) {
let sem = DispatchSemaphore.init(value: 0)
let downloadTask = URLSession.shared.downloadTask(with: remoteURL) { url, response, error in
if let localURL = url {
do {
// Replace existing APKINDEX.tar.gz files
if FileManager.default.fileExists(atPath: destLocalURL.path()) {
try FileManager.default.removeItem(at: destLocalURL)
}
// Move temporary to the new location
try FileManager.default.moveItem(at: localURL, to: destLocalURL)
} catch {
print("Download error: \(error.localizedDescription)")
}
}
sem.signal()
}
downloadTask.resume()
sem.wait()
}
public static func fetch(repository: ApkIndexRepository) async throws(FetchError) -> URL {
let localDestinationURL = URL(filePath: repository.localName)

View File

@ -0,0 +1,102 @@
/*
* darwin-apk © 2024 Gay Pizza Specifications
* SPDX-License-Identifier: Apache-2.0
*/
import Foundation
public struct ApkIndexReader {
static func read(from indexURL: URL) throws -> ApkIndex {
let timed = false
var timer: ContinuousClock.Instant
let durFormat = Duration.UnitsFormatStyle(
allowedUnits: [ .seconds, .milliseconds ],
width: .condensedAbbreviated,
fractionalPart: .show(length: 3))
if timed {
timer = ContinuousClock.now
}
let file = try FileInputStream(indexURL)
//var file = try MemoryInputStream(buffer: try Data(contentsOf: indexURL))
var gzip = GZipReader()
var tarRecords = [TarReader.Entry]()
for tarData in try (0..<2).map({ _ in try gzip.read(inStream: file) }) {
let tarStream = MemoryInputStream(buffer: tarData)
tarRecords += try TarReader.read(tarStream)
}
guard case .file(let signatureName, _) = tarRecords.first else {
throw ReadingError.missingSignature
}
guard let apkIndexFile = tarRecords.firstFile(name: "APKINDEX") else {
throw ReadingError.missingIndex
}
guard let description = tarRecords.firstFile(name: "DESCRIPTION") else {
throw ReadingError.missingDescription
}
if timed {
print("\(indexURL.lastPathComponent): Extract time: \((ContinuousClock.now - timer).formatted(durFormat))")
timer = ContinuousClock.now
}
let index = try ApkIndex(raw:
try ApkRawIndex(lines: MemoryInputStream(buffer: apkIndexFile).lines))
if timed {
print("\(indexURL.lastPathComponent): Index time: \((ContinuousClock.now - timer).formatted(durFormat))")
}
return index
}
public static func resolve<S: Sequence<ApkIndexRepository>>(_ repositories: S, fetch: FetchMode) async throws -> ApkIndex {
try await withThrowingTaskGroup(of: ApkIndex.self) { group in
for repository in repositories {
group.addTask(priority: .userInitiated) {
let local: URL
switch fetch {
case .local:
local = URL(filePath: repository.localName)
case .lazy:
if !FileManager.default.fileExists(atPath: repository.localName) {
fallthrough
}
local = URL(filePath: repository.localName)
case .update:
//FIXME: Don't call print in the lib
print("Fetching \"\(repository.resolved)\"")
local = try await ApkIndexDownloader.fetch(repository: repository)
}
let index = try Self.read(from: local)
return index
}
}
return try await ApkIndex.merge(group.reduce(into: []) { $0.append($1) })
}
}
}
public extension ApkIndexReader {
enum FetchMode: Sendable {
case update
case lazy
case local
}
enum ReadingError: Error, LocalizedError {
case missingSignature
case missingIndex
case missingDescription
public var errorDescription: String? {
switch self {
case .missingSignature: "Missing signature"
case .missingIndex: "APKINDEX missing"
case .missingDescription: "DESCRIPTION missing"
}
}
}
}

View File

@ -1,79 +0,0 @@
/*
* darwin-apk © 2024 Gay Pizza Specifications
* SPDX-License-Identifier: Apache-2.0
*/
import Foundation
public extension ApkIndex {
init(readFrom indexURL: URL) throws {
let file = try FileInputStream(indexURL)
var gzip = GZipReader()
var tarRecords = [TarReader.Entry]()
for tarData in try (0..<2).map({ _ in try gzip.read(inStream: file) }) {
let tarStream = MemoryInputStream(buffer: tarData)
tarRecords += try TarReader.read(tarStream)
}
guard case .file(let signatureName, _) = tarRecords.first else {
throw ApkIndexReadingError.missingSignature
}
guard let apkIndexFile = tarRecords.firstFile(name: "APKINDEX") else {
throw ApkIndexReadingError.missingIndex
}
guard let description = tarRecords.firstFile(name: "DESCRIPTION") else {
throw ApkIndexReadingError.missingDescription
}
try self.init(raw:
try ApkRawIndex(lines: MemoryInputStream(buffer: apkIndexFile).lines))
}
}
public extension ApkIndex {
static func resolve<S: Sequence>(_ repositories: S, fetch: ApkIndexFetchMode) async throws -> Self where S.Element == ApkIndexRepository {
try await withThrowingTaskGroup(of: Self.self) { group in
for repository in repositories {
group.addTask(priority: .userInitiated) {
let local: URL
switch fetch {
case .local:
local = URL(filePath: repository.localName)
case .lazy:
if !FileManager.default.fileExists(atPath: repository.localName) {
fallthrough
}
local = URL(filePath: repository.localName)
case .update:
//FIXME: Don't call print in the lib
print("Fetching \"\(repository.resolved)\"")
local = try await ApkIndexDownloader.fetch(repository: repository)
}
let index = try ApkIndex(readFrom: local)
return index
}
}
return try await ApkIndex.merge(group.reduce(into: []) { $0.append($1) })
}
}
}
public enum ApkIndexFetchMode: Sendable {
case update
case lazy
case local
}
public enum ApkIndexReadingError: Error, LocalizedError {
case missingSignature
case missingIndex
case missingDescription
public var errorDescription: String? {
switch self {
case .missingSignature: "Missing signature"
case .missingIndex: "APKINDEX missing"
case .missingDescription: "DESCRIPTION missing"
}
}
}

View File

@ -12,25 +12,10 @@ public struct ApkIndexUpdater {
self.repositories = []
}
public func update() {
let downloader = ApkIndexDownloader()
for repo in self.repositories {
let localIndex = URL(filePath: repo.localName)
#if false
let shouldDownload = true
#else
let shouldDownload = !FileManager.default.fileExists(atPath: localIndex.path())
#endif
if shouldDownload {
print("Fetching index for \"\(repo.name)\"")
downloader.downloadFile(remote: repo.url, destination: localIndex)
}
}
public func buildGraph() async {
let graph: ApkPackageGraph
do {
let tables = try self.repositories.map { try Self.readIndex(URL(filePath: $0.localName)) }
graph = ApkPackageGraph(index: ApkIndex.merge(tables))
graph = ApkPackageGraph(index: try await ApkIndexReader.resolve(self.repositories, fetch: .lazy))
graph.buildGraphNode()
try graph.pkgIndex.description.write(to: URL(filePath: "packages.txt"), atomically: false, encoding: .utf8)
@ -45,52 +30,4 @@ public struct ApkIndexUpdater {
for node in graph.deepIsolates { print(node, to: &out) }
}
}
public static func readIndex(_ indexURL: URL) throws -> ApkIndex {
let tarSignature: [TarReader.Entry]
let tarRecords: [TarReader.Entry]
let arcName = indexURL.lastPathComponent
let durFormat = Duration.UnitsFormatStyle(
allowedUnits: [ .seconds, .milliseconds ],
width: .condensedAbbreviated,
fractionalPart: .show(length: 3))
let gzipStart = ContinuousClock.now
var tars = [Data]()
do {
var file = try FileInputStream(indexURL)
//var file = try MemoryInputStream(buffer: try Data(contentsOf: indexURL))
var gzip = GZipReader()
tars.append(try gzip.read(inStream: file))
tars.append(try gzip.read(inStream: file))
} catch {
fatalError(error.localizedDescription)
}
print("\(arcName): Gzip time: \((ContinuousClock.now - gzipStart).formatted(durFormat))")
let untarStart = ContinuousClock.now
let signatureStream = MemoryInputStream(buffer: tars[0])
tarSignature = try TarReader.read(signatureStream)
let recordsStream = MemoryInputStream(buffer: tars[1])
tarRecords = try TarReader.read(recordsStream)
guard case .file(let signatureName, _) = tarSignature.first
else { fatalError("Missing signature") }
guard let apkIndexFile = tarRecords.firstFile(name: "APKINDEX")
else { fatalError("APKINDEX missing") }
guard let description = tarRecords.firstFile(name: "DESCRIPTION")
else { fatalError("DESCRIPTION missing") }
print("\(arcName): TAR time: \((ContinuousClock.now - untarStart).formatted(durFormat))")
let indexStart = ContinuousClock.now
defer {
print("\(arcName): Index time: \((ContinuousClock.now - indexStart).formatted(durFormat))")
}
return try ApkIndex(raw:
try ApkRawIndex(lines: MemoryInputStream(buffer: apkIndexFile).lines))
}
}

View File

@ -31,8 +31,8 @@ public struct ApkRepositoriesConfig {
}
}
public extension ApkIndex {
@inlinable static func resolve(_ config: ApkRepositoriesConfig, fetch: ApkIndexFetchMode) async throws -> Self {
public extension ApkIndexReader {
@inlinable static func resolve(_ config: ApkRepositoriesConfig, fetch: FetchMode) async throws -> ApkIndex {
try await Self.resolve(config.repositories, fetch: fetch)
}
}

View File

@ -44,7 +44,7 @@ struct DpkSearchCommand: AsyncParsableCommand {
let localRepositories = try await ApkRepositoriesConfig()
let index: ApkIndex
do {
index = try await ApkIndex.resolve(localRepositories, fetch: .local)
index = try await ApkIndexReader.resolve(localRepositories, fetch: .local)
} catch {
eprint("Failed to build package index: \(error.localizedDescription)")
throw .failure

View File

@ -19,7 +19,7 @@ struct DpkUpdateCommand: AsyncParsableCommand {
func run() async throws {
let repositories = try await ApkRepositoriesConfig().repositories
eprint("Updating package repositories")
let index = try await ApkIndex.resolve(repositories, fetch: self.lazyDownload ? .lazy : .update)
let index = try await ApkIndexReader.resolve(repositories, fetch: self.lazyDownload ? .lazy : .update)
eprint("Indexed \(index.packages.count) package(s)")
}
}