From f884d72a8b79eb78c2d3884d6e6617fef20a73ce Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Thu, 11 May 2023 18:51:09 -0500 Subject: [PATCH] =?UTF-8?q?Verifies=20source=E2=80=99s=20identifier=20does?= =?UTF-8?q?n=E2=80=99t=20match=20existing=20sources=20when=20adding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AltStore/Managing Apps/AppManager.swift | 8 ++++-- .../Operations/FetchSourceOperation.swift | 15 +++++++++++ AltStore/Sources/SourcesViewController.swift | 26 ++++++++++++++++--- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index 02ac66a8..9ef033c4 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -365,8 +365,12 @@ extension AppManager let action = await UIAlertAction(title: NSLocalizedString("Add Source", comment: ""), style: .default) try await presentingViewController.presentConfirmationAlert(title: title, message: message, primaryAction: action) - // Wait for fetch to finish before saving context. - _ = try await fetchedSource + // Wait for fetch to finish before saving context to make + // sure there isn't already a source with this identifier. + let sourceExists = try await fetchedSource.isAdded + + // This is just a sanity check, so pass nil for previousSourceName to keep code simple. + guard !sourceExists else { throw SourceError.duplicate(source, previousSourceName: nil) } try await context.performAsync { try context.save() diff --git a/AltStore/Operations/FetchSourceOperation.swift b/AltStore/Operations/FetchSourceOperation.swift index e6189a34..70d03b62 100644 --- a/AltStore/Operations/FetchSourceOperation.swift +++ b/AltStore/Operations/FetchSourceOperation.swift @@ -23,6 +23,7 @@ extension SourceError case duplicateVersion case changedID + case duplicate } static func unsupported(_ source: Source) -> SourceError { SourceError(code: .unsupported, source: source) } @@ -30,6 +31,7 @@ extension SourceError static func duplicateVersion(_ version: String, for app: StoreApp, source: Source) -> SourceError { SourceError(code: .duplicateVersion, source: source, app: app, version: version) } static func changedID(_ identifier: String, previousID: String, source: Source) -> SourceError { SourceError(code: .changedID, source: source, sourceID: identifier, previousSourceID: previousID) } + static func duplicate(_ source: Source, previousSourceName: String?) -> SourceError { SourceError(code: .duplicate, source: source, previousSourceName: previousSourceName) } } struct SourceError: ALTLocalizedError @@ -43,6 +45,8 @@ struct SourceError: ALTLocalizedError var bundleID: String? var version: String? + @UserInfoValue var previousSourceName: String? + // Store in userInfo so they can be viewed from Error Log. @UserInfoValue var sourceID: String? @UserInfoValue var previousSourceID: String? @@ -79,6 +83,13 @@ struct SourceError: ALTLocalizedError case .changedID: let failureReason = String(format: NSLocalizedString("The identifier of the source “%@” has changed.", comment: ""), self.$source.name) return failureReason + + case .duplicate: + let baseMessage = String(format: NSLocalizedString("A source with the identifier '%@' already exists", comment: ""), self.$source.identifier) + guard let previousSourceName else { return baseMessage + "." } + + let failureReason = baseMessage + " (“\(previousSourceName)”)." + return failureReason } } @@ -86,6 +97,10 @@ struct SourceError: ALTLocalizedError switch self.code { case .changedID: return NSLocalizedString("A source cannot change its identifier once added. This source can no longer be updated.", comment: "") + case .duplicate: + let failureReason = NSLocalizedString("Please remove the existing source in order to add this one.", comment: "") + return failureReason + default: return nil } } diff --git a/AltStore/Sources/SourcesViewController.swift b/AltStore/Sources/SourcesViewController.swift index 202b191c..11df703a 100644 --- a/AltStore/Sources/SourcesViewController.swift +++ b/AltStore/Sources/SourcesViewController.swift @@ -283,13 +283,31 @@ private extension SourcesViewController AppManager.shared.fetchSource(sourceURL: url, dependencies: dependencies) { (result) in do { + // Use @Managed before calling perform() to keep + // strong reference to source.managedObjectContext. @Managed var source = try result.get() - DispatchQueue.main.async { - self.showSourceDetails(for: source) + let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext() + backgroundContext.perform { + do + { + let predicate = NSPredicate(format: "%K == %@", #keyPath(Source.identifier), $source.identifier) + if let existingSource = Source.first(satisfying: predicate, in: backgroundContext) + { + throw SourceError.duplicate(source, previousSourceName: existingSource.name) + } + + DispatchQueue.main.async { + self.showSourceDetails(for: source) + } + + finish(.success(())) + } + catch + { + finish(.failure(error)) + } } - - finish(.success(())) } catch {