From 7ea1ad5af0edba7726c780f9d2a752d6de1411ef Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Tue, 15 Nov 2022 16:21:44 -0600 Subject: [PATCH] Replaces StoreApp.latestVersion with latestSupportedVersion + latestAvailableVersion We now store the latest supported version as a relationship on StoreApp, rather than the latest available version. This allows us to reference the latest supported version in predicates and sort descriptors. However, we kept the underlying Core Data property name the same to avoid extra migration. --- .../App Detail/AppContentViewController.swift | 2 +- AltStore/App Detail/AppViewController.swift | 2 +- AltStore/AppDelegate.swift | 2 +- AltStore/Browse/BrowseViewController.swift | 4 +-- AltStore/My Apps/MyAppsViewController.swift | 10 +++---- AltStore/News/NewsViewController.swift | 4 +-- AltStoreCore/Model/AppVersion.swift | 6 +++- AltStoreCore/Model/DatabaseManager.swift | 2 +- AltStoreCore/Model/InstalledApp.swift | 2 +- AltStoreCore/Model/MergePolicy.swift | 2 +- .../StoreApp10ToStoreApp11Policy.swift | 2 +- AltStoreCore/Model/StoreApp.swift | 28 +++++++++++++++---- 12 files changed, 44 insertions(+), 22 deletions(-) diff --git a/AltStore/App Detail/AppContentViewController.swift b/AltStore/App Detail/AppContentViewController.swift index b6dc5ba8..bd6c2a6e 100644 --- a/AltStore/App Detail/AppContentViewController.swift +++ b/AltStore/App Detail/AppContentViewController.swift @@ -81,7 +81,7 @@ class AppContentViewController: UITableViewController self.subtitleLabel.text = self.app.subtitle self.descriptionTextView.text = self.app.localizedDescription - if let version = self.app.latestVersion + if let version = self.app.latestAvailableVersion { self.versionDescriptionTextView.text = version.localizedDescription self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.version) diff --git a/AltStore/App Detail/AppViewController.swift b/AltStore/App Detail/AppViewController.swift index a68aec57..4d3ed3f8 100644 --- a/AltStore/App Detail/AppViewController.swift +++ b/AltStore/App Detail/AppViewController.swift @@ -384,7 +384,7 @@ private extension AppViewController button.progress = progress } - if let versionDate = self.app.latestVersion?.date, versionDate > Date() + if let versionDate = self.app.latestAvailableVersion?.date, versionDate > Date() { self.bannerView.button.countdownDate = versionDate self.navigationBarDownloadButton.countdownDate = versionDate diff --git a/AltStore/AppDelegate.swift b/AltStore/AppDelegate.swift index 85b8c861..fb39fed7 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -384,7 +384,7 @@ private extension AppDelegate for update in updates { guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue } - guard let storeApp = update.storeApp, let version = storeApp.version else { continue } + guard let storeApp = update.storeApp, let version = storeApp.latestSupportedVersion else { continue } let content = UNMutableNotificationContent() content.title = NSLocalizedString("New Update Available", comment: "") diff --git a/AltStore/Browse/BrowseViewController.swift b/AltStore/Browse/BrowseViewController.swift index 27e7d283..548e3a47 100644 --- a/AltStore/Browse/BrowseViewController.swift +++ b/AltStore/Browse/BrowseViewController.swift @@ -113,9 +113,9 @@ private extension BrowseViewController let progress = AppManager.shared.installationProgress(for: app) cell.bannerView.button.progress = progress - if let versionDate = app.latestVersion?.date, versionDate > Date() + if let versionDate = app.latestSupportedVersion?.date, versionDate > Date() { - cell.bannerView.button.countdownDate = app.versionDate + cell.bannerView.button.countdownDate = versionDate } else { diff --git a/AltStore/My Apps/MyAppsViewController.swift b/AltStore/My Apps/MyAppsViewController.swift index dc2f8b5b..cf48b470 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -186,7 +186,7 @@ private extension MyAppsViewController func makeUpdatesDataSource() -> RSTFetchedResultsCollectionViewPrefetchingDataSource { let fetchRequest = InstalledApp.updatesFetchRequest() - fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.storeApp?.latestVersion?.date, ascending: true), + fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \InstalledApp.storeApp?.latestSupportedVersion?.date, ascending: false), NSSortDescriptor(keyPath: \InstalledApp.name, ascending: true)] fetchRequest.returnsObjectsAsFaults = false @@ -195,21 +195,21 @@ private extension MyAppsViewController dataSource.cellIdentifierHandler = { _ in "UpdateCell" } dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in guard let self = self else { return } - guard let app = installedApp.storeApp, let latestVersion = app.latestVersion else { return } + guard let app = installedApp.storeApp, let latestSupportedVersion = app.latestSupportedVersion else { return } let cell = cell as! UpdateCollectionViewCell cell.layoutMargins.left = self.view.layoutMargins.left cell.layoutMargins.right = self.view.layoutMargins.right cell.tintColor = app.tintColor ?? .altPrimary - cell.versionDescriptionTextView.text = app.versionDescription + cell.versionDescriptionTextView.text = latestSupportedVersion.localizedDescription cell.bannerView.iconImageView.image = nil cell.bannerView.iconImageView.isIndicatingActivity = true cell.bannerView.configure(for: app) - let versionDate = Date().relativeDateString(since: latestVersion.date, dateFormatter: self.dateFormatter) + let versionDate = Date().relativeDateString(since: latestSupportedVersion.date, dateFormatter: self.dateFormatter) cell.bannerView.subtitleLabel.text = versionDate let appName: String @@ -223,7 +223,7 @@ private extension MyAppsViewController appName = app.name } - cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestVersion.version, versionDate) + cell.bannerView.accessibilityLabel = String(format: NSLocalizedString("%@ %@ update. Released on %@.", comment: ""), appName, latestSupportedVersion.version, versionDate) cell.bannerView.button.isIndicatingActivity = false cell.bannerView.button.addTarget(self, action: #selector(MyAppsViewController.updateApp(_:)), for: .primaryActionTriggered) diff --git a/AltStore/News/NewsViewController.swift b/AltStore/News/NewsViewController.swift index e41dcad0..44e8009a 100644 --- a/AltStore/News/NewsViewController.swift +++ b/AltStore/News/NewsViewController.swift @@ -392,9 +392,9 @@ extension NewsViewController let progress = AppManager.shared.installationProgress(for: storeApp) footerView.bannerView.button.progress = progress - if let versionDate = storeApp.latestVersion?.date, versionDate > Date() + if let versionDate = storeApp.latestSupportedVersion?.date, versionDate > Date() { - footerView.bannerView.button.countdownDate = storeApp.versionDate + footerView.bannerView.button.countdownDate = versionDate } else { diff --git a/AltStoreCore/Model/AppVersion.swift b/AltStoreCore/Model/AppVersion.swift index 2bbde4a6..dc0ffe80 100644 --- a/AltStoreCore/Model/AppVersion.swift +++ b/AltStoreCore/Model/AppVersion.swift @@ -40,7 +40,7 @@ public class AppVersion: NSManagedObject, Decodable, Fetchable /* Relationships */ @NSManaged public private(set) var app: StoreApp? - @NSManaged public private(set) var latestVersionApp: StoreApp? + @NSManaged @objc(latestVersionApp) public internal(set) var latestSupportedVersionApp: StoreApp? private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { @@ -113,4 +113,8 @@ public extension AppVersion return appVersion } + + var isSupported: Bool { + return true + } } diff --git a/AltStoreCore/Model/DatabaseManager.swift b/AltStoreCore/Model/DatabaseManager.swift index 3d443a62..83f7e6d2 100644 --- a/AltStoreCore/Model/DatabaseManager.swift +++ b/AltStoreCore/Model/DatabaseManager.swift @@ -232,7 +232,7 @@ private extension DatabaseManager else { storeApp = StoreApp.makeAltStoreApp(in: context) - storeApp.latestVersion?.version = localApp.version + storeApp.latestSupportedVersion?.version = localApp.version storeApp.source = altStoreSource } diff --git a/AltStoreCore/Model/InstalledApp.swift b/AltStoreCore/Model/InstalledApp.swift index cb5e2bdd..41ff1475 100644 --- a/AltStoreCore/Model/InstalledApp.swift +++ b/AltStoreCore/Model/InstalledApp.swift @@ -146,7 +146,7 @@ public extension InstalledApp { let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K != nil AND %K != %K", - #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.latestVersion.version)) + #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.storeApp), #keyPath(InstalledApp.version), #keyPath(InstalledApp.storeApp.latestSupportedVersion.version)) return fetchRequest } diff --git a/AltStoreCore/Model/MergePolicy.swift b/AltStoreCore/Model/MergePolicy.swift index 08038988..a963a1fa 100644 --- a/AltStoreCore/Model/MergePolicy.swift +++ b/AltStoreCore/Model/MergePolicy.swift @@ -44,7 +44,7 @@ open class MergePolicy: RSTRelationshipPreservingMergePolicy let conflictingAppVersions = conflict.conflictingObjects.lazy.compactMap { $0 as? AppVersion } // Primary AppVersion == AppVersion whose latestVersionApp.latestVersion points back to itself. - if let primaryAppVersion = conflictingAppVersions.first(where: { $0.latestVersionApp?.latestVersion == $0 }), + if let primaryAppVersion = conflictingAppVersions.first(where: { $0.latestSupportedVersionApp?.latestSupportedVersion == $0 }), let secondaryAppVersion = conflictingAppVersions.first(where: { $0 != primaryAppVersion }) { secondaryAppVersion.managedObjectContext?.delete(secondaryAppVersion) diff --git a/AltStoreCore/Model/Migrations/Policies/StoreApp10ToStoreApp11Policy.swift b/AltStoreCore/Model/Migrations/Policies/StoreApp10ToStoreApp11Policy.swift index ead50098..7ca75c60 100644 --- a/AltStoreCore/Model/Migrations/Policies/StoreApp10ToStoreApp11Policy.swift +++ b/AltStoreCore/Model/Migrations/Policies/StoreApp10ToStoreApp11Policy.swift @@ -48,7 +48,7 @@ fileprivate extension NSManagedObject func setStoreAppLatestVersion(_ appVersion: NSManagedObject) { - self.setValue(appVersion, forKey: #keyPath(StoreApp.latestVersion)) + self.setValue(appVersion, forKey: #keyPath(StoreApp.latestSupportedVersion)) let versions = NSOrderedSet(array: [appVersion]) self.setValue(versions, forKey: #keyPath(StoreApp._versions)) diff --git a/AltStoreCore/Model/StoreApp.swift b/AltStoreCore/Model/StoreApp.swift index f0702ab9..265d44ad 100644 --- a/AltStoreCore/Model/StoreApp.swift +++ b/AltStoreCore/Model/StoreApp.swift @@ -78,7 +78,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable @NSManaged @objc(source) public var _source: Source? @NSManaged @objc(permissions) public var _permissions: NSOrderedSet - @NSManaged public private(set) var latestVersion: AppVersion? + @NSManaged @objc(latestVersion) public private(set) var latestSupportedVersion: AppVersion? @NSManaged @objc(versions) public private(set) var _versions: NSOrderedSet @NSManaged public private(set) var loggedErrors: NSSet /* Set */ // Use NSSet to avoid eagerly fetching values. @@ -227,16 +227,30 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable } } -private extension StoreApp +internal extension StoreApp { func setVersions(_ versions: [AppVersion]) { - guard let latestVersion = versions.first else { preconditionFailure("StoreApp must have at least one AppVersion.") } - - self.latestVersion = latestVersion self._versions = NSOrderedSet(array: versions) + let latestSupportedVersion = versions.first(where: { $0.isSupported }) + self.latestSupportedVersion = latestSupportedVersion + + for case let version as AppVersion in self._versions + { + if version == latestSupportedVersion + { + version.latestSupportedVersionApp = self + } + else + { + // Ensure we replace any previous relationship when merging. + version.latestSupportedVersionApp = nil + } + } + // Preserve backwards compatibility by assigning legacy property values. + guard let latestVersion = versions.first else { preconditionFailure("StoreApp must have at least one AppVersion.") } self._version = latestVersion.version self._versionDate = latestVersion.date self._versionDescription = latestVersion.localizedDescription @@ -247,6 +261,10 @@ private extension StoreApp public extension StoreApp { + var latestAvailableVersion: AppVersion? { + return self._versions.firstObject as? AppVersion + } + @nonobjc class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "StoreApp")