mirror of
				https://github.com/GayPizzaSpecifications/darwin-apk.git
				synced 2025-11-04 07:59:38 +00:00 
			
		
		
		
	Consolidate update+reader & remove some dead code
This commit is contained in:
		@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										102
									
								
								Sources/apk/Index/ApkIndexReader.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								Sources/apk/Index/ApkIndexReader.swift
									
									
									
									
									
										Normal 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"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -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"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -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))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user