+ Selecting of apps ~ Async app info loader

This commit is contained in:
David Bureš 2025-10-07 18:21:43 +02:00
parent bd1cf722ef
commit 6b3fc0c24b
No known key found for this signature in database
4 changed files with 59 additions and 25 deletions

View File

@ -38,7 +38,7 @@ extension BrewPackagesTracker
/// Get a list of casks that can be adopted into the Homebrew updating mechanism
func getAdoptableCasks(
cacheUsePolicy: HomebrewDataCacheUsePolicy
) async throws(AdoptableCasksLoadingError) -> Set<AdoptableCaskComparable>
) async throws(AdoptableCasksLoadingError) -> [AdoptableCaskComparable]
{
do
{
@ -60,7 +60,7 @@ extension BrewPackagesTracker
{
let installedApps: Set<String> = try self.getInstalledApps()
let processedAdoptableCasks: Set<AdoptableCaskComparable> = getAdoptableAppsFromAvailableCasks(
let processedAdoptableCasks: [AdoptableCaskComparable] = await getAdoptableAppsFromAvailableCasks(
installedApps: installedApps,
allAvailableCasks: processedAvailableCasks
)
@ -118,19 +118,26 @@ extension BrewPackagesTracker
}
/// A struct for holding a Cask's name and its executable
struct AdoptableCaskComparable: Identifiable, Hashable
struct AdoptableCaskComparable: Identifiable, Hashable, Sendable
{
let id: UUID = .init()
let caskName: String
let caskExecutable: String
let fullAppUrl: URL
var isMarkedForAdoption: Bool
var app: Application?
init(caskName: String, caskExecutable: String)
{
self.caskName = caskName
self.caskExecutable = caskExecutable
self.fullAppUrl = URL.applicationDirectory.appendingPathComponent(caskExecutable, conformingTo: .application)
self.isMarkedForAdoption = true
}
@ -138,6 +145,11 @@ extension BrewPackagesTracker
{
self.isMarkedForAdoption.toggle()
}
func constructAppBundle() async -> Application?
{
return try? .init(from: self.fullAppUrl)
}
}
/// Take the array that contains all Casks, and transform them into a list of Cask names, associated with their executable names for comparing with the contents of the Applications folder
@ -195,7 +207,7 @@ extension BrewPackagesTracker
private func getAdoptableAppsFromAvailableCasks(
installedApps: Set<String>,
allAvailableCasks: Set<AdoptableCaskComparable>
) -> Set<AdoptableCaskComparable>
) async-> [AdoptableCaskComparable]
{
/// Filter out those available Casks whose executables match those in the Applications folder
let caskNamesOfAppsNotInstalledThroughHomebrew: Set<AdoptableCaskComparable> = allAvailableCasks.filter
@ -210,7 +222,30 @@ extension BrewPackagesTracker
let caskNamesOfAppsNotInstalledThroughHomebrewThatAreAlsoNotInTheCaskTracker: Set<AdoptableCaskComparable> = caskNamesOfAppsNotInstalledThroughHomebrew.filter { !installedCaskNames.contains($0.caskName) }.filter { !$0.caskName.contains("@") }
print("Finally processed casks: \(caskNamesOfAppsNotInstalledThroughHomebrewThatAreAlsoNotInTheCaskTracker)")
var adoptableAppsWithConstructedBundles: [BrewPackagesTracker.AdoptableCaskComparable] = .init()
await withTaskGroup(of: AdoptableCaskComparable.self)
{ taskGroup in
for adoptableApp in caskNamesOfAppsNotInstalledThroughHomebrewThatAreAlsoNotInTheCaskTracker
{
taskGroup.addTask {
var mutableAdoptableApp: BrewPackagesTracker.AdoptableCaskComparable = adoptableApp
mutableAdoptableApp.app = await mutableAdoptableApp.constructAppBundle()
return mutableAdoptableApp
}
for await constructedAdoptableApp in taskGroup
{
adoptableAppsWithConstructedBundles.append(
constructedAdoptableApp
)
}
}
}
return caskNamesOfAppsNotInstalledThroughHomebrewThatAreAlsoNotInTheCaskTracker
return adoptableAppsWithConstructedBundles
}
}

View File

@ -11,13 +11,26 @@ import CorkShared
// TODO: Move this over to the `ApplicationInspector` external library once we figure out how to use Tuist projects as external dependencies
public struct Application
public struct Application: Identifiable, Hashable, Sendable
{
public let id: UUID = .init()
public let name: String
public let iconPath: URL?
public let iconImage: Image?
public static func == (lhs: Application, rhs: Application) -> Bool
{
lhs.id == rhs.id
}
public func hash(into hasher: inout Hasher)
{
hasher.combine(name)
hasher.combine(id)
}
public enum ApplicationInitializationError: LocalizedError
{

View File

@ -120,7 +120,7 @@ class BrewPackagesTracker
}
}
var adoptableCasks: Set<AdoptableCaskComparable> = .init()
var adoptableCasks: [AdoptableCaskComparable] = .init()
}
extension BrewPackagesTracker

View File

@ -92,7 +92,7 @@ struct AdoptablePackagesBox: View
adoptableCask.isMarkedForAdoption
}, set: { toggleState in
if let index = brewPackagesTracker.adoptableCasks.firstIndex(where: { $0.id == adoptableCask.id }) {
brewPackagesTracker.adoptableCasks[index].changeMarkedState() // This WOULD trigger onChange
brewPackagesTracker.adoptableCasks[index].changeMarkedState()
}
}
)) {
@ -107,9 +107,6 @@ struct AdoptablePackagesBox: View
selectAllButton
}
}
.onChange(of: brewPackagesTracker.adoptableCasks) { oldValue, newValue in
print("CHANGE!")
}
}
.listStyle(.bordered(alternatesRowBackgrounds: true))
}
@ -143,24 +140,13 @@ struct AdoptablePackageListItem: View
{
let adoptableCask: BrewPackagesTracker.AdoptableCaskComparable
let adoptableCaskAppLocation: URL
let adoptableCaskApp: Application?
init(adoptableCask: BrewPackagesTracker.AdoptableCaskComparable)
{
self.adoptableCask = adoptableCask
self.adoptableCaskAppLocation = URL.applicationDirectory.appendingPathComponent(adoptableCask.caskExecutable, conformingTo: .application)
self.adoptableCaskApp = try? .init(from: self.adoptableCaskAppLocation)
}
var body: some View
{
HStack(alignment: .center, spacing: 5)
{
if let adoptableCaskApp
if let app = adoptableCask.app
{
if let adoptableCaskIcon = adoptableCaskApp.iconImage
if let adoptableCaskIcon = app.iconImage
{
adoptableCaskIcon
.resizable()
@ -184,7 +170,7 @@ struct AdoptablePackageListItem: View
Button
{
adoptableCaskAppLocation.revealInFinder(.openParentDirectoryAndHighlightTarget)
adoptableCask.fullAppUrl.revealInFinder(.openParentDirectoryAndHighlightTarget)
} label: {
Label("action.reveal-\(adoptableCask.caskExecutable)-in-finder", systemImage: "finder")
}