mirror of https://github.com/buresdv/Cork
~ Tagging now using SwiftData
This commit is contained in:
parent
0a818a717e
commit
fc88b4f896
|
|
@ -68,8 +68,6 @@ final class AppState
|
|||
|
||||
// MARK: - Tagging
|
||||
|
||||
var taggedPackageNames: Set<String> = .init()
|
||||
|
||||
var corruptedPackage: String = ""
|
||||
|
||||
// MARK: - Other
|
||||
|
|
|
|||
|
|
@ -309,23 +309,11 @@ private extension View
|
|||
// MARK: - Getting tagged packages
|
||||
do
|
||||
{
|
||||
view.appState.taggedPackageNames = try loadTaggedIDsFromDisk()
|
||||
|
||||
AppConstants.shared.logger.info("Tagged packages in appState: \(view.appState.taggedPackageNames)")
|
||||
|
||||
do
|
||||
{
|
||||
try await view.brewPackagesTracker.applyTags(appState: view.appState)
|
||||
}
|
||||
catch let taggedStateApplicationError as NSError
|
||||
{
|
||||
AppConstants.shared.logger.error("Error while applying tagged state to packages: \(taggedStateApplicationError, privacy: .public)")
|
||||
view.appState.showAlert(errorToShow: .couldNotApplyTaggedStateToPackages)
|
||||
}
|
||||
try await view.brewPackagesTracker.applyTags()
|
||||
}
|
||||
catch let uuidLoadingError as NSError
|
||||
catch let taggedStateApplicationError as NSError
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while loading UUIDs from file: \(uuidLoadingError, privacy: .public)")
|
||||
AppConstants.shared.logger.error("Error while applying tagged state to packages: \(taggedStateApplicationError, privacy: .public)")
|
||||
view.appState.showAlert(errorToShow: .couldNotApplyTaggedStateToPackages)
|
||||
}
|
||||
|
||||
|
|
@ -489,18 +477,6 @@ private extension View
|
|||
{
|
||||
restartApp()
|
||||
}
|
||||
.onChange(of: view.appState.taggedPackageNames)
|
||||
{
|
||||
AppConstants.shared.logger.info("Will try to save tagged IDs to disk")
|
||||
do
|
||||
{
|
||||
try saveTaggedIDsToDisk(appState: view.appState)
|
||||
}
|
||||
catch let dataSavingError as NSError
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while trying to save data to disk: \(dataSavingError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@
|
|||
|
||||
// swiftlint:disable file_length
|
||||
|
||||
import ButtonKit
|
||||
import CorkNotifications
|
||||
import CorkShared
|
||||
import DavidFoundation
|
||||
import Defaults
|
||||
import SwiftData
|
||||
import SwiftUI
|
||||
import UserNotifications
|
||||
import ButtonKit
|
||||
import Defaults
|
||||
|
||||
// swiftlint:disable type_body_length
|
||||
@main
|
||||
|
|
@ -23,7 +24,7 @@ struct CorkApp: App
|
|||
|
||||
@State var brewPackagesTracker: BrewPackagesTracker = .init()
|
||||
@State var tapTracker: TapTracker = .init()
|
||||
|
||||
|
||||
@State var cachedDownloadsTracker: CachedDownloadsTracker = .init()
|
||||
|
||||
@State var topPackagesTracker: TopPackagesTracker = .init()
|
||||
|
|
@ -39,14 +40,14 @@ struct CorkApp: App
|
|||
@Default(.hasFinishedLicensingWorkflow) var hasFinishedLicensingWorkflow: Bool
|
||||
|
||||
@Environment(\.openWindow) private var openWindow: OpenWindowAction
|
||||
|
||||
|
||||
@Default(.showInMenuBar) var showInMenuBar: Bool
|
||||
|
||||
@Default(.areNotificationsEnabled) var areNotificationsEnabled: Bool
|
||||
@Default(.outdatedPackageNotificationType) var outdatedPackageNotificationType: OutdatedPackageNotificationType
|
||||
|
||||
|
||||
@Default(.lastSubmittedCorkVersion) var lastSubmittedCorkVersion: String
|
||||
|
||||
|
||||
@AppStorage("defaultBackupDateFormat") var defaultBackupDateFormat: Date.FormatStyle.DateStyle = .numeric
|
||||
|
||||
@State private var sendStandardUpdatesAvailableNotification: Bool = true
|
||||
|
|
@ -89,6 +90,9 @@ struct CorkApp: App
|
|||
.environment(updateProgressTracker)
|
||||
.environment(outdatedPackagesTracker)
|
||||
.environment(topPackagesTracker)
|
||||
.modelContainer(for: [
|
||||
SavedTaggedPackage.self
|
||||
])
|
||||
.task
|
||||
{
|
||||
NSWindow.allowsAutomaticWindowTabbing = false
|
||||
|
|
@ -420,7 +424,7 @@ struct CorkApp: App
|
|||
}
|
||||
.windowResizability(.contentSize)
|
||||
.windowToolbarStyle(.unifiedCompact)
|
||||
|
||||
|
||||
WindowGroup(id: .errorInspectorWindowID, for: String.self)
|
||||
{ $errorToInspect in
|
||||
if let errorToInspect
|
||||
|
|
@ -727,7 +731,7 @@ struct CorkApp: App
|
|||
} label: {
|
||||
Text("debug.action.licensing")
|
||||
}
|
||||
|
||||
|
||||
Menu
|
||||
{
|
||||
Button
|
||||
|
|
@ -757,7 +761,7 @@ struct CorkApp: App
|
|||
NSApp.dockTile.badgeLabel = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func setWhetherToSendStandardUpdatesAvailableNotification(to newValue: Bool)
|
||||
{
|
||||
self.sendStandardUpdatesAvailableNotification = newValue
|
||||
|
|
|
|||
|
|
@ -7,13 +7,39 @@
|
|||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
import SwiftData
|
||||
|
||||
extension BrewPackagesTracker
|
||||
{
|
||||
/// Load tagged package from storage, and apply them to the relevant packages
|
||||
@MainActor
|
||||
func applyTags(appState: AppState) async throws
|
||||
func applyTags() async throws
|
||||
{
|
||||
for taggedName in appState.taggedPackageNames
|
||||
let storageContext: ModelContext = AppConstants.shared.modelContainer.mainContext
|
||||
|
||||
let taggedPackageFetcher: FetchDescriptor = FetchDescriptor<SavedTaggedPackage>(
|
||||
predicate: #Predicate { _ in
|
||||
return true
|
||||
}
|
||||
)
|
||||
|
||||
/// Try to fetch the saved tagged packages, and stop execution if there are none
|
||||
guard let loadedTaggedPackages = try? storageContext.fetch(taggedPackageFetcher) else
|
||||
{
|
||||
AppConstants.shared.logger.log("Failed to load tagged packages")
|
||||
return
|
||||
}
|
||||
|
||||
guard !loadedTaggedPackages.isEmpty else
|
||||
{
|
||||
AppConstants.shared.logger.log("There are no tagged packages to apply the tagged status to")
|
||||
return
|
||||
}
|
||||
|
||||
/// Change the custom saveable object into strings
|
||||
let taggedPackagesFullNames: [String] = loadedTaggedPackages.map({ $0.fullName })
|
||||
|
||||
for taggedName in taggedPackagesFullNames
|
||||
{
|
||||
AppConstants.shared.logger.log("Will attempt to place package name \(taggedName, privacy: .public)")
|
||||
self.installedFormulae = Set(self.installedFormulae.map
|
||||
|
|
@ -23,7 +49,7 @@ extension BrewPackagesTracker
|
|||
case .success(var brewPackage):
|
||||
if brewPackage.name == taggedName
|
||||
{
|
||||
brewPackage.changeTaggedStatus()
|
||||
brewPackage.changeTaggedStatus(purpose: .justLoading)
|
||||
}
|
||||
return .success(brewPackage)
|
||||
case .failure(let error):
|
||||
|
|
@ -38,7 +64,7 @@ extension BrewPackagesTracker
|
|||
case .success(var brewPackage):
|
||||
if brewPackage.name == taggedName
|
||||
{
|
||||
brewPackage.changeTaggedStatus()
|
||||
brewPackage.changeTaggedStatus(purpose: .justLoading)
|
||||
}
|
||||
return .success(brewPackage)
|
||||
case .failure(let error):
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
//
|
||||
// Load Tagged IDs from Disk.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 21.03.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
|
||||
func loadTaggedIDsFromDisk() throws -> Set<String>
|
||||
{
|
||||
var nameSet: Set<String> = .init()
|
||||
|
||||
do
|
||||
{
|
||||
let rawPackageNamesFromFile: String = try String(contentsOf: AppConstants.shared.metadataFilePath, encoding: .utf8)
|
||||
let packageNamesAsArray: [String] = rawPackageNamesFromFile.components(separatedBy: ":")
|
||||
|
||||
for packageNameAsString in packageNamesAsArray
|
||||
{
|
||||
nameSet.insert(packageNameAsString)
|
||||
}
|
||||
}
|
||||
catch let dataReadingError as NSError
|
||||
{
|
||||
AppConstants.shared.logger.error("Failed while reading data from disk: \(dataReadingError, privacy: .public)")
|
||||
}
|
||||
|
||||
AppConstants.shared.logger.debug("Loaded name set: \(nameSet, privacy: .public)")
|
||||
|
||||
return nameSet
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// Save Tagged IDs to Disk.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš on 21.03.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CorkShared
|
||||
|
||||
@MainActor
|
||||
func saveTaggedIDsToDisk(appState: AppState) throws
|
||||
{
|
||||
let namesAsString: String = appState.taggedPackageNames.compactMap { $0 }.joined(separator: ":")
|
||||
AppConstants.shared.logger.debug("Names as string: \(namesAsString, privacy: .public)")
|
||||
|
||||
do
|
||||
{
|
||||
try namesAsString.write(to: AppConstants.shared.metadataFilePath, atomically: true, encoding: .utf8)
|
||||
}
|
||||
catch let writingError as NSError
|
||||
{
|
||||
AppConstants.shared.logger.error("Error while writing to file: \(writingError, privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
|
@ -74,9 +74,46 @@ struct BrewPackage: Identifiable, Equatable, Hashable, Codable
|
|||
{
|
||||
return versions.formatted(.list(type: .and))
|
||||
}
|
||||
|
||||
mutating func changeTaggedStatus()
|
||||
|
||||
/// The purpose of the tagged status change operation
|
||||
enum TaggedStatusChangePurpose: String
|
||||
{
|
||||
/// Only load and apply the tagged status to packages
|
||||
///
|
||||
/// For when the tagged packages are just being loaded and applied to the packages
|
||||
case justLoading = "loading"
|
||||
|
||||
/// Change and persist the change.
|
||||
///
|
||||
/// For when the user initiates the change.
|
||||
case actuallyChangingTheTaggedState = "actually changing the tagged state"
|
||||
}
|
||||
|
||||
/// Change the tagged status of a package, and optionally persist that change in the database
|
||||
///
|
||||
/// - Parameter purpose: The purpose of this operation
|
||||
@MainActor
|
||||
mutating func changeTaggedStatus(purpose: TaggedStatusChangePurpose)
|
||||
{
|
||||
|
||||
let packageName: String = self.name
|
||||
|
||||
AppConstants.shared.logger.debug("Will change the tagged status of package \(packageName) for the purpose of \(purpose.rawValue)")
|
||||
|
||||
if purpose == .actuallyChangingTheTaggedState
|
||||
{
|
||||
let modelContext: ModelContext = AppConstants.shared.modelContainer.mainContext
|
||||
|
||||
if !isTagged
|
||||
{
|
||||
modelContext.insert(SavedTaggedPackage(fullName: self.name))
|
||||
}
|
||||
else
|
||||
{
|
||||
modelContext.delete(SavedTaggedPackage(fullName: self.name))
|
||||
}
|
||||
}
|
||||
|
||||
isTagged.toggle()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,44 +5,26 @@
|
|||
// Created by David Bureš - P on 22.04.2025.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CorkShared
|
||||
import SwiftUI
|
||||
|
||||
struct TagUntagButton: View
|
||||
{
|
||||
@Environment(AppState.self) var appState: AppState
|
||||
@Environment(BrewPackagesTracker.self) var brewPackagesTracker: BrewPackagesTracker
|
||||
|
||||
|
||||
let package: BrewPackage
|
||||
|
||||
var body: some View {
|
||||
|
||||
var body: some View
|
||||
{
|
||||
Button
|
||||
{
|
||||
changeTaggedStatus()
|
||||
brewPackagesTracker.updatePackageInPlace(package)
|
||||
{ package in
|
||||
package.changeTaggedStatus(purpose: .actuallyChangingTheTaggedState)
|
||||
}
|
||||
} label: {
|
||||
Label(package.isTagged ? "sidebar.section.all.contextmenu.untag-\(package.name)" : "sidebar.section.all.contextmenu.tag-\(package.name)", systemImage: package.isTagged ? "tag.slash" : "tag")
|
||||
}
|
||||
}
|
||||
|
||||
func changeTaggedStatus()
|
||||
{
|
||||
AppConstants.shared.logger.info("Will change tagged status of \(package.name). Current state of the tagged package tracker: \(appState.taggedPackageNames)")
|
||||
|
||||
brewPackagesTracker.updatePackageInPlace(package)
|
||||
{ package in
|
||||
package.changeTaggedStatus()
|
||||
}
|
||||
|
||||
if package.isTagged
|
||||
{
|
||||
AppConstants.shared.logger.info("Tagged package tracker DOES contain \(package.name). Will remove")
|
||||
appState.taggedPackageNames.remove(package.name)
|
||||
}
|
||||
else
|
||||
{
|
||||
AppConstants.shared.logger.info("Tagged package tracker does NOT contain \(package.name). Will insert")
|
||||
appState.taggedPackageNames.insert(package.name)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
import OSLog
|
||||
import SwiftData
|
||||
@preconcurrency import UserNotifications
|
||||
|
||||
public struct AppConstants: Sendable
|
||||
|
|
@ -67,12 +68,24 @@ public struct AppConstants: Sendable
|
|||
self.homebrewVariablesPath = localHomebrewVariablesPath
|
||||
|
||||
self.logger = internalLogger
|
||||
|
||||
let modelConfiguration: ModelConfiguration = .init(isStoredInMemoryOnly: false)
|
||||
|
||||
guard let initializedModelContainer = try? ModelContainer(for: SavedTaggedPackage.self, configurations: modelConfiguration) else
|
||||
{
|
||||
fatalError("Failed to initialize persistence container")
|
||||
}
|
||||
|
||||
self.modelContainer = initializedModelContainer
|
||||
}
|
||||
|
||||
// MARK: - Shared Instance
|
||||
|
||||
public static let shared: AppConstants = .init()
|
||||
|
||||
// MARK: - Persistence
|
||||
public let modelContainer: ModelContainer
|
||||
|
||||
// MARK: - Logging
|
||||
|
||||
public let logger: Logger
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// Tagged Packages Storage.swift
|
||||
// Cork
|
||||
//
|
||||
// Created by David Bureš - P on 18.05.2025.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
@Model
|
||||
public final class SavedTaggedPackage
|
||||
{
|
||||
/// Full names of packages, which includes the Homebrew version
|
||||
@Attribute(.unique) @Attribute(.spotlight)
|
||||
public var fullName: String
|
||||
|
||||
public init(fullName: String) {
|
||||
self.fullName = fullName
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ func corkTarget(configureWithSelfCompiled: Bool) -> ProjectDescription.Target {
|
|||
destinations: [.mac],
|
||||
product: .app,
|
||||
productName: "Cork",
|
||||
bundleId: "com.davidbures.cork",
|
||||
bundleId: "eu.davidbures.cork",
|
||||
deploymentTargets: .macOS("14.0.0"),
|
||||
infoPlist: .file(path: "Cork/Info.plist"),
|
||||
sources: [
|
||||
|
|
@ -83,7 +83,8 @@ let project = Project(
|
|||
name: "CorkShared",
|
||||
destinations: [.mac],
|
||||
product: .staticLibrary,
|
||||
bundleId: "com.davidbures.cork-shared",
|
||||
bundleId: "eu.davidbures.cork-shared",
|
||||
deploymentTargets: .macOS("14.0.0"),
|
||||
sources: [
|
||||
"Modules/Shared/**/*.swift"
|
||||
],
|
||||
|
|
@ -105,7 +106,8 @@ let project = Project(
|
|||
name: "CorkNotifications",
|
||||
destinations: [.mac],
|
||||
product: .staticLibrary,
|
||||
bundleId: "com.davidbures.cork-notifications",
|
||||
bundleId: "eu.davidbures.cork-notifications",
|
||||
deploymentTargets: .macOS("14.0.0"),
|
||||
sources: [
|
||||
"Modules/Notifications/**/*.swift"
|
||||
],
|
||||
|
|
@ -127,7 +129,7 @@ let project = Project(
|
|||
name: "CorkHelp",
|
||||
destinations: [.mac],
|
||||
product: .bundle,
|
||||
bundleId: "com.davidbures.corkhelp",
|
||||
bundleId: "eu.davidbures.corkhelp",
|
||||
settings: .settings(configurations: [
|
||||
.debug(
|
||||
name: "Debug",
|
||||
|
|
@ -143,7 +145,7 @@ let project = Project(
|
|||
name: "CorkTests",
|
||||
destinations: [.mac],
|
||||
product: .unitTests,
|
||||
bundleId: "com.davidbures.cork-tests",
|
||||
bundleId: "eu.davidbures.cork-tests",
|
||||
sources: [
|
||||
"Tests/**",
|
||||
"Cork/**/*.swift"
|
||||
|
|
|
|||
Loading…
Reference in New Issue