~ Basic conformance to new style rules

This commit is contained in:
David Bureš 2024-08-15 15:59:34 +02:00
parent 64b5315661
commit ec70520a42
186 changed files with 1454 additions and 1369 deletions

View File

@ -6,7 +6,6 @@ disabled_rules: # rule identifiers turned on by default to exclude from running
- empty_count
- function_body_length
- shorthand_operator
- cyclomatic_complexity # Turn this on once install function refactor is in main
- opening_brace
- line_length
- statement_position
@ -21,8 +20,8 @@ disabled_rules: # rule identifiers turned on by default to exclude from running
opt_in_rules: # some rules are turned off by default, so you need to opt-in
- empty_count # find all the available rules by running: `swiftlint rules`
- explicit_init
- contrasted_opening_brace
# - explicit_type_interface # Force the specification of types
# - contrasted_opening_brace
- explicit_type_interface # Force the specification of types
# Alternatively, specify all rules explicitly by uncommenting this option:
# only_rules: # delete `disabled_rules` & `opt_in_rules` if using this
@ -93,4 +92,4 @@ custom_rules:
# name: "Else Statement Position"
# regex: '\}[ \t]*\n[ \t]*else'
# message: "Else statements should be on their own line, right after the closing brace of the preceding block."
# severity: error
# severity: error

View File

@ -6,46 +6,54 @@
//
import Foundation
import UserNotifications
import OSLog
import UserNotifications
struct AppConstants
enum AppConstants
{
// MARK: - Logging
static let logger: Logger = Logger(subsystem: "com.davidbures.cork", category: "Cork")
static let logger: Logger = .init(subsystem: "com.davidbures.cork", category: "Cork")
// MARK: - Notification stuff
static let notificationCenter = UNUserNotificationCenter.current()
// MARK: - Proxy settings
static let proxySettings: (host: String, port: Int)? =
{
static let proxySettings: (host: String, port: Int)? = {
let proxySettings = CFNetworkCopySystemProxySettings()?.takeUnretainedValue() as? [String: Any]
guard let httpProxyHost = proxySettings?[kCFNetworkProxiesHTTPProxy as String] as? String else {
guard let httpProxyHost = proxySettings?[kCFNetworkProxiesHTTPProxy as String] as? String
else
{
AppConstants.logger.error("Could not get proxy host")
return nil
}
guard let httpProxyPort = proxySettings?[kCFNetworkProxiesHTTPPort as String] as? Int else {
guard let httpProxyPort = proxySettings?[kCFNetworkProxiesHTTPPort as String] as? Int
else
{
AppConstants.logger.error("Could not get proxy port")
return nil
}
return (host: httpProxyHost, port: httpProxyPort)
}()
// MARK: - Basic executables and file locations
static let brewExecutablePath: URL =
{
static let brewExecutablePath: URL = {
/// If a custom Homebrew path is defined, use it. Otherwise, use the default paths
if let homebrewPath = UserDefaults.standard.string(forKey: "customHomebrewPath"), !homebrewPath.isEmpty {
if let homebrewPath = UserDefaults.standard.string(forKey: "customHomebrewPath"), !homebrewPath.isEmpty
{
let customHomebrewPath = URL(string: homebrewPath)!
return customHomebrewPath
} else {
}
else
{
if FileManager.default.fileExists(atPath: "/opt/homebrew/bin/brew")
{ // Apple Sillicon
return URL(string: "/opt/homebrew/bin/brew")!
@ -57,8 +65,7 @@ struct AppConstants
}
}()
static let brewCellarPath: URL =
{
static let brewCellarPath: URL = {
if FileManager.default.fileExists(atPath: "/opt/homebrew/Cellar")
{ // Apple Sillicon
return URL(filePath: "/opt/homebrew/Cellar")
@ -69,8 +76,7 @@ struct AppConstants
}
}()
static let brewCaskPath: URL =
{
static let brewCaskPath: URL = {
if FileManager.default.fileExists(atPath: "/opt/homebrew/Caskroom")
{ // Apple Sillicon
return URL(filePath: "/opt/homebrew/Caskroom")
@ -81,8 +87,7 @@ struct AppConstants
}
}()
static let tapPath: URL =
{
static let tapPath: URL = {
if FileManager.default.fileExists(atPath: "/opt/homebrew/Library/Taps")
{ // Apple Sillicon
return URL(filePath: "/opt/homebrew/Library/Taps")
@ -108,22 +113,24 @@ struct AppConstants
/// This one has all the downloaded files themselves
static let brewCachedDownloadsPath: URL = brewCachePath.appendingPathComponent("downloads", conformingTo: .directory)
// MARK: - Licensing
static let demoLengthInSeconds: Double = 604800 // 7 days
static let authorizationEndpointURL: URL = URL(string: "https://automation.tomoserver.eu/webhook/38aacca6-5da8-453c-a001-804b15751319")!
static let demoLengthInSeconds: Double = 604_800 // 7 days
static let authorizationEndpointURL: URL = .init(string: "https://automation.tomoserver.eu/webhook/38aacca6-5da8-453c-a001-804b15751319")!
static let licensingAuthorization: (username: String, passphrase: String) = ("cork-authorization", "choosy-defame-neon-resume-cahoots")
// MARK: - Temporary OS version submission
static let osSubmissionEndpointURL: URL = URL(string: "https://automation.tomoserver.eu/webhook/3a971576-fa96-479e-9dc4-e052fe33270b")!
static let osSubmissionEndpointURL: URL = .init(string: "https://automation.tomoserver.eu/webhook/3a971576-fa96-479e-9dc4-e052fe33270b")!
// MARK: - Misc Stuff
static let backgroundUpdateInterval: TimeInterval = 10 * 60
static let backgroundUpdateIntervalTolerance: TimeInterval = 1 * 60
static let osVersionString: (lookupName: String, fullName: String) =
{
static let osVersionString: (lookupName: String, fullName: String) = {
let versionDictionary: [Int: (lookupName: String, fullName: String)] = [
15: ("sequoia", "Sequoia"),
14: ("sonoma", "Sonoma"),

View File

@ -1,30 +1,34 @@
//
// AppState.swift
// App State.swift
// Cork
//
// Created by David Bureš on 05.02.2023.
//
import Foundation
import AppKit
import Foundation
@preconcurrency import UserNotifications
/// Class that holds the global state of the app, excluding services
@MainActor
class AppState: ObservableObject
class AppState: ObservableObject
{
// MARK: - Licensing
@Published var licensingState: LicensingState = .notBoughtOrHasNotActivatedDemo
@Published var isShowingLicensingSheet: Bool = false
// MARK: - Navigation
@Published var navigationSelection: UUID?
// MARK: - Notifications
@Published var notificationEnabledInSystemSettings: Bool?
@Published var notificationAuthStatus: UNAuthorizationStatus = .notDetermined
// MARK: - Stuff for controlling various sheets from the menu bar
@Published var isShowingInstallationSheet: Bool = false
@Published var isShowingPackageReinstallationSheet: Bool = false
@Published var isShowingUninstallationSheet: Bool = false
@ -32,198 +36,207 @@ class AppState: ObservableObject
@Published var isShowingFastCacheDeletionMaintenanceView: Bool = false
@Published var isShowingAddTapSheet: Bool = false
@Published var isShowingUpdateSheet: Bool = false
// MARK: - Stuff for controlling the UI in general
@Published var isSearchFieldFocused: Bool = false
// MARK: - Brewfile importing and exporting
@Published var isShowingBrewfileExportProgress: Bool = false
@Published var isShowingBrewfileImportProgress: Bool = false
@Published var brewfileImportingStage: BrewfileImportStage = .importing
@Published var isCheckingForPackageUpdates: Bool = true
@Published var isShowingUninstallationProgressView: Bool = false
@Published var isShowingFatalError: Bool = false
@Published var fatalAlertType: DisplayableAlert? = nil
@Published var isShowingSudoRequiredForUninstallSheet: Bool = false
@Published var packageTryingToBeUninstalledWithSudo: BrewPackage?
@Published var isShowingRemoveTapFailedAlert: Bool = false
@Published var isShowingIncrementalUpdateSheet: Bool = false
@Published var isLoadingFormulae: Bool = true
@Published var isLoadingCasks: Bool = true
@Published var isLoadingTopPackages: Bool = false
@Published var failedWhileLoadingTopPackages: Bool = false
@Published var cachedDownloadsFolderSize: Int64 = AppConstants.brewCachedDownloadsPath.directorySize
@Published var cachedDownloads: [CachedDownload] = .init()
private var cachedDownloadsTemp: [CachedDownload] = .init()
@Published var taggedPackageNames: Set<String> = .init()
@Published var corruptedPackage: String = ""
// MARK: - Showing errors
func showAlert(errorToShow: DisplayableAlert)
{
self.fatalAlertType = errorToShow
self.isShowingFatalError = true
fatalAlertType = errorToShow
isShowingFatalError = true
}
func dismissAlert()
{
self.isShowingFatalError = false
self.fatalAlertType = nil
isShowingFatalError = false
fatalAlertType = nil
}
// MARK: - Notification setup
func setupNotifications() async
{
let notificationCenter = AppConstants.notificationCenter
let authStatus = await notificationCenter.authorizationStatus()
switch authStatus
{
case .notDetermined:
AppConstants.logger.debug("Notification authorization status not determined. Will request notifications again")
await self.requestNotificationAuthorization()
case .denied:
AppConstants.logger.debug("Notifications were refused")
case .authorized:
AppConstants.logger.debug("Notifications were authorized")
case .provisional:
AppConstants.logger.debug("Notifications are provisional")
case .ephemeral:
AppConstants.logger.debug("Notifications are ephemeral")
@unknown default:
AppConstants.logger.error("Something got really fucked up about notifications setup")
case .notDetermined:
AppConstants.logger.debug("Notification authorization status not determined. Will request notifications again")
await requestNotificationAuthorization()
case .denied:
AppConstants.logger.debug("Notifications were refused")
case .authorized:
AppConstants.logger.debug("Notifications were authorized")
case .provisional:
AppConstants.logger.debug("Notifications are provisional")
case .ephemeral:
AppConstants.logger.debug("Notifications are ephemeral")
@unknown default:
AppConstants.logger.error("Something got really fucked up about notifications setup")
}
notificationAuthStatus = authStatus
}
func requestNotificationAuthorization() async
{
let notificationCenter = AppConstants.notificationCenter
do
{
try await notificationCenter.requestAuthorization(options: [.alert, .sound, .badge])
notificationEnabledInSystemSettings = true
}
catch let notificationPermissionsObtainingError as NSError
{
AppConstants.logger.error("Notification permissions obtaining error: \(notificationPermissionsObtainingError.localizedDescription, privacy: .public)\nError code: \(notificationPermissionsObtainingError.code, privacy: .public)")
notificationEnabledInSystemSettings = false
}
}
// MARK: - Initiating the update process from legacy contexts
@objc func startUpdateProcessForLegacySelectors(_ sender: NSMenuItem!) -> Void
@objc func startUpdateProcessForLegacySelectors(_: NSMenuItem!)
{
self.isShowingUpdateSheet = true
isShowingUpdateSheet = true
sendNotification(title: String(localized: "notification.upgrade-process-started"))
}
func loadCachedDownloadedPackages() async
{
let smallestDispalyableSize: Int = Int(self.cachedDownloadsFolderSize / 50)
var packagesThatAreTooSmallToDisplaySize: Int = 0
guard let cachedDownloadsFolderContents: [URL] = try? FileManager.default.contentsOfDirectory(at: AppConstants.brewCachedDownloadsPath, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles]) else
let smallestDispalyableSize = Int(cachedDownloadsFolderSize / 50)
var packagesThatAreTooSmallToDisplaySize = 0
guard let cachedDownloadsFolderContents: [URL] = try? FileManager.default.contentsOfDirectory(at: AppConstants.brewCachedDownloadsPath, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles])
else
{
return
}
let usableCachedDownloads: [URL] = cachedDownloadsFolderContents.filter({ $0.pathExtension != "json" })
let usableCachedDownloads: [URL] = cachedDownloadsFolderContents.filter { $0.pathExtension != "json" }
for usableCachedDownload in usableCachedDownloads
{
guard var itemName: String = try? regexMatch(from: usableCachedDownload.lastPathComponent, regex: "(?<=--)(.*?)(?=\\.)") else
guard var itemName: String = try? regexMatch(from: usableCachedDownload.lastPathComponent, regex: "(?<=--)(.*?)(?=\\.)")
else
{
return
}
AppConstants.logger.debug("Temp item name: \(itemName, privacy: .public)")
if itemName.contains("--")
{
do
{
itemName = try regexMatch(from: itemName, regex: ".*?(?=--)")
}
catch
{
}
catch {}
}
guard let itemAttributes = try? FileManager.default.attributesOfItem(atPath: usableCachedDownload.path) else
guard let itemAttributes = try? FileManager.default.attributesOfItem(atPath: usableCachedDownload.path)
else
{
return
}
guard let itemSize = itemAttributes[.size] as? Int else
guard let itemSize = itemAttributes[.size] as? Int
else
{
return
}
if itemSize < smallestDispalyableSize
{
packagesThatAreTooSmallToDisplaySize = packagesThatAreTooSmallToDisplaySize + itemSize
}
else
{
self.cachedDownloads.append(CachedDownload(packageName: itemName, sizeInBytes: itemSize))
cachedDownloads.append(CachedDownload(packageName: itemName, sizeInBytes: itemSize))
}
AppConstants.logger.debug("Others size: \(packagesThatAreTooSmallToDisplaySize, privacy: .public)")
}
AppConstants.logger.log("Cached downloads contents: \(self.cachedDownloads)")
self.cachedDownloads = self.cachedDownloads.sorted(by: { $0.sizeInBytes < $1.sizeInBytes })
self.cachedDownloads.append(.init(packageName: String(localized: "start-page.cached-downloads.graph.other-smaller-packages"), sizeInBytes: packagesThatAreTooSmallToDisplaySize, packageType: .other))
cachedDownloads = cachedDownloads.sorted(by: { $0.sizeInBytes < $1.sizeInBytes })
cachedDownloads.append(.init(packageName: String(localized: "start-page.cached-downloads.graph.other-smaller-packages"), sizeInBytes: packagesThatAreTooSmallToDisplaySize, packageType: .other))
}
}
private extension UNUserNotificationCenter {
func authorizationStatus() async -> UNAuthorizationStatus {
private extension UNUserNotificationCenter
{
func authorizationStatus() async -> UNAuthorizationStatus
{
await notificationSettings().authorizationStatus
}
}
extension AppState
{
func assignPackageTypeToCachedDownloads(brewData: BrewDataStorage) -> Void
func assignPackageTypeToCachedDownloads(brewData: BrewDataStorage)
{
var cachedDownloadsTracker: [CachedDownload] = .init()
AppConstants.logger.debug("Package tracker in cached download assignment function has \(brewData.installedFormulae.count + brewData.installedCasks.count) packages")
for cachedDownload in self.cachedDownloads
for cachedDownload in cachedDownloads
{
let normalizedCachedPackageName: String = cachedDownload.packageName.onlyLetters
if brewData.installedFormulae.contains(where: { $0.name.localizedCaseInsensitiveContains(normalizedCachedPackageName) })
{ /// The cached package is a formula
AppConstants.logger.debug("Cached package \(cachedDownload.packageName) (\(normalizedCachedPackageName)) is a formula")
@ -240,7 +253,7 @@ extension AppState
cachedDownloadsTracker.append(.init(packageName: cachedDownload.packageName, sizeInBytes: cachedDownload.sizeInBytes, packageType: .unknown))
}
}
self.cachedDownloads = cachedDownloadsTracker
cachedDownloads = cachedDownloadsTracker
}
}

View File

@ -14,10 +14,10 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject
{
@AppStorage("showInMenuBar") var showInMenuBar = false
@AppStorage("startWithoutWindow") var startWithoutWindow: Bool = false
@MainActor let appState = AppState()
func applicationWillFinishLaunching(_ notification: Notification)
func applicationWillFinishLaunching(_: Notification)
{
if startWithoutWindow
{
@ -28,26 +28,28 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject
NSApp.setActivationPolicy(.regular)
}
}
func applicationDidFinishLaunching(_ notification: Notification)
func applicationDidFinishLaunching(_: Notification)
{
if startWithoutWindow
{
if let window = NSApplication.shared.windows.first {
if let window = NSApplication.shared.windows.first
{
window.close()
}
}
}
func applicationWillBecomeActive(_ notification: Notification)
func applicationWillBecomeActive(_: Notification)
{
NSApp.setActivationPolicy(.regular)
}
func applicationWillUnhide(_ notification: Notification)
func applicationWillUnhide(_: Notification)
{
NSApp.setActivationPolicy(.regular)
}
func applicationShouldTerminateAfterLastWindowClosed(_: NSApplication) -> Bool
{
if showInMenuBar
@ -62,7 +64,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject
}
}
func applicationWillTerminate(_ notification: Notification) {
func applicationWillTerminate(_: Notification)
{
AppConstants.logger.debug("Will die...")
do
{
@ -74,15 +77,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject
}
AppConstants.logger.debug("Died")
}
func applicationDockMenu(_ sender: NSApplication) -> NSMenu? {
func applicationDockMenu(_: NSApplication) -> NSMenu?
{
let menu = NSMenu()
menu.autoenablesItems = false
let updatePackagesMenuItem = NSMenuItem()
updatePackagesMenuItem.action = #selector(appState.startUpdateProcessForLegacySelectors(_:))
updatePackagesMenuItem.target = appState
if appState.isCheckingForPackageUpdates
{
updatePackagesMenuItem.title = String(localized: "start-page.updates.loading")
@ -98,9 +102,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject
updatePackagesMenuItem.title = String(localized: "navigation.menu.packages.update")
updatePackagesMenuItem.isEnabled = true
}
menu.addItem(updatePackagesMenuItem)
return menu
}
}

View File

@ -5,9 +5,9 @@
// Created by David Bureš on 03.07.2022.
//
import DavidFoundation
import SwiftUI
import UserNotifications
import DavidFoundation
@main
struct CorkApp: App
@ -134,14 +134,14 @@ struct CorkApp: App
if let demoActivatedAt
{
let timeDemoWillRunOutAt: Date = demoActivatedAt + AppConstants.demoLengthInSeconds
AppConstants.logger.debug("There is \(demoActivatedAt.timeIntervalSinceNow.formatted()) to go on the demo")
AppConstants.logger.debug("Demo will time out at \(timeDemoWillRunOutAt.formatted(date: .complete, time: .complete))")
if ((demoActivatedAt.timeIntervalSinceNow) + AppConstants.demoLengthInSeconds) > 0
{ // Check if there is still time on the demo
/// do stuff if there is
/// do stuff if there is
}
else
{
@ -226,9 +226,8 @@ struct CorkApp: App
}
.onChange(of: outdatedPackageTracker.displayableOutdatedPackages.count)
{ outdatedPackageCount in
AppConstants.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
{
@ -443,29 +442,27 @@ struct CorkApp: App
ButtonThatOpensWebsites(
websiteURL: URL(string: "https://forum.rikidar.eu/forumdisplay.php?fid=8")!, buttonText: "actiton.report-bugs.forum"
)
/*
Button
{
let emailSubject = "Cork Error Report: v\(NSApplication.appVersion!)-\(NSApplication.buildVersion!)"
let emailBody = "This is what went wrong:\n\nThis is what I expected to happen:\n\nDid Cork crash?"
Button
{
let emailSubject = "Cork Error Report: v\(NSApplication.appVersion!)-\(NSApplication.buildVersion!)"
let emailBody = "This is what went wrong:\n\nThis is what I expected to happen:\n\nDid Cork crash?"
let emailService = NSSharingService(named: NSSharingService.Name.composeEmail)
emailService?.recipients = ["bug-reporting@corkmac.app"]
emailService?.subject = emailSubject
emailService?.perform(withItems: [emailBody])
let emailService = NSSharingService(named: NSSharingService.Name.composeEmail)
emailService?.recipients = ["bug-reporting@corkmac.app"]
emailService?.subject = emailSubject
emailService?.perform(withItems: [emailBody])
} label: {
Text("action.report-bugs.email")
}
*/
} label: {
Text("action.report-bugs.email")
}
*/
} label: {
Text("action.report-bugs.menu-category")
}
Divider()
#endif
}
@ -519,9 +516,8 @@ struct CorkApp: App
}
catch let brewfileExportError as BrewfileDumpingError
{
AppConstants.logger.error("\(brewfileExportError)")
switch brewfileExportError
{
case .couldNotDetermineWorkingDirectory:
@ -567,7 +563,7 @@ struct CorkApp: App
catch let brewfileImportingError
{
AppConstants.logger.error("\(brewfileImportingError.localizedDescription, privacy: .public)")
appDelegate.appState.showAlert(errorToShow: .malformedBrewfile)
appDelegate.appState.isShowingBrewfileImportProgress = false

View File

@ -26,6 +26,7 @@ enum DisplayableAlert: LocalizedError
case couldNotSynchronizePackages(error: String)
// MARK: - Brewfile exporting/importing
case couldNotGetWorkingDirectory, couldNotDumpBrewfile(error: String), couldNotReadBrewfile
case couldNotGetBrewfileLocation, couldNotImportBrewfile, malformedBrewfile
}

View File

@ -64,7 +64,7 @@ extension DisplayableAlert
return String(localized: "alert.could-not-find-package-in-parent-directory.title")
case .fatalPackageInstallationError:
return String(localized: "alert.fatal-installation.error")
case .couldNotSynchronizePackages(_):
case .couldNotSynchronizePackages:
return String(localized: "alert.fatal.could-not-synchronize-packages.title")
case .couldNotGetWorkingDirectory:
return String(localized: "alert.could-not-get-brewfile-working-directory.title")

View File

@ -14,16 +14,16 @@ extension ServicesFatalError
{
switch self
{
case .couldNotLoadServices:
return String(localized: "services.error.could-not-load-services")
case .homebrewOutdated:
return String(localized: "services.error.homebrew-outdated")
case let .couldNotStartService(offendingService, _):
return String(localized: "services.error.could-not-start-service.\(offendingService)")
case let .couldNotStopService(offendingService, _):
return String(localized: "services.error.could-not-stop-service.\(offendingService)")
case .couldNotSynchronizeServices:
return String(localized: "services.error.could-not-synchronize-services")
case .couldNotLoadServices:
return String(localized: "services.error.could-not-load-services")
case .homebrewOutdated:
return String(localized: "services.error.homebrew-outdated")
case .couldNotStartService(let offendingService, _):
return String(localized: "services.error.could-not-start-service.\(offendingService)")
case .couldNotStopService(let offendingService, _):
return String(localized: "services.error.could-not-stop-service.\(offendingService)")
case .couldNotSynchronizeServices:
return String(localized: "services.error.could-not-synchronize-services")
}
}
}

View File

@ -10,21 +10,25 @@ import SwiftUI
enum DiscoverabilityDaySpans: Int, Hashable, Identifiable, CaseIterable
{
var id: Self { self }
var id: Self
{
self
}
case month = 30
case quarterYear = 90
case year = 365
var key: LocalizedStringKey
{
switch self {
case .month:
return "settings.discoverability.time-span.month"
case .quarterYear:
return "settings.discoverability.time-span.quarter-year"
case .year:
return "settings.discoverability.time-span.year"
switch self
{
case .month:
return "settings.discoverability.time-span.month"
case .quarterYear:
return "settings.discoverability.time-span.quarter-year"
case .year:
return "settings.discoverability.time-span.year"
}
}
}

View File

@ -10,19 +10,23 @@ import SwiftUI
enum TopPackageSorting: Int, Hashable, Identifiable, CaseIterable
{
var id: Self { self }
var id: Self
{
self
}
case mostDownloads, fewestDownloads, random
var key: LocalizedStringKey
{
switch self {
case .mostDownloads:
return "settings.discoverability.sorting.by-most-downloads"
case .fewestDownloads:
return "settings.discoverability.sorting.by-fewest-downloads"
case .random:
return "settings.discoverability.sorting.random"
switch self
{
case .mostDownloads:
return "settings.discoverability.sorting.by-most-downloads"
case .fewestDownloads:
return "settings.discoverability.sorting.by-fewest-downloads"
case .random:
return "settings.discoverability.sorting.random"
}
}
}

View File

@ -10,9 +10,9 @@ import Foundation
enum LicensingState
{
case notBoughtOrHasNotActivatedDemo
case demo
case bought
case selfCompiled
}

View File

@ -9,8 +9,11 @@ import Foundation
enum OutdatedPackageInfoAmount: String, Identifiable, Codable, CaseIterable
{
var id: Self { self }
var id: Self
{
self
}
case none, versionOnly, all
var localizedName: String

View File

@ -5,8 +5,8 @@
// Created by David Bureš on 17.05.2024.
//
import Foundation
import Charts
import Foundation
import SwiftUI
enum CachedDownloadType: String, CustomStringConvertible, Plottable
@ -15,33 +15,34 @@ enum CachedDownloadType: String, CustomStringConvertible, Plottable
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")
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
switch self
{
case .formula:
return .purple
case .cask:
return .orange
case .other:
return .mint
default:
return .gray
}
}
}

View File

@ -5,35 +5,36 @@
// Created by David Bureš on 05.02.2023.
//
import Foundation
import Charts
import AppIntents
import Charts
import Foundation
enum PackageType: String, CustomStringConvertible, Plottable, AppEntity
{
case formula
case cask
var description: String
{
switch self
{
case .formula:
return String(localized: "package-details.type.formula")
case .cask:
return String(localized: "package-details.type.cask")
case .formula:
return String(localized: "package-details.type.formula")
case .cask:
return String(localized: "package-details.type.cask")
}
}
static var 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")
switch self
{
case .formula:
DisplayRepresentation(title: "package-details.type.formula")
case .cask:
DisplayRepresentation(title: "package-details.type.cask")
}
}
}

View File

@ -44,8 +44,8 @@ enum ServiceStatus: Codable, Hashable, CustomStringConvertible
init(from decoder: Decoder) throws
{
let container = try decoder.singleValueContainer()
let rawValue = try container.decode(String.self)
let container: SingleValueDecodingContainer = try decoder.singleValueContainer()
let rawValue: String = try container.decode(String.self)
switch rawValue
{
@ -66,7 +66,7 @@ enum ServiceStatus: Codable, Hashable, CustomStringConvertible
func encode(to encoder: Encoder) throws
{
var container = encoder.singleValueContainer()
var container: SingleValueEncodingContainer = encoder.singleValueContainer()
try container.encode(description)
}

View File

@ -10,14 +10,15 @@ 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)")
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)")
}
}
}

View File

@ -9,7 +9,7 @@ import Foundation
extension String
{
static let mainWindowID = "main"
static let servicesWindowID = "services"
static let aboutWindowID = "about"
static let mainWindowID: String = "main"
static let servicesWindowID: String = "services"
static let aboutWindowID: String = "about"
}

View File

@ -8,8 +8,10 @@
import Foundation
import UniformTypeIdentifiers
extension UTType {
static var homebrewBackup: UTType {
extension UTType
{
static var homebrewBackup: UTType
{
UTType(exportedAs: "com.davidbures.homebrew-backup")
}
}

View File

@ -12,30 +12,31 @@ struct GetInstalledCasksIntent: AppIntent
{
static var title: LocalizedStringResource = "intent.get-installed-casks.title"
static var description: LocalizedStringResource = "intent.get-installed-casks.description"
static var isDiscoverable: Bool = true
static var openAppWhenRun: Bool = false
func perform() async throws -> some ReturnsValue<[MinimalHomebrewPackage]>
{
let allowAccessToFile = AppConstants.brewCaskPath.startAccessingSecurityScopedResource()
let allowAccessToFile: Bool = AppConstants.brewCaskPath.startAccessingSecurityScopedResource()
if allowAccessToFile
{
let installedFormulae = await loadUpPackages(whatToLoad: .cask, appState: AppState())
let installedFormulae: Set<BrewPackage> = await loadUpPackages(whatToLoad: .cask, appState: AppState())
AppConstants.brewCaskPath.stopAccessingSecurityScopedResource()
let minimalPackages: [MinimalHomebrewPackage] = installedFormulae.map { package in
return .init(name: package.name, type: .cask, installDate: package.installedOn, installedIntentionally: true)
let minimalPackages: [MinimalHomebrewPackage] = installedFormulae.map
{ package in
.init(name: package.name, type: .cask, installDate: package.installedOn, installedIntentionally: true)
}
return .result(value: minimalPackages)
}
else
{
print("Could not obtain access to folder")
throw FolderAccessingError.couldNotObtainPermissionToAccessFolder(formattedPath: AppConstants.brewCaskPath.absoluteString)
}
}

View File

@ -50,7 +50,7 @@ struct GetInstalledFormulaeIntent: AppIntent
if getOnlyManuallyInstalledPackages
{
minimalPackages = minimalPackages.filter { $0.installedIntentionally }
minimalPackages = minimalPackages.filter({ $0.installedIntentionally })
}
return .result(value: minimalPackages)

View File

@ -12,20 +12,19 @@ struct GetInstalledPackagesIntent: AppIntent
{
@Parameter(title: "intent.get-installed-packages.limit-to-manually-installed-packages")
var getOnlyManuallyInstalledPackages: Bool
static var title: LocalizedStringResource = "intent.get-installed-packages.title"
static var description: LocalizedStringResource = "intent.get-installed-packages.description"
static var isDiscoverable: Bool = true
static var openAppWhenRun: Bool = false
func perform() async throws -> some ReturnsValue<[MinimalHomebrewPackage]>
{
let installedMinimalFormulae: [MinimalHomebrewPackage] = try await GetInstalledFormulaeIntent(getOnlyManuallyInstalledPackages: $getOnlyManuallyInstalledPackages).perform().value ?? .init()
let installedMinimalCasks: [MinimalHomebrewPackage] = try await GetInstalledCasksIntent().perform().value ?? .init()
return .result(value: installedMinimalFormulae + installedMinimalCasks)
}
}

View File

@ -16,7 +16,7 @@ enum RefreshIntentResult: String, AppEnum
static var typeDisplayRepresentation: TypeDisplayRepresentation = .init(name: "intent.refresh.result.display-representation")
static var caseDisplayRepresentations: [RefreshIntentResult : DisplayRepresentation] = [
static var caseDisplayRepresentations: [RefreshIntentResult: DisplayRepresentation] = [
.refreshed: DisplayRepresentation(title: "intent.refresh.result.refreshed"),
.refreshedWithErrors: DisplayRepresentation(title: "intent.refresh.result.refreshed-with-errors"),
.failed: DisplayRepresentation(title: "intent.refresh.result.failed")
@ -36,8 +36,8 @@ struct RefreshPackagesIntent: AppIntent
let refreshCommandResult: TerminalOutput = await shell(AppConstants.brewExecutablePath, ["update"])
var refreshErrorWithoutBuggedHomebrewMessages: [String] = refreshCommandResult.standardError.components(separatedBy: "\n")
refreshErrorWithoutBuggedHomebrewMessages = refreshErrorWithoutBuggedHomebrewMessages.filter({ !$0.contains("Updating Homebrew") })
refreshErrorWithoutBuggedHomebrewMessages = refreshErrorWithoutBuggedHomebrewMessages.filter { !$0.contains("Updating Homebrew") }
if !refreshErrorWithoutBuggedHomebrewMessages.isEmpty
{
return .result(value: .refreshedWithErrors)

View File

@ -5,8 +5,8 @@
// Created by David Bureš on 26.05.2024.
//
import Foundation
import AppIntents
import Foundation
struct CorkShortcuts: AppShortcutsProvider
{

View File

@ -30,10 +30,9 @@ extension TopPackagesTracker
func loadTopPackages(numberOfDays: Int = 30, appState: AppState) async
{
/// The magic number here is the result of 1000/30, a base limit for 30 days: If the user selects the number of days to be 30, only show packages with more than 1000 downloads
let packageDownloadsCutoff: Int = 33 * numberOfDays
let packageDownloadsCutoff = 33 * numberOfDays
let decoder: JSONDecoder =
{
let decoder: JSONDecoder = {
let decoder: JSONDecoder = .init()
decoder.keyDecodingStrategy = .convertFromSnakeCase

View File

@ -120,7 +120,7 @@ private extension URL
/// e.g. only actual package URLs
var validPackageURLs: [String]?
{
let items: [String]? = try? FileManager.default.contentsOfDirectory(atPath: self.path).filter { !$0.hasPrefix(".") }.filter
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)
@ -147,24 +147,26 @@ private extension URL
guard let localPackagePath = versionURLs.first
else
{
throw PackageLoadingError.failedWhileLoadingCertainPackage(self.lastPathComponent, self, failureReason: String(localized: "error.package-loading.could-not-load-version-to-check-from-available-versions"))
throw PackageLoadingError.failedWhileLoadingCertainPackage(lastPathComponent, self, failureReason: String(localized: "error.package-loading.could-not-load-version-to-check-from-available-versions"))
}
guard localPackagePath.lastPathComponent != "Cellar" else
guard localPackagePath.lastPathComponent != "Cellar"
else
{
AppConstants.logger.error("The last path component of the requested URL is the package container folder itself - perhaps a misconfigured package folder? Tried to load URL \(localPackagePath)")
throw PackageLoadingError.failedWhileLoadingPackages(failureReason: String(localized: "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.formulae"))
}
guard localPackagePath.lastPathComponent != "Caskroom" else
guard localPackagePath.lastPathComponent != "Caskroom"
else
{
AppConstants.logger.error("The last path component of the requested URL is the package container folder itself - perhaps a misconfigured package folder? Tried to load URL \(localPackagePath)")
throw PackageLoadingError.failedWhileLoadingPackages(failureReason: String(localized: "error.package-loading.last-path-component-of-checked-package-url-is-folder-containing-packages-itself.casks"))
}
if self.path.contains("Cellar")
if path.contains("Cellar")
{
let localPackageInfoJSONPath = localPackagePath.appendingPathComponent("INSTALL_RECEIPT.json", conformingTo: .json)
if FileManager.default.fileExists(atPath: localPackageInfoJSONPath.path)
@ -174,8 +176,7 @@ private extension URL
let installedOnRequest: Bool
}
let decoder: JSONDecoder =
{
let decoder: JSONDecoder = {
let decoder: JSONDecoder = .init()
decoder.keyDecodingStrategy = .convertFromSnakeCase
@ -194,7 +195,7 @@ private extension URL
{
AppConstants.logger.error("Failed to decode install receipt for package \(self.lastPathComponent, privacy: .public) with error \(installReceiptParsingError.localizedDescription, privacy: .public)")
throw PackageLoadingError.failedWhileLoadingCertainPackage(self.lastPathComponent, self, failureReason: String(localized:"error.package-loading.could-not-decode-installa-receipt-\(installReceiptParsingError.localizedDescription)"))
throw PackageLoadingError.failedWhileLoadingCertainPackage(self.lastPathComponent, self, failureReason: String(localized: "error.package-loading.could-not-decode-installa-receipt-\(installReceiptParsingError.localizedDescription)"))
}
}
catch let installReceiptLoadingError
@ -213,7 +214,7 @@ private extension URL
if shouldStrictlyCheckForHomebrewErrors
{
throw PackageLoadingError.failedWhileLoadingCertainPackage(self.lastPathComponent, self, failureReason: String(localized: "error.package-loading.missing-install-receipt"))
throw PackageLoadingError.failedWhileLoadingCertainPackage(lastPathComponent, self, failureReason: String(localized: "error.package-loading.missing-install-receipt"))
}
else
{
@ -221,20 +222,20 @@ private extension URL
}
}
}
else if self.path.contains("Caskroom")
else if path.contains("Caskroom")
{
return true
}
else
{
throw PackageLoadingError.failedWhileLoadingCertainPackage(self.lastPathComponent, self, failureReason: String(localized: "error.package-loading.unexpected-folder-name"))
throw PackageLoadingError.failedWhileLoadingCertainPackage(lastPathComponent, self, failureReason: String(localized: "error.package-loading.unexpected-folder-name"))
}
}
/// Determine a package's type type from its URL
var packageType: PackageType
{
if self.path.contains("Cellar")
if path.contains("Cellar")
{
return .formula
}
@ -265,7 +266,7 @@ private extension URL
}
catch
{
AppConstants.logger.error("Failed while loading version for package \(self.lastPathComponent, privacy: .public) at URL \(self, privacy: .public)")
AppConstants.logger.error("Failed while loading version for package \(lastPathComponent, privacy: .public) at URL \(self, privacy: .public)")
return nil
}
@ -277,7 +278,7 @@ extension [URL]
/// Returns an array of versions from an array of URLs to available versions
var versions: [String]
{
return self.map
return map
{ versionURL in
versionURL.lastPathComponent
}

View File

@ -13,14 +13,14 @@ extension URL
{
do
{
let fileAttributes = try self.resourceValues(forKeys: [.isSymbolicLinkKey])
let fileAttributes = try resourceValues(forKeys: [.isSymbolicLinkKey])
return fileAttributes.isSymbolicLink
}
catch let symlinkCheckingError
{
AppConstants.logger.error("Error checking if \(self) is symlink: \(symlinkCheckingError)")
return nil
}
}

View File

@ -20,9 +20,9 @@ extension URL
{
return 0
}
var size: Int64 = 0
for url in contents
{
let isDirectoryResourceValue: URLResourceValues
@ -34,7 +34,7 @@ extension URL
{
continue
}
if isDirectoryResourceValue.isDirectory == true
{
size += url.directorySize
@ -50,11 +50,11 @@ extension URL
{
continue
}
size += Int64(fileSizeResourceValue.fileSize ?? 0)
}
}
return size
}
}

View File

@ -5,8 +5,8 @@
// Created by David Bureš on 01.10.2023.
//
import Foundation
import AppKit
import Foundation
extension String
{

View File

@ -10,19 +10,23 @@ import Foundation
enum RegexError: LocalizedError
{
case regexFunctionCouldNotMatchAnything
var errorDescription: String?
{
switch self {
case .regexFunctionCouldNotMatchAnything:
return String(localized: "error.regex.nothing-matched")
switch self
{
case .regexFunctionCouldNotMatchAnything:
return String(localized: "error.regex.nothing-matched")
}
}
}
func regexMatch(from string: String, regex: String) throws -> String
{
guard let matchedRange = string.range(of: regex, options: .regularExpression) else { throw RegexError.regexFunctionCouldNotMatchAnything }
guard let matchedRange = string.range(of: regex, options: .regularExpression) else
{
throw RegexError.regexFunctionCouldNotMatchAnything
}
return String(string[matchedRange])
}

View File

@ -5,8 +5,8 @@
// Created by David Bureš on 30.03.2024.
//
import Foundation
import AppKit
import Foundation
func switchCorkToForeground()
{
@ -17,7 +17,7 @@ func switchCorkToForeground()
else
{
let runningApps: [NSRunningApplication] = NSWorkspace.shared.runningApplications
for app in runningApps
{
if app.localizedName == "Cork"
@ -29,4 +29,4 @@ func switchCorkToForeground()
}
}
}
}
}

View File

@ -8,7 +8,6 @@
import Foundation
import SwiftUI
/// What this function does:
/// **Background**
/// When a package is installed or uninstalled, its dependencies don't show up in the package list, because there's no system for getting them. I needed a way for all new packages to show up/disappear even when they were not installed manually
@ -32,10 +31,9 @@ import SwiftUI
/// 4. In the second for loop, check each package in the old array against each package in the final *mutable* array. If the names of packages match, remove them; if they match, it means that this package has been removed
@MainActor
func synchronizeInstalledPackages(brewData: BrewDataStorage) async -> Void
func synchronizeInstalledPackages(brewData: BrewDataStorage) async
{
let dummyAppState: AppState = AppState()
let dummyAppState = AppState()
dummyAppState.isLoadingFormulae = false
dummyAppState.isLoadingCasks = false
@ -45,7 +43,7 @@ func synchronizeInstalledPackages(brewData: BrewDataStorage) async -> Void
if newFormulae.count != brewData.installedFormulae.count
{
withAnimation
withAnimation
{
brewData.installedFormulae = newFormulae
}
@ -53,7 +51,7 @@ func synchronizeInstalledPackages(brewData: BrewDataStorage) async -> Void
if newCasks.count != brewData.installedCasks.count
{
withAnimation
withAnimation
{
brewData.installedCasks = newCasks
}

View File

@ -106,7 +106,7 @@ func loadUpTappedTaps() async -> [BrewTap]
return finalAvailableTaps
}
private func checkIfTapIsAdded(tapToCheck: String) async -> Bool
private func checkIfTapIsAdded(tapToCheck _: String) async -> Bool
{
return true
}

View File

@ -7,7 +7,7 @@
import Foundation
func deleteCachedDownloads() -> Void
func deleteCachedDownloads()
{
/// This folder has the symlinks, so we have do **delete ONLY THE SYMLINKS**
for url in getContentsOfFolder(targetFolder: AppConstants.brewCachedFormulaeDownloadsPath)
@ -28,7 +28,7 @@ func deleteCachedDownloads() -> Void
AppConstants.logger.warning("Could not check symlink status of \(url)")
}
}
/// This folder has the symlinks, so we have to **delete ONLY THE SYMLINKS**
for url in getContentsOfFolder(targetFolder: AppConstants.brewCachedCasksDownloadsPath)
{

View File

@ -7,10 +7,10 @@
import Foundation
/*enum CachePurgeError: Error
{
case standardErrorNotEmpty
}*/
/* enum CachePurgeError: Error
{
case standardErrorNotEmpty
} */
func purgeBrewCache() async throws -> TerminalOutput
{

View File

@ -10,14 +10,15 @@ import Foundation
enum HomebrewCachePurgeError: LocalizedError
{
case purgingCommandFailed, regexMatchingCouldNotMatchAnything
var errorDescription: String?
{
switch self {
case .purgingCommandFailed:
return String(localized: "error.maintenance.cache-purging.command-failed")
case .regexMatchingCouldNotMatchAnything:
return String(localized: "error.regex.nothing-matched")
switch self
{
case .purgingCommandFailed:
return String(localized: "error.maintenance.cache-purging.command-failed")
case .regexMatchingCouldNotMatchAnything:
return String(localized: "error.regex.nothing-matched")
}
}
}
@ -46,7 +47,8 @@ func purgeHomebrewCacheUtility() async throws -> [String]
let packageHoldingBackCachePurgeNameRegex = "(?<=Skipping ).*?(?=:)"
guard let packageHoldingBackCachePurgeName = try? regexMatch(from: blockingPackageRaw, regex: packageHoldingBackCachePurgeNameRegex) else
guard let packageHoldingBackCachePurgeName = try? regexMatch(from: blockingPackageRaw, regex: packageHoldingBackCachePurgeNameRegex)
else
{
throw HomebrewCachePurgeError.regexMatchingCouldNotMatchAnything
}

View File

@ -16,7 +16,7 @@ enum OrphanRemovalError: LocalizedError
switch self
{
case .couldNotUninstallOrphans(let output):
return String(localized: "error.maintenance.orphan-removal.could-not-uninstall-orphans.\(output)")
return String(localized: "error.maintenance.orphan-removal.could-not-uninstall-orphans.\(output)")
case .couldNotGetNumberOfUninstalledOrphans:
return String(localized: "error.maintenance.orphan-removal.could-not-get-number-of-uninstalled-orphans")
}
@ -39,7 +39,7 @@ func uninstallOrphansUtility() async throws -> Int
}
else
{
let numberOfUninstalledOrphansRegex: String = "(?<=Autoremoving ).*?(?= unneeded)"
let numberOfUninstalledOrphansRegex = "(?<=Autoremoving ).*?(?= unneeded)"
guard let numberOfRemovedOrphans = try Int(regexMatch(from: orphanUninstallationOutput.standardOutput, regex: numberOfUninstalledOrphansRegex))
else

View File

@ -9,32 +9,32 @@ import Foundation
import SwiftUI
import UserNotifications
func sendNotification(title: String, body: String? = nil, subtitle: String? = nil, sensitivity: UNNotificationInterruptionLevel = .timeSensitive) -> Void
func sendNotification(title: String, body: String? = nil, subtitle: String? = nil, sensitivity: UNNotificationInterruptionLevel = .timeSensitive)
{
// Get whether we can send notifications
let notificationsAreEnabled = UserDefaults.standard.bool(forKey: "areNotificationsEnabled")
if notificationsAreEnabled
{
let notification = UNMutableNotificationContent()
notification.title = title
if let body
{
notification.body = body
}
if let subtitle
{
notification.subtitle = subtitle
}
notification.sound = .default
notification.interruptionLevel = sensitivity
let request = UNNotificationRequest(identifier: UUID().uuidString, content: notification, trigger: nil)
AppConstants.notificationCenter.add(request)
}
else

View File

@ -5,13 +5,13 @@
// Created by David Bureš on 19.11.2023.
//
import Foundation
import AppKit
import Foundation
func openTerminal()
{
guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.apple.Terminal") else { return }
let path = "/bin"
let configuration = NSWorkspace.OpenConfiguration()
configuration.arguments = [path]

View File

@ -62,10 +62,10 @@ extension OutdatedPackageTracker
{
// First, we have to pull the newest updates
await shell(AppConstants.brewExecutablePath, ["update"])
// Then we can get the updating under way
let rawOutput: TerminalOutput = await shell(AppConstants.brewExecutablePath, ["outdated", "--json=v2"])
print("Outdated package function oputput: \(rawOutput)")
// MARK: - Error checking
@ -84,8 +84,7 @@ extension OutdatedPackageTracker
// MARK: - Decoding
let outdatedPackagesOutputDecoder: JSONDecoder =
{
let outdatedPackagesOutputDecoder: JSONDecoder = {
let decoder: JSONDecoder = .init()
decoder.keyDecodingStrategy = .convertFromSnakeCase
@ -108,7 +107,7 @@ extension OutdatedPackageTracker
async let finalOutdatedFormulae: Set<OutdatedPackage> = await getOutdatedFormulae(from: rawDecodedOutdatedPackages.formulae, brewData: brewData)
async let finalOutdatedCasks: Set<OutdatedPackage> = await getOutdatedCasks(from: rawDecodedOutdatedPackages.casks, brewData: brewData)
self.outdatedPackages = await finalOutdatedFormulae.union(finalOutdatedCasks)
outdatedPackages = await finalOutdatedFormulae.union(finalOutdatedCasks)
}
catch let decodingError
{

View File

@ -34,6 +34,7 @@ extension BrewPackage
private struct PackageCommandOutput: Codable
{
// MARK: - Formulae
struct Formulae: Codable
{
/// Name of the formula
@ -79,7 +80,7 @@ extension BrewPackage
/// Various info about the installation
let installed: [Installed]
struct Bottle: Codable
{
struct Stable: Codable
@ -90,15 +91,15 @@ extension BrewPackage
let url: URL
let sha256: String
}
/// The individual files
let files: [String: FileInfo]
}
/// The stable files
let stable: Stable
}
/// Info about the relevant files
let bottle: Bottle
@ -109,9 +110,10 @@ extension BrewPackage
let pinned: Bool
// MARK: - Formuale functions
func extractDependencies() -> [BrewPackageDependency]?
{
let allDependencies = self.installed.flatMap
let allDependencies = installed.flatMap
{ installed in
installed.runtimeDependencies ?? []
}
@ -126,24 +128,25 @@ extension BrewPackage
.init(name: dependency.fullName, version: dependency.version, directlyDeclared: dependency.declaredDirectly)
}
}
func getCompatibility() -> Bool
{
for compatibleSystem in self.bottle.stable.files.keys
for compatibleSystem in bottle.stable.files.keys
{
if compatibleSystem.contains(AppConstants.osVersionString.lookupName)
{
AppConstants.logger.debug("Package \(self.name) is compatible")
AppConstants.logger.debug("Package \(name) is compatible")
return true
}
}
AppConstants.logger.debug("Package \(self.name) is NOT compatible")
AppConstants.logger.debug("Package \(name) is NOT compatible")
return false
}
}
// MARK: - Casks
struct Casks: Codable
{
/// Name of the cask
@ -175,7 +178,7 @@ extension BrewPackage
/// Extract dependencies from the struct so they can be used
func extractDependencies() -> [BrewPackageDependency]?
{
guard let formulae = self.formulae
guard let formulae = formulae
else
{
return nil
@ -205,8 +208,7 @@ extension BrewPackage
@MainActor
func loadDetails() async throws -> BrewPackageDetails
{
let decoder: JSONDecoder =
{
let decoder: JSONDecoder = {
let decoder: JSONDecoder = .init()
decoder.keyDecodingStrategy = .convertFromSnakeCase
@ -215,13 +217,13 @@ extension BrewPackage
var rawOutput: TerminalOutput?
switch self.type
switch type
{
case .formula:
rawOutput = await shell(AppConstants.brewExecutablePath, ["info", "--json=v2", self.name])
rawOutput = await shell(AppConstants.brewExecutablePath, ["info", "--json=v2", name])
case .cask:
rawOutput = await shell(AppConstants.brewExecutablePath, ["info", "--json=v2", "--cask", self.name])
rawOutput = await shell(AppConstants.brewExecutablePath, ["info", "--json=v2", "--cask", name])
}
// MARK: - Error checking
@ -251,7 +253,7 @@ extension BrewPackage
}
AppConstants.logger.debug("JSON output: \(rawOutput.standardOutput)")
guard let decodableData: Data = rawOutput.standardOutput.data(using: .utf8, allowLossyConversion: false)
else
{
@ -266,7 +268,7 @@ extension BrewPackage
{
let rawDecodedPackageInfo: PackageCommandOutput = try decoder.decode(PackageCommandOutput.self, from: decodableData)
switch self.type
switch type
{
case .formula:
guard let formulaInfo: PackageCommandOutput.Formulae = rawDecodedPackageInfo.formulae?.first
@ -286,7 +288,7 @@ extension BrewPackage
dependencies: formulaInfo.extractDependencies(),
outdated: formulaInfo.outdated,
caveats: formulaInfo.caveats,
pinned: formulaInfo.pinned,
pinned: formulaInfo.pinned,
isCompatible: formulaInfo.getCompatibility()
)

View File

@ -7,12 +7,12 @@
import Foundation
func pinAndUnpinPackage(package: BrewPackage, pinned: Bool) async -> Void
func pinAndUnpinPackage(package: BrewPackage, pinned: Bool) async
{
if pinned
{
let pinResult = await shell(AppConstants.brewExecutablePath, ["pin", package.name])
if !pinResult.standardError.isEmpty
{
AppConstants.logger.error("Error pinning: \(pinResult.standardError, privacy: .public)")

View File

@ -7,39 +7,37 @@
import Foundation
private enum PackageRetrievalByUUIDError: LocalizedError
{
case couldNotfindAnypackagesInTracker
var errorDescription: String?
{
switch self {
case .couldNotfindAnypackagesInTracker:
return String(localized: "error.package-retrieval.uuid.could-not-find-any-packages-in-tracker")
switch self
{
case .couldNotfindAnypackagesInTracker:
return String(localized: "error.package-retrieval.uuid.could-not-find-any-packages-in-tracker")
}
}
}
func getPackageFromUUID(requestedPackageUUID: UUID, tracker: SearchResultTracker) throws -> BrewPackage
{
var filteredPackage: BrewPackage?
AppConstants.logger.log("Formula tracker: \(tracker.foundFormulae.count)")
AppConstants.logger.log("Cask tracker: \(tracker.foundCasks.count)")
if tracker.foundFormulae.count != 0
{
filteredPackage = tracker.foundFormulae.filter({ $0.id == requestedPackageUUID }).first
filteredPackage = tracker.foundFormulae.filter { $0.id == requestedPackageUUID }.first
}
if filteredPackage == nil
{
filteredPackage = tracker.foundCasks.filter({ $0.id == requestedPackageUUID }).first
filteredPackage = tracker.foundCasks.filter { $0.id == requestedPackageUUID }.first
}
if let filteredPackage
{
return filteredPackage
@ -53,12 +51,13 @@ func getPackageFromUUID(requestedPackageUUID: UUID, tracker: SearchResultTracker
enum TopPackageRetrievalError: LocalizedError
{
case resultingArrayWasEmptyEvenThoughPackagesWereInIt
var errorDescription: String?
{
switch self {
case .resultingArrayWasEmptyEvenThoughPackagesWereInIt:
return String(localized: "error.top-packages.impossible-error")
switch self
{
case .resultingArrayWasEmptyEvenThoughPackagesWereInIt:
return String(localized: "error.top-packages.impossible-error")
}
}
}
@ -68,53 +67,55 @@ func getTopPackageFromUUID(requestedPackageUUID: UUID, packageType: PackageType,
{
if packageType == .formula
{
guard let foundTopFormula: TopPackage = topPackageTracker.topFormulae.filter({ $0.id == requestedPackageUUID }).first else
guard let foundTopFormula: TopPackage = topPackageTracker.topFormulae.filter({ $0.id == requestedPackageUUID }).first
else
{
throw TopPackageRetrievalError.resultingArrayWasEmptyEvenThoughPackagesWereInIt
}
return .init(name: foundTopFormula.packageName, type: .formula, installedOn: nil, versions: [], sizeInBytes: nil)
}
else
{
guard let foundTopCask: TopPackage = topPackageTracker.topCasks.filter({ $0.id == requestedPackageUUID }).first else
guard let foundTopCask: TopPackage = topPackageTracker.topCasks.filter({ $0.id == requestedPackageUUID }).first
else
{
throw TopPackageRetrievalError.resultingArrayWasEmptyEvenThoughPackagesWereInIt
}
return .init(name: foundTopCask.packageName, type: .cask, installedOn: nil, versions: [], sizeInBytes: nil)
}
}
/*
func getPackageNamesFromUUID(selectionBinding: Set<UUID>, tracker: SearchResultTracker) -> [String]
{
let foundFormulae: [SearchResult] = tracker.foundFormulae
let foundCasks: [SearchResult] = tracker.foundCasks
func getPackageNamesFromUUID(selectionBinding: Set<UUID>, tracker: SearchResultTracker) -> [String]
{
let foundFormulae: [SearchResult] = tracker.foundFormulae
let foundCasks: [SearchResult] = tracker.foundCasks
var resultArray = [String]()
var resultArray = [String]()
for selection in selectionBinding
{
/// Step 1: Look through formulae
for item in foundFormulae
{
if selection == item.id
{
resultArray.append(item.packageName)
}
}
for selection in selectionBinding
{
/// Step 1: Look through formulae
for item in foundFormulae
{
if selection == item.id
{
resultArray.append(item.packageName)
}
}
/// Step 2: Look through casks
for item in foundCasks
{
if selection == item.id
{
resultArray.append(item.packageName)
}
}
}
/// Step 2: Look through casks
for item in foundCasks
{
if selection == item.id
{
resultArray.append(item.packageName)
}
}
}
return resultArray
}
*/
return resultArray
}
*/

View File

@ -25,8 +25,8 @@ func searchForPackage(packageName: String, packageType: PackageType) async throw
}
finalPackageArray.removeLast()
AppConstants.logger.info("Search found these packages: \(finalPackageArray, privacy: .auto)")
return finalPackageArray
}

View File

@ -54,8 +54,7 @@ extension ServicesTracker
/// Load services into the service tracker
func loadServices() async throws
{
let decoder: JSONDecoder =
{
let decoder: JSONDecoder = {
let decoder: JSONDecoder = .init()
decoder.keyDecodingStrategy = .convertFromSnakeCase
@ -103,7 +102,7 @@ extension ServicesTracker
))
}
self.services = finalServices
services = finalServices
}
catch let servicesParsingError
{

View File

@ -13,25 +13,25 @@ extension ServicesTracker
{
for await output in shell(AppConstants.brewExecutablePath, ["services", "kill", serviceToKill.name])
{
switch output
switch output
{
case .standardOutput(let outputLine):
AppConstants.logger.debug("Service killing output: \(outputLine)")
case .standardError(let errorLine):
AppConstants.logger.error("Service killing error: \(errorLine)")
case .standardOutput(let outputLine):
AppConstants.logger.debug("Service killing output: \(outputLine)")
case .standardError(let errorLine):
AppConstants.logger.error("Service killing error: \(errorLine)")
}
}
do
{
serviceModificationProgress.progress = 5.0
try await synchronizeServices(preserveIDs: true)
}
catch let servicesSynchronizationError
{
AppConstants.logger.error("Could not synchronize services: \(servicesSynchronizationError.localizedDescription)")
servicesState.showError(.couldNotSynchronizeServices(errorThrown: servicesSynchronizationError.localizedDescription))
}
}

View File

@ -15,7 +15,7 @@ extension ServicesTracker
{
switch output
{
case let .standardOutput(outputLine):
case .standardOutput(let outputLine):
AppConstants.logger.debug("Services startup output line: \(outputLine)")
switch outputLine
{
@ -27,7 +27,7 @@ extension ServicesTracker
serviceModificationProgress.progress += 1
case let .standardError(errorLine):
case .standardError(let errorLine):
switch errorLine
{
case _ where errorLine.contains("must be run as root"):
@ -40,14 +40,13 @@ extension ServicesTracker
servicesState.showError(.couldNotStartService(offendingService: serviceToStart.name, errorThrown: errorLine))
}
}
}
do
{
serviceModificationProgress.progress = 5.0
try await synchronizeServices(preserveIDs: true)
}
catch let servicesSynchronizationError

View File

@ -12,11 +12,11 @@ extension ServicesTracker
func synchronizeServices(preserveIDs: Bool) async throws
{
do
{
{
let dummyServicesTracker: ServicesTracker = .init()
try await dummyServicesTracker.loadServices()
let updatedServices: Set<HomebrewService> = dummyServicesTracker.services
if !preserveIDs
@ -32,9 +32,8 @@ extension ServicesTracker
let updatedServicesWithOldIDs: Set<HomebrewService> = Set(updatedServices.map
{ updatedService in
var copyUpdatedService = updatedService
for originalServiceWithItsOldUUID in originalServicesWithTheirUUIDs
{
if originalServiceWithItsOldUUID.key == copyUpdatedService.name
@ -42,10 +41,10 @@ extension ServicesTracker
copyUpdatedService.id = originalServiceWithItsOldUUID.value
}
}
return copyUpdatedService
})
services = updatedServicesWithOldIDs
}
}

View File

@ -8,47 +8,49 @@
import Foundation
extension HomebrewService
{
{
@MainActor
func loadDetails() async throws -> ServiceDetails?
{
AppConstants.logger.debug("Will try to load up service details for service \(self.name)")
let rawOutput: TerminalOutput = await shell(AppConstants.brewExecutablePath, ["services", "info", self.name, "--json"])
let decoder: JSONDecoder =
{
AppConstants.logger.debug("Will try to load up service details for service \(name)")
let rawOutput: TerminalOutput = await shell(AppConstants.brewExecutablePath, ["services", "info", name, "--json"])
let decoder: JSONDecoder = {
let decoder: JSONDecoder = .init()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
// MARK: - Error checking
if !rawOutput.standardError.isEmpty
{
AppConstants.logger.error("Failed while loading up service details: Standard Error not empty")
throw HomebrewServiceLoadingError.standardErrorNotEmpty(standardError: rawOutput.standardError)
}
do
{
guard let decodableOutput: Data = rawOutput.standardOutput.data(using: .utf8, allowLossyConversion: false) else
guard let decodableOutput: Data = rawOutput.standardOutput.data(using: .utf8, allowLossyConversion: false)
else
{
return nil
}
guard let decodedOutput: ServiceDetails = try decoder.decode([ServiceDetails].self, from: decodableOutput).first else
guard let decodedOutput: ServiceDetails = try decoder.decode([ServiceDetails].self, from: decodableOutput).first
else
{
return nil
}
return decodedOutput
}
catch let parsingError
{
AppConstants.logger.error("Parsing of service details of service \(self.name) failed: \(parsingError)")
throw HomebrewServiceLoadingError.servicesParsingFailed
}
}

View File

@ -8,7 +8,6 @@
import Foundation
import SwiftUI
struct SearchResults
{
let foundFormulae: [String]

View File

@ -21,9 +21,9 @@ func shell(
{
switch streamedOutput
{
case let .standardOutput(output):
case .standardOutput(let output):
allOutput.append(output)
case let .standardError(error):
case .standardError(let error):
allErrors.append(error)
}
}
@ -34,7 +34,6 @@ func shell(
)
}
/// # Usage:
/// for await output in shell(AppConstants.brewExecutablePath, ["install", package.name])
/// {
@ -45,18 +44,20 @@ func shell(
/// case let .error(errorLine):
/// // Do something with `errorLine`
/// }
///}
/// }
func shell(
_ launchPath: URL,
_ arguments: [String],
environment: [String: String]? = nil,
workingDirectory: URL? = nil
) -> AsyncStream<StreamedTerminalOutput> {
) -> AsyncStream<StreamedTerminalOutput>
{
let task = Process()
var finalEnvironment: [String: String] = .init()
// MARK: - Set up the $HOME environment variable so brew commands work on versions 4.1 and up
if var environment
{
environment["HOME"] = FileManager.default.homeDirectoryForCurrentUser.path
@ -66,43 +67,46 @@ func shell(
{
finalEnvironment = ["HOME": FileManager.default.homeDirectoryForCurrentUser.path]
}
// MARK: - Set up proxy if it's enabled
if let proxySettings = AppConstants.proxySettings
{
AppConstants.logger.info("Proxy is enabled")
finalEnvironment["ALL_PROXY"] = "\(proxySettings.host):\(proxySettings.port)"
}
// MARK: - Block automatic cleanup is configured
if !UserDefaults.standard.bool(forKey: "isAutomaticCleanupEnabled")
{
finalEnvironment["HOMEBREW_NO_INSTALL_CLEANUP"] = "TRUE"
}
AppConstants.logger.debug("Final environment: \(finalEnvironment)")
// MARK: - Set working directory if provided
if let workingDirectory
{
AppConstants.logger.info("Working directory configured: \(workingDirectory)")
task.currentDirectoryURL = workingDirectory
}
let sudoHelperURL: URL = Bundle.main.resourceURL!.appendingPathComponent("Sudo Helper", conformingTo: .executable)
finalEnvironment["SUDO_ASKPASS"] = sudoHelperURL.path
task.environment = finalEnvironment
task.launchPath = launchPath.absoluteString
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
let errorPipe = Pipe()
task.standardError = errorPipe
do
{
try task.run()
@ -112,20 +116,26 @@ func shell(
AppConstants.logger.error("\(String(describing: error))")
}
return AsyncStream { continuation in
return AsyncStream
{ continuation in
pipe.fileHandleForReading.readabilityHandler = { handler in
guard let standardOutput = String(data: handler.availableData, encoding: .utf8) else
guard let standardOutput = String(data: handler.availableData, encoding: .utf8)
else
{
return
}
guard !standardOutput.isEmpty else { return }
guard !standardOutput.isEmpty else
{
return
}
continuation.yield(.standardOutput(standardOutput))
}
errorPipe.fileHandleForReading.readabilityHandler = { handler in
guard let errorOutput = String(data: handler.availableData, encoding: .utf8) else
guard let errorOutput = String(data: handler.availableData, encoding: .utf8)
else
{
return
}

View File

@ -5,13 +5,13 @@
// Created by David Bureš on 31.03.2024.
//
import Foundation
import AppKit
import Foundation
func submitSystemVersion() async throws -> Void
func submitSystemVersion() async throws
{
let corkVersion: String = await String(NSApplication.appVersion!)
let sessionConfiguration = URLSessionConfiguration.default
if AppConstants.proxySettings != nil
{
@ -21,47 +21,49 @@ func submitSystemVersion() async throws -> Void
kCFNetworkProxiesHTTPProxy: AppConstants.proxySettings!.host
] as [AnyHashable: Any]
}
let session: URLSession = URLSession(configuration: sessionConfiguration)
var isSelfCompiled: Bool = false
let session = URLSession(configuration: sessionConfiguration)
var isSelfCompiled = false
if ProcessInfo.processInfo.environment["SELF_COMPILED"] == "true"
{
isSelfCompiled = true
}
var urlComponents = URLComponents(url: AppConstants.osSubmissionEndpointURL, resolvingAgainstBaseURL: false)
urlComponents?.queryItems = [
URLQueryItem(name: "systemVersion", value: String(ProcessInfo.processInfo.operatingSystemVersion.majorVersion)),
URLQueryItem(name: "corkVersion", value: corkVersion),
URLQueryItem(name: "isSelfCompiled", value: String(isSelfCompiled))
URLQueryItem(name: "isSelfCompiled", value: String(isSelfCompiled)),
]
guard let modifiedURL = urlComponents?.url else {
guard let modifiedURL = urlComponents?.url
else
{
return
}
var request: URLRequest = URLRequest(url: modifiedURL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
var request = URLRequest(url: modifiedURL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
request.httpMethod = "GET"
let authorizationComplex = "\(AppConstants.licensingAuthorization.username):\(AppConstants.licensingAuthorization.passphrase)"
guard let authorizationComplexAsData: Data = authorizationComplex.data(using: .utf8, allowLossyConversion: false)
else
{
return
}
request.addValue("Basic \(authorizationComplexAsData.base64EncodedString())", forHTTPHeaderField: "Authorization")
let (_, response) = try await session.data(for: request)
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200
{
#if DEBUG
AppConstants.logger.debug("Sucessfully submitted system version")
AppConstants.logger.debug("Sucessfully submitted system version")
#endif
UserDefaults.standard.setValue(corkVersion, forKey: "lastSubmittedCorkVersion")
}
}

View File

@ -8,28 +8,29 @@
import Foundation
@MainActor
func applyTagsToPackageTrackingArray(appState: AppState, brewData: BrewDataStorage) async throws -> Void
func applyTagsToPackageTrackingArray(appState: AppState, brewData: BrewDataStorage) async throws
{
for taggedName in appState.taggedPackageNames
{
AppConstants.logger.log("Will attempt to place package name \(taggedName, privacy: .public)")
brewData.installedFormulae = Set(brewData.installedFormulae.map({ formula in
brewData.installedFormulae = Set(brewData.installedFormulae.map
{ formula in
var copyFormula = formula
if copyFormula.name == taggedName
{
copyFormula.changeTaggedStatus()
}
return copyFormula
}))
})
brewData.installedCasks = Set(brewData.installedCasks.map({ cask in
brewData.installedCasks = Set(brewData.installedCasks.map
{ cask in
var copyCask = cask
if copyCask.name == taggedName
{
copyCask.changeTaggedStatus()
}
return copyCask
}))
})
}
}

View File

@ -8,29 +8,31 @@
import Foundation
@MainActor
func changePackageTagStatus(package: BrewPackage, brewData: BrewDataStorage, appState: AppState) async -> Void
func changePackageTagStatus(package: BrewPackage, brewData: BrewDataStorage, appState: AppState) async
{
if package.type == .formula
{
brewData.installedFormulae = Set(brewData.installedFormulae.map({ formula in
brewData.installedFormulae = Set(brewData.installedFormulae.map
{ formula in
var copyFormula = formula
if copyFormula.name == package.name
{
copyFormula.changeTaggedStatus()
}
return copyFormula
}))
})
}
else
{
brewData.installedFormulae = Set(brewData.installedCasks.map({ cask in
brewData.installedFormulae = Set(brewData.installedCasks.map
{ cask in
var copyCask = cask
if copyCask.name == package.name
{
copyCask.changeTaggedStatus()
}
return copyCask
}))
})
}
if appState.taggedPackageNames.contains(package.name)
@ -43,6 +45,6 @@ func changePackageTagStatus(package: BrewPackage, brewData: BrewDataStorage, app
}
AppConstants.logger.debug("Tagged package with ID \(package.id, privacy: .public): \(package.name, privacy: .public)")
AppConstants.logger.debug("Tagged packages: \(appState.taggedPackageNames, privacy: .public)")
}

View File

@ -10,24 +10,23 @@ import Foundation
func loadTaggedIDsFromDisk() throws -> Set<String>
{
var nameSet: Set<String> = .init()
do
{
let rawPackageNamesFromFile: String = try String(contentsOf: AppConstants.metadataFilePath, encoding: .utf8)
let packageNamesAsArray: [String] = rawPackageNamesFromFile.components(separatedBy: ":")
for packageNameAsString in packageNamesAsArray
{
nameSet.insert(packageNameAsString)
}
}
catch let dataReadingError as NSError
{
AppConstants.logger.error("Failed while reading data from disk: \(dataReadingError, privacy: .public)")
}
AppConstants.logger.debug("Loaded name set: \(nameSet, privacy: .public)")
return nameSet
}

View File

@ -10,7 +10,7 @@ import Foundation
func addTap(name: String, forcedRepoAddress: String? = nil) async -> String
{
var tapResult: String
if let forcedRepoAddress
{
tapResult = await shell(AppConstants.brewExecutablePath, ["tap", name, forcedRepoAddress]).standardError

View File

@ -9,27 +9,28 @@ import Foundation
func parseTapInfo(from rawJSON: String) async throws -> TapInfo?
{
let decoder: JSONDecoder =
{
let decoder: JSONDecoder = JSONDecoder()
let decoder: JSONDecoder = {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
return decoder
}()
do
{
guard let jsonAsData: Data = rawJSON.data(using: .utf8, allowLossyConversion: false) else
guard let jsonAsData: Data = rawJSON.data(using: .utf8, allowLossyConversion: false)
else
{
AppConstants.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 {
}
catch let decodingError
{
AppConstants.logger.error("Failed while decoding tap info: \(decodingError.localizedDescription, privacy: .public)\n-\(decodingError, privacy: .public)")
throw JSONParsingError.couldNotDecode(failureReason: decodingError.localizedDescription)
}
}

View File

@ -11,21 +11,21 @@ import SwiftUI
enum UntapError: LocalizedError
{
case couldNotUntap(tapName: String, failureReason: String)
var errorDescription: String?
{
switch self {
case .couldNotUntap(let tapName, let failureReason):
return String(localized: "error.tap.untap.could-not-untap.tap-\(tapName).failure-reason-\(failureReason)")
switch self
{
case .couldNotUntap(let tapName, let failureReason):
return String(localized: "error.tap.untap.could-not-untap.tap-\(tapName).failure-reason-\(failureReason)")
}
}
}
@MainActor
func removeTap(name: String, availableTaps: AvailableTaps, appState: AppState, shouldApplyUninstallSpinnerToRelevantItemInSidebar: Bool = false) async throws -> Void
func removeTap(name: String, availableTaps: AvailableTaps, appState: AppState, shouldApplyUninstallSpinnerToRelevantItemInSidebar: Bool = false) async throws
{
var indexToReplaceGlobal: Int? = nil
var indexToReplaceGlobal: Int?
/// Store the old navigation selection to see if it got updated in the middle of switching
let oldNavigationSelectionID: UUID? = appState.navigationSelection
@ -55,8 +55,10 @@ func removeTap(name: String, availableTaps: AvailableTaps, appState: AppState, s
if untapResult.contains("Untapped")
{
AppConstants.logger.info("Untapping was successful")
DispatchQueue.main.async {
withAnimation {
DispatchQueue.main.async
{
withAnimation
{
availableTaps.addedTaps.removeAll(where: { $0.name == name })
}
}
@ -75,7 +77,7 @@ func removeTap(name: String, availableTaps: AvailableTaps, appState: AppState, s
AppConstants.logger.warning("Untapping failed")
if untapResult.contains("because it contains the following installed formulae or casks")
{
{
appState.showAlert(errorToShow: .couldNotRemoveTapDueToPackagesFromItStillBeingInstalled(offendingTapProhibitingRemovalOfTap: name))
}

View File

@ -17,8 +17,8 @@ func refreshPackages(_ updateProgressTracker: UpdateProgressTracker, outdatedPac
{
switch output
{
case let .standardOutput(outputLine):
AppConstants.logger.log("Update function output: \(outputLine, privacy: .public)")
case .standardOutput(let outputLine):
AppConstants.logger.log("Update function output: \(outputLine, privacy: .public)")
if showRealTimeTerminalOutputs
{
@ -36,7 +36,7 @@ func refreshPackages(_ updateProgressTracker: UpdateProgressTracker, outdatedPac
}
}
case let .standardError(errorLine):
case .standardError(let errorLine):
if showRealTimeTerminalOutputs
{

View File

@ -9,7 +9,7 @@ import Foundation
import SwiftUI
@MainActor
func updatePackages(updateProgressTracker: UpdateProgressTracker, appState _: AppState, outdatedPackageTracker _: OutdatedPackageTracker, detailStage: UpdatingProcessDetails) async
func updatePackages(updateProgressTracker: UpdateProgressTracker, detailStage: UpdatingProcessDetails) async
{
let showRealTimeTerminalOutputs = UserDefaults.standard.bool(forKey: "showRealTimeTerminalOutputOfOperations")
@ -17,7 +17,7 @@ func updatePackages(updateProgressTracker: UpdateProgressTracker, appState _: Ap
{
switch output
{
case let .standardOutput(outputLine):
case .standardOutput(let outputLine):
AppConstants.logger.log("Upgrade function output: \(outputLine, privacy: .public)")
if showRealTimeTerminalOutputs
@ -54,7 +54,7 @@ func updatePackages(updateProgressTracker: UpdateProgressTracker, appState _: Ap
updateProgressTracker.updateProgress = updateProgressTracker.updateProgress + 0.1
case let .standardError(errorLine):
case .standardError(let errorLine):
if showRealTimeTerminalOutputs
{

View File

@ -30,6 +30,7 @@ class BrewDataStorage: ObservableObject
{
removePackageFromTracker(withName: name, tracker: .formula)
}
func removeCaskFromTracker(withName name: String)
{
removePackageFromTracker(withName: name, tracker: .cask)
@ -37,17 +38,18 @@ class BrewDataStorage: ObservableObject
private func removePackageFromTracker(withName name: String, tracker: PackageType)
{
switch tracker {
case .formula:
if let index = installedFormulae.firstIndex(where: { $0.name == name })
{
installedFormulae.remove(at: index)
}
case .cask:
if let index = installedCasks.firstIndex(where: { $0.name == name })
{
installedCasks.remove(at: index)
}
switch tracker
{
case .formula:
if let index = installedFormulae.firstIndex(where: { $0.name == name })
{
installedFormulae.remove(at: index)
}
case .cask:
if let index = installedCasks.firstIndex(where: { $0.name == name })
{
installedCasks.remove(at: index)
}
}
}
}

View File

@ -11,11 +11,11 @@ struct BrewTap: Identifiable, Hashable
{
let id = UUID()
let name: String
var isBeingModified: Bool = false
mutating func changeBeingModifiedStatus() -> Void
mutating func changeBeingModifiedStatus()
{
self.isBeingModified.toggle()
isBeingModified.toggle()
}
}

View File

@ -5,16 +5,16 @@
// Created by David Bureš on 04.11.2023.
//
import Charts
import Foundation
import SwiftUI
import Charts
struct CachedDownload: Identifiable, Hashable
{
var id: UUID = UUID()
var id: UUID = .init()
let packageName: String
let sizeInBytes: Int
var packageType: CachedDownloadType?
}

View File

@ -1,5 +1,5 @@
//
// DataFile.swift
// Data File.swift
// Cork
//
// Created by David Bureš on 10.11.2023.
@ -11,8 +11,15 @@ import UniformTypeIdentifiers
struct StringFile: FileDocument
{
static var readableContentTypes: [UTType] { [.homebrewBackup, .plainText] }
static var writableContentTypes: [UTType] { [.homebrewBackup] }
static var readableContentTypes: [UTType]
{
[.homebrewBackup, .plainText]
}
static var writableContentTypes: [UTType]
{
[.homebrewBackup]
}
var text: String

View File

@ -9,8 +9,8 @@ import Foundation
struct TopPackage: Hashable, Identifiable
{
var id: UUID = UUID()
var id: UUID = .init()
let packageName: String
let packageDownloads: Int
}

View File

@ -21,11 +21,11 @@ class TopPackagesTracker: ObservableObject, Sendable
switch sortTopPackagesBy
{
case .mostDownloads:
return self.topFormulae.sorted(by: { $0.packageDownloads > $1.packageDownloads })
return topFormulae.sorted(by: { $0.packageDownloads > $1.packageDownloads })
case .fewestDownloads:
return self.topFormulae.sorted(by: { $0.packageDownloads < $1.packageDownloads })
return topFormulae.sorted(by: { $0.packageDownloads < $1.packageDownloads })
case .random:
return self.topFormulae.shuffled()
return topFormulae.shuffled()
}
}
@ -34,11 +34,11 @@ class TopPackagesTracker: ObservableObject, Sendable
switch sortTopPackagesBy
{
case .mostDownloads:
return self.topCasks.sorted(by: { $0.packageDownloads > $1.packageDownloads })
return topCasks.sorted(by: { $0.packageDownloads > $1.packageDownloads })
case .fewestDownloads:
return self.topCasks.sorted(by: { $0.packageDownloads < $1.packageDownloads })
return topCasks.sorted(by: { $0.packageDownloads < $1.packageDownloads })
case .random:
return self.topCasks.shuffled()
return topCasks.shuffled()
}
}
}

View File

@ -10,14 +10,14 @@ import Foundation
struct MinimalHomebrewPackage: Identifiable, Hashable, AppEntity
{
var id: UUID = UUID()
var id: UUID = .init()
var name: String
var type: PackageType
var installDate: Date?
var installedIntentionally: Bool
static var typeDisplayRepresentation: TypeDisplayRepresentation = .init(name: "intents.type.minimal-homebrew-package")
@ -25,7 +25,7 @@ struct MinimalHomebrewPackage: Identifiable, Hashable, AppEntity
var displayRepresentation: DisplayRepresentation
{
DisplayRepresentation(
title: "\(self.name)",
title: "\(name)",
subtitle: "intents.type.minimal-homebrew-package.representation.subtitle"
)
}

View File

@ -12,41 +12,42 @@ 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
}

View File

@ -16,4 +16,3 @@ struct UsedPackage: Identifiable
let whyIsItUsed: LocalizedStringKey
let packageURL: URL
}

View File

@ -12,9 +12,9 @@ import SwiftUI
class OutdatedPackageTracker: ObservableObject, Sendable
{
@AppStorage("displayOnlyIntentionallyInstalledPackagesByDefault") var displayOnlyIntentionallyInstalledPackagesByDefault: Bool = true
@Published var outdatedPackages: Set<OutdatedPackage> = .init()
var displayableOutdatedPackages: Set<OutdatedPackage>
{
if displayOnlyIntentionallyInstalledPackagesByDefault

View File

@ -9,12 +9,12 @@ import Foundation
struct OutdatedPackage: Identifiable, Equatable, Hashable
{
let id: UUID = UUID()
let id: UUID = .init()
let package: BrewPackage
let installedVersions: [String]
let newerVersion: String
var isMarkedForUpdating: Bool = true
}

View File

@ -67,7 +67,7 @@ class InstallationProgressTracker: ObservableObject
{
switch output
{
case let .standardOutput(outputLine):
case .standardOutput(let outputLine):
AppConstants.logger.debug("Package instrall line out: \(outputLine, privacy: .public)")
@ -147,7 +147,7 @@ class InstallationProgressTracker: ObservableObject
AppConstants.logger.debug("Current installation stage: \(self.packageBeingInstalled.installationStage.description, privacy: .public)")
case let .standardError(errorLine):
case .standardError(let errorLine):
AppConstants.logger.error("Errored out: \(errorLine, privacy: .public)")
if showRealTimeTerminalOutputs
@ -183,7 +183,7 @@ class InstallationProgressTracker: ObservableObject
{
switch output
{
case let .standardOutput(outputLine):
case .standardOutput(let outputLine):
AppConstants.logger.info("Output line: \(outputLine, privacy: .public)")
if showRealTimeTerminalOutputs
@ -226,9 +226,9 @@ class InstallationProgressTracker: ObservableObject
else if outputLine.contains("Purging files")
{
AppConstants.logger.info("Purging old version of cask \(package.name)")
packageBeingInstalled.installationStage = .installingCask
packageBeingInstalled.packageInstallationProgress = packageBeingInstalled.packageInstallationProgress + 1
}
else if outputLine.contains("was successfully installed")
@ -240,7 +240,7 @@ class InstallationProgressTracker: ObservableObject
packageBeingInstalled.packageInstallationProgress = 10
}
case let .standardError(errorLine):
case .standardError(let errorLine):
AppConstants.logger.error("Line had error: \(errorLine, privacy: .public)")
if showRealTimeTerminalOutputs
@ -257,7 +257,7 @@ class InstallationProgressTracker: ObservableObject
else if errorLine.contains("there is already an App at")
{
AppConstants.logger.warning("The app already exists")
packageBeingInstalled.installationStage = .binaryAlreadyExists
}
else if errorLine.contains(/depends on hardware architecture being.+but you are running/)

View File

@ -16,10 +16,10 @@ struct RealTimeTerminalLine: Identifiable, Hashable, Equatable
struct PackageInProgressOfBeingInstalled: Identifiable
{
let id = UUID()
let package: BrewPackage
var installationStage: PackageInstallationStage
var packageInstallationProgress: Double
var realTimeTerminalOutput: [RealTimeTerminalLine] = .init()
}

View File

@ -10,9 +10,9 @@ import SwiftUI
@MainActor
class UninstallationConfirmationTracker: ObservableObject
{
{
@Published var isShowingUninstallOrPurgeConfirmation: Bool = false
@Published private(set) var packageThatNeedsConfirmation: BrewPackage = .init(name: "", type: .formula, installedOn: Date(), versions: [], sizeInBytes: 0)
@Published private(set) var shouldPurge: Bool = false
@Published private(set) var isCalledFromSidebar: Bool = false
@ -22,17 +22,17 @@ class UninstallationConfirmationTracker: ObservableObject
self.packageThatNeedsConfirmation = packageThatNeedsConfirmation
self.shouldPurge = shouldPurge
self.isCalledFromSidebar = isCalledFromSidebar
self.isShowingUninstallOrPurgeConfirmation = true
isShowingUninstallOrPurgeConfirmation = true
}
func dismissConfirmationDialog()
{
if self.isShowingUninstallOrPurgeConfirmation
if isShowingUninstallOrPurgeConfirmation
{
self.isShowingUninstallOrPurgeConfirmation = false
isShowingUninstallOrPurgeConfirmation = false
}
self.packageThatNeedsConfirmation = .init(name: "", type: .formula, installedOn: Date(), versions: [], sizeInBytes: 0)
packageThatNeedsConfirmation = .init(name: "", type: .formula, installedOn: Date(), versions: [], sizeInBytes: 0)
}
}

View File

@ -10,12 +10,13 @@ import Foundation
enum PinningUnpinningError: LocalizedError
{
case failedWhileChangingPinnedStatus
var errorDescription: String?
{
switch self {
case .failedWhileChangingPinnedStatus:
return String(localized: "error.package-details.couldnt-pin-unpin")
switch self
{
case .failedWhileChangingPinnedStatus:
return String(localized: "error.package-details.couldnt-pin-unpin")
}
}
}
@ -24,6 +25,7 @@ enum PinningUnpinningError: LocalizedError
class BrewPackageDetails: ObservableObject
{
// MARK: - Immutable properties
/// Name of the package
let name: String
@ -35,14 +37,16 @@ class BrewPackageDetails: ObservableObject
let dependencies: [BrewPackageDependency]?
let outdated: Bool
let caveats: String?
let isCompatible: Bool
// MARK: - Mutable properties
@Published var dependents: [String]?
@Published var pinned: Bool
// MARK: - Init
init(name: String, description: String?, homepage: URL, tap: BrewTap, installedAsDependency: Bool, dependents: [String]? = nil, dependencies: [BrewPackageDependency]? = nil, outdated: Bool, caveats: String? = nil, pinned: Bool, isCompatible: Bool)
{
self.name = name
@ -59,23 +63,24 @@ class BrewPackageDetails: ObservableObject
}
// MARK: - Functions
func loadDependents() async
{
AppConstants.logger.debug("Will load dependents for \(self.name)")
let packageDependentsRaw: String = await shell(AppConstants.brewExecutablePath, ["uses", "--installed", self.name]).standardOutput
let packageDependentsRaw: String = await shell(AppConstants.brewExecutablePath, ["uses", "--installed", name]).standardOutput
let finalDependents: [String] = packageDependentsRaw.components(separatedBy: "\n").dropLast()
AppConstants.logger.debug("Dependents loaded: \(finalDependents)")
self.dependents = finalDependents
dependents = finalDependents
}
func changePinnedStatus() async throws
{
if self.pinned
if pinned
{
let pinResult = await shell(AppConstants.brewExecutablePath, ["unpin", self.name])
let pinResult = await shell(AppConstants.brewExecutablePath, ["unpin", name])
if !pinResult.standardError.isEmpty
{
@ -85,14 +90,14 @@ class BrewPackageDetails: ObservableObject
}
else
{
let unpinResult = await shell(AppConstants.brewExecutablePath, ["pin", self.name])
let unpinResult = await shell(AppConstants.brewExecutablePath, ["pin", name])
if !unpinResult.standardError.isEmpty
{
AppConstants.logger.error("Error unpinning: \(unpinResult.standardError, privacy: .public)")
throw PinningUnpinningError.failedWhileChangingPinnedStatus
}
}
self.pinned.toggle()
pinned.toggle()
}
}

View File

@ -5,18 +5,17 @@
// Created by David Bureš on 03.07.2022.
//
import Foundation
import AppKit
import DavidFoundation
import Foundation
/// A representation of a Homebrew package
struct BrewPackage: Identifiable, Equatable, Hashable
{
var id = UUID()
let name: String
lazy var sanitizedName: String? =
{
lazy var sanitizedName: String? = {
var packageNameWithoutTap: String
{ /// First, remove the tap name from the package name if it has it
if self.name.contains("/")
@ -35,7 +34,7 @@ struct BrewPackage: Identifiable, Equatable, Hashable
return self.name
}
}
if packageNameWithoutTap.contains("@")
{ /// Only do the matching if the name contains @
if let sanitizedName = try? regexMatch(from: packageNameWithoutTap, regex: ".+?(?=@)")
@ -55,54 +54,53 @@ struct BrewPackage: Identifiable, Equatable, Hashable
let type: PackageType
var isTagged: Bool = false
let installedOn: Date?
let versions: [String]
var installedIntentionally: Bool = true
let sizeInBytes: Int64?
var isBeingModified: Bool = false
func getFormattedVersions() -> String
{
return self.versions.formatted(.list(type: .and))
return versions.formatted(.list(type: .and))
}
mutating func changeTaggedStatus() -> Void
mutating func changeTaggedStatus()
{
self.isTagged.toggle()
isTagged.toggle()
}
mutating func changeBeingModifiedStatus() -> Void
mutating func changeBeingModifiedStatus()
{
self.isBeingModified.toggle()
isBeingModified.toggle()
}
mutating func purgeSanitizedName() -> Void
mutating func purgeSanitizedName()
{
self.sanitizedName = nil
sanitizedName = nil
}
/// Open the location of this package in Finder
func revealInFinder() throws
{
enum FinderRevealError: LocalizedError
{
case couldNotFindPackageInParent
var errorDescription: String?
{
return String(localized: "error.finder-reveal.could-not-find-package-in-parent")
}
}
var packageURL: URL?
var packageLocationParent: URL
{
if self.type == .formula
if type == .formula
{
return AppConstants.brewCellarPath
}
@ -111,19 +109,23 @@ struct BrewPackage: Identifiable, Equatable, Hashable
return AppConstants.brewCaskPath
}
}
let contentsOfParentFolder = try! FileManager.default.contentsOfDirectory(at: packageLocationParent, includingPropertiesForKeys: [.isDirectoryKey])
packageURL = contentsOfParentFolder.filter({ $0.lastPathComponent.contains(name) }).first
guard let packageURL else
packageURL = contentsOfParentFolder.filter
{
$0.lastPathComponent.contains(name)
}.first
guard let packageURL
else
{
throw FinderRevealError.couldNotFindPackageInParent
}
packageURL.revealInFinder(.openParentDirectoryAndHighlightTarget)
//NSWorkspace.shared.selectFile(packageURL.path, inFileViewerRootedAtPath: packageURL.deletingLastPathComponent().path)
// NSWorkspace.shared.selectFile(packageURL.path, inFileViewerRootedAtPath: packageURL.deletingLastPathComponent().path)
}
}
@ -131,6 +133,6 @@ extension FormatStyle where Self == Date.FormatStyle
{
static var packageInstallationStyle: Self
{
Self.dateTime.day().month(.wide).year().weekday(.wide).hour().minute()
dateTime.day().month(.wide).year().weekday(.wide).hour().minute()
}
}

View File

@ -9,7 +9,7 @@ import Foundation
struct BrewPackageDependency: Identifiable, Hashable
{
let id: UUID = UUID()
let id: UUID = .init()
let name: String
let version: String
let directlyDeclared: Bool

View File

@ -7,7 +7,6 @@
import Foundation
class SearchResultTracker: ObservableObject
{
/// These two have to be arrays because the order matters

View File

@ -23,8 +23,10 @@ extension PackageSearchToken: Identifiable
extension PackageSearchToken
{
var name: LocalizedStringKey {
switch self {
var name: LocalizedStringKey
{
switch self
{
case .formula:
return "search.token.filter-formulae"
case .cask:
@ -38,8 +40,10 @@ extension PackageSearchToken
}
}
var icon: String {
switch self {
var icon: String
{
switch self
{
case .formula:
return "terminal"
case .cask:

View File

@ -11,38 +11,39 @@ struct ServiceDetails: Hashable, Codable
{
let name: String
let serviceName: String
let running: Bool
let loaded: Bool
let schedulable: Bool
let pid: Int?
let exitCode: Int?
let user: String?
let status: ServiceStatus
let file: URL?
let command: URL?
let workingDir: URL?
let rootDir: URL?
let logPath: URL?
let errorLogPath: URL?
let interval: String?
let cron: String?
// MARK: - Legacy stuff
/*
let loaded: Bool
let schedulable: Bool
let pid: Int?
let rootDir: URL?
let logPath: URL?
let errorLogPath: URL?
*/
let loaded: Bool
let schedulable: Bool
let pid: Int?
let rootDir: URL?
let logPath: URL?
let errorLogPath: URL?
*/
}

View File

@ -5,22 +5,22 @@
// Created by David Bureš on 20.03.2024.
//
import Foundation
import AppKit
import Foundation
struct HomebrewService: Identifiable, Hashable, Codable
{
var id: UUID = UUID()
var id: UUID = .init()
let name: String
var status: ServiceStatus
let user: String?
let location: URL
let exitCode: Int?
func revealInFinder()
{
location.revealInFinder(.openParentDirectoryAndHighlightTarget)

View File

@ -38,14 +38,16 @@ class ServicesTracker: ObservableObject
func changeServiceStatus(_ serviceToChange: HomebrewService, newStatus: ServiceStatus)
{
self.replaceServiceInTracker(
replaceServiceInTracker(
serviceToChange,
with: .init(
name: serviceToChange.name,
status: newStatus,
user: serviceToChange.user,
location: serviceToChange.location,
exitCode: serviceToChange.exitCode),
performInPlaceReplacement: true)
exitCode: serviceToChange.exitCode
),
performInPlaceReplacement: true
)
}
}

View File

@ -15,20 +15,21 @@ enum UpdateProcessStages: LocalizedStringKey, CustomStringConvertible
case cleanup = "update-packages.detail-stage.cleanup"
case backingUp = "update-packages.detail-stage.backing-up"
case linking = "update-packages.detail-stage.linking"
var description: String
{
switch self {
case .downloading:
return "Downloading"
case .pouring:
return "Pouring"
case .cleanup:
return "Cleanup"
case .backingUp:
return "Backing Up"
case .linking:
return "Linkling"
switch self
{
case .downloading:
return "Downloading"
case .pouring:
return "Pouring"
case .cleanup:
return "Cleanup"
case .backingUp:
return "Backing Up"
case .linking:
return "Linkling"
}
}
}

View File

@ -7,8 +7,10 @@
import SwiftUI
struct LargeButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
struct LargeButtonStyle: ButtonStyle
{
func makeBody(configuration: Configuration) -> some View
{
configuration.label
.padding(.horizontal, 27)
.padding(.vertical, 7)
@ -18,4 +20,3 @@ struct LargeButtonStyle: ButtonStyle {
.shadow(radius: 1)
}
}

View File

@ -17,7 +17,9 @@ struct NoPadding: DisclosureGroupStyle
{
configuration.isExpanded.toggle()
}
} label: {
}
label:
{
HStack(alignment: .center, spacing: 4)
{
Image(systemName: configuration.isExpanded ? "chevron.down" : "chevron.right")

View File

@ -10,9 +10,8 @@ import SwiftUI
struct MiniGaugeStyle: GaugeStyle
{
let tint: Color
func makeBody(configuration: Configuration) -> some View
{
ZStack

View File

@ -79,9 +79,9 @@ struct AboutView: View
reasonForAcknowledgement: "about.contributors.4.purpose",
profileService: .mastodon,
profileURL: URL(string: "https://hachyderm.io/@oscb")!
),
)
]
private let translators: [AcknowledgedContributor] = [
AcknowledgedContributor(
name: "about.translator.1.name",
@ -93,27 +93,32 @@ struct AboutView: View
name: "about.translator.2.name",
reasonForAcknowledgement: "about.translator.2.purpose",
profileService: .github,
profileURL: URL(string: "https://github.com/sh95014")!),
profileURL: URL(string: "https://github.com/sh95014")!
),
AcknowledgedContributor(
name: "about.translator.3.name",
reasonForAcknowledgement: "about.translator.3.purpose",
profileService: .github,
profileURL: URL(string: "https://github.com/hecaex")!),
AcknowledgedContributor(
name: "about.translator.4.name",
reasonForAcknowledgement: "about.translator.4.purpose",
profileService: .github,
profileURL: URL(string: "https://github.com/louchebem06")!),
profileURL: URL(string: "https://github.com/hecaex")!
),
AcknowledgedContributor(
name: "about.translator.4.name",
reasonForAcknowledgement: "about.translator.4.purpose",
profileService: .github,
profileURL: URL(string: "https://github.com/louchebem06")!
),
AcknowledgedContributor(
name: "about.translator.5.name",
reasonForAcknowledgement: "about.translator.5.purpose",
profileService: .github,
profileURL: URL(string: "https://github.com/utkinn")!),
profileURL: URL(string: "https://github.com/utkinn")!
),
AcknowledgedContributor(
name: "about.translator.6.name",
reasonForAcknowledgement: "about.translator.6.purpose",
profileService: .github,
profileURL: URL(string: "https://github.com/smitt14ua")!),
profileURL: URL(string: "https://github.com/smitt14ua")!
)
]
@State private var isPackageGroupExpanded: Bool = false
@ -231,7 +236,7 @@ struct AboutView: View
Text("about.contributors")
}
.animation(.none, value: isContributorGroupExpanded)
DisclosureGroup
{
List
@ -264,7 +269,7 @@ struct AboutView: View
Text("about.translators")
}
.animation(.none, value: isTranslatorGroupExpanded)
Text("about.privacy-policy")
.font(.subheadline)
.foregroundColor(.secondary)
@ -293,6 +298,6 @@ struct AboutView: View
.transaction { $0.animation = nil }
}
.padding()
//.fixedSize()
// .fixedSize()
}
}

View File

@ -15,7 +15,7 @@ struct SearchResultRow: View, Sendable
@EnvironmentObject var brewData: BrewDataStorage
let searchedForPackage: BrewPackage
@State private var description: String?
@State private var isCompatible: Bool?
@ -51,7 +51,8 @@ struct SearchResultRow: View, Sendable
{
if showCompatibilityWarning
{
HStack(alignment: .center, spacing: 4) {
HStack(alignment: .center, spacing: 4)
{
Image(systemName: "exclamationmark.circle")
Text("add-package.result.not-optimized-for-\(AppConstants.osVersionString.fullName)")
}
@ -61,7 +62,7 @@ struct SearchResultRow: View, Sendable
}
}
}
if showDescriptionsInSearchResults
{
if !descriptionParsingFailed
@ -92,7 +93,6 @@ struct SearchResultRow: View, Sendable
.foregroundColor(.orange)
}
}
}
.task
{
@ -106,28 +106,27 @@ struct SearchResultRow: View, Sendable
{
isLoadingDescription = false
}
AppConstants.logger.info("\(searchedForPackage.name, privacy: .auto) does not have its description loaded")
do
{
let searchedForPackage: BrewPackage = .init(name: searchedForPackage.name, type: searchedForPackage.type, installedOn: Date(), versions: [], sizeInBytes: nil)
do
{
let parsedPackageInfo: BrewPackageDetails = try await searchedForPackage.loadDetails()
description = parsedPackageInfo.description
isCompatible = parsedPackageInfo.isCompatible
}
catch let descriptionParsingError
{ // This happens when a package doesn' have any description at all, hence why we don't display an error
AppConstants.logger.error("Failed while parsing searched-for package info: \(descriptionParsingError.localizedDescription, privacy: .public)")
descriptionParsingFailed = true
}
}
}
else

View File

@ -10,16 +10,16 @@ import SwiftUI
struct InstallationInitialView: View
{
@Environment(\.dismiss) var dismiss
@AppStorage("enableDiscoverability") var enableDiscoverability: Bool = false
@AppStorage("discoverabilityDaySpan") var discoverabilityDaySpan: DiscoverabilityDaySpans = .month
@EnvironmentObject var appState: AppState
@EnvironmentObject var brewData: BrewDataStorage
@EnvironmentObject var topPackagesTracker: TopPackagesTracker
@ObservedObject var searchResultTracker: SearchResultTracker
@State private var isTopFormulaeSectionCollapsed: Bool = false
@ -30,7 +30,7 @@ struct InstallationInitialView: View
@Binding var foundPackageSelection: UUID?
@ObservedObject var installationProgressTracker: InstallationProgressTracker
@Binding var packageInstallationProcessStep: PackageInstallationProcessSteps
@State var isSearchFieldFocused: Bool = true
@ -65,8 +65,9 @@ struct InstallationInitialView: View
}
}
}
InstallProcessCustomSearchField(search: $packageRequested, isFocused: $isSearchFieldFocused, customPromptText: String(localized: "add-package.search.prompt")) {
InstallProcessCustomSearchField(search: $packageRequested, isFocused: $isSearchFieldFocused, customPromptText: String(localized: "add-package.search.prompt"))
{
foundPackageSelection = nil // Clear all selected items when the user looks for a different package
}
@ -83,9 +84,9 @@ struct InstallationInitialView: View
if let foundPackageSelection
{
AppConstants.logger.debug("Would install package \(foundPackageSelection)")
let topCasksSet = Set(topPackagesTracker.topCasks)
var selectedTopPackageType: PackageType
{
// If this UUID is in the top casks tracker, it means it's a cask. Otherwise, it's a formula. So we test if the result of looking for the selected package in the cask tracker returns nothing; if it does return nothing, it's a formula (since the package is not in the cask tracker)
@ -98,31 +99,30 @@ struct InstallationInitialView: View
return .cask
}
}
do
{
let packageToInstall: BrewPackage = try getTopPackageFromUUID(requestedPackageUUID: foundPackageSelection, packageType: selectedTopPackageType, topPackageTracker: topPackagesTracker)
installationProgressTracker.packageBeingInstalled = PackageInProgressOfBeingInstalled(package: packageToInstall, installationStage: .ready, packageInstallationProgress: 0)
AppConstants.logger.debug("Packages to install: \(installationProgressTracker.packageBeingInstalled.package.name, privacy: .public)")
packageInstallationProcessStep = .installing
}
catch let topPackageInstallationError
{
AppConstants.logger.error("Failed while trying to get top package to install: \(topPackageInstallationError, privacy: .public)")
dismiss()
appState.showAlert(errorToShow: .topPackageArrayFilterCouldNotRetrieveAnyPackages)
}
}
else
{
AppConstants.logger.warning("Could not find the UUID in the package list")
//appState.showAlert(errorToShow: .couldNotFindPackageUUIDInList)
// appState.showAlert(errorToShow: .couldNotFindPackageUUIDInList)
}
} label: {
Text("add-package.install.action")

View File

@ -9,17 +9,16 @@ import SwiftUI
struct TopPackageListItem: View
{
let topPackage: TopPackage
var body: some View
{
HStack(alignment: .center)
{
SanitizedPackageName(packageName: topPackage.packageName, shouldShowVersion: true)
Spacer()
Text("add-package.top-packages.list-item-\(topPackage.packageDownloads)")
.foregroundStyle(.secondary)
.font(.caption)

View File

@ -122,7 +122,7 @@ struct InstallingPackageView: View
if [.installingCask, .installingPackage, .ready].contains(installationStage)
{
AppConstants.logger.warning("The installation process quit before it was supposed to")
installationProgressTracker.packageBeingInstalled.installationStage = .terminatedUnexpectedly
}
}

View File

@ -11,11 +11,11 @@ import SwiftUI
struct BinaryAlreadyExistsView: View, Sendable
{
@Environment(\.dismiss) var dismiss
@EnvironmentObject var brewData: BrewDataStorage
@ObservedObject var installationProgressTracker: InstallationProgressTracker
var body: some View
{
ComplexWithImage(image: Image(localURL: URL(filePath: "/System/Library/CoreServices/KeyboardSetupAssistant.app/Contents/Resources/AppIcon.icns"))!)
@ -25,10 +25,11 @@ struct BinaryAlreadyExistsView: View, Sendable
HeadlineWithSubheadline(
headline: "add-package.install.binary-already-exists-\(installationProgressTracker.packageBeingInstalled.package.name)",
subheadline: "add-package.install.binary-already-exists.subheadline",
alignment: .leading)
alignment: .leading
)
Spacer()
HStack
{
Button
@ -37,13 +38,13 @@ struct BinaryAlreadyExistsView: View, Sendable
} label: {
Text("action.reveal-applications-folder-in-finder")
}
Spacer()
Button
{
dismiss()
Task.detached
{
await synchronizeInstalledPackages(brewData: brewData)

View File

@ -10,9 +10,9 @@ import SwiftUI
struct InstallationTerminatedUnexpectedlyView: View
{
let terminalOutputOfTheInstallation: [RealTimeTerminalLine]
@State private var usableLiveTerminalOutput: [RealTimeTerminalLine] = .init()
var body: some View
{
ComplexWithIcon(systemName: "xmark.seal")
@ -24,10 +24,11 @@ struct InstallationTerminatedUnexpectedlyView: View
subheadline: "add-package.install.installation-terminated.subheadline",
alignment: .leading
)
DisclosureGroup
{
List {
List
{
ForEach(usableLiveTerminalOutput)
{ outputLine in
Text(outputLine.line)
@ -41,7 +42,7 @@ struct InstallationTerminatedUnexpectedlyView: View
HStack
{
Spacer()
DismissSheetButton()
}
}

View File

@ -10,11 +10,11 @@ import SwiftUI
struct SudoRequiredView: View, Sendable
{
@Environment(\.dismiss) var dismiss
@EnvironmentObject var brewData: BrewDataStorage
@ObservedObject var installationProgressTracker: InstallationProgressTracker
var body: some View
{
VStack(alignment: .leading)
@ -68,26 +68,26 @@ struct SudoRequiredView: View, Sendable
private struct ManualInstallInstructions: View
{
let installationProgressTracker: InstallationProgressTracker
var manualInstallCommand: String
{
return "brew install \(installationProgressTracker.packageBeingInstalled.package.type == .cask ? "--cask" : "") \(installationProgressTracker.packageBeingInstalled.package.name)"
}
var body: some View
{
VStack
{
Text("add-package.install.requires-sudo-password.description")
GroupBox
{
HStack(alignment: .center, spacing: 5)
{
Text(manualInstallCommand)
Divider()
Button
{
manualInstallCommand.copyToClipboard()

View File

@ -7,15 +7,16 @@
import SwiftUI
struct WrongArchitectureView: View, Sendable {
struct WrongArchitectureView: View, Sendable
{
@Environment(\.dismiss) var dismiss
@EnvironmentObject var brewData: BrewDataStorage
@ObservedObject var installationProgressTracker: InstallationProgressTracker
var body: some View {
var body: some View
{
ComplexWithIcon(systemName: "cpu")
{
VStack(alignment: .leading, spacing: 10)
@ -23,16 +24,17 @@ struct WrongArchitectureView: View, Sendable {
HeadlineWithSubheadline(
headline: "add-package.install.wrong-architecture.title",
subheadline: "add-package.install.wrong-architecture-\(installationProgressTracker.packageBeingInstalled.package.name).user-architecture-is-\(ProcessInfo().CPUArchitecture == .arm ? "Apple Silicon" : "Intel")",
alignment: .leading)
alignment: .leading
)
HStack
{
Spacer()
Button
{
dismiss()
Task.detached
{
await synchronizeInstalledPackages(brewData: brewData)

View File

@ -11,22 +11,23 @@ struct LicensingView: View
{
@AppStorage("demoActivatedAt") var demoActivatedAt: Date?
@AppStorage("hasValidatedEmail") var hasValidatedEmail: Bool = false
@EnvironmentObject var appState: AppState
var body: some View
{
VStack
{
switch appState.licensingState {
case .notBoughtOrHasNotActivatedDemo:
Licensing_NotBoughtOrActivatedView()
case .demo:
Licensing_DemoView()
case .bought:
Licensing_BoughtView()
case .selfCompiled:
Licensing_SelfCompiledView()
switch appState.licensingState
{
case .notBoughtOrHasNotActivatedDemo:
Licensing_NotBoughtOrActivatedView()
case .demo:
Licensing_DemoView()
case .bought:
Licensing_BoughtView()
case .selfCompiled:
Licensing_SelfCompiledView()
}
}
.onAppear
@ -38,7 +39,7 @@ struct LicensingView: View
else
{
AppConstants.logger.debug("Has validated email? \(hasValidatedEmail ? "YES" : "NO")")
if hasValidatedEmail
{
appState.licensingState = .bought

View File

@ -9,29 +9,28 @@ import SwiftUI
struct Licensing_BoughtView: View
{
@AppStorage("demoActivatedAt") var demoActivatedAt: Date?
@AppStorage("hasFinishedLicensingWorkflow") var hasFinishedLicensingWorkflow: Bool = false
@AppStorage("hasValidatedEmail") var hasValidatedEmail: Bool = false
@Environment(\.dismiss) var dismiss
@EnvironmentObject var appState: AppState
var body: some View
{
VStack(alignment: .center, spacing: 15)
{
{
Image(systemName: "checkmark.seal")
.resizable()
.foregroundColor(.green)
.frame(width: 50, height: 50)
Text("licensing.bought.title")
.font(.title)
Text("licensing.bought.body")
HStack(alignment: .center, spacing: 20)
{
Button
@ -53,9 +52,8 @@ struct Licensing_BoughtView: View
.onAppear
{
demoActivatedAt = nil // Reset the demo, since it won't be needed anymore
hasValidatedEmail = true
}
}
}

View File

@ -10,11 +10,11 @@ import SwiftUI
struct Licensing_DemoView: View
{
@AppStorage("demoActivatedAt") var demoActivatedAt: Date?
@Environment(\.dismiss) var dismiss
@EnvironmentObject var appState: AppState
var body: some View
{
VStack(alignment: .center, spacing: 15)
@ -23,9 +23,9 @@ struct Licensing_DemoView: View
{
Text("licensing.demo-activated.title")
.font(.title)
Text("licensing.demo.time-until-\((demoActivatedAt + AppConstants.demoLengthInSeconds).formatted(date: .complete, time: .complete))")
HStack
{
Button
@ -35,9 +35,9 @@ struct Licensing_DemoView: View
Text("action.close")
}
.keyboardShortcut(.cancelAction)
Spacer()
Button
{
appState.licensingState = .notBoughtOrHasNotActivatedDemo
@ -52,4 +52,3 @@ struct Licensing_DemoView: View
.fixedSize()
}
}

View File

@ -180,7 +180,7 @@ struct Licensing_NotBoughtOrActivatedView: View
catch let licenseCheckingError as CorkLicenseRetrievalError
{
AppConstants.logger.error("\(licenseCheckingError.localizedDescription, privacy: .public)")
switch licenseCheckingError
{
case .authorizationComplexNotEncodedProperly:

View File

@ -1,5 +1,5 @@
//
// Self-Compiled.swift
// Self-Compiled View.swift
// Cork
//
// Created by David Bureš on 13.05.2024.

View File

@ -60,7 +60,7 @@ struct MaintenanceView: View
shouldDeleteDownloads: shouldDeleteDownloads,
shouldPerformHealthCheck: shouldPerformHealthCheck,
numberOfOrphansRemoved: $numberOfOrphansRemoved,
packagesHoldingBackCachePurge: $packagesHoldingBackCachePurge,
packagesHoldingBackCachePurge: $packagesHoldingBackCachePurge,
reclaimedSpaceAfterCachePurge: $reclaimedSpaceAfterCachePurge,
brewHealthCheckFoundNoProblems: $brewHealthCheckFoundNoProblems,
maintenanceSteps: $maintenanceSteps
@ -72,7 +72,7 @@ struct MaintenanceView: View
shouldPurgeCache: shouldPurgeCache,
shouldDeleteDownloads: shouldDeleteDownloads,
shouldPerformHealthCheck: shouldPerformHealthCheck,
packagesHoldingBackCachePurge: packagesHoldingBackCachePurge,
packagesHoldingBackCachePurge: packagesHoldingBackCachePurge,
numberOfOrphansRemoved: numberOfOrphansRemoved,
reclaimedSpaceAfterCachePurge: reclaimedSpaceAfterCachePurge,
brewHealthCheckFoundNoProblems: brewHealthCheckFoundNoProblems,

Some files were not shown because too many files have changed in this diff Show More