mirror of
https://github.com/XcodesOrg/XcodesApp.git
synced 2026-03-25 08:55:46 +00:00
more WIP
This commit is contained in:
parent
487cbb0045
commit
6ffce23616
21 changed files with 237 additions and 231 deletions
|
|
@ -79,7 +79,6 @@
|
|||
CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFAA482593162500380FEE /* Bundle+InfoPlistValues.swift */; };
|
||||
CAC28188259EE27200B8AB0B /* CombineExpectations in Frameworks */ = {isa = PBXBuildFile; productRef = CAC28187259EE27200B8AB0B /* CombineExpectations */; };
|
||||
CAC281CD259F97FA00B8AB0B /* ObservingProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */; };
|
||||
CAC281DA259F985100B8AB0B /* InstallationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281D9259F985100B8AB0B /* InstallationStep.swift */; };
|
||||
CAC281E2259FA44600B8AB0B /* Bundle+XcodesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281E1259FA44600B8AB0B /* Bundle+XcodesTests.swift */; };
|
||||
CAC281E7259FA45A00B8AB0B /* Environment+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC281E6259FA45A00B8AB0B /* Environment+Mock.swift */; };
|
||||
CAC9F92D25BCDA4400B4965F /* HelperInstallState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAC9F92C25BCDA4400B4965F /* HelperInstallState.swift */; };
|
||||
|
|
@ -269,7 +268,6 @@
|
|||
CABFAA422593104F00380FEE /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; };
|
||||
CABFAA482593162500380FEE /* Bundle+InfoPlistValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+InfoPlistValues.swift"; sourceTree = "<group>"; };
|
||||
CAC281CC259F97FA00B8AB0B /* ObservingProgressIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservingProgressIndicator.swift; sourceTree = "<group>"; };
|
||||
CAC281D9259F985100B8AB0B /* InstallationStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallationStep.swift; sourceTree = "<group>"; };
|
||||
CAC281E1259FA44600B8AB0B /* Bundle+XcodesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+XcodesTests.swift"; sourceTree = "<group>"; };
|
||||
CAC281E6259FA45A00B8AB0B /* Environment+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+Mock.swift"; sourceTree = "<group>"; };
|
||||
CAC9F92C25BCDA4400B4965F /* HelperInstallState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperInstallState.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -482,7 +480,6 @@
|
|||
CABFA9AC2592EEE900380FEE /* Foundation.swift */,
|
||||
CA9FF9352595B44700E47BAF /* HelperClient.swift */,
|
||||
CAC9F92C25BCDA4400B4965F /* HelperInstallState.swift */,
|
||||
CAC281D9259F985100B8AB0B /* InstallationStep.swift */,
|
||||
CA9FF8862595607900E47BAF /* InstalledXcode.swift */,
|
||||
CAA8587B25A2B37900ACF8C0 /* IsTesting.swift */,
|
||||
E89342F925EDCC17007CF557 /* NotificationManager.swift */,
|
||||
|
|
@ -863,7 +860,6 @@
|
|||
CAFE4ABC25B7D54B0064FE51 /* UpdatesPreferencePane.swift in Sources */,
|
||||
CABFA9BD2592EEEA00380FEE /* Environment.swift in Sources */,
|
||||
CABFA9C32592EEEA00380FEE /* Downloads.swift in Sources */,
|
||||
CAC281DA259F985100B8AB0B /* InstallationStep.swift in Sources */,
|
||||
E8CBDB8B27AE02FF00B22292 /* ExperiementsPreferencePane.swift in Sources */,
|
||||
E8E98A9625D863D700EC89A0 /* InstallationStepDetailView.swift in Sources */,
|
||||
CA378F992466567600A58CE0 /* AppState.swift in Sources */,
|
||||
|
|
@ -1427,8 +1423,6 @@
|
|||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/xcodereleases/data";
|
||||
requirement = {
|
||||
kind = revision;
|
||||
revision = a43ad89e536d7a3da525fcc23fb182c37b756ecc;
|
||||
branch = main;
|
||||
kind = branch;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
"package": "XcodeReleases",
|
||||
"repositoryURL": "https://github.com/xcodereleases/data",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"branch": "main",
|
||||
"revision": "a43ad89e536d7a3da525fcc23fb182c37b756ecc",
|
||||
"version": null
|
||||
}
|
||||
|
|
@ -69,8 +69,8 @@
|
|||
"repositoryURL": "https://github.com/mxcl/Path.swift",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "9c6f807b0a76be0e27aecc908bc6f173400d839e",
|
||||
"version": "1.4.0"
|
||||
"revision": "8e355c28e9393c42e58b18c54cace2c42c98a616",
|
||||
"version": "1.4.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -490,7 +490,7 @@ extension AppState {
|
|||
|
||||
// MARK: -
|
||||
|
||||
func setInstallationStep(of version: Version, to step: InstallationStep) {
|
||||
func setInstallationStep(of version: Version, to step: XcodeInstallationStep) {
|
||||
DispatchQueue.main.async {
|
||||
guard let index = self.allXcodes.firstIndex(where: { $0.version.isEquivalent(to: version) }) else { return }
|
||||
self.allXcodes[index].installState = .installing(step)
|
||||
|
|
@ -499,14 +499,13 @@ extension AppState {
|
|||
Current.notificationManager.scheduleNotification(title: xcode.id.appleDescription, body: step.description, category: .normal)
|
||||
}
|
||||
}
|
||||
func setInstallationStep(of runtime: DownloadableRuntime, to step: InstallationStep) {
|
||||
func setInstallationStep(of runtime: DownloadableRuntime, to step: RuntimeInstallationStep) {
|
||||
DispatchQueue.main.async {
|
||||
|
||||
guard let index = self.downloadableRuntimes.firstIndex(where: { $0.identifier == runtime.identifier }) else { return }
|
||||
self.downloadableRuntimes[index].installState = .installing(step)
|
||||
|
||||
// let xcode = self.allXcodes[index]
|
||||
// Current.notificationManager.scheduleNotification(title: xcode.id.appleDescription, body: step.description, category: .normal)
|
||||
Current.notificationManager.scheduleNotification(title: runtime.name, body: step.description, category: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,12 @@ extension AppState {
|
|||
|
||||
func downloadRuntime(runtime: DownloadableRuntime) {
|
||||
Task {
|
||||
try? await downloadRunTimeFull(runtime: runtime)
|
||||
do {
|
||||
try await downloadRunTimeFull(runtime: runtime)
|
||||
}
|
||||
catch {
|
||||
Logger.appState.error("Error downloading runtime: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
// self.runtimePublishers[runtime.identifier] = downloadRunTimeFull(runtime: runtime)
|
||||
|
|
@ -78,33 +83,20 @@ extension AppState {
|
|||
let downloader = Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2
|
||||
Logger.appState.info("Downloading \(runtime.visibleIdentifier) with \(downloader)")
|
||||
|
||||
let url = try await self.downloadRuntime(for: runtime, downloader: downloader, progressChanged: { [unowned self] progress in
|
||||
DispatchQueue.main.async {
|
||||
self.setInstallationStep(of: runtime, to: .downloading(progress: progress))
|
||||
}
|
||||
}).async()
|
||||
|
||||
return validateADCSession(path: runtime.downloadPath)
|
||||
.flatMap { _ in
|
||||
// we shouldn't have to be authenticated to download runtimes
|
||||
let downloader = Downloader(rawValue: UserDefaults.standard.string(forKey: "downloader") ?? "aria2") ?? .aria2
|
||||
Logger.appState.info("Downloading \(runtime.visibleIdentifier) with \(downloader)")
|
||||
|
||||
return self.downloadRuntime(for: runtime, downloader: downloader, progressChanged: { [unowned self] progress in
|
||||
DispatchQueue.main.async {
|
||||
self.setInstallationStep(of: runtime, to: .downloading(progress: progress))
|
||||
}
|
||||
})
|
||||
.map { return (runtime, $0) }
|
||||
}
|
||||
.flatMap { runtime, url -> AnyPublisher<URL, Error> in
|
||||
switch runtime.contentType {
|
||||
case .package:
|
||||
return self.installFromPackage(dmgURL: url, runtime: runtime)
|
||||
case .diskImage:
|
||||
return self.installFromImage(dmgURL: url)
|
||||
}
|
||||
}
|
||||
.map { url in
|
||||
// Done deleting
|
||||
Logger.appState.debug("URL: \(url)")
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
Logger.appState.debug("Done downloading: \(url)")
|
||||
//self.setInstallationStep(of: runtime, to: .downloading(progress: progress))
|
||||
switch runtime.contentType {
|
||||
case .package:
|
||||
try await self.installFromPackage(dmgURL: url, runtime: runtime)
|
||||
case .diskImage:
|
||||
try await self.installFromImage(dmgURL: url)
|
||||
}
|
||||
}
|
||||
|
||||
func downloadRuntime(for runtime: DownloadableRuntime, downloader: Downloader, progressChanged: @escaping (Progress) -> Void) -> AnyPublisher<URL, Error> {
|
||||
|
|
@ -142,7 +134,7 @@ extension AppState {
|
|||
progressChanged: progressChanged)
|
||||
|
||||
case .urlSession:
|
||||
|
||||
// TODO: Support runtime download via URL Session
|
||||
return Just(runtime.url)
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
|
|
@ -171,53 +163,56 @@ extension AppState {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public func downloadRuntimeWithAria2(_ runtime: DownloadableRuntime, to destination: Path, aria2Path: Path, progressChanged: @escaping (Progress) -> Void) async -> URL {
|
||||
|
||||
public func installFromImage(dmgURL: URL) async throws {
|
||||
|
||||
try? self.runtimeService.installRuntimeImage(dmgURL: dmgURL)
|
||||
|
||||
}
|
||||
|
||||
public func installFromImage(dmgURL: URL) -> AnyPublisher<URL, Error> {
|
||||
|
||||
|
||||
try? self.runtimeService.installRuntimeImage(dmgURL: dmgURL)
|
||||
|
||||
|
||||
return Just(dmgURL)
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
}
|
||||
|
||||
public func installFromPackage(dmgURL: URL, runtime: DownloadableRuntime) -> AnyPublisher<URL, Error> {
|
||||
public func installFromPackage(dmgURL: URL, runtime: DownloadableRuntime) async throws {
|
||||
Logger.appState.info("Mounting DMG")
|
||||
Task {
|
||||
do {
|
||||
let mountedUrl = try await self.runtimeService.mountDMG(dmgUrl: dmgURL)
|
||||
|
||||
// 2-Get the first path under the mounted path, should be a .pkg
|
||||
let pkgPath = Path(url: mountedUrl)!.ls().first!
|
||||
try Path.xcodesCaches.mkdir().setCurrentUserAsOwner()
|
||||
|
||||
let expandedPkgPath = Path.xcodesCaches/runtime.identifier
|
||||
//try expandedPkgPath.mkdir()
|
||||
Logger.appState.info("PKG Path: \(pkgPath)")
|
||||
Logger.appState.info("Expanded PKG Path: \(expandedPkgPath)")
|
||||
//try? Current.files.removeItem(at: expandedPkgPath.url)
|
||||
|
||||
// 5-Expand (not install) the pkg to temporary path
|
||||
try await self.runtimeService.expand(pkgPath: pkgPath, expandedPkgPath: expandedPkgPath)
|
||||
//try await self.runtimeService.unmountDMG(mountedURL: mountedUrl)
|
||||
|
||||
} catch {
|
||||
Logger.appState.error("Error installing runtime: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
return Just(dmgURL)
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
do {
|
||||
let mountedUrl = try await self.runtimeService.mountDMG(dmgUrl: dmgURL)
|
||||
|
||||
// 2-Get the first path under the mounted path, should be a .pkg
|
||||
let pkgPath = Path(url: mountedUrl)!.ls().first!
|
||||
try Path.xcodesCaches.mkdir().setCurrentUserAsOwner()
|
||||
|
||||
let expandedPkgPath = Path.xcodesCaches/runtime.identifier
|
||||
//try expandedPkgPath.mkdir()
|
||||
Logger.appState.info("PKG Path: \(pkgPath)")
|
||||
Logger.appState.info("Expanded PKG Path: \(expandedPkgPath)")
|
||||
//try? Current.files.removeItem(at: expandedPkgPath.url)
|
||||
|
||||
// 5-Expand (not install) the pkg to temporary path
|
||||
try await self.runtimeService.expand(pkgPath: pkgPath, expandedPkgPath: expandedPkgPath)
|
||||
//try await self.runtimeService.unmountDMG(mountedURL: mountedUrl)
|
||||
|
||||
} catch {
|
||||
Logger.appState.error("Error installing runtime: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyPublisher {
|
||||
func async() async throws -> Output {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
var cancellable: AnyCancellable?
|
||||
|
||||
cancellable = first()
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
break
|
||||
case let .failure(error):
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
cancellable?.cancel()
|
||||
} receiveValue: { value in
|
||||
continuation.resume(with: .success(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,100 +112,73 @@ public struct Shell {
|
|||
return (progress, publisher)
|
||||
}
|
||||
|
||||
public var downloadWithAria2Async: (Path, URL, Path, [HTTPCookie]) async throws -> Progress = { aria2Path, url, destination, cookies in
|
||||
let process = Process()
|
||||
process.executableURL = aria2Path.url
|
||||
process.arguments = [
|
||||
"--header=Cookie: \(cookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; "))",
|
||||
"--max-connection-per-server=16",
|
||||
"--split=16",
|
||||
"--summary-interval=1",
|
||||
"--stop-with-process=\(ProcessInfo.processInfo.processIdentifier)", // if xcodes quits, stop aria2 process
|
||||
"--dir=\(destination.parent.string)",
|
||||
"--out=\(destination.basename())",
|
||||
"--human-readable=false", // sets the output to use bytes instead of formatting
|
||||
url.absoluteString,
|
||||
]
|
||||
let stdOutPipe = Pipe()
|
||||
process.standardOutput = stdOutPipe
|
||||
let stdErrPipe = Pipe()
|
||||
process.standardError = stdErrPipe
|
||||
|
||||
var progress = Progress()
|
||||
progress.kind = .file
|
||||
progress.fileOperationKind = .downloading
|
||||
|
||||
let observer = NotificationCenter.default.addObserver(
|
||||
forName: .NSFileHandleDataAvailable,
|
||||
object: nil,
|
||||
queue: OperationQueue.main
|
||||
) { note in
|
||||
guard
|
||||
// This should always be the case for Notification.Name.NSFileHandleDataAvailable
|
||||
let handle = note.object as? FileHandle,
|
||||
handle === stdOutPipe.fileHandleForReading || handle === stdErrPipe.fileHandleForReading
|
||||
else { return }
|
||||
|
||||
defer { handle.waitForDataInBackgroundAndNotify() }
|
||||
|
||||
let string = String(decoding: handle.availableData, as: UTF8.self)
|
||||
|
||||
progress.updateFromAria2(string: string)
|
||||
}
|
||||
|
||||
stdOutPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||
stdErrPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||
|
||||
do {
|
||||
|
||||
defer {
|
||||
//DispatchQueue.global(qos: .default).async {
|
||||
process.waitUntilExit()
|
||||
|
||||
NotificationCenter.default.removeObserver(observer, name: .NSFileHandleDataAvailable, object: nil)
|
||||
|
||||
guard process.terminationReason == .exit, process.terminationStatus == 0 else {
|
||||
if let aria2cError = Aria2CError(exitStatus: process.terminationStatus) {
|
||||
throw aria2cError
|
||||
} else {
|
||||
throw ProcessExecutionError(process: process, standardOutput: "", standardError: "")
|
||||
}
|
||||
}
|
||||
return
|
||||
// }
|
||||
}
|
||||
try process.run()
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
|
||||
|
||||
// let publisher = Deferred {
|
||||
// Future<Void, Error> { promise in
|
||||
// DispatchQueue.global(qos: .default).async {
|
||||
// public var downloadWithAria2Async: (Path, URL, Path, [HTTPCookie]) async throws -> Progress = { aria2Path, url, destination, cookies in
|
||||
// let process = Process()
|
||||
// process.executableURL = aria2Path.url
|
||||
// process.arguments = [
|
||||
// "--header=Cookie: \(cookies.map { "\($0.name)=\($0.value)" }.joined(separator: "; "))",
|
||||
// "--max-connection-per-server=16",
|
||||
// "--split=16",
|
||||
// "--summaraasdy-interval=1",
|
||||
// "--stop-with-process=\(ProcessInfo.processInfo.processIdentifier)", // if xcodes quits, stop aria2 process
|
||||
// "--dir=\(destination.parent.string)",
|
||||
// "--out=\(destination.basename())",
|
||||
// "--human-readable=false", // sets the output to use bytes instead of formatting
|
||||
// url.absoluteString,
|
||||
// ]
|
||||
// let stdOutPipe = Pipe()
|
||||
// process.standardOutput = stdOutPipe
|
||||
// let stdErrPipe = Pipe()
|
||||
// process.standardError = stdErrPipe
|
||||
//
|
||||
// var progress = Progress()
|
||||
// progress.kinasdas
|
||||
// progress.fileOperationKind = .downloadingasdfasd
|
||||
//
|
||||
// let observer = NotificationCenter.default.addObserver(
|
||||
// forName: .NSFileHandleDataAvailable,
|
||||
// object: nil,
|
||||
// queue: OperationQueue.main
|
||||
// ) { note in
|
||||
// guard
|
||||
// // This should always be the case for Notification.Name.NSFileHandleDataAvailable
|
||||
// let handle = note.object as? FileHandle,
|
||||
// handle === stdOutPipe.fileHandleForReading || handle === stdErrPipe.fileHandleForReading
|
||||
// else { return }
|
||||
//
|
||||
// defer { handle.waitForDataInBackgroundAndNotify() }
|
||||
//
|
||||
// let string = String(decoding: handle.availableData, as: UTF8.self)
|
||||
//
|
||||
// progress.updateFromAria2(string: string)
|
||||
// }
|
||||
//
|
||||
// stdOutPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||
// stdErrPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
|
||||
//
|
||||
// do {
|
||||
//
|
||||
// defer {
|
||||
// //DispatchQueue.global(qos: .default).async {
|
||||
// process.waitUntilExit()
|
||||
//
|
||||
//
|
||||
// NotificationCenter.default.removeObserver(observer, name: .NSFileHandleDataAvailable, object: nil)
|
||||
//
|
||||
//
|
||||
// guard process.terminationReason == .exit, process.terminationStatus == 0 else {
|
||||
// if let aria2cError = Aria2CError(exitStatus: process.terminationStatus) {
|
||||
// return promise(.failure(aria2cError))
|
||||
// throw aria2cError
|
||||
// } else {
|
||||
// return promise(.failure(ProcessExecutionError(process: process, standardOutput: "", standardError: "")))
|
||||
// throw ProcessExecutionError(process: process, standardOutput: "", standardError: "")
|
||||
// }
|
||||
// }
|
||||
// promise(.success(()))
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// try process.run()
|
||||
// } catch {
|
||||
// throw error
|
||||
// }
|
||||
// .handleEvents(receiveCancel: {
|
||||
// process.terminate()
|
||||
// NotificationCenter.default.removeObserver(observer, name: .NSFileHandleDataAvailable, object: nil)
|
||||
// })
|
||||
// .eraseToAnyPublisher()
|
||||
//
|
||||
// return (progress, publisher)
|
||||
}
|
||||
// }
|
||||
|
||||
public var unxipExperiment: (URL) -> AnyPublisher<ProcessOutput, Error> = { url in
|
||||
let unxipPath = Path(url: Bundle.main.url(forAuxiliaryExecutable: "unxip")!)!
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
//import Foundation
|
||||
//
|
||||
///// A numbered step
|
||||
//enum InstallationStep: Equatable, CustomStringConvertible {
|
||||
// case downloading(progress: Progress)
|
||||
// case unarchiving
|
||||
// case moving(destination: String)
|
||||
// case trashingArchive
|
||||
// case checkingSecurity
|
||||
// case finishing
|
||||
//
|
||||
// var description: String {
|
||||
// "(\(stepNumber)/\(stepCount)) \(message)"
|
||||
// }
|
||||
//
|
||||
// var message: String {
|
||||
// switch self {
|
||||
// case .downloading:
|
||||
// return localizeString("Downloading")
|
||||
// case .unarchiving:
|
||||
// return localizeString("Unarchiving")
|
||||
// case .moving(let destination):
|
||||
// return String(format: localizeString("Moving"), destination)
|
||||
// case .trashingArchive:
|
||||
// return localizeString("TrashingArchive")
|
||||
// case .checkingSecurity:
|
||||
// return localizeString("CheckingSecurity")
|
||||
// case .finishing:
|
||||
// return localizeString("Finishing")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var stepNumber: Int {
|
||||
// switch self {
|
||||
// case .downloading: return 1
|
||||
// case .unarchiving: return 2
|
||||
// case .moving: return 3
|
||||
// case .trashingArchive: return 4
|
||||
// case .checkingSecurity: return 5
|
||||
// case .finishing: return 6
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var stepCount: Int { 6 }
|
||||
//}
|
||||
|
|
@ -4,6 +4,8 @@ import os.log
|
|||
import Path
|
||||
import XcodesKit
|
||||
|
||||
public typealias ProcessOutput = (status: Int32, out: String, err: String)
|
||||
|
||||
extension Process {
|
||||
@discardableResult
|
||||
static func run(_ executable: any Pathish, workingDirectory: URL? = nil, input: String? = nil, _ arguments: String...) -> AnyPublisher<ProcessOutput, Error> {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import XcodesKit
|
|||
|
||||
enum XcodeInstallState: Equatable {
|
||||
case notInstalled
|
||||
case installing(InstallationStep)
|
||||
case installing(XcodeInstallationStep)
|
||||
case installed(Path)
|
||||
|
||||
var notInstalled: Bool {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import SwiftUI
|
|||
import XcodesKit
|
||||
|
||||
struct InstallationStepDetailView: View {
|
||||
let installationStep: InstallationStep
|
||||
let installationStep: XcodeInstallationStep
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import SwiftUI
|
|||
import XcodesKit
|
||||
|
||||
struct InstallationStepRowView: View {
|
||||
let installationStep: InstallationStep
|
||||
let installationStep: XcodeInstallationStep
|
||||
let highlighted: Bool
|
||||
let cancel: () -> Void
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{\rtf1\ansi\ansicpg1252\cocoartf2709
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf2758
|
||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 .SFNS-Regular;}
|
||||
{\colortbl;\red255\green255\blue255;}
|
||||
{\*\expandedcolortbl;;}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import Foundation
|
||||
|
||||
public struct Environment {
|
||||
public var shell = Shell()
|
||||
}
|
||||
|
||||
public var Current = Environment()
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// RuntimeInstallState.swift
|
||||
//
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-11-23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Path
|
||||
|
||||
public enum RuntimeInstallState: Equatable {
|
||||
case notInstalled
|
||||
case installing(RuntimeInstallationStep)
|
||||
case installed(Path)
|
||||
|
||||
var notInstalled: Bool {
|
||||
switch self {
|
||||
case .notInstalled: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
var installing: Bool {
|
||||
switch self {
|
||||
case .installing: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
var installed: Bool {
|
||||
switch self {
|
||||
case .installed: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// RuntimeInstallationStep.swift
|
||||
//
|
||||
//
|
||||
// Created by Matt Kiazyk on 2023-11-23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum RuntimeInstallationStep: Equatable, CustomStringConvertible {
|
||||
case downloading(progress: Progress)
|
||||
case unarchiving
|
||||
case moving(destination: String)
|
||||
case trashingArchive
|
||||
case checkingSecurity
|
||||
case finishing
|
||||
|
||||
public var description: String {
|
||||
"(\(stepNumber)/\(stepCount)) \(message)"
|
||||
}
|
||||
|
||||
public var message: String {
|
||||
switch self {
|
||||
case .downloading:
|
||||
return localizeString("Downloading")
|
||||
case .unarchiving:
|
||||
return localizeString("Unarchiving")
|
||||
case .moving(let destination):
|
||||
return String(format: localizeString("Moving"), destination)
|
||||
case .trashingArchive:
|
||||
return localizeString("TrashingArchive")
|
||||
case .checkingSecurity:
|
||||
return localizeString("CheckingSecurity")
|
||||
case .finishing:
|
||||
return localizeString("Finishing")
|
||||
}
|
||||
}
|
||||
|
||||
public var stepNumber: Int {
|
||||
switch self {
|
||||
case .downloading: return 1
|
||||
case .unarchiving: return 2
|
||||
case .moving: return 3
|
||||
case .trashingArchive: return 4
|
||||
case .checkingSecurity: return 5
|
||||
case .finishing: return 6
|
||||
}
|
||||
}
|
||||
|
||||
public var stepCount: Int { 6 }
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ public struct DownloadableRuntime: Codable {
|
|||
}
|
||||
|
||||
// dynamically updated - not decoded
|
||||
public var installState: InstallState = .notInstalled
|
||||
public var installState: RuntimeInstallState = .notInstalled
|
||||
public var sdkBuildUpdate: String?
|
||||
|
||||
enum CodingKeys: CodingKey {
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
import Foundation
|
||||
import Path
|
||||
|
||||
public enum InstallState: Equatable {
|
||||
public enum XcodeInstallState: Equatable {
|
||||
case notInstalled
|
||||
case installing(InstallationStep)
|
||||
case installing(XcodeInstallationStep)
|
||||
case installed(Path)
|
||||
|
||||
var notInstalled: Bool {
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
import Foundation
|
||||
|
||||
// A numbered step
|
||||
public enum InstallationStep: Equatable, CustomStringConvertible {
|
||||
public enum XcodeInstallationStep: Equatable, CustomStringConvertible {
|
||||
case downloading(progress: Progress)
|
||||
case unarchiving
|
||||
case moving(destination: String)
|
||||
|
|
@ -50,6 +50,7 @@ public enum InstallationStep: Equatable, CustomStringConvertible {
|
|||
|
||||
public var stepCount: Int { 6 }
|
||||
}
|
||||
|
||||
func localizeString(_ key: String, comment: String = "") -> String {
|
||||
if #available(macOS 12, *) {
|
||||
return String(localized: String.LocalizationValue(key))
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import Foundation
|
||||
import Path
|
||||
|
||||
public struct Shell {
|
||||
public struct XcodesShell {
|
||||
public var installedRuntimes: () async throws -> ProcessOutput = {
|
||||
try await Process.run(Path.root.usr.bin.join("xcrun"), "simctl", "runtime", "list", "-j")
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import Foundation
|
||||
|
||||
public struct XcodesKitEnvironment {
|
||||
public var shell = XcodesShell()
|
||||
}
|
||||
|
||||
public var Current = XcodesKitEnvironment()
|
||||
|
|
@ -4,6 +4,8 @@ import CombineExpectations
|
|||
import Path
|
||||
import Version
|
||||
import XCTest
|
||||
import XcodesKit
|
||||
|
||||
@testable import Xcodes
|
||||
|
||||
class AppStateTests: XCTestCase {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import Combine
|
|||
import Foundation
|
||||
@testable import Xcodes
|
||||
|
||||
extension Environment {
|
||||
static var mock = Environment(
|
||||
extension Xcodes.Environment {
|
||||
static var mock = Xcodes.Environment(
|
||||
shell: .mock,
|
||||
files: .mock,
|
||||
network: .mock,
|
||||
|
|
|
|||
Loading…
Reference in a new issue