From 91ea34110bb286614fb70a3d2f938597fdc7c511 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Mon, 20 Nov 2023 14:06:04 -0600 Subject: [PATCH] Verifies StoreApp.isPledged status when updating source --- .../Operations/FetchSourceOperation.swift | 58 +++++++++++++++++++ AltStoreCore/Model/DatabaseManager.swift | 2 +- AltStoreCore/Model/StoreApp.swift | 12 ++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/AltStore/Operations/FetchSourceOperation.swift b/AltStore/Operations/FetchSourceOperation.swift index 9ce9a4a3..c161f797 100644 --- a/AltStore/Operations/FetchSourceOperation.swift +++ b/AltStore/Operations/FetchSourceOperation.swift @@ -154,6 +154,7 @@ final class FetchSourceOperation: ResultOperation let identifier = source.identifier try self.verify(source, response: response) + try self.verifyPledges(for: source, in: childContext) try childContext.save() @@ -223,6 +224,63 @@ private extension FetchSourceOperation } } + func verifyPledges(for source: Source, in context: NSManagedObjectContext) throws + { + guard let patreonURL = source.patreonURL, let patreonAccount = DatabaseManager.shared.patreonAccount(in: context) else { return } + + let normalizedPatreonURL = try patreonURL.normalized() + + guard let pledge = patreonAccount.pledges.first(where: { pledge in + do + { + let normalizedCampaignURL = try pledge.campaignURL.normalized() + return normalizedCampaignURL == normalizedPatreonURL + } + catch + { + Logger.main.error("Failed to normalize Patreon URL \(pledge.campaignURL, privacy: .public). \(error.localizedDescription, privacy: .public)") + return false + } + }) else { return } + + // User is pledged to this source's Patreon, so check which apps they're pledged to. + + // We only assign `isPledged = true` because false is already the default, + // and only one check needs to be true for isPledged to be true. + + for app in source.apps where app.isPledgeRequired + { + if let requiredAppPledge = app.pledgeAmount + { + if pledge.amount >= requiredAppPledge + { + app.isPledged = true + continue + } + } + + if let tierIDs = app._tierIDs + { + let tier = pledge.tiers.first { tierIDs.contains($0.identifier) } + if tier != nil + { + app.isPledged = true + continue + } + } + + if let rewardID = app._rewardID + { + let reward = pledge.rewards.first { $0.identifier == rewardID } + if reward != nil + { + app.isPledged = true + continue + } + } + } + } + func verifySourceNotBlocked(_ source: Source, response: URLResponse?) throws { guard let blockedSources = UserDefaults.shared.blockedSources else { return } diff --git a/AltStoreCore/Model/DatabaseManager.swift b/AltStoreCore/Model/DatabaseManager.swift index b1d1299e..2c8111a3 100644 --- a/AltStoreCore/Model/DatabaseManager.swift +++ b/AltStoreCore/Model/DatabaseManager.swift @@ -224,7 +224,7 @@ public extension DatabaseManager let predicate = NSPredicate(format: "%K == %@", #keyPath(PatreonAccount.identifier), patreonAccountID) - let patreonAccount = PatreonAccount.first(satisfying: predicate, in: context) + let patreonAccount = PatreonAccount.first(satisfying: predicate, in: context, requestProperties: [\.relationshipKeyPathsForPrefetching: [#keyPath(PatreonAccount._pledges)]]) return patreonAccount } } diff --git a/AltStoreCore/Model/StoreApp.swift b/AltStoreCore/Model/StoreApp.swift index 69f27e56..09275f4a 100644 --- a/AltStoreCore/Model/StoreApp.swift +++ b/AltStoreCore/Model/StoreApp.swift @@ -182,6 +182,12 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable @NSManaged public private(set) var loggedErrors: NSSet /* Set */ // Use NSSet to avoid eagerly fetching values. + /* Non-Core Data Properties */ + + // Used to set isPledged after fetching source. + public var _tierIDs: Set? + public var _rewardID: String? + @nonobjc public var source: Source? { set { self._source = newValue @@ -420,6 +426,9 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable // No conditions, so default to pledgeAmount of 0 to simplify logic. self._pledgeAmount = 0 as NSDecimalNumber } + + self._tierIDs = patreon.tiers + self._rewardID = patreon.benefit } else { @@ -427,6 +436,9 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable self.isHiddenWithoutPledge = false self._pledgeAmount = nil self.pledgeCurrency = nil + + self._tierIDs = nil + self._rewardID = nil } } catch