mirror of https://github.com/buresdv/Cork
Merge branch 'outdated-package-display-fixes.model-refactoring' into adoptable-discoverability.outdated-package-display-fixes
This commit is contained in:
commit
12570b1441
|
|
@ -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 ?? "")
|
||||
|
|
@ -716,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")
|
||||
}
|
||||
|
|
@ -724,9 +600,10 @@ struct CorkApp: App
|
|||
Text("debug.action.ui")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Functions
|
||||
|
||||
|
||||
// MARK: - App badge
|
||||
func setAppBadge(outdatedPackageNotificationType: OutdatedPackageNotificationType)
|
||||
{
|
||||
if outdatedPackageNotificationType == .badge || outdatedPackageNotificationType == .both
|
||||
|
|
@ -741,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()
|
||||
}
|
||||
}
|
||||
|
|
@ -6625,6 +6625,38 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"action.hide-adoptable-packages-section-if-only-excluded-apps-available" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Hide section…"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Masquer la section…"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"action.hide-adoptable-packages-section-if-only-excluded-apps-available.confirm" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Hide section"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Masquer la section"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"action.inspect-error" : {
|
||||
"localizations" : {
|
||||
"cs" : {
|
||||
|
|
@ -6883,6 +6915,24 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"action.package-adoption.include.%@" : {
|
||||
"comment" : "A menu item that allows the user to include a package in the app store. The argument is the name of the package, and the second argument is the system image for the plus sign.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Stop ignoring %@"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ne plus ignorer %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"action.preview-package-app-would-be-adopted-as.%@" : {
|
||||
"comment" : "A context menu item that previews the app that would be adopted. The argument is a localized string describing the app that would be adopted.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
|
|
@ -15589,6 +15639,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"adoptable-packages.excluded-label" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ignored adoptable apps"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Applications adoptables ignorées"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"adoptable-packages.excluded.label" : {
|
||||
"comment" : "A label for the section that lists apps that are excluded from being adopted.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"adoptable-packages.label" : {
|
||||
"comment" : "The label for the disclosure group that lists adoptable packages.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
|
|
@ -21576,6 +21647,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"DEBUG: Log packages to be adopted" : {
|
||||
"comment" : "A button that logs to the debug log all the packages that would be adopted.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"debug.action.activate-demo" : {
|
||||
"localizations" : {
|
||||
"cs" : {
|
||||
|
|
@ -22607,7 +22683,8 @@
|
|||
"value" : "error.data-downloading.couldnt-execute-request.%@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"shouldTranslate" : false
|
||||
},
|
||||
"error.data-downloading.invalid-response.%lld" : {
|
||||
"localizations" : {
|
||||
|
|
@ -27008,6 +27085,38 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"hide-adoptable-packages-section-if-only-excluded-apps-available.confirmation.message" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "When a new adoptable app becomes available, this section will automatically re-appear.\n\nYou can show the section again at any time in the “Discoverability“ tab of Cork’s Settings"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Lorsqu’une nouvelle application devient disponible, cette section re-apparaitra automatiquement.\n\nVous pouvez afficher la section à tout moment dans l’onglet « Découvrabilité » des paramètres de Cork"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hide-adoptable-packages-section-if-only-excluded-apps-available.confirmation.title" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Are you sure you want to hide the Adoptable Packages section?"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Êtes vous sûr de vouloir masquer la section des Paquets Adoptable ?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"homebrew/core" : {
|
||||
"localizations" : {
|
||||
"cs" : {
|
||||
|
|
@ -48779,6 +48888,40 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"settings.discoverability.mass-adoption.hide-adoptable-packages-section-if-there-are-only-excluded-apps-available.label" : {
|
||||
"comment" : "A label describing a toggle that controls whether the \"Adoptable Packages\" section is hidden when there are only excluded apps available.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Hide when all adoptable apps are excluded"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Masquer lorsque toutes les applications adoptables sont exclues"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.discoverability.mass-adoption.label" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "App adoption:"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Adoption d’application :"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings.discoverability.mass-adoption.toggle" : {
|
||||
"comment" : "A toggle that allows or disallows mass adoption of packages.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
|
|
@ -58104,6 +58247,106 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"start-page.adoptable-packages.available.%lld-excluded.%lld" : {
|
||||
"comment" : "A headline that describes how many adoptable packages are available, including any that have been excluded. The first argument is the count of adoptable packages that are not excluded. The second argument is the count of adoptable packages that have been excluded.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "There are %1$lld installed apps -excluded.%2$lld"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Il y a %1$lld applications installées (exclues : %2$lld)."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"start-page.adoptable-packages.excluded.%lld" : {
|
||||
"comment" : "A subheading displaying the number of apps that will not be adopted. The argument is the count of apps that will not be adopted.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "There is %lld additional app that is ignored"
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "There are %lld additional apps that are ignored"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Il y a %lld application additionnelle qui est ignorée"
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Il y a %lld applications additionnelles qui sont ignorées"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"start-page.adoptable-packages.only-%lld-excluded-available" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "There is %lld ignored adoptable apps"
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "There are %lld ignored adoptable apps"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"variations" : {
|
||||
"plural" : {
|
||||
"one" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Il y a %lld application adoptable qui est ignorée"
|
||||
}
|
||||
},
|
||||
"other" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Il y a %lld applications adoptables qui sont ignorées"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"start-page.analytics.disabled" : {
|
||||
"localizations" : {
|
||||
"cs" : {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,141 +0,0 @@
|
|||
//
|
||||
// Application.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš - P on 07.10.2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
|
||||
// TODO: Move this over to the `ApplicationInspector` external library once we figure out how to use Tuist projects as external dependencies
|
||||
|
||||
public struct Application: Identifiable, Hashable, Sendable
|
||||
{
|
||||
public let id: UUID = .init()
|
||||
|
||||
public let name: String
|
||||
|
||||
public let url: URL
|
||||
|
||||
public let iconPath: URL?
|
||||
|
||||
public let iconImage: Image?
|
||||
|
||||
public static func == (lhs: Application, rhs: Application) -> Bool
|
||||
{
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher)
|
||||
{
|
||||
hasher.combine(name)
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
||||
public enum ApplicationInitializationError: LocalizedError
|
||||
{
|
||||
public enum MandatoryAppInformation: String, Sendable
|
||||
{
|
||||
case name
|
||||
|
||||
public var description: String
|
||||
{
|
||||
switch self {
|
||||
case .name:
|
||||
return "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case applicationExecutableNotReadable(checkedPath: String)
|
||||
case couldNotAccessApplicationExecutable(error: Error)
|
||||
case couldNotReadBundle(applicationPath: String)
|
||||
case couldNotGetInfoDictionary
|
||||
case couldNotGetMandatoryAppInformation(_ mandatoryInformation: MandatoryAppInformation)
|
||||
|
||||
public var errorDescription: String?
|
||||
{
|
||||
switch self {
|
||||
case .applicationExecutableNotReadable(let checkedPath):
|
||||
return "Couldn't read application executable at \(checkedPath)"
|
||||
case .couldNotAccessApplicationExecutable(let error):
|
||||
return "Couldn't read application executable: \(error)"
|
||||
case .couldNotReadBundle(let applicationPath):
|
||||
return "Couldn't read application bundle at \(applicationPath)"
|
||||
case .couldNotGetInfoDictionary:
|
||||
return "Couldn't read application info.plist"
|
||||
case .couldNotGetMandatoryAppInformation(let mandatoryInformation):
|
||||
return "Couldn't read mandatory app information: \(mandatoryInformation.description)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public init(from appURL: URL) throws(ApplicationInitializationError)
|
||||
{
|
||||
do
|
||||
{
|
||||
guard FileManager.default.isReadableFile(atPath: appURL.path) == true else
|
||||
{
|
||||
throw ApplicationInitializationError.applicationExecutableNotReadable(checkedPath: appURL.path)
|
||||
}
|
||||
|
||||
guard let appBundle: Bundle = .init(url: appURL)
|
||||
else
|
||||
{
|
||||
throw ApplicationInitializationError.couldNotReadBundle(applicationPath: appURL.absoluteString)
|
||||
}
|
||||
|
||||
AppConstants.shared.logger.debug("Will try to initialize and App object form bundle \(appBundle)")
|
||||
|
||||
guard let appBundleInfoDictionary: [String: Any] = appBundle.infoDictionary
|
||||
else
|
||||
{
|
||||
throw ApplicationInitializationError.couldNotGetInfoDictionary
|
||||
}
|
||||
|
||||
guard let appName: String = Application.getAppName(fromInfoDictionary: appBundleInfoDictionary) else
|
||||
{
|
||||
throw ApplicationInitializationError.couldNotGetMandatoryAppInformation(.name)
|
||||
}
|
||||
|
||||
self.name = appName
|
||||
|
||||
self.url = appURL
|
||||
|
||||
self.iconPath = Application.getAppIconPath(fromInfoDictionary: appBundleInfoDictionary, appBundle: appBundle)
|
||||
|
||||
if let iconPath = self.iconPath
|
||||
{
|
||||
self.iconImage = .init(
|
||||
nsImage: .init(byReferencing: iconPath)
|
||||
)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.iconImage = nil
|
||||
}
|
||||
}
|
||||
catch let applicationDirectoryAccessError
|
||||
{
|
||||
throw .couldNotAccessApplicationExecutable(error: applicationDirectoryAccessError)
|
||||
}
|
||||
}
|
||||
|
||||
private static func getAppName(fromInfoDictionary infoDictionary: [String: Any]) -> String?
|
||||
{
|
||||
return infoDictionary["CFBundleName"] as? String
|
||||
}
|
||||
|
||||
private static func getAppIconPath(fromInfoDictionary infoDictionary: [String: Any], appBundle: Bundle) -> URL?
|
||||
{
|
||||
guard let iconFileName: String = infoDictionary["CFBundleIconFile"] as? String
|
||||
else
|
||||
{
|
||||
return nil
|
||||
}
|
||||
|
||||
return appBundle.resourceURL?.appendingPathComponent(iconFileName, conformingTo: .icns)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,6 +7,8 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import CorkModels
|
||||
import CorkTerminalFunctions
|
||||
|
||||
@Observable
|
||||
class InstallationProgressTracker
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
@ -130,7 +131,7 @@ struct SearchResultRow: View, Sendable
|
|||
|
||||
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
|
||||
{
|
||||
|
|
@ -186,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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,7 @@ 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
|
||||
|
||||
|
|
@ -238,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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import ButtonKit
|
||||
import CorkModels
|
||||
|
||||
struct PinUnpinButton: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import ApplicationInspector
|
||||
import CorkModels
|
||||
|
||||
struct PackageSystemInfo: View
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import ApplicationInspector
|
||||
|
||||
/// Show the icon of a linked app
|
||||
struct AppIconDisplay: View
|
||||
|
|
|
|||
|
|
@ -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