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