Adds support for isBackgroundRefreshEnabled setting

This commit is contained in:
Riley Testut
2019-09-05 11:51:49 -07:00
parent 88ab3f0c37
commit 859f8a255c
2 changed files with 158 additions and 117 deletions

View File

@@ -63,6 +63,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
ServerManager.shared.startDiscovering() ServerManager.shared.startDiscovering()
UserDefaults.standard.registerDefaults()
if UserDefaults.standard.firstLaunch == nil if UserDefaults.standard.firstLaunch == nil
{ {
Keychain.shared.reset() Keychain.shared.reset()
@@ -129,8 +131,11 @@ extension AppDelegate
} }
func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void) func application(_ application: UIApplication, performFetchWithCompletionHandler backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void)
{
if UserDefaults.standard.isBackgroundRefreshEnabled
{ {
ServerManager.shared.startDiscovering() ServerManager.shared.startDiscovering()
}
let refreshIdentifier = UUID().uuidString let refreshIdentifier = UUID().uuidString
@@ -140,9 +145,11 @@ extension AppDelegate
{ {
// If finish is actually called, that means an error occured during installation. // If finish is actually called, that means an error occured during installation.
if UserDefaults.standard.isBackgroundRefreshEnabled
{
ServerManager.shared.stopDiscovering() ServerManager.shared.stopDiscovering()
self.scheduleFinishedRefreshingNotification(for: result, identifier: refreshIdentifier, delay: 0) self.scheduleFinishedRefreshingNotification(for: result, identifier: refreshIdentifier, delay: 0)
}
taskCompletionHandler() taskCompletionHandler()
} }
@@ -183,12 +190,73 @@ private extension AppDelegate
backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void, backgroundFetchCompletionHandler: @escaping (UIBackgroundFetchResult) -> Void,
completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void) completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
{ {
var fetchSourceResult: Result<Source, Error>?
var serversResult: Result<Void, Error>?
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
AppManager.shared.fetchSource() { (result) in
fetchSourceResult = result
do
{
let source = try result.get()
guard let context = source.managedObjectContext else { return }
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest() as! NSFetchRequest<NSFetchRequestResult>
previousUpdatesFetchRequest.includesPendingChanges = false
previousUpdatesFetchRequest.resultType = .dictionaryResultType
previousUpdatesFetchRequest.propertiesToFetch = [#keyPath(InstalledApp.bundleIdentifier)]
let previousUpdates = try context.fetch(previousUpdatesFetchRequest) as! [[String: String]]
try context.save()
let updatesFetchRequest = InstalledApp.updatesFetchRequest()
let updates = try context.fetch(updatesFetchRequest)
for update in updates
{
guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue }
guard let storeApp = update.storeApp else { continue }
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("New Update Available", comment: "")
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, storeApp.version)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
}
DispatchQueue.main.async {
UIApplication.shared.applicationIconBadgeNumber = updates.count
}
}
catch
{
print("Error fetching apps:", error)
fetchSourceResult = .failure(error)
}
dispatchGroup.leave()
}
if UserDefaults.standard.isBackgroundRefreshEnabled
{
dispatchGroup.enter()
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context) let installedApps = InstalledApp.fetchAppsForBackgroundRefresh(in: context)
guard !installedApps.isEmpty else { guard !installedApps.isEmpty else {
backgroundFetchCompletionHandler(.noData) serversResult = .success(())
dispatchGroup.leave()
completionHandler(.failure(RefreshError.noInstalledApps)) completionHandler(.failure(RefreshError.noInstalledApps))
return return
} }
@@ -210,74 +278,6 @@ private extension AppDelegate
} }
} }
var fetchSourceResult: Result<Source, Error>?
var serversResult: Result<Void, Error>?
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
dispatchGroup.enter()
AppManager.shared.fetchSource() { (result) in
fetchSourceResult = result
dispatchGroup.leave()
do
{
let source = try result.get()
guard let context = source.managedObjectContext else { return }
let updatesFetchRequest = InstalledApp.updatesFetchRequest()
updatesFetchRequest.includesPendingChanges = true
let previousUpdatesFetchRequest = InstalledApp.updatesFetchRequest()
previousUpdatesFetchRequest.includesPendingChanges = false
let previousUpdates = try context.fetch(previousUpdatesFetchRequest)
try context.save()
let updates = try context.fetch(updatesFetchRequest)
for update in updates
{
guard !previousUpdates.contains(where: { $0.bundleIdentifier == update.bundleIdentifier }) else { continue }
guard let storeApp = update.storeApp else { continue }
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("New Update Available", comment: "")
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, storeApp.version)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
}
DispatchQueue.main.async {
UIApplication.shared.applicationIconBadgeNumber = updates.count
}
}
catch
{
print("Error fetching apps:", error)
}
}
dispatchGroup.notify(queue: .main) {
guard let fetchSourceResult = fetchSourceResult, let serversResult = serversResult else {
backgroundFetchCompletionHandler(.failed)
return
}
// Call completionHandler early to improve chances of refreshing in the background again.
switch (fetchSourceResult, serversResult)
{
case (.success, .success): backgroundFetchCompletionHandler(.newData)
case (.success, .failure(ConnectionError.serverNotFound)): backgroundFetchCompletionHandler(.newData)
case (.failure, _), (_, .failure): backgroundFetchCompletionHandler(.failed)
}
}
// Wait for three seconds to: // Wait for three seconds to:
// a) give us time to discover AltServers // a) give us time to discover AltServers
// b) give other processes a chance to respond to requestAppState notification // b) give other processes a chance to respond to requestAppState notification
@@ -328,6 +328,40 @@ private extension AppDelegate
} }
} }
dispatchGroup.notify(queue: .main) {
if !UserDefaults.standard.isBackgroundRefreshEnabled
{
guard let fetchSourceResult = fetchSourceResult else {
backgroundFetchCompletionHandler(.failed)
return
}
switch fetchSourceResult
{
case .failure: backgroundFetchCompletionHandler(.failed)
case .success: backgroundFetchCompletionHandler(.newData)
}
completionHandler(.success([:]))
}
else
{
guard let fetchSourceResult = fetchSourceResult, let serversResult = serversResult else {
backgroundFetchCompletionHandler(.failed)
return
}
// Call completionHandler early to improve chances of refreshing in the background again.
switch (fetchSourceResult, serversResult)
{
case (.success, .success): backgroundFetchCompletionHandler(.newData)
case (.success, .failure(ConnectionError.serverNotFound)): backgroundFetchCompletionHandler(.newData)
case (.failure, _), (_, .failure): backgroundFetchCompletionHandler(.failed)
}
}
}
}
func receivedApplicationState(notification: CFNotificationName) func receivedApplicationState(notification: CFNotificationName)
{ {
let baseName = String(CFNotificationName.appIsRunning.rawValue) let baseName = String(CFNotificationName.appIsRunning.rawValue)

View File

@@ -15,4 +15,11 @@ extension UserDefaults
@NSManaged var firstLaunch: Date? @NSManaged var firstLaunch: Date?
@NSManaged var preferredServerID: String? @NSManaged var preferredServerID: String?
@NSManaged var isBackgroundRefreshEnabled: Bool
func registerDefaults()
{
self.register(defaults: [#keyPath(UserDefaults.isBackgroundRefreshEnabled): true])
}
} }