mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Improves error message when registering app + app extension after App ID limit is reached
This commit is contained in:
29
AltStore/Extensions/ALTApplication+AppExtensions.swift
Normal file
29
AltStore/Extensions/ALTApplication+AppExtensions.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// ALTApplication+AppExtensions.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Riley Testut on 2/10/20.
|
||||
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||
//
|
||||
|
||||
import AltSign
|
||||
|
||||
extension ALTApplication
|
||||
{
|
||||
var appExtensions: Set<ALTApplication> {
|
||||
guard let bundle = Bundle(url: self.fileURL) else { return [] }
|
||||
|
||||
var appExtensions: Set<ALTApplication> = []
|
||||
|
||||
if let directory = bundle.builtInPlugInsURL, let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
|
||||
{
|
||||
for case let fileURL as URL in enumerator where fileURL.pathExtension.lowercased() == "appex"
|
||||
{
|
||||
guard let appExtension = ALTApplication(fileURL: fileURL) else { continue }
|
||||
appExtensions.insert(appExtension)
|
||||
}
|
||||
}
|
||||
|
||||
return appExtensions
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,11 @@ extension ALTTeamType
|
||||
}
|
||||
}
|
||||
|
||||
extension Team
|
||||
{
|
||||
static let maximumFreeAppIDs = 10
|
||||
}
|
||||
|
||||
@objc(Team)
|
||||
class Team: NSManagedObject, Fetchable
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ enum OperationError: LocalizedError
|
||||
case invalidParameters
|
||||
|
||||
case iOSVersionNotSupported(ALTApplication)
|
||||
case maximumAppIDLimitReached(Date)
|
||||
case maximumAppIDLimitReached(application: ALTApplication, requiredAppIDs: Int, availableAppIDs: Int, nextExpirationDate: Date)
|
||||
|
||||
case noSources
|
||||
|
||||
@@ -58,26 +58,38 @@ enum OperationError: LocalizedError
|
||||
var recoverySuggestion: String? {
|
||||
switch self
|
||||
{
|
||||
case .maximumAppIDLimitReached(let date):
|
||||
let remainingTime: String
|
||||
case .maximumAppIDLimitReached(let application, let requiredAppIDs, let availableAppIDs, let date):
|
||||
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
||||
let message: String
|
||||
|
||||
let numberOfDays = date.numberOfCalendarDays(since: Date())
|
||||
switch numberOfDays {
|
||||
case 0:
|
||||
let components = Calendar.current.dateComponents([.hour], from: Date(), to: date)
|
||||
let numberOfHours = components.hour!
|
||||
if requiredAppIDs > 1
|
||||
{
|
||||
let availableText: String
|
||||
|
||||
switch numberOfHours
|
||||
switch availableAppIDs
|
||||
{
|
||||
case 1: remainingTime = NSLocalizedString("1 hour", comment: "")
|
||||
default: remainingTime = String(format: NSLocalizedString("%@ hours", comment: ""), NSNumber(value: numberOfHours))
|
||||
case 0: availableText = NSLocalizedString("none are available", comment: "")
|
||||
case 1: availableText = NSLocalizedString("only 1 is available", comment: "")
|
||||
default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs))
|
||||
}
|
||||
|
||||
case 1: remainingTime = NSLocalizedString("1 day", comment: "")
|
||||
default: remainingTime = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
||||
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), application.name, NSNumber(value: requiredAppIDs), availableText)
|
||||
message = prefixMessage + " " + baseMessage
|
||||
}
|
||||
else
|
||||
{
|
||||
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: date)
|
||||
|
||||
let dateComponentsFormatter = DateComponentsFormatter()
|
||||
dateComponentsFormatter.maximumUnitCount = 1
|
||||
dateComponentsFormatter.unitsStyle = .full
|
||||
|
||||
let remainingTime = dateComponentsFormatter.string(from: dateComponents)!
|
||||
|
||||
let remainingTimeMessage = String(format: NSLocalizedString("You can register another App ID in %@.", comment: ""), remainingTime)
|
||||
message = baseMessage + " " + remainingTimeMessage
|
||||
}
|
||||
|
||||
let message = String(format: NSLocalizedString("Delete sideloaded apps to free up App ID slots. You can register another App ID in %@.", comment: ""), remainingTime)
|
||||
return message
|
||||
|
||||
default: return nil
|
||||
|
||||
@@ -102,53 +102,43 @@ private extension ResignAppOperation
|
||||
{
|
||||
func prepareProvisioningProfiles(_ fileURL: URL, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void)
|
||||
{
|
||||
guard let bundle = Bundle(url: fileURL), let app = ALTApplication(fileURL: fileURL) else { return completionHandler(.failure(OperationError.invalidApp)) }
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
|
||||
var profiles = [String: ALTProvisioningProfile]()
|
||||
var error: Error?
|
||||
|
||||
dispatchGroup.enter()
|
||||
guard let app = ALTApplication(fileURL: fileURL) else { return completionHandler(.failure(OperationError.invalidApp)) }
|
||||
|
||||
self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let e): error = e
|
||||
case .failure(let error): completionHandler(.failure(error))
|
||||
case .success(let profile):
|
||||
profiles[app.bundleIdentifier] = profile
|
||||
}
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
|
||||
if let directory = bundle.builtInPlugInsURL, let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
|
||||
{
|
||||
for case let fileURL as URL in enumerator where fileURL.pathExtension.lowercased() == "appex"
|
||||
{
|
||||
guard let appExtension = ALTApplication(fileURL: fileURL) else { continue }
|
||||
var profiles = [app.bundleIdentifier: profile]
|
||||
var error: Error?
|
||||
|
||||
dispatchGroup.enter()
|
||||
let dispatchGroup = DispatchGroup()
|
||||
|
||||
self.prepareProvisioningProfile(for: appExtension, parentApp: app, team: team, session: session) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let e): error = e
|
||||
case .success(let profile):
|
||||
profiles[appExtension.bundleIdentifier] = profile
|
||||
for appExtension in app.appExtensions
|
||||
{
|
||||
dispatchGroup.enter()
|
||||
|
||||
self.prepareProvisioningProfile(for: appExtension, parentApp: app, team: team, session: session) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let e): error = e
|
||||
case .success(let profile): profiles[appExtension.bundleIdentifier] = profile
|
||||
}
|
||||
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .global()) {
|
||||
if let error = error
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(.success(profiles))
|
||||
}
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .global()) {
|
||||
if let error = error
|
||||
{
|
||||
completionHandler(.failure(error))
|
||||
}
|
||||
else
|
||||
{
|
||||
completionHandler(.success(profiles))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,8 +171,19 @@ private extension ResignAppOperation
|
||||
preferredBundleID = app.bundleIdentifier.replacingOccurrences(of: parentBundleID, with: updatedParentBundleID)
|
||||
}
|
||||
|
||||
let preferredName: String
|
||||
|
||||
if let parentApp = parentApp
|
||||
{
|
||||
preferredName = "\(parentApp.name) - \(app.name)"
|
||||
}
|
||||
else
|
||||
{
|
||||
preferredName = app.name
|
||||
}
|
||||
|
||||
// Register
|
||||
self.register(app, bundleIdentifier: preferredBundleID, team: team, session: session) { (result) in
|
||||
self.registerAppID(for: app, name: preferredName, bundleIdentifier: preferredBundleID, team: team, session: session) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let error): completionHandler(.failure(error))
|
||||
@@ -215,10 +216,8 @@ private extension ResignAppOperation
|
||||
}
|
||||
}
|
||||
|
||||
func register(_ app: ALTApplication, bundleIdentifier: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
|
||||
func registerAppID(for application: ALTApplication, name: String, bundleIdentifier: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
|
||||
{
|
||||
let appName = app.name
|
||||
|
||||
ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) { (appIDs, error) in
|
||||
do
|
||||
{
|
||||
@@ -230,7 +229,27 @@ private extension ResignAppOperation
|
||||
}
|
||||
else
|
||||
{
|
||||
ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
|
||||
let requiredAppIDs = 1 + application.appExtensions.count
|
||||
let availableAppIDs = max(0, Team.maximumFreeAppIDs - appIDs.count)
|
||||
|
||||
let sortedExpirationDates = appIDs.compactMap { $0.expirationDate }.sorted(by: { $0 < $1 })
|
||||
|
||||
if team.type == .free
|
||||
{
|
||||
if requiredAppIDs > availableAppIDs
|
||||
{
|
||||
if let expirationDate = sortedExpirationDates.first
|
||||
{
|
||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ALTAppleAPIError(.maximumAppIDLimitReached)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ALTAppleAPI.shared.addAppID(withName: name, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
|
||||
do
|
||||
{
|
||||
do
|
||||
@@ -240,11 +259,9 @@ private extension ResignAppOperation
|
||||
}
|
||||
catch ALTAppleAPIError.maximumAppIDLimitReached
|
||||
{
|
||||
let sortedExpirationDates = appIDs.compactMap { $0.expirationDate }.sorted(by: { $0 < $1 })
|
||||
|
||||
if let expirationDate = sortedExpirationDates.first
|
||||
{
|
||||
throw OperationError.maximumAppIDLimitReached(expirationDate)
|
||||
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user