Merge pull request #459 from Grublady/installation-stage

Fixes in adjusted installation process
This commit is contained in:
David Bureš 2025-02-09 20:33:46 +01:00 committed by GitHub
commit e2cfc6e81b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 113 additions and 143 deletions

View File

@ -1,52 +0,0 @@
//
// Package Installation Status.swift
// Cork
//
// Created by David Bureš on 22.02.2023.
//
import Foundation
enum PackageInstallationStage: CustomStringConvertible
{
case ready, loadingDependencies, fetchingDependencies, installingDependencies, installingPackage, finished // For Formulae
case downloadingCask, installingCask, movingCask, linkingCaskBinary // For Casks
case requiresSudoPassword, wrongArchitecture, binaryAlreadyExists, terminatedUnexpectedly // For Both
var description: String
{
switch self
{
case .ready:
return "Ready"
case .loadingDependencies:
return "Loading Dependencies"
case .fetchingDependencies:
return "Fetching Dependencies"
case .installingDependencies:
return "Installing Dependencies"
case .installingPackage:
return "Installing Package"
case .finished:
return "Installation Finished"
case .downloadingCask:
return "Downloaing Cask"
case .installingCask:
return "Installing Cask"
case .movingCask:
return "Moving Cask"
case .linkingCaskBinary:
return "Linking Cask Binary"
case .requiresSudoPassword:
return "Sudo Password Required"
case .wrongArchitecture:
return "Wrong Package Architecture"
case .binaryAlreadyExists:
return "Binary Already Exists"
case .terminatedUnexpectedly:
return "Terminated Unexpectedly"
}
}
}

View File

@ -7410,6 +7410,16 @@
} }
} }
}, },
"add-package.install.calculating-dependencies" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Calculating Dependencies..."
}
}
}
},
"add-package.install.downloading-cask-%@" : { "add-package.install.downloading-cask-%@" : {
"localizations" : { "localizations" : {
"cs" : { "cs" : {
@ -8139,6 +8149,7 @@
} }
}, },
"add-package.install.loading-dependencies" : { "add-package.install.loading-dependencies" : {
"extractionState" : "stale",
"localizations" : { "localizations" : {
"cs" : { "cs" : {
"stringUnit" : { "stringUnit" : {

View File

@ -10,7 +10,7 @@ import Foundation
class InstallationProgressTracker: ObservableObject class InstallationProgressTracker: ObservableObject
{ {
@Published var packageBeingInstalled: PackageInProgressOfBeingInstalled = .init(package: .init(name: "", type: .formula, installedOn: nil, versions: [], sizeInBytes: 0), installationStage: .downloadingCask, packageInstallationProgress: 0) @Published var packageBeingInstalled: PackageInProgressOfBeingInstalled = .init(package: .init(name: "", type: .formula, installedOn: nil, versions: [], sizeInBytes: 0), installationStage: .ready, packageInstallationProgress: 0)
@Published var numberOfPackageDependencies: Int = 0 @Published var numberOfPackageDependencies: Int = 0
@Published var numberInLineOfPackageCurrentlyBeingFetched: Int = 0 @Published var numberInLineOfPackageCurrentlyBeingFetched: Int = 0
@ -76,97 +76,99 @@ class InstallationProgressTracker: ObservableObject
{ {
switch output switch output
{ {
case .standardOutput(let outputLine): case .standardOutput(let outputLines):
AppConstants.shared.logger.debug("Package install line out: \(outputLine, privacy: .public)") for outputLine in outputLines.split(separator: "\n")
if showRealTimeTerminalOutputs
{ {
packageBeingInstalled.realTimeTerminalOutput.append(RealTimeTerminalLine(line: outputLine)) let outputLine: String = String(outputLine)
} AppConstants.shared.logger.debug("Package install line out: \(outputLine, privacy: .public)")
if let stage = BrewInstallationStage.matchingFormula( if showRealTimeTerminalOutputs
outputLine,
packageName: package.name,
packageDependencies: packageDependencies,
hasAlreadyMatchedPackage: hasAlreadyMatchedPackage
)
{
switch stage
{ {
case .calculatingDependencies: packageBeingInstalled.realTimeTerminalOutput.append(RealTimeTerminalLine(line: outputLine))
AppConstants.shared.logger.warning("Output line: \(outputLine)")
if var matchedDependencies = try? outputLine.regexMatch("(?<=\(package.name): ).*?(.*)")
{
AppConstants.shared.logger.info("Matched a line describing the dependencies that will be downloaded")
matchedDependencies = matchedDependencies.replacingOccurrences(of: " and", with: ",")
packageDependencies = matchedDependencies.components(separatedBy: ", ")
AppConstants.shared.logger.debug("Will fetch \(packageDependencies.count) dependencies!")
numberOfPackageDependencies = packageDependencies.count
}
packageBeingInstalled.packageInstallationProgress = 1
case .fetchingDependencies(let packageDependencies):
AppConstants.shared.logger.info("Will fetch dependencies!")
packageBeingInstalled.installationStage = .fetchingDependencies
numberInLineOfPackageCurrentlyBeingFetched = numberInLineOfPackageCurrentlyBeingFetched + 1
AppConstants.shared.logger.info("Fetching dependency \(self.numberInLineOfPackageCurrentlyBeingFetched) of \(packageDependencies.count)")
packageBeingInstalled.packageInstallationProgress = packageBeingInstalled.packageInstallationProgress + Double(Double(10) / (Double(3) * (Double(numberOfPackageDependencies) * Double(5))))
case .installingDependencies(let packageName):
AppConstants.shared.logger.info("Will install dependencies!")
packageBeingInstalled.installationStage = .installingDependencies
numberInLineOfPackageCurrentlyBeingInstalled += 1
packageBeingInstalled.packageInstallationProgress += Double(10) / (3 * Double(numberOfPackageDependencies))
case .installingPackage(let packageName, let isFirstMatch):
if hasAlreadyMatchedPackage
{
AppConstants.shared.logger.info("Will install the package itself!")
packageBeingInstalled.installationStage = .installingPackage
}
else
{
AppConstants.shared.logger.info("Matched the dud line about the package itself being installed!")
hasAlreadyMatchedPackage = true
}
packageBeingInstalled.packageInstallationProgress += (10 - packageBeingInstalled.packageInstallationProgress) / 2
case .requiresSudoPassword:
packageBeingInstalled.installationStage = .requiresSudoPassword
case .finished:
packageBeingInstalled.packageInstallationProgress = 10
packageBeingInstalled.installationStage = .finished
default:
break
} }
if let stage = BrewInstallationStage.matchingFormula(
outputLine,
packageName: package.name,
packageDependencies: packageDependencies,
hasAlreadyMatchedPackage: hasAlreadyMatchedPackage
)
{
packageBeingInstalled.installationStage = stage
switch stage
{
case .calculatingDependencies:
AppConstants.shared.logger.warning("Output line: \(outputLine)")
if var matchedDependencies = try? outputLine.regexMatch("(?<=\(package.name): ).*?(.*)")
{
AppConstants.shared.logger.info("Matched a line describing the dependencies that will be downloaded")
matchedDependencies = matchedDependencies.replacingOccurrences(of: " and", with: ",")
packageDependencies = matchedDependencies.components(separatedBy: ", ")
AppConstants.shared.logger.debug("Will fetch \(packageDependencies.count) dependencies!")
numberOfPackageDependencies = packageDependencies.count
}
packageBeingInstalled.packageInstallationProgress = 1
case .fetchingDependencies(let packageDependencies):
AppConstants.shared.logger.info("Will fetch dependencies!")
numberInLineOfPackageCurrentlyBeingFetched = numberInLineOfPackageCurrentlyBeingFetched + 1
AppConstants.shared.logger.info("Fetching dependency \(self.numberInLineOfPackageCurrentlyBeingFetched) of \(packageDependencies.count)")
packageBeingInstalled.packageInstallationProgress = packageBeingInstalled.packageInstallationProgress + Double(Double(10) / (Double(3) * (Double(numberOfPackageDependencies) * Double(5))))
case .installingDependencies:
AppConstants.shared.logger.info("Will install dependencies!")
numberInLineOfPackageCurrentlyBeingInstalled += 1
packageBeingInstalled.packageInstallationProgress += Double(10) / (3 * Double(numberOfPackageDependencies))
case .installingPackage:
if hasAlreadyMatchedPackage
{
AppConstants.shared.logger.info("Will install the package itself!")
}
else
{
AppConstants.shared.logger.info("Matched the dud line about the package itself being installed!")
hasAlreadyMatchedPackage = true
}
packageBeingInstalled.packageInstallationProgress += (10 - packageBeingInstalled.packageInstallationProgress) / 2
case .requiresSudoPassword:
packageBeingInstalled.installationStage = .requiresSudoPassword
case .finished:
packageBeingInstalled.packageInstallationProgress = 10
packageBeingInstalled.installationStage = .finished
default:
break
}
}
installOutput.append(outputLine)
AppConstants.shared.logger.debug("Current installation stage: \(self.packageBeingInstalled.installationStage.description, privacy: .public)")
} }
installOutput.append(outputLine) case .standardError(let errorLines):
AppConstants.shared.logger.debug("Current installation stage: \(self.packageBeingInstalled.installationStage.description, privacy: .public)") AppConstants.shared.logger.error("Errored out: \(errorLines, privacy: .public)")
case .standardError(let errorLine):
AppConstants.shared.logger.error("Errored out: \(errorLine, privacy: .public)")
if showRealTimeTerminalOutputs if showRealTimeTerminalOutputs
{ {
packageBeingInstalled.realTimeTerminalOutput.append(RealTimeTerminalLine(line: errorLine)) packageBeingInstalled.realTimeTerminalOutput.append(RealTimeTerminalLine(line: errorLines))
} }
if let stage = BrewInstallationStage.matchingFormula( if let stage = BrewInstallationStage.matchingFormula(
errorLine, errorLines,
packageName: package.name, packageName: package.name,
packageDependencies: packageDependencies, packageDependencies: packageDependencies,
hasAlreadyMatchedPackage: hasAlreadyMatchedPackage hasAlreadyMatchedPackage: hasAlreadyMatchedPackage
) )
{ {
packageBeingInstalled.installationStage = .requiresSudoPassword packageBeingInstalled.installationStage = stage
} }
} }
} }
@ -198,31 +200,27 @@ class InstallationProgressTracker: ObservableObject
if let stage = BrewInstallationStage.matchingCask(outputLine) if let stage = BrewInstallationStage.matchingCask(outputLine)
{ {
packageBeingInstalled.installationStage = stage
switch stage switch stage
{ {
case .downloadingCask: case .downloadingCask:
AppConstants.shared.logger.info("Will download Cask") AppConstants.shared.logger.info("Will download Cask")
packageBeingInstalled.installationStage = .downloadingCask
packageBeingInstalled.packageInstallationProgress += 2 packageBeingInstalled.packageInstallationProgress += 2
case .installingCask: case .installingCask:
AppConstants.shared.logger.info("Will install Cask") AppConstants.shared.logger.info("Will install Cask")
packageBeingInstalled.installationStage = .installingCask
packageBeingInstalled.packageInstallationProgress += 2 packageBeingInstalled.packageInstallationProgress += 2
case .movingCask: case .movingCask:
AppConstants.shared.logger.info("Moving App") AppConstants.shared.logger.info("Moving App")
packageBeingInstalled.installationStage = .movingCask
packageBeingInstalled.packageInstallationProgress += 2 packageBeingInstalled.packageInstallationProgress += 2
case .linkingCaskBinary: case .linkingCaskBinary:
AppConstants.shared.logger.info("Linking Binary") AppConstants.shared.logger.info("Linking Binary")
packageBeingInstalled.installationStage = .linkingCaskBinary
packageBeingInstalled.packageInstallationProgress += 2 packageBeingInstalled.packageInstallationProgress += 2
case .finished: case .finished:
AppConstants.shared.logger.info("Finished installing app") AppConstants.shared.logger.info("Finished installing app")
packageBeingInstalled.installationStage = .finished
packageBeingInstalled.packageInstallationProgress = 10 packageBeingInstalled.packageInstallationProgress = 10
default: default:
@ -240,7 +238,7 @@ class InstallationProgressTracker: ObservableObject
if let stage = BrewInstallationStage.matchingCask(errorLine) if let stage = BrewInstallationStage.matchingCask(errorLine)
{ {
packageBeingInstalled.installationStage = .terminatedUnexpectedly packageBeingInstalled.installationStage = stage
} }
} }
} }
@ -262,7 +260,7 @@ private protocol InstallationStage
// MARK: - Installation Stage Enum // MARK: - Installation Stage Enum
enum BrewInstallationStage: InstallationStage enum BrewInstallationStage: InstallationStage, Equatable
{ {
// Formula-specific stages // Formula-specific stages
case calculatingDependencies case calculatingDependencies
@ -277,10 +275,12 @@ enum BrewInstallationStage: InstallationStage
case linkingCaskBinary case linkingCaskBinary
// Common stages // Common stages
case ready
case requiresSudoPassword case requiresSudoPassword
case finished case finished
case binaryAlreadyExists case binaryAlreadyExists
case wrongArchitecture case wrongArchitecture
case terminatedUnexpectedly
fileprivate var matchConditions: [MatchCondition] fileprivate var matchConditions: [MatchCondition]
{ {
@ -308,7 +308,7 @@ enum BrewInstallationStage: InstallationStage
} }
] ]
case .installingPackage(let packageName, let isFirstMatch): case .installingPackage(let packageName, _):
return [ return [
.simple("Fetching \(packageName)"), .simple("Fetching \(packageName)"),
.simple("Installing \(packageName)") .simple("Installing \(packageName)")
@ -334,6 +334,9 @@ enum BrewInstallationStage: InstallationStage
return [ return [
.simple("==> Linking binary") .simple("==> Linking binary")
] ]
case .ready:
return []
case .requiresSudoPassword: case .requiresSudoPassword:
return [ return [
@ -358,6 +361,9 @@ enum BrewInstallationStage: InstallationStage
line.contains("but you are running") line.contains("but you are running")
} }
] ]
case .terminatedUnexpectedly:
return []
} }
} }
@ -381,6 +387,8 @@ enum BrewInstallationStage: InstallationStage
return "Moving Cask" return "Moving Cask"
case .linkingCaskBinary: case .linkingCaskBinary:
return "Linking Binary" return "Linking Binary"
case .ready:
return "Ready"
case .requiresSudoPassword: case .requiresSudoPassword:
return "Requires Sudo Password" return "Requires Sudo Password"
case .finished: case .finished:
@ -389,6 +397,8 @@ enum BrewInstallationStage: InstallationStage
return "Binary Already Exists" return "Binary Already Exists"
case .wrongArchitecture: case .wrongArchitecture:
return "Wrong Architecture" return "Wrong Architecture"
case .terminatedUnexpectedly:
return "Terminated Unexpectedly"
} }
} }

View File

@ -18,7 +18,7 @@ struct PackageInProgressOfBeingInstalled: Identifiable
let id: UUID = .init() let id: UUID = .init()
let package: BrewPackage let package: BrewPackage
var installationStage: PackageInstallationStage var installationStage: BrewInstallationStage
var packageInstallationProgress: Double var packageInstallationProgress: Double
var realTimeTerminalOutput: [RealTimeTerminalLine] = .init() var realTimeTerminalOutput: [RealTimeTerminalLine] = .init()

View File

@ -39,9 +39,9 @@ struct InstallingPackageView: View
Text("add-package.install.ready") Text("add-package.install.ready")
// FORMULAE // FORMULAE
case .loadingDependencies: case .calculatingDependencies:
Text("add-package.install.loading-dependencies") Text("add-package.install.calculating-dependencies")
case .fetchingDependencies: case .fetchingDependencies:
Text("add-package.install.fetching-dependencies") Text("add-package.install.fetching-dependencies")
@ -125,12 +125,13 @@ struct InstallingPackageView: View
AppConstants.shared.logger.debug("Installation result:\nStandard output: \(installationResult.standardOutput, privacy: .public)\nStandard error: \(installationResult.standardError, privacy: .public)") AppConstants.shared.logger.debug("Installation result:\nStandard output: \(installationResult.standardOutput, privacy: .public)\nStandard error: \(installationResult.standardError, privacy: .public)")
/// Check if the package installation stag at the end of the install process was something unexpected. Normal package installations go through multiple steps, and the three listed below are not supposed to be the end state. This means that something went wrong during the installation /// Check if the package installation stag at the end of the install process was something unexpected. Normal package installations go through multiple steps, and the three listed below are not supposed to be the end state. This means that something went wrong during the installation
let installationStage: PackageInstallationStage = installationProgressTracker.packageBeingInstalled.installationStage switch installationProgressTracker.packageBeingInstalled.installationStage {
if [.installingCask, .installingPackage, .ready].contains(installationStage) case .installingCask, .installingPackage, .ready:
{
AppConstants.shared.logger.warning("The installation process quit before it was supposed to") AppConstants.shared.logger.warning("The installation process quit before it was supposed to")
installationProgressTracker.packageBeingInstalled.installationStage = .terminatedUnexpectedly installationProgressTracker.packageBeingInstalled.installationStage = .terminatedUnexpectedly
default:
break
} }
} }
catch let fatalInstallationError catch let fatalInstallationError