mirror of https://github.com/buresdv/Cork
Merge branch 'adoptable-discoverability.outdated-package-display-fixes' into main.adoptable-discoverability
# Conflicts: # Cork/Localizable.xcstrings # Cork/Logic/App Adoption/Adopt Package.swift # Cork/Models/Packages/Brew Package Details.swift # Cork/Views/Mass App Adoption/Components/Adoption Results List.swift # Cork/Views/Mass App Adoption/Mass Adoption Stages/Mass Adoption Stage - Adopting.swift # Cork/Views/Mass App Adoption/Mass App Adoption View.swift # Cork/Views/Packages/Package Details/Sub-Views/Package System Info.swift # Cork/Views/Start Page/Start Page.swift # Cork/Views/Start Page/Sub-Views/Adoptable Packages Box.swift # Modules/Packages/PackagesModels/Logic/Package Loading/Load Up Installed Packages.swift # Modules/Packages/PackagesModels/Logic/Packages/Load Up Package Info.swift # Modules/Packages/PackagesModels/Trackers/Brew Packages Tracker.swift # Modules/Shared/Logic/Networking/Download Data From URL.swift # Project.swift
This commit is contained in:
commit
5ce0743699
|
|
@ -12,6 +12,7 @@ import SwiftUI
|
|||
import CorkShared
|
||||
import Defaults
|
||||
import DefaultsMacros
|
||||
import CorkModels
|
||||
|
||||
@Observable
|
||||
class AppDelegate: NSObject, NSApplicationDelegate
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import ButtonKit
|
|||
import CorkShared
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
struct ContentView: View, Sendable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import Defaults
|
|||
import SwiftData
|
||||
import SwiftUI
|
||||
import UserNotifications
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
@main
|
||||
struct CorkApp: App
|
||||
|
|
@ -90,7 +92,8 @@ struct CorkApp: App
|
|||
.environment(outdatedPackagesTracker)
|
||||
.environment(topPackagesTracker)
|
||||
.modelContainer(for: [
|
||||
SavedTaggedPackage.self
|
||||
SavedTaggedPackage.self,
|
||||
ExcludedAdoptableApp.self
|
||||
])
|
||||
.task
|
||||
{
|
||||
|
|
@ -131,146 +134,19 @@ struct CorkApp: App
|
|||
}
|
||||
.onAppear
|
||||
{
|
||||
print("Licensing state: \(appDelegate.appState.licensingState)")
|
||||
|
||||
#if SELF_COMPILED
|
||||
AppConstants.shared.logger.debug("Will set licensing state to Self Compiled")
|
||||
appDelegate.appState.licensingState = .selfCompiled
|
||||
#else
|
||||
if !hasValidatedEmail
|
||||
{
|
||||
if appDelegate.appState.licensingState != .selfCompiled
|
||||
{
|
||||
if let demoActivatedAt
|
||||
{
|
||||
let timeDemoWillRunOutAt: Date = demoActivatedAt + AppConstants.shared.demoLengthInSeconds
|
||||
|
||||
AppConstants.shared.logger.debug("There is \(demoActivatedAt.timeIntervalSinceNow.formatted()) to go on the demo")
|
||||
|
||||
AppConstants.shared.logger.debug("Demo will time out at \(timeDemoWillRunOutAt.formatted(date: .complete, time: .complete))")
|
||||
|
||||
if ((demoActivatedAt.timeIntervalSinceNow) + AppConstants.shared.demoLengthInSeconds) > 0
|
||||
{ // Check if there is still time on the demo
|
||||
/// do stuff if there is
|
||||
}
|
||||
else
|
||||
{
|
||||
hasFinishedLicensingWorkflow = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
handleLicensing()
|
||||
}
|
||||
.onAppear
|
||||
{
|
||||
// Start the background update scheduler when the app starts
|
||||
backgroundUpdateTimer.schedule
|
||||
{ (completion: NSBackgroundActivityScheduler.CompletionHandler) in
|
||||
AppConstants.shared.logger.log("Scheduled event fired at \(Date(), privacy: .auto)")
|
||||
|
||||
Task
|
||||
{
|
||||
var updateResult: TerminalOutput = await shell(AppConstants.shared.brewExecutablePath, ["update"])
|
||||
|
||||
AppConstants.shared.logger.debug("Update result:\nStandard output: \(updateResult.standardOutput, privacy: .public)\nStandard error: \(updateResult.standardError, privacy: .public)")
|
||||
|
||||
do
|
||||
{
|
||||
let temporaryOutdatedPackageTracker: OutdatedPackagesTracker = await .init()
|
||||
|
||||
try await temporaryOutdatedPackageTracker.getOutdatedPackages(brewPackagesTracker: brewPackagesTracker)
|
||||
|
||||
var newOutdatedPackages: Set<OutdatedPackage> = await temporaryOutdatedPackageTracker.outdatedPackages
|
||||
|
||||
AppConstants.shared.logger.debug("Outdated packages checker output: \(newOutdatedPackages, privacy: .public)")
|
||||
|
||||
defer
|
||||
{
|
||||
AppConstants.shared.logger.log("Will purge temporary update trackers")
|
||||
|
||||
updateResult = .init(standardOutput: "", standardError: "")
|
||||
newOutdatedPackages = .init()
|
||||
}
|
||||
|
||||
if await newOutdatedPackages.count > outdatedPackagesTracker.outdatedPackages.count
|
||||
{
|
||||
AppConstants.shared.logger.log("New updates found")
|
||||
|
||||
/// Set this to `true` so the normal notification doesn't get sent
|
||||
await setWhetherToSendStandardUpdatesAvailableNotification(to: false)
|
||||
|
||||
let differentPackages: Set<OutdatedPackage> = await newOutdatedPackages.subtracting(outdatedPackagesTracker.displayableOutdatedPackages)
|
||||
AppConstants.shared.logger.debug("Changed packages: \(differentPackages, privacy: .auto)")
|
||||
|
||||
sendNotification(title: String(localized: "notification.new-outdated-packages-found.title"), subtitle: differentPackages.map(\.package.name).formatted(.list(type: .and)))
|
||||
|
||||
await outdatedPackagesTracker.setOutdatedPackages(to: newOutdatedPackages)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1)
|
||||
{
|
||||
sendStandardUpdatesAvailableNotification = true
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AppConstants.shared.logger.log("No new updates found")
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
AppConstants.shared.logger.error("Something got fucked up about checking for outdated packages")
|
||||
}
|
||||
}
|
||||
|
||||
completion(NSBackgroundActivityScheduler.Result.finished)
|
||||
}
|
||||
handleBackgroundUpdating()
|
||||
}
|
||||
.onChange(of: demoActivatedAt) // React to when the user activates the demo
|
||||
{ _, newValue in
|
||||
if let newValue
|
||||
{ // If the demo has not been activated, `demoActivatedAt` is nil. So, when it's not nil anymore, it means the user activated it
|
||||
AppConstants.shared.logger.debug("The user activated the demo at \(newValue.formatted(date: .complete, time: .complete), privacy: .public)")
|
||||
hasFinishedLicensingWorkflow = true
|
||||
}
|
||||
handleDemoTiming(newValue: newValue)
|
||||
}
|
||||
.onChange(of: outdatedPackagesTracker.displayableOutdatedPackages.count)
|
||||
{ _, outdatedPackageCount in
|
||||
AppConstants.shared.logger.debug("Number of displayable outdated packages changed (\(outdatedPackageCount))")
|
||||
|
||||
// TODO: Remove this once I figure out why the updating spinner sometimes doesn't disappear
|
||||
withAnimation
|
||||
{
|
||||
outdatedPackagesTracker.isCheckingForPackageUpdates = false
|
||||
}
|
||||
|
||||
if outdatedPackageCount == 0
|
||||
{
|
||||
NSApp.dockTile.badgeLabel = ""
|
||||
}
|
||||
else
|
||||
{
|
||||
if areNotificationsEnabled
|
||||
{
|
||||
if outdatedPackageNotificationType == .badge || outdatedPackageNotificationType == .both
|
||||
{
|
||||
NSApp.dockTile.badgeLabel = String(outdatedPackageCount)
|
||||
}
|
||||
|
||||
// TODO: Changing the package display type sends a notificaiton, which is not visible since the app is in the foreground. Once macOS 15 comes out, move `sendStandardUpdatesAvailableNotification` into the AppState and suppress it
|
||||
if outdatedPackageNotificationType == .notification || outdatedPackageNotificationType == .both
|
||||
{
|
||||
AppConstants.shared.logger.log("Will try to send notification")
|
||||
|
||||
/// This needs to be checked because when the background update system finds an update, we don't want to send this normal notification.
|
||||
/// Instead, we want to send a more succinct notification that includes only the new package
|
||||
if sendStandardUpdatesAvailableNotification
|
||||
{
|
||||
sendNotification(title: String(localized: "notification.outdated-packages-found.title"), subtitle: String(localized: "notification.outdated-packages-found.body-\(outdatedPackageCount)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
handleOutdatedPackageChangeAppBadge(outdatedPackageCount: outdatedPackageCount)
|
||||
}
|
||||
.onChange(of: outdatedPackageNotificationType) // Set the correct app badge number when the user changes their notification settings
|
||||
{ _, newValue in
|
||||
|
|
@ -415,7 +291,7 @@ struct CorkApp: App
|
|||
WindowGroup(id: .previewWindowID, for: MinimalHomebrewPackage.self)
|
||||
{ $packageToPreview in
|
||||
|
||||
let convertedMinimalPackage: BrewPackage? = .init(from: packageToPreview)
|
||||
let convertedMinimalPackage: BrewPackage? = BrewPackage(using: packageToPreview)
|
||||
|
||||
PackagePreview(packageToPreview: convertedMinimalPackage)
|
||||
.navigationTitle(packageToPreview?.name ?? "")
|
||||
|
|
@ -657,13 +533,8 @@ struct CorkApp: App
|
|||
.environment(appDelegate.appState)
|
||||
.environment(outdatedPackagesTracker)
|
||||
|
||||
Button
|
||||
{
|
||||
appDelegate.appState.showSheet(ofType: .fullUpdate)
|
||||
} label: {
|
||||
Text("navigation.menu.packages.update")
|
||||
}
|
||||
.keyboardShortcut("r", modifiers: [.control, .command])
|
||||
UpgradePackagesButton(appState: appDelegate.appState)
|
||||
.keyboardShortcut("r", modifiers: [.control, .command])
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
|
@ -721,7 +592,7 @@ struct CorkApp: App
|
|||
{
|
||||
Button
|
||||
{
|
||||
openWindow(id: .errorInspectorWindowID, value: PackageLoadingError.packageIsNotAFolder("Hello I am an error", packageURL: .applicationDirectory).localizedDescription)
|
||||
openWindow(id: .errorInspectorWindowID, value: BrewPackage.PackageLoadingError.packageIsNotAFolder("Hello I am an error", packageURL: .applicationDirectory).localizedDescription)
|
||||
} label: {
|
||||
Text("debug.action.show-error-inspector")
|
||||
}
|
||||
|
|
@ -729,9 +600,10 @@ struct CorkApp: App
|
|||
Text("debug.action.ui")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Functions
|
||||
|
||||
|
||||
// MARK: - App badge
|
||||
func setAppBadge(outdatedPackageNotificationType: OutdatedPackageNotificationType)
|
||||
{
|
||||
if outdatedPackageNotificationType == .badge || outdatedPackageNotificationType == .both
|
||||
|
|
@ -746,9 +618,160 @@ struct CorkApp: App
|
|||
NSApp.dockTile.badgeLabel = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func setWhetherToSendStandardUpdatesAvailableNotification(to newValue: Bool)
|
||||
{
|
||||
self.sendStandardUpdatesAvailableNotification = newValue
|
||||
}
|
||||
|
||||
func handleOutdatedPackageChangeAppBadge(outdatedPackageCount: Int)
|
||||
{
|
||||
AppConstants.shared.logger.debug("Number of displayable outdated packages changed (\(outdatedPackageCount))")
|
||||
|
||||
// TODO: Remove this once I figure out why the updating spinner sometimes doesn't disappear
|
||||
withAnimation
|
||||
{
|
||||
outdatedPackagesTracker.isCheckingForPackageUpdates = false
|
||||
}
|
||||
|
||||
if outdatedPackageCount == 0
|
||||
{
|
||||
NSApp.dockTile.badgeLabel = ""
|
||||
}
|
||||
else
|
||||
{
|
||||
if areNotificationsEnabled
|
||||
{
|
||||
if outdatedPackageNotificationType == .badge || outdatedPackageNotificationType == .both
|
||||
{
|
||||
NSApp.dockTile.badgeLabel = String(outdatedPackageCount)
|
||||
}
|
||||
|
||||
// TODO: Changing the package display type sends a notificaiton, which is not visible since the app is in the foreground. Once macOS 15 comes out, move `sendStandardUpdatesAvailableNotification` into the AppState and suppress it
|
||||
if outdatedPackageNotificationType == .notification || outdatedPackageNotificationType == .both
|
||||
{
|
||||
AppConstants.shared.logger.log("Will try to send notification")
|
||||
|
||||
/// This needs to be checked because when the background update system finds an update, we don't want to send this normal notification.
|
||||
/// Instead, we want to send a more succinct notification that includes only the new package
|
||||
if sendStandardUpdatesAvailableNotification
|
||||
{
|
||||
sendNotification(title: String(localized: "notification.outdated-packages-found.title"), subtitle: String(localized: "notification.outdated-packages-found.body-\(outdatedPackageCount)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Background updating
|
||||
|
||||
func handleBackgroundUpdating()
|
||||
{
|
||||
// Start the background update scheduler when the app starts
|
||||
backgroundUpdateTimer.schedule
|
||||
{ (completion: NSBackgroundActivityScheduler.CompletionHandler) in
|
||||
AppConstants.shared.logger.log("Scheduled event fired at \(Date(), privacy: .auto)")
|
||||
|
||||
Task
|
||||
{
|
||||
var updateResult: TerminalOutput = await shell(AppConstants.shared.brewExecutablePath, ["update"])
|
||||
|
||||
AppConstants.shared.logger.debug("Update result:\nStandard output: \(updateResult.standardOutput, privacy: .public)\nStandard error: \(updateResult.standardError, privacy: .public)")
|
||||
|
||||
do
|
||||
{
|
||||
let temporaryOutdatedPackageTracker: OutdatedPackagesTracker = await .init()
|
||||
|
||||
try await temporaryOutdatedPackageTracker.getOutdatedPackages(brewPackagesTracker: brewPackagesTracker)
|
||||
|
||||
var newOutdatedPackages: Set<OutdatedPackage> = await temporaryOutdatedPackageTracker.outdatedPackages
|
||||
|
||||
AppConstants.shared.logger.debug("Outdated packages checker output: \(newOutdatedPackages, privacy: .public)")
|
||||
|
||||
defer
|
||||
{
|
||||
AppConstants.shared.logger.log("Will purge temporary update trackers")
|
||||
|
||||
updateResult = .init(standardOutput: "", standardError: "")
|
||||
newOutdatedPackages = .init()
|
||||
}
|
||||
|
||||
if await newOutdatedPackages.count > outdatedPackagesTracker.outdatedPackages.count
|
||||
{
|
||||
AppConstants.shared.logger.log("New updates found")
|
||||
|
||||
/// Set this to `true` so the normal notification doesn't get sent
|
||||
await setWhetherToSendStandardUpdatesAvailableNotification(to: false)
|
||||
|
||||
let differentPackages: Set<OutdatedPackage> = await newOutdatedPackages.subtracting(outdatedPackagesTracker.displayableOutdatedPackages)
|
||||
AppConstants.shared.logger.debug("Changed packages: \(differentPackages, privacy: .auto)")
|
||||
|
||||
sendNotification(title: String(localized: "notification.new-outdated-packages-found.title"), subtitle: differentPackages.map(\.package.name).formatted(.list(type: .and)))
|
||||
|
||||
await outdatedPackagesTracker.setOutdatedPackages(to: newOutdatedPackages)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1)
|
||||
{
|
||||
sendStandardUpdatesAvailableNotification = true
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AppConstants.shared.logger.log("No new updates found")
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
AppConstants.shared.logger.error("Something got fucked up about checking for outdated packages")
|
||||
}
|
||||
}
|
||||
|
||||
completion(NSBackgroundActivityScheduler.Result.finished)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Licensing
|
||||
func handleLicensing()
|
||||
{
|
||||
print("Licensing state: \(appDelegate.appState.licensingState)")
|
||||
|
||||
#if SELF_COMPILED
|
||||
AppConstants.shared.logger.debug("Will set licensing state to Self Compiled")
|
||||
appDelegate.appState.licensingState = .selfCompiled
|
||||
#else
|
||||
if !hasValidatedEmail
|
||||
{
|
||||
if appDelegate.appState.licensingState != .selfCompiled
|
||||
{
|
||||
if let demoActivatedAt
|
||||
{
|
||||
let timeDemoWillRunOutAt: Date = demoActivatedAt + AppConstants.shared.demoLengthInSeconds
|
||||
|
||||
AppConstants.shared.logger.debug("There is \(demoActivatedAt.timeIntervalSinceNow.formatted()) to go on the demo")
|
||||
|
||||
AppConstants.shared.logger.debug("Demo will time out at \(timeDemoWillRunOutAt.formatted(date: .complete, time: .complete))")
|
||||
|
||||
if ((demoActivatedAt.timeIntervalSinceNow) + AppConstants.shared.demoLengthInSeconds) > 0
|
||||
{ // Check if there is still time on the demo
|
||||
/// do stuff if there is
|
||||
}
|
||||
else
|
||||
{
|
||||
hasFinishedLicensingWorkflow = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func handleDemoTiming(newValue: Date?)
|
||||
{
|
||||
if let newValue
|
||||
{ // If the demo has not been activated, `demoActivatedAt` is nil. So, when it's not nil anymore, it means the user activated it
|
||||
AppConstants.shared.logger.debug("The user activated the demo at \(newValue.formatted(date: .complete, time: .complete), privacy: .public)")
|
||||
hasFinishedLicensingWorkflow = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
//
|
||||
// Brewfile Import Stage.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 11.11.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum BrewfileImportStage
|
||||
{
|
||||
case importing, finished
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// Licensing State.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 18.03.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum LicensingState
|
||||
{
|
||||
case notBoughtOrHasNotActivatedDemo
|
||||
|
||||
case demo
|
||||
case bought
|
||||
|
||||
case selfCompiled
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CorkModels
|
||||
|
||||
enum NavigationTargetMainWindow: Hashable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
//
|
||||
// Outdated Package Type.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 17.05.2024.
|
||||
//
|
||||
|
||||
import Charts
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum CachedDownloadType: String, CustomStringConvertible, Plottable
|
||||
{
|
||||
case formula
|
||||
case cask
|
||||
case other
|
||||
case unknown
|
||||
|
||||
var description: String
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return String(localized: "package-details.type.formula")
|
||||
case .cask:
|
||||
return String(localized: "package-details.type.cask")
|
||||
case .other:
|
||||
return String(localized: "start-page.cached-downloads.graph.other-smaller-packages")
|
||||
default:
|
||||
return String(localized: "cached-downloads.type.unknown")
|
||||
}
|
||||
}
|
||||
|
||||
var color: Color
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return .purple
|
||||
case .cask:
|
||||
return .orange
|
||||
case .other:
|
||||
return .mint
|
||||
default:
|
||||
return .gray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
//
|
||||
// Package Types.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 05.02.2023.
|
||||
//
|
||||
|
||||
import AppIntents
|
||||
import Charts
|
||||
import CorkShared
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum PackageType: String, CustomStringConvertible, Plottable, AppEntity, Codable
|
||||
{
|
||||
case formula
|
||||
case cask
|
||||
|
||||
/// User-readable description of the package type
|
||||
var description: String
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return String(localized: "package-details.type.formula")
|
||||
case .cask:
|
||||
return String(localized: "package-details.type.cask")
|
||||
}
|
||||
}
|
||||
|
||||
/// Localization keys for description of the package type
|
||||
var localizableDescription: LocalizedStringKey
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return "package-details.type.formula"
|
||||
case .cask:
|
||||
return "package-details.type.cask"
|
||||
}
|
||||
}
|
||||
|
||||
/// Parent folder for this package type
|
||||
var parentFolder: URL
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return AppConstants.shared.brewCellarPath
|
||||
case .cask:
|
||||
return AppConstants.shared.brewCaskPath
|
||||
}
|
||||
}
|
||||
|
||||
/// Accessibility representation
|
||||
var accessibilityLabel: LocalizedStringKey
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
return "accessibility.label.package-type.formula"
|
||||
case .cask:
|
||||
return "accessibility.label.package-type.cask"
|
||||
}
|
||||
}
|
||||
|
||||
static let typeDisplayRepresentation: TypeDisplayRepresentation = .init(name: "package-details.type")
|
||||
|
||||
var displayRepresentation: DisplayRepresentation
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .formula:
|
||||
DisplayRepresentation(title: "package-details.type.formula")
|
||||
case .cask:
|
||||
DisplayRepresentation(title: "package-details.type.cask")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
//
|
||||
// JSON Parsing Error.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 21.06.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum JSONParsingError: LocalizedError
|
||||
{
|
||||
case couldNotConvertStringToData(failureReason: String?), couldNotDecode(failureReason: String)
|
||||
|
||||
var errorDescription: String?
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .couldNotConvertStringToData(let failureReason):
|
||||
return String(localized: "error.json-parsing.could-not-convert-string-to-data.\(failureReason ?? "")")
|
||||
case .couldNotDecode(let failureReason):
|
||||
return String(localized: "error.json-parsing.could-not-decode.\(failureReason)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
//
|
||||
// Package Loading Error.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 10.11.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Error representing failures while loading
|
||||
enum PackageLoadingError: LocalizedError, Hashable, Identifiable
|
||||
{
|
||||
/// Tried to treat the folder `Cellar` or `Caskroom` itself as a package - means Homebrew itself is broken
|
||||
case triedToThreatFolderContainingPackagesAsPackage(packageType: PackageType)
|
||||
|
||||
/// The `Cellar` and `Caskroom` folder itself couldn't be loaded
|
||||
case couldNotReadContentsOfParentFolder(failureReason: String, folderURL: URL)
|
||||
|
||||
/// Failed while trying to read contents of package folder
|
||||
case failedWhileReadingContentsOfPackageFolder(folderURL: URL, reportedError: String)
|
||||
|
||||
case failedWhileTryingToDetermineIntentionalInstallation(folderURL: URL, associatedIntentionalDiscoveryError: IntentionalInstallationDiscoveryError)
|
||||
|
||||
/// The package root folder exists, but the package itself doesn't have any versions
|
||||
case packageDoesNotHaveAnyVersionsInstalled(packageURL: URL)
|
||||
|
||||
/// A folder that should have contained the package is not actually a folder
|
||||
case packageIsNotAFolder(String, packageURL: URL)
|
||||
|
||||
/// The number of loaded packages does not match the number of package parent folders
|
||||
case numberOLoadedPackagesDosNotMatchNumberOfPackageFolders
|
||||
|
||||
var errorDescription: String?
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .couldNotReadContentsOfParentFolder(let failureReason, _):
|
||||
return String(localized: "error.package-loading.could-not-read-contents-of-parent-folder.\(failureReason)")
|
||||
|
||||
case .triedToThreatFolderContainingPackagesAsPackage(let packageType):
|
||||
switch packageType
|
||||
{
|
||||
case .formula:
|
||||
return "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.formulae"
|
||||
case .cask:
|
||||
return "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.casks"
|
||||
}
|
||||
|
||||
case .failedWhileReadingContentsOfPackageFolder(let folderURL, let reportedError):
|
||||
return String(localized: "error.package-loading.could-not-load-\(folderURL.packageNameFromURL())-at-\(folderURL.absoluteString)-because-\(reportedError)", comment: "Couldn't load package (package name) at (package URL) because (failure reason)")
|
||||
|
||||
case .failedWhileTryingToDetermineIntentionalInstallation(_, let associatedIntentionalDiscoveryError):
|
||||
return associatedIntentionalDiscoveryError.localizedDescription
|
||||
|
||||
case .packageDoesNotHaveAnyVersionsInstalled(let packageURL):
|
||||
return String(localized: "error.package-loading.\(packageURL.packageNameFromURL())-does-not-have-any-versions-installed")
|
||||
|
||||
case .packageIsNotAFolder(let string, _):
|
||||
return String(localized: "error.package-loading.\(string)-not-a-folder", comment: "Package folder in this context means a folder that encloses package versions. Every package has its own folder, and this error occurs when the provided URL does not point to a folder that encloses package versions")
|
||||
|
||||
case .numberOLoadedPackagesDosNotMatchNumberOfPackageFolders:
|
||||
return String(localized: "error.package-loading.number-of-loaded-poackages-does-not-match-number-of-package-folders", comment: "This error occurs when there's a mismatch between the number of loaded packages, and the number of package folders in the package folders")
|
||||
}
|
||||
}
|
||||
|
||||
var id: UUID
|
||||
{
|
||||
return UUID()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
extension MassAppAdoptionView.MassAppAdoptionTacker
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,16 +8,20 @@
|
|||
import AppIntents
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkIntents
|
||||
|
||||
struct GetInstalledCasksIntent: AppIntent
|
||||
public struct GetInstalledCasksIntent: AppIntent
|
||||
{
|
||||
static let title: LocalizedStringResource = "intent.get-installed-casks.title"
|
||||
static let description: LocalizedStringResource = "intent.get-installed-casks.description"
|
||||
public init() {}
|
||||
|
||||
public static let title: LocalizedStringResource = "intent.get-installed-casks.title"
|
||||
public static let description: LocalizedStringResource = "intent.get-installed-casks.description"
|
||||
|
||||
static let isDiscoverable: Bool = true
|
||||
static let openAppWhenRun: Bool = false
|
||||
public static let isDiscoverable: Bool = true
|
||||
public static let openAppWhenRun: Bool = false
|
||||
|
||||
func perform() async throws -> some ReturnsValue<[MinimalHomebrewPackage]>
|
||||
public func perform() async throws -> some ReturnsValue<[MinimalHomebrewPackage]>
|
||||
{
|
||||
let allowAccessToFile: Bool = AppConstants.shared.brewCaskPath.startAccessingSecurityScopedResource()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import AppIntents
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
|
||||
enum FolderAccessingError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import AppIntents
|
||||
import Foundation
|
||||
import CorkModels
|
||||
|
||||
struct GetInstalledPackagesIntent: AppIntent
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import AppIntents
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum RefreshIntentResult: String, AppEnum
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum BrewfileDumpingError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum BrewfileReadingError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
|
||||
enum TopPackageLoadingError: LocalizedError
|
||||
{
|
||||
|
|
@ -99,7 +100,15 @@ extension TopPackagesTracker
|
|||
|
||||
if normalizedDownloadNumber > downloadsCutoff
|
||||
{
|
||||
return .init(name: rawTopFormula.formula, type: .formula, installedOn: nil, versions: .init(), sizeInBytes: nil, downloadCount: normalizedDownloadNumber)
|
||||
return .init(
|
||||
name: rawTopFormula.formula,
|
||||
type: .formula,
|
||||
installedOn: nil,
|
||||
versions: .init(),
|
||||
url: nil,
|
||||
sizeInBytes: nil,
|
||||
downloadCount: normalizedDownloadNumber
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -155,7 +164,15 @@ extension TopPackagesTracker
|
|||
|
||||
if normalizedDownloadNumber > downloadsCutoff
|
||||
{
|
||||
return .init(name: rawTopCask.cask, type: .cask, installedOn: nil, versions: .init(), sizeInBytes: nil, downloadCount: normalizedDownloadNumber)
|
||||
return .init(
|
||||
name: rawTopCask.cask,
|
||||
type: .cask,
|
||||
installedOn: nil,
|
||||
versions: .init(),
|
||||
url: nil,
|
||||
sizeInBytes: nil,
|
||||
downloadCount: normalizedDownloadNumber
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,177 +0,0 @@
|
|||
//
|
||||
// Get Contents of Folder.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 03.07.2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
|
||||
/*
|
||||
func getContentsOfFolder(targetFolder: URL) async throws -> Set<BrewPackage>
|
||||
{
|
||||
do
|
||||
{
|
||||
guard let items = targetFolder.validPackageURLs
|
||||
else
|
||||
{
|
||||
throw PackageLoadingError.failedWhileLoadingPackages(failureReason: String(localized: "alert.fatal.could-not-filter-invalid-packages"))
|
||||
}
|
||||
|
||||
let loadedPackages: Set<BrewPackage> = try await withThrowingTaskGroup(of: BrewPackage.self, returning: Set<BrewPackage>.self)
|
||||
{ taskGroup in
|
||||
for item in items
|
||||
{
|
||||
let fullURLToPackageFolderCurrentlyBeingProcessed: URL = targetFolder.appendingPathComponent(item, conformingTo: .folder)
|
||||
|
||||
taskGroup.addTask(priority: .high)
|
||||
{
|
||||
guard let versionURLs: [URL] = fullURLToPackageFolderCurrentlyBeingProcessed.packageVersionURLs
|
||||
else
|
||||
{
|
||||
if targetFolder.appendingPathComponent(item, conformingTo: .fileURL).isDirectory
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while getting package version for package \(fullURLToPackageFolderCurrentlyBeingProcessed.lastPathComponent). Package does not have any version installed.")
|
||||
throw PackageLoadingError.packageDoesNotHaveAnyVersionsInstalled(item)
|
||||
}
|
||||
else
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while getting package version for package \(fullURLToPackageFolderCurrentlyBeingProcessed.lastPathComponent). Package is not a folder")
|
||||
throw PackageLoadingError.packageIsNotAFolder(item, targetFolder.appendingPathComponent(item, conformingTo: .fileURL))
|
||||
}
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
if versionURLs.isEmpty
|
||||
{
|
||||
throw PackageLoadingError.packageDoesNotHaveAnyVersionsInstalled(item)
|
||||
}
|
||||
|
||||
let wasPackageInstalledIntentionally: Bool = try await targetFolder.checkIfPackageWasInstalledIntentionally(versionURLs)
|
||||
|
||||
let foundPackage: BrewPackage = .init(
|
||||
name: item,
|
||||
type: targetFolder.packageType,
|
||||
installedOn: fullURLToPackageFolderCurrentlyBeingProcessed.creationDate,
|
||||
versions: versionURLs.versions,
|
||||
installedIntentionally: wasPackageInstalledIntentionally,
|
||||
sizeInBytes: fullURLToPackageFolderCurrentlyBeingProcessed.directorySize
|
||||
)
|
||||
|
||||
return foundPackage
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var loadedPackages: Set<BrewPackage> = .init()
|
||||
for try await package in taskGroup
|
||||
{
|
||||
loadedPackages.insert(package)
|
||||
}
|
||||
return loadedPackages
|
||||
}
|
||||
|
||||
return loadedPackages
|
||||
}
|
||||
catch
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while accessing folder: \(error)")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// MARK: - Sub-functions
|
||||
|
||||
private extension URL
|
||||
{
|
||||
/// ``[URL]`` to packages without hidden files or symlinks.
|
||||
/// e.g. only actual package URLs
|
||||
var validPackageURLs: [String]?
|
||||
{
|
||||
let items: [String]? = try? FileManager.default.contentsOfDirectory(atPath: path).filter { !$0.hasPrefix(".") }.filter
|
||||
{ item in
|
||||
/// Filter out all symlinks from the folder
|
||||
let completeURLtoItem: URL = self.appendingPathComponent(item, conformingTo: .folder)
|
||||
|
||||
guard let isSymlink = completeURLtoItem.isSymlink()
|
||||
else
|
||||
{
|
||||
return false
|
||||
}
|
||||
|
||||
return !isSymlink
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
/// Get URLs to a package's versions
|
||||
var packageVersionURLs: [URL]?
|
||||
{
|
||||
AppConstants.shared.logger.debug("Will check URL \(self)")
|
||||
do
|
||||
{
|
||||
let versions: [URL] = try FileManager.default.contentsOfDirectory(at: self, includingPropertiesForKeys: [.isHiddenKey], options: .skipsHiddenFiles)
|
||||
|
||||
if versions.isEmpty
|
||||
{
|
||||
AppConstants.shared.logger.warning("Package URL \(self, privacy: .public) has no versions installed")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
AppConstants.shared.logger.debug("URL \(self) has these versions: \(versions))")
|
||||
|
||||
return versions
|
||||
}
|
||||
catch
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while loading version for package \(lastPathComponent, privacy: .public) at URL \(self, privacy: .public)")
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension [URL]
|
||||
{
|
||||
/// Returns an array of versions from an array of URLs to available versions
|
||||
var versions: [String]
|
||||
{
|
||||
return map
|
||||
{ versionURL in
|
||||
versionURL.lastPathComponent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Getting list of URLs in folder
|
||||
|
||||
func getContentsOfFolder(targetFolder: URL, options: FileManager.DirectoryEnumerationOptions? = nil) throws -> [URL]
|
||||
{
|
||||
do
|
||||
{
|
||||
if let options
|
||||
{
|
||||
return try FileManager.default.contentsOfDirectory(at: targetFolder, includingPropertiesForKeys: nil, options: options)
|
||||
}
|
||||
else
|
||||
{
|
||||
return try FileManager.default.contentsOfDirectory(at: targetFolder, includingPropertiesForKeys: nil)
|
||||
}
|
||||
}
|
||||
catch let folderReadingError
|
||||
{
|
||||
AppConstants.shared.logger.error("\(folderReadingError.localizedDescription)")
|
||||
|
||||
throw folderReadingError
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
|
||||
func deleteCachedDownloads() throws(CachedDownloadDeletionError)
|
||||
{
|
||||
|
|
@ -15,7 +16,7 @@ func deleteCachedDownloads() throws(CachedDownloadDeletionError)
|
|||
/// This folder has the symlinks, so we have do **delete ONLY THE SYMLINKS**
|
||||
do
|
||||
{
|
||||
for url in try getContentsOfFolder(targetFolder: AppConstants.shared.brewCachedFormulaeDownloadsPath)
|
||||
for url in try AppConstants.shared.brewCachedFormulaeDownloadsPath.getContents()
|
||||
{
|
||||
if let isSymlink = url.isSymlink()
|
||||
{
|
||||
|
|
@ -47,7 +48,7 @@ func deleteCachedDownloads() throws(CachedDownloadDeletionError)
|
|||
/// This folder has the symlinks, so we have to **delete ONLY THE SYMLINKS**
|
||||
do
|
||||
{
|
||||
for url in try getContentsOfFolder(targetFolder: AppConstants.shared.brewCachedCasksDownloadsPath)
|
||||
for url in try AppConstants.shared.brewCachedCasksDownloadsPath.getContents()
|
||||
{
|
||||
if let isSymlink = url.isSymlink()
|
||||
{
|
||||
|
|
@ -79,7 +80,7 @@ func deleteCachedDownloads() throws(CachedDownloadDeletionError)
|
|||
/// This folder has the downloads themselves, so we have do **DELETE EVERYTHING THAT IS NOT A SYMLINK**
|
||||
do
|
||||
{
|
||||
for url in try getContentsOfFolder(targetFolder: AppConstants.shared.brewCachedDownloadsPath)
|
||||
for url in try AppConstants.shared.brewCachedDownloadsPath.getContents()
|
||||
{
|
||||
if let isSymlink = url.isSymlink()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum HealthCheckError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
/* enum CachePurgeError: Error
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum OrphanUninstallationError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum HomebrewCachePurgeError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum OrphanRemovalError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
//
|
||||
// Load up Installed Packages.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 11.02.2023.
|
||||
//
|
||||
|
||||
import CorkShared
|
||||
import Foundation
|
||||
|
||||
/*
|
||||
@MainActor
|
||||
func loadUpPackages(whatToLoad: PackageType, appState: AppState) async -> Set<BrewPackage>
|
||||
{
|
||||
AppConstants.shared.logger.info("Started \(whatToLoad == .formula ? "Formula" : "Cask", privacy: .public) loading task at \(Date(), privacy: .public)")
|
||||
|
||||
var contentsOfFolder: Set<BrewPackage> = .init()
|
||||
|
||||
do
|
||||
{
|
||||
switch whatToLoad
|
||||
{
|
||||
case .formula:
|
||||
contentsOfFolder = try await getContentsOfFolder(targetFolder: AppConstants.shared.brewCellarPath)
|
||||
case .cask:
|
||||
contentsOfFolder = try await getContentsOfFolder(targetFolder: AppConstants.shared.brewCaskPath)
|
||||
}
|
||||
}
|
||||
catch let packageLoadingError as PackageLoadingError
|
||||
{
|
||||
switch packageLoadingError
|
||||
{
|
||||
case .couldNotReadContentsOfParentFolder(let failureReason):
|
||||
appState.showAlert(errorToShow: .couldNotGetContentsOfPackageFolder(failureReason))
|
||||
case .failedWhileLoadingPackages:
|
||||
appState.showAlert(errorToShow: .couldNotLoadAnyPackages(packageLoadingError))
|
||||
case .failedWhileLoadingCertainPackage(let offendingPackage, let offendingPackageURL, let failureReason):
|
||||
appState.showAlert(errorToShow: .couldNotLoadCertainPackage(offendingPackage, offendingPackageURL, failureReason: failureReason))
|
||||
case .packageDoesNotHaveAnyVersionsInstalled(let offendingPackage):
|
||||
appState.showAlert(errorToShow: .installedPackageHasNoVersions(corruptedPackageName: offendingPackage))
|
||||
case .packageIsNotAFolder(let offendingFile, let offendingFileURL):
|
||||
appState.showAlert(errorToShow: .installedPackageIsNotAFolder(itemName: offendingFile, itemURL: offendingFileURL))
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
print("Something got completely fucked up while loading packages")
|
||||
}
|
||||
|
||||
AppConstants.shared.logger.info("Finished \(whatToLoad == .formula ? "Formula" : "Cask", privacy: .public) loading task at \(Date(), privacy: .auto)")
|
||||
|
||||
return contentsOfFolder
|
||||
}
|
||||
*/
|
||||
|
|
@ -7,8 +7,10 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
import CorkModels
|
||||
|
||||
func searchForPackage(packageName: String, packageType: PackageType) async -> [String]
|
||||
func searchForPackage(packageName: String, packageType: BrewPackage.PackageType) async -> [String]
|
||||
{
|
||||
var finalPackageArray: [String]
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum HomebrewServiceLoadingError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
extension ServicesTracker
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
extension ServicesTracker
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
enum ServiceStoppingError: LocalizedError
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
extension HomebrewService
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
//
|
||||
// Parse Tap Info.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 21.06.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
|
||||
func parseTapInfo(from rawJSON: String) async throws -> TapInfo?
|
||||
{
|
||||
let decoder: JSONDecoder = {
|
||||
let decoder: JSONDecoder = .init()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
|
||||
return decoder
|
||||
}()
|
||||
|
||||
do
|
||||
{
|
||||
guard let jsonAsData: Data = rawJSON.data(using: .utf8, allowLossyConversion: false)
|
||||
else
|
||||
{
|
||||
AppConstants.shared.logger.error("Could not convert tap JSON string into data")
|
||||
throw JSONParsingError.couldNotConvertStringToData(failureReason: nil)
|
||||
}
|
||||
|
||||
return try decoder.decode([TapInfo].self, from: jsonAsData).first
|
||||
}
|
||||
catch let decodingError
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while decoding tap info: \(decodingError.localizedDescription, privacy: .public)\n-\(decodingError, privacy: .public)")
|
||||
|
||||
throw JSONParsingError.couldNotDecode(failureReason: decodingError.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
import CorkModels
|
||||
|
||||
@MainActor
|
||||
func refreshPackages(_ updateProgressTracker: UpdateProgressTracker, outdatedPackagesTracker: OutdatedPackagesTracker) async -> PackageUpdateAvailability
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkTerminalFunctions
|
||||
|
||||
@MainActor
|
||||
func updatePackages(updateProgressTracker: UpdateProgressTracker, detailStage: UpdatingProcessDetails) async
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
//
|
||||
// Brew Tap.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 10.02.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct BrewTap: Identifiable, Hashable
|
||||
{
|
||||
let id: UUID = .init()
|
||||
let name: String
|
||||
|
||||
var isBeingModified: Bool = false
|
||||
|
||||
mutating func changeBeingModifiedStatus()
|
||||
{
|
||||
isBeingModified.toggle()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
//
|
||||
// Cached Download.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 04.11.2023.
|
||||
//
|
||||
|
||||
import Charts
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct CachedDownload: Identifiable, Hashable
|
||||
{
|
||||
var id: UUID = .init()
|
||||
|
||||
let packageName: String
|
||||
let sizeInBytes: Int
|
||||
|
||||
var packageType: CachedDownloadType?
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// Corrupted Package.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 28.03.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct CorruptedPackage: Identifiable, Equatable
|
||||
{
|
||||
let id: UUID = .init()
|
||||
let name: String
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import SwiftUI
|
|||
import CorkShared
|
||||
import Defaults
|
||||
import DefaultsMacros
|
||||
import CorkModels
|
||||
|
||||
@Observable @MainActor
|
||||
class TopPackagesTracker
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
//
|
||||
// Minimal Homebrew Package.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 25.05.2024.
|
||||
//
|
||||
|
||||
import AppIntents
|
||||
import Foundation
|
||||
|
||||
struct MinimalHomebrewPackage: Identifiable, Hashable, AppEntity, Codable
|
||||
{
|
||||
var id: UUID = .init()
|
||||
|
||||
var name: String
|
||||
|
||||
var type: PackageType
|
||||
|
||||
var installDate: Date?
|
||||
|
||||
var installedIntentionally: Bool
|
||||
|
||||
static let typeDisplayRepresentation: TypeDisplayRepresentation = .init(name: "intents.type.minimal-homebrew-package")
|
||||
|
||||
var displayRepresentation: DisplayRepresentation
|
||||
{
|
||||
DisplayRepresentation(
|
||||
title: "\(name)",
|
||||
subtitle: "intents.type.minimal-homebrew-package.representation.subtitle"
|
||||
)
|
||||
}
|
||||
|
||||
static let defaultQuery: MinimalHomebrewPackageIntentQuery = .init()
|
||||
}
|
||||
|
||||
extension MinimalHomebrewPackage
|
||||
{
|
||||
init?(from homebrewPackage: BrewPackage?)
|
||||
{
|
||||
guard let homebrewPackage = homebrewPackage
|
||||
else
|
||||
{
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(
|
||||
name: homebrewPackage.name,
|
||||
type: homebrewPackage.type,
|
||||
installedIntentionally: homebrewPackage.installedIntentionally
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct MinimalHomebrewPackageIntentQuery: EntityQuery
|
||||
{
|
||||
func entities(for _: [UUID]) async throws -> [MinimalHomebrewPackage]
|
||||
{
|
||||
return .init()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
//
|
||||
// Tap Codable Model.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 21.06.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Decodable tap info
|
||||
struct TapInfo: Codable
|
||||
{
|
||||
/// The name of the tap
|
||||
let name: String
|
||||
|
||||
/// The user responsible for the tap
|
||||
let user: String
|
||||
|
||||
/// Name of the upstream repo
|
||||
let repo: String
|
||||
|
||||
/// Path to the tap
|
||||
let path: URL
|
||||
|
||||
/// Whether the tap is currently added
|
||||
let installed: Bool
|
||||
|
||||
/// Whether the tap is from the Homebrew developers
|
||||
let official: Bool
|
||||
|
||||
// MARK: - The contents of the tap
|
||||
|
||||
/// The formulae included in the tap
|
||||
let formulaNames: [String]
|
||||
|
||||
/// The casks included in the tap
|
||||
let caskTokens: [String]
|
||||
|
||||
/// The paths to the formula files
|
||||
let formulaFiles: [URL]?
|
||||
|
||||
/// The paths to the cask files
|
||||
let caskFiles: [URL]?
|
||||
|
||||
/// No idea, honestly
|
||||
let commandFiles: [String]?
|
||||
|
||||
/// Link to the actual repo
|
||||
let remote: URL?
|
||||
|
||||
/// IDK
|
||||
let customRemote: Bool?
|
||||
|
||||
var numberOfPackages: Int
|
||||
{
|
||||
return self.formulaNames.count + self.caskTokens.count
|
||||
}
|
||||
|
||||
/// Formulae that include the package type. Useful for rpeviewing packages.
|
||||
var includedFormulaeWithAdditionalMetadata: [MinimalHomebrewPackage]
|
||||
{
|
||||
return formulaNames.map
|
||||
{ formulaName in
|
||||
.init(name: formulaName, type: .formula, installedIntentionally: false)
|
||||
}
|
||||
}
|
||||
|
||||
var includedCasksWithAdditionalMetadata: [MinimalHomebrewPackage]
|
||||
{
|
||||
return caskTokens.map
|
||||
{ caskName in
|
||||
.init(name: caskName, type: .cask, installedIntentionally: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
//
|
||||
// Outdated Package.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 05.04.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct OutdatedPackage: Identifiable, Equatable, Hashable
|
||||
{
|
||||
enum PackageUpdatingType
|
||||
{
|
||||
/// The package is updating through Homebrew
|
||||
case homebrew
|
||||
|
||||
/// The package updates itself
|
||||
case selfUpdating
|
||||
|
||||
var argument: String
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .homebrew:
|
||||
return .init()
|
||||
case .selfUpdating:
|
||||
return "--greedy"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let id: UUID = .init()
|
||||
|
||||
let package: BrewPackage
|
||||
|
||||
let installedVersions: [String]
|
||||
let newerVersion: String
|
||||
|
||||
var isMarkedForUpdating: Bool = true
|
||||
|
||||
var updatingManagedBy: PackageUpdatingType
|
||||
|
||||
static func == (lhs: OutdatedPackage, rhs: OutdatedPackage) -> Bool
|
||||
{
|
||||
return lhs.package.name == rhs.package.name
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(package.name)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,11 +7,13 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
@Observable
|
||||
class InstallationProgressTracker
|
||||
{
|
||||
var packageBeingInstalled: PackageInProgressOfBeingInstalled = .init(package: .init(name: "", type: .formula, installedOn: nil, versions: [], sizeInBytes: 0, downloadCount: nil), installationStage: .downloadingCask, packageInstallationProgress: 0)
|
||||
var packageBeingInstalled: PackageInProgressOfBeingInstalled = .init(package: .init(name: "", type: .formula, installedOn: nil, versions: [], url: nil, sizeInBytes: 0, downloadCount: nil), installationStage: .downloadingCask, packageInstallationProgress: 0)
|
||||
|
||||
var numberOfPackageDependencies: Int = 0
|
||||
var numberInLineOfPackageCurrentlyBeingFetched: Int = 0
|
||||
|
|
@ -205,7 +207,7 @@ class InstallationProgressTracker
|
|||
AppConstants.shared.logger.info("Package is Cask")
|
||||
AppConstants.shared.logger.debug("Installing package \(package.name, privacy: .public)")
|
||||
|
||||
let (stream, process): (AsyncStream<StreamedTerminalOutput>, Process) = shell(AppConstants.shared.brewExecutablePath, ["install", "--no-quarantine", package.name])
|
||||
let (stream, process): (AsyncStream<StreamedTerminalOutput>, Process) = shell(AppConstants.shared.brewExecutablePath, ["install", package.name])
|
||||
installationProcess = process
|
||||
for await output in stream
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CorkModels
|
||||
|
||||
struct RealTimeTerminalLine: Identifiable, Hashable, Equatable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
//
|
||||
// Brew Package Details.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 18.07.2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
|
||||
enum PinningUnpinningError: LocalizedError
|
||||
{
|
||||
case failedWhileChangingPinnedStatus
|
||||
|
||||
var errorDescription: String?
|
||||
{
|
||||
switch self
|
||||
{
|
||||
case .failedWhileChangingPinnedStatus:
|
||||
return String(localized: "error.package-details.couldnt-pin-unpin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Observable @MainActor
|
||||
class BrewPackageDetails
|
||||
{
|
||||
// MARK: - Immutable properties
|
||||
|
||||
/// Name of the package
|
||||
let name: String
|
||||
|
||||
let description: String?
|
||||
|
||||
let homepage: URL
|
||||
let tap: BrewTap
|
||||
let installedAsDependency: Bool
|
||||
let dependencies: [BrewPackageDependency]?
|
||||
let outdated: Bool
|
||||
let caveats: String?
|
||||
|
||||
let deprecated: Bool
|
||||
let deprecationReason: String?
|
||||
|
||||
let isCompatible: Bool?
|
||||
|
||||
var dependents: [String]?
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(
|
||||
name: String,
|
||||
description: String?,
|
||||
homepage: URL,
|
||||
tap: BrewTap,
|
||||
installedAsDependency: Bool,
|
||||
dependents: [String]? = nil,
|
||||
dependencies: [BrewPackageDependency]? = nil,
|
||||
outdated: Bool,
|
||||
caveats: String? = nil,
|
||||
deprecated: Bool,
|
||||
deprecationReason: String? = nil,
|
||||
isCompatible: Bool?
|
||||
){
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.homepage = homepage
|
||||
self.tap = tap
|
||||
self.installedAsDependency = installedAsDependency
|
||||
self.dependents = dependents
|
||||
self.dependencies = dependencies
|
||||
self.outdated = outdated
|
||||
self.deprecated = deprecated
|
||||
self.deprecationReason = deprecationReason
|
||||
self.caveats = caveats
|
||||
self.isCompatible = isCompatible
|
||||
}
|
||||
|
||||
// MARK: - Functions
|
||||
|
||||
func loadDependents() async
|
||||
{
|
||||
AppConstants.shared.logger.debug("Will load dependents for \(self.name)")
|
||||
let packageDependentsRaw: String = await shell(AppConstants.shared.brewExecutablePath, ["uses", "--installed", name]).standardOutput
|
||||
|
||||
let finalDependents: [String] = packageDependentsRaw.components(separatedBy: "\n").dropLast()
|
||||
|
||||
AppConstants.shared.logger.debug("Dependents loaded: \(finalDependents)")
|
||||
|
||||
dependents = finalDependents
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
//
|
||||
// Package Dependency.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 27.02.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct BrewPackageDependency: Identifiable, Hashable
|
||||
{
|
||||
let id: UUID = .init()
|
||||
let name: String
|
||||
let version: String
|
||||
let directlyDeclared: Bool
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import CorkModels
|
||||
|
||||
@Observable
|
||||
class SearchResultTracker
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
//
|
||||
// Terminal Output.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 12.02.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TerminalOutput
|
||||
{
|
||||
var standardOutput: String
|
||||
var standardError: String
|
||||
}
|
||||
|
||||
enum StreamedTerminalOutput
|
||||
{
|
||||
case standardOutput(String)
|
||||
case standardError(String)
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
protocol DismissablePane: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct BrewfileImportProgressView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import CorkShared
|
|||
import SwiftUI
|
||||
import ButtonKit
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct AddFormulaView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import CorkShared
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct SearchResultRow: View, Sendable
|
||||
{
|
||||
|
|
@ -126,11 +127,11 @@ struct SearchResultRow: View, Sendable
|
|||
|
||||
do
|
||||
{
|
||||
let searchedForPackage: BrewPackage = .init(name: searchedForPackage.name, type: searchedForPackage.type, installedOn: Date(), versions: [], sizeInBytes: nil, downloadCount: nil)
|
||||
let searchedForPackage: BrewPackage = .init(name: searchedForPackage.name, type: searchedForPackage.type, installedOn: Date(), versions: [], url: nil, sizeInBytes: nil, downloadCount: nil)
|
||||
|
||||
do
|
||||
{
|
||||
let parsedPackageInfo: BrewPackageDetails = try await searchedForPackage.loadDetails()
|
||||
let parsedPackageInfo: BrewPackage.BrewPackageDetails = try await searchedForPackage.loadDetails()
|
||||
|
||||
description = parsedPackageInfo.description
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import CorkShared
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct InstallationInitialView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import CorkShared
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct TopPackagesSection: View
|
||||
{
|
||||
|
|
@ -15,7 +16,7 @@ struct TopPackagesSection: View
|
|||
|
||||
let packageTracker: TopPackagesTracker
|
||||
|
||||
let trackerType: PackageType
|
||||
let trackerType: BrewPackage.PackageType
|
||||
|
||||
private var packages: [BrewPackage]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
struct InstallingPackageView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import CorkShared
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct PresentingSearchResultsView: View
|
||||
{
|
||||
|
|
@ -129,6 +130,7 @@ struct PresentingSearchResultsView: View
|
|||
AppConstants.shared.logger.debug("Would preview package \(selectedPackage.name)")
|
||||
}
|
||||
.disabled(foundPackageSelection == nil)
|
||||
.labelStyle(.titleOnly)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
|
@ -185,7 +187,7 @@ struct PresentingSearchResultsView: View
|
|||
|
||||
private struct SearchResultsSection: View
|
||||
{
|
||||
let sectionType: PackageType
|
||||
let sectionType: BrewPackage.PackageType
|
||||
|
||||
let packageList: [BrewPackage]
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct InstallationSearchingView: View, Sendable
|
||||
{
|
||||
|
|
@ -28,11 +29,11 @@ struct InstallationSearchingView: View, Sendable
|
|||
|
||||
for formula in await foundFormulae
|
||||
{
|
||||
searchResultTracker.foundFormulae.append(BrewPackage(name: formula, type: .formula, installedOn: nil, versions: [], sizeInBytes: nil, downloadCount: nil))
|
||||
searchResultTracker.foundFormulae.append(BrewPackage(name: formula, type: .formula, installedOn: nil, versions: [], url: nil, sizeInBytes: nil, downloadCount: nil))
|
||||
}
|
||||
for cask in await foundCasks
|
||||
{
|
||||
searchResultTracker.foundCasks.append(BrewPackage(name: cask, type: .cask, installedOn: nil, versions: [], sizeInBytes: nil, downloadCount: nil))
|
||||
searchResultTracker.foundCasks.append(BrewPackage(name: cask, type: .cask, installedOn: nil, versions: [], url: nil, sizeInBytes: nil, downloadCount: nil))
|
||||
}
|
||||
|
||||
packageInstallationProcessStep = .presentingSearchResults
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import CorkShared
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
struct AdoptingAlreadyInstalledCaskView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct AnotherProcessAlreadyRunningView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct BinaryAlreadyExistsView: View, Sendable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct InstallationFatalErrorView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import CorkNotifications
|
|||
import CorkShared
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct InstallationFinishedSuccessfullyView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -27,23 +27,11 @@ struct InstallationTerminatedUnexpectedlyView: View
|
|||
|
||||
if usableLiveTerminalOutput.isEmpty
|
||||
{
|
||||
OutlinedPillText(text: "add-package.install.installation-terminated.no-terminal-output-provided", color: .secondary)
|
||||
noOutputProvided
|
||||
}
|
||||
else
|
||||
{
|
||||
DisclosureGroup
|
||||
{
|
||||
List
|
||||
{
|
||||
ForEach(usableLiveTerminalOutput)
|
||||
{ outputLine in
|
||||
Text(outputLine.line)
|
||||
}
|
||||
}
|
||||
.frame(height: 100, alignment: .leading)
|
||||
} label: {
|
||||
Text("action.show-terminal-output")
|
||||
}
|
||||
someOutputProvided
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,4 +42,34 @@ struct InstallationTerminatedUnexpectedlyView: View
|
|||
usableLiveTerminalOutput = terminalOutputOfTheInstallation
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var noOutputProvided: some View
|
||||
{
|
||||
Text("add-package.install.installation-terminated.no-terminal-output-provided")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var someOutputProvided: some View
|
||||
{
|
||||
DisclosureGroup
|
||||
{
|
||||
List
|
||||
{
|
||||
ForEach(usableLiveTerminalOutput)
|
||||
{ outputLine in
|
||||
Text(outputLine.line)
|
||||
}
|
||||
}
|
||||
.frame(maxHeight: 100, alignment: .leading)
|
||||
} label: {
|
||||
Text("action.show-terminal-output")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct SudoRequiredView: View, Sendable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct WrongArchitectureView: View, Sendable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import CorkShared
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct LicensingView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct Licensing_BoughtView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import CorkShared
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct Licensing_DemoView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import SwiftUI
|
|||
import CorkShared
|
||||
import ButtonKit
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct Licensing_NotBoughtOrActivatedView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct Licensing_SelfCompiledView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
enum MaintenanceSteps
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import CorkShared
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct MaintenanceFinishedView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
struct MaintenanceRunningView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ struct AdoptionResultsList: View
|
|||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 100, alignment: .leading)
|
||||
.listStyle(.bordered(alternatesRowBackgrounds: true))
|
||||
.frame(minHeight: 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import CorkShared
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MassAdoptionStage_Adopting: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
typealias AdoptionProcessResult = Result<BrewPackagesTracker.AdoptableApp, MassAppAdoptionView.AdoptionAttemptFailure>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MenuBarItem: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkNotifications
|
||||
import CorkModels
|
||||
|
||||
struct MenuBar_CachedDownloadsCleanup: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import ButtonKit
|
|||
import CorkNotifications
|
||||
import CorkShared
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MenuBar_OrphanCleanup: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MenuBar_PackageInstallation: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MenuBar_PackageOverview: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct MenuBar_PackageUpdating: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct PackagePreview: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import CorkShared
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
import ApplicationInspector
|
||||
|
||||
struct PackageDetailView: View, Sendable, DismissablePane
|
||||
{
|
||||
|
|
@ -39,7 +41,9 @@ struct PackageDetailView: View, Sendable, DismissablePane
|
|||
|
||||
var isInPreviewWindow: Bool = false
|
||||
|
||||
@State private var packageDetails: BrewPackageDetails? = nil
|
||||
@State private var packageDetails: BrewPackage.BrewPackageDetails? = nil
|
||||
|
||||
@State private var caskExecutable: Application? = nil
|
||||
|
||||
@Environment(BrewPackagesTracker.self) var brewPackagesTracker: BrewPackagesTracker
|
||||
|
||||
|
|
@ -92,9 +96,15 @@ struct PackageDetailView: View, Sendable, DismissablePane
|
|||
isShowingExpandedCaveats: $isShowingExpandedCaveats
|
||||
)
|
||||
|
||||
PackageDependencies(dependencies: packageDetails?.dependencies, isDependencyDisclosureGroupExpanded: $isShowingExpandedDependencies)
|
||||
PackageDependencies(
|
||||
dependencies: packageDetails?.dependencies,
|
||||
isDependencyDisclosureGroupExpanded: $isShowingExpandedDependencies
|
||||
)
|
||||
|
||||
PackageSystemInfo(package: packageStructureToUse)
|
||||
PackageSystemInfo(
|
||||
package: packageStructureToUse,
|
||||
caskExecutable: caskExecutable
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -157,6 +167,19 @@ struct PackageDetailView: View, Sendable, DismissablePane
|
|||
erroredOut = (true, packageInfoDecodingError.localizedDescription)
|
||||
}
|
||||
}
|
||||
.task(id: package.id)
|
||||
{ // For casks, try to load the application executable
|
||||
if package.type == .cask
|
||||
{
|
||||
AppConstants.shared.logger.info("Package is cask, will see what the app's location is for url \(package.url as NSObject?)")
|
||||
|
||||
if let packageURL = package.url
|
||||
{
|
||||
AppConstants.shared.logger.info("Will try to load app icon for URL \(packageURL)")
|
||||
caskExecutable = try? .init(from: packageURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -217,10 +240,10 @@ private extension BrewPackagesTracker
|
|||
struct FastPackageComparableRepresentation: Hashable
|
||||
{
|
||||
let name: String
|
||||
let type: PackageType
|
||||
let type: BrewPackage.PackageType
|
||||
let versions: [String]
|
||||
|
||||
init(name: String, type: PackageType, versions: [String])
|
||||
init(name: String, type: BrewPackage.PackageType, versions: [String])
|
||||
{
|
||||
self.name = name
|
||||
self.type = type
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@
|
|||
import CorkShared
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct BasicPackageInfoView: View
|
||||
{
|
||||
@Default(.caveatDisplayOptions) var caveatDisplayOptions: PackageCaveatDisplay
|
||||
|
||||
let package: BrewPackage
|
||||
let packageDetails: BrewPackageDetails
|
||||
let packageDetails: BrewPackage.BrewPackageDetails
|
||||
|
||||
let isLoadingDetails: Bool
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct DependencyList: View
|
||||
{
|
||||
|
|
@ -43,7 +44,7 @@ struct DependencyList: View
|
|||
{
|
||||
TableColumn("package-details.dependencies.results.name")
|
||||
{ dependency in
|
||||
SanitizedPackageName(package: .init(name: dependency.name, type: .formula, installedOn: nil, versions: [dependency.version], sizeInBytes: nil, downloadCount: nil), shouldShowVersion: false)
|
||||
SanitizedPackageName(package: .init(name: dependency.name, type: .formula, installedOn: nil, versions: [dependency.version], url: nil, sizeInBytes: nil, downloadCount: nil), shouldShowVersion: false)
|
||||
}
|
||||
TableColumn("package-details.dependencies.results.version")
|
||||
{ dependency in
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct PackageDependencies: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
|
||||
struct PackageDetailHeaderComplex: View
|
||||
{
|
||||
|
|
@ -22,7 +23,7 @@ struct PackageDetailHeaderComplex: View
|
|||
|
||||
var isInPreviewWindow: Bool
|
||||
|
||||
@Bindable var packageDetails: BrewPackageDetails
|
||||
@Bindable var packageDetails: BrewPackage.BrewPackageDetails
|
||||
|
||||
let isLoadingDetails: Bool
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import SwiftUI
|
|||
import CorkShared
|
||||
import ButtonKit
|
||||
import Defaults
|
||||
import CorkModels
|
||||
|
||||
struct PackageModificationButtons: View
|
||||
{
|
||||
|
|
@ -21,7 +22,7 @@ struct PackageModificationButtons: View
|
|||
@Environment(OutdatedPackagesTracker.self) var outdatedPackagesTracker: OutdatedPackagesTracker
|
||||
|
||||
let package: BrewPackage
|
||||
@Bindable var packageDetails: BrewPackageDetails
|
||||
@Bindable var packageDetails: BrewPackage.BrewPackageDetails
|
||||
|
||||
let isLoadingDetails: Bool
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ struct PackageModificationButtons: View
|
|||
if package.type == .formula
|
||||
{
|
||||
PinUnpinButton(package: package)
|
||||
.labelStyle(.titleOnly)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
|
@ -52,12 +54,14 @@ struct PackageModificationButtons: View
|
|||
if !allowMoreCompleteUninstallations
|
||||
{
|
||||
UninstallPackageButton(package: package)
|
||||
.labelStyle(.titleOnly)
|
||||
}
|
||||
else
|
||||
{
|
||||
Menu
|
||||
{
|
||||
PurgePackageButton(package: package)
|
||||
.labelStyle(.titleOnly)
|
||||
} label: {
|
||||
Text("action.uninstall-\(package.name)")
|
||||
} primaryAction: {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import ButtonKit
|
||||
import CorkModels
|
||||
|
||||
struct PinUnpinButton: View
|
||||
{
|
||||
|
|
@ -23,7 +24,9 @@ struct PinUnpinButton: View
|
|||
{
|
||||
await package.performPinnedStatusChangeAction(appState: appState, brewPackagesTracker: brewPackagesTracker)
|
||||
} label: {
|
||||
Text(package.isPinned ? "package-details.action.unpin-version-\(package.versions.formatted(.list(type: .and)))" : "package-details.action.pin-version-\(package.versions.formatted(.list(type: .and)))")
|
||||
let labelText: LocalizedStringKey = package.isPinned ? "package-details.action.unpin-version-\(package.versions.formatted(.list(type: .and)))" : "package-details.action.pin-version-\(package.versions.formatted(.list(type: .and)))"
|
||||
|
||||
Label(labelText, systemImage: "pin.fill")
|
||||
}
|
||||
.asyncButtonStyle(.leading)
|
||||
.disabledWhenLoading()
|
||||
|
|
|
|||
|
|
@ -6,10 +6,14 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import ApplicationInspector
|
||||
import CorkModels
|
||||
|
||||
struct PackageSystemInfo: View
|
||||
{
|
||||
let package: BrewPackage
|
||||
|
||||
let caskExecutable: Application?
|
||||
|
||||
@State private var isShowingCaskSizeHelpPopover: Bool = false
|
||||
|
||||
|
|
@ -19,22 +23,57 @@ struct PackageSystemInfo: View
|
|||
{
|
||||
Section
|
||||
{
|
||||
LabeledContent
|
||||
{
|
||||
Text(installedOnDate.formatted(.packageInstallationStyle))
|
||||
} label: {
|
||||
Text("package-details.install-date")
|
||||
}
|
||||
caskInstalledAsLine
|
||||
|
||||
installedOnDateLine(installedOnDate)
|
||||
|
||||
if let packageSize = package.sizeInBytes
|
||||
{
|
||||
LabeledContent
|
||||
{
|
||||
Text(packageSize.formatted(.byteCount(style: .file)))
|
||||
} label: {
|
||||
Text("package-details.size")
|
||||
}
|
||||
}
|
||||
packageSizeLine(package.sizeInBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
var caskInstalledAsLine: some View
|
||||
{
|
||||
|
||||
if let caskExecutable
|
||||
{
|
||||
LabeledContent
|
||||
{
|
||||
AppIconDisplay(
|
||||
displayType: .asIconWithAppNameDisplayed(
|
||||
usingApp: caskExecutable,
|
||||
namePosition: .besideAppIcon
|
||||
),
|
||||
allowRevealingInFinderFromIcon: true
|
||||
)
|
||||
} label: {
|
||||
Text("package-details.installed-as")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func installedOnDateLine(_ installedOnDate: Date) -> some View
|
||||
{
|
||||
LabeledContent
|
||||
{
|
||||
Text(installedOnDate.formatted(.packageInstallationStyle))
|
||||
} label: {
|
||||
Text("package-details.install-date")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func packageSizeLine(_ packageSize: Int64?) -> some View
|
||||
{
|
||||
if let packageSize = package.sizeInBytes
|
||||
{
|
||||
LabeledContent
|
||||
{
|
||||
Text(packageSize.formatted(.byteCount(style: .file)))
|
||||
} label: {
|
||||
Text("package-details.size")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct PackageListItem: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
struct ReinstallCorruptedPackageView: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import ButtonKit
|
||||
import CorkModels
|
||||
|
||||
struct SudoRequiredForRemovalSheet: View, Sendable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
//
|
||||
// App Icon Display.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš - P on 22.10.2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ApplicationInspector
|
||||
|
||||
/// Show the icon of a linked app
|
||||
struct AppIconDisplay: View
|
||||
{
|
||||
enum DisplayType
|
||||
{
|
||||
case asIcon(
|
||||
usingApp: Application
|
||||
)
|
||||
case asIconWithAppNameDisplayed(
|
||||
usingApp: Application,
|
||||
namePosition: AppNamePosition
|
||||
)
|
||||
case asPathControl(
|
||||
usingURL: URL
|
||||
)
|
||||
|
||||
enum AppNamePosition
|
||||
{
|
||||
case besideAppIcon
|
||||
case underAppIcon
|
||||
}
|
||||
}
|
||||
|
||||
let displayType: DisplayType
|
||||
|
||||
let allowRevealingInFinderFromIcon: Bool
|
||||
|
||||
var body: some View
|
||||
{
|
||||
switch displayType
|
||||
{
|
||||
case .asIcon(let usingApp):
|
||||
ApplicationIconImage(
|
||||
app: usingApp,
|
||||
allowRevealingInFinderThroughIcon: allowRevealingInFinderFromIcon
|
||||
)
|
||||
case .asIconWithAppNameDisplayed(let usingApp, let namePosition):
|
||||
switch namePosition
|
||||
{
|
||||
case .besideAppIcon:
|
||||
HStack(alignment: .center, spacing: 5)
|
||||
{
|
||||
ApplicationIconImage(
|
||||
app: usingApp,
|
||||
allowRevealingInFinderThroughIcon: allowRevealingInFinderFromIcon
|
||||
)
|
||||
|
||||
applicationName(app: usingApp)
|
||||
}
|
||||
case .underAppIcon:
|
||||
|
||||
VStack(alignment: .center, spacing: 5)
|
||||
{
|
||||
ApplicationIconImage(
|
||||
app: usingApp,
|
||||
allowRevealingInFinderThroughIcon: allowRevealingInFinderFromIcon
|
||||
)
|
||||
|
||||
applicationName(app: usingApp)
|
||||
}
|
||||
}
|
||||
case .asPathControl(let usingURL):
|
||||
AppIconDisplay_AsPathControl(urlToApp: usingURL)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func applicationName(app: Application) -> some View
|
||||
{
|
||||
Text(app.name)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ApplicationIconImage: View
|
||||
{
|
||||
let app: Application
|
||||
|
||||
let allowRevealingInFinderThroughIcon: Bool
|
||||
|
||||
var body: some View
|
||||
{
|
||||
if let appIconImage = app.iconImage
|
||||
{
|
||||
appIconImage
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 35)
|
||||
.contextMenu {
|
||||
Button
|
||||
{
|
||||
app.url.revealInFinder(.openParentDirectoryAndHighlightTarget)
|
||||
} label: {
|
||||
Label("action.reveal-\(app.name)-in-finder", systemImage: "finder")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct AppIconDisplay_AsPathControl: NSViewRepresentable
|
||||
{
|
||||
typealias NSViewType = NSPathControl
|
||||
|
||||
let urlToApp: URL
|
||||
|
||||
func makeNSView(context _: Context) -> NSPathControl
|
||||
{
|
||||
let pathControl: NSPathControl = .init()
|
||||
|
||||
pathControl.url = urlToApp
|
||||
|
||||
if let lastPathItem = pathControl.pathItems.last
|
||||
{
|
||||
pathControl.pathItems = [lastPathItem]
|
||||
}
|
||||
|
||||
return pathControl
|
||||
}
|
||||
|
||||
func updateNSView(_: NSPathControl, context _: Context)
|
||||
{}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
|
||||
struct CheckForOutdatedPackagesButton: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct DeleteCachedDownloadsButton: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct OpenMaintenanceSheetButton: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkModels
|
||||
|
||||
struct InstallPackageButton: View
|
||||
{
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue