From d718d2155fc279d219853ac0fc2c595678ff9b07 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 | 4 +-- AltStoreCore/Model/MergePolicy.swift | 2 +- .../StoreApp10ToStoreApp11Policy.swift | 2 +- AltStoreCore/Model/StoreApp.swift | 28 +++++++++++++++---- 12 files changed, 45 insertions(+), 23 deletions(-) diff --git a/AltStore/App Detail/AppContentViewController.swift b/AltStore/App Detail/AppContentViewController.swift index 6dc52b87..2aacf37c 100644 --- a/AltStore/App Detail/AppContentViewController.swift +++ b/AltStore/App Detail/AppContentViewController.swift @@ -81,7 +81,7 @@ final 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 7977ce79..6ea8a81f 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 64d08e7c..eeaabaf1 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -382,7 +382,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 21ab4073..1addee55 100644 --- a/AltStore/Browse/BrowseViewController.swift +++ b/AltStore/Browse/BrowseViewController.swift @@ -114,9 +114,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 f5e5155a..d6a337cb 100644 --- a/AltStore/My Apps/MyAppsViewController.swift +++ b/AltStore/My Apps/MyAppsViewController.swift @@ -195,7 +195,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 @@ -204,21 +204,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 @@ -232,7 +232,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 f57e9117..c6b55cb6 100644 --- a/AltStore/News/NewsViewController.swift +++ b/AltStore/News/NewsViewController.swift @@ -390,9 +390,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 cdda104b..690b42f1 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 f59d05d0..b577472e 100644 --- a/AltStoreCore/Model/InstalledApp.swift +++ b/AltStoreCore/Model/InstalledApp.swift @@ -163,8 +163,8 @@ public extension InstalledApp class func updatesFetchRequest() -> NSFetchRequest { let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest - fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K == YES", - #keyPath(InstalledApp.isActive), #keyPath(InstalledApp.hasUpdate)) + fetchRequest.predicate = NSPredicate(format: "%K == YES AND %K != nil AND %K != %K", + #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 2e38b57d..f1c821ce 100644 --- a/AltStoreCore/Model/StoreApp.swift +++ b/AltStoreCore/Model/StoreApp.swift @@ -146,7 +146,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. @@ -314,16 +314,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 @@ -334,6 +348,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")