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")