2018-02-05 22:29:46 +01:00
|
|
|
//
|
|
|
|
// AppDelegate.swift
|
2019-04-15 22:03:05 +02:00
|
|
|
// NetNewsWire
|
2018-02-05 22:29:46 +01:00
|
|
|
//
|
2019-04-15 22:03:05 +02:00
|
|
|
// Created by Maurice Parker on 4/8/19.
|
|
|
|
// Copyright © 2019 Ranchero Software. All rights reserved.
|
2018-02-05 22:29:46 +01:00
|
|
|
//
|
|
|
|
|
|
|
|
import UIKit
|
2024-04-02 04:31:57 +02:00
|
|
|
import Web
|
2019-04-15 22:03:05 +02:00
|
|
|
import Account
|
2024-07-01 03:14:42 +02:00
|
|
|
@preconcurrency import BackgroundTasks
|
2019-04-26 14:44:00 +02:00
|
|
|
import os.log
|
2020-11-18 03:49:12 +01:00
|
|
|
import WidgetKit
|
2024-03-21 04:49:15 +01:00
|
|
|
import Core
|
2024-04-16 07:21:17 +02:00
|
|
|
import Images
|
2024-09-30 06:03:24 +02:00
|
|
|
import libxml2
|
2019-04-15 22:03:05 +02:00
|
|
|
|
2024-04-03 07:07:19 +02:00
|
|
|
@MainActor var appDelegate: AppDelegate!
|
2018-02-05 22:29:46 +01:00
|
|
|
|
|
|
|
@UIApplicationMain
|
2024-09-30 06:58:39 +02:00
|
|
|
final class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, UnreadCountProvider {
|
2019-06-19 17:26:03 +02:00
|
|
|
|
2019-11-06 23:47:33 +01:00
|
|
|
private var bgTaskDispatchQueue = DispatchQueue.init(label: "BGTaskScheduler")
|
|
|
|
|
2019-11-01 01:20:52 +01:00
|
|
|
private var waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
2019-06-20 01:09:42 +02:00
|
|
|
private var syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
|
|
|
|
2019-05-20 20:51:08 +02:00
|
|
|
var syncTimer: ArticleStatusSyncTimer?
|
2019-04-24 14:30:35 +02:00
|
|
|
|
2019-05-20 20:51:08 +02:00
|
|
|
var shuttingDown = false {
|
|
|
|
didSet {
|
|
|
|
if shuttingDown {
|
|
|
|
syncTimer?.shuttingDown = shuttingDown
|
|
|
|
syncTimer?.invalidate()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-06-19 17:26:03 +02:00
|
|
|
|
2024-05-04 08:10:57 +02:00
|
|
|
nonisolated(unsafe) let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
|
2019-06-19 17:26:03 +02:00
|
|
|
|
2019-10-03 02:42:16 +02:00
|
|
|
var userNotificationManager: UserNotificationManager!
|
2020-02-09 22:08:11 +01:00
|
|
|
var extensionContainersFile: ExtensionContainersFile!
|
|
|
|
var extensionFeedAddRequestFile: ExtensionFeedAddRequestFile!
|
2019-06-19 17:26:03 +02:00
|
|
|
|
2019-04-15 22:03:05 +02:00
|
|
|
var unreadCount = 0 {
|
|
|
|
didSet {
|
|
|
|
if unreadCount != oldValue {
|
2024-07-03 06:43:52 +02:00
|
|
|
handleUnreadCountDidChange()
|
2019-04-15 22:03:05 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-01 23:51:25 +01:00
|
|
|
var isSyncArticleStatusRunning = false
|
|
|
|
var isWaitingForSyncTasks = false
|
2024-03-20 04:33:54 +01:00
|
|
|
|
2019-04-15 22:03:05 +02:00
|
|
|
override init() {
|
2019-09-12 17:59:26 +02:00
|
|
|
|
2024-08-19 02:25:29 +02:00
|
|
|
xmlInitParser()
|
|
|
|
|
2024-03-20 04:33:54 +01:00
|
|
|
super.init()
|
|
|
|
|
|
|
|
appDelegate = self
|
2024-03-11 06:22:41 +01:00
|
|
|
|
2019-04-15 22:03:05 +02:00
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
2019-04-26 22:24:39 +02:00
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(accountRefreshDidFinish(_:)), name: .AccountRefreshDidFinish, object: nil)
|
2019-04-15 22:03:05 +02:00
|
|
|
}
|
2019-06-19 17:26:03 +02:00
|
|
|
|
2019-04-15 20:30:10 +02:00
|
|
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
2019-04-15 22:03:05 +02:00
|
|
|
|
2024-07-08 01:59:54 +02:00
|
|
|
AppDefaults.registerDefaults()
|
|
|
|
if AppDefaults.shared.isFirstRun {
|
|
|
|
os_log(.debug, "Is first run.")
|
2019-10-18 17:24:32 +02:00
|
|
|
}
|
2024-07-08 01:59:54 +02:00
|
|
|
|
2024-10-01 07:16:54 +02:00
|
|
|
FaviconGenerator.faviconTemplateImage = AppAsset.faviconTemplateImage
|
2024-04-16 07:21:17 +02:00
|
|
|
|
2024-07-08 01:59:54 +02:00
|
|
|
importFeedsIfNeeded()
|
|
|
|
|
2019-11-06 23:53:13 +01:00
|
|
|
registerBackgroundTasks()
|
2019-11-08 19:20:21 +01:00
|
|
|
CacheCleaner.purgeIfNecessary()
|
2019-09-01 23:54:07 +02:00
|
|
|
initializeDownloaders()
|
|
|
|
initializeHomeScreenQuickActions()
|
2019-06-19 17:26:03 +02:00
|
|
|
|
2024-03-20 04:33:54 +01:00
|
|
|
Task { @MainActor in
|
2024-07-08 01:23:47 +02:00
|
|
|
self.unreadCount = AccountManager.shared.unreadCount
|
2019-04-15 22:03:05 +02:00
|
|
|
}
|
2024-03-20 04:33:54 +01:00
|
|
|
|
2020-09-23 00:39:29 +02:00
|
|
|
UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .sound, .alert]) { (granted, error) in
|
|
|
|
if granted {
|
2024-03-20 04:33:54 +01:00
|
|
|
Task { @MainActor in
|
2019-04-23 14:48:22 +02:00
|
|
|
UIApplication.shared.registerForRemoteNotifications()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-03-30 09:48:25 +02:00
|
|
|
|
2019-10-03 02:42:16 +02:00
|
|
|
UNUserNotificationCenter.current().delegate = self
|
|
|
|
userNotificationManager = UserNotificationManager()
|
|
|
|
|
2020-02-09 22:08:11 +01:00
|
|
|
extensionContainersFile = ExtensionContainersFile()
|
|
|
|
extensionFeedAddRequestFile = ExtensionFeedAddRequestFile()
|
|
|
|
|
2019-05-20 20:51:08 +02:00
|
|
|
syncTimer = ArticleStatusSyncTimer()
|
2019-06-19 17:26:03 +02:00
|
|
|
|
2019-05-20 20:51:08 +02:00
|
|
|
#if DEBUG
|
2019-06-19 17:26:03 +02:00
|
|
|
syncTimer!.update()
|
2019-05-20 20:51:08 +02:00
|
|
|
#endif
|
2021-11-02 11:44:21 +01:00
|
|
|
|
2018-02-05 22:29:46 +01:00
|
|
|
return true
|
|
|
|
}
|
2019-06-19 17:26:03 +02:00
|
|
|
|
2024-03-25 07:36:05 +01:00
|
|
|
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) async -> UIBackgroundFetchResult {
|
|
|
|
|
|
|
|
resumeDatabaseProcessingIfNecessary()
|
2024-07-08 01:23:47 +02:00
|
|
|
await AccountManager.shared.receiveRemoteNotification(userInfo: userInfo)
|
2024-03-25 07:36:05 +01:00
|
|
|
suspendApplication()
|
|
|
|
return .newData
|
|
|
|
}
|
|
|
|
|
2019-06-28 17:28:02 +02:00
|
|
|
func applicationWillTerminate(_ application: UIApplication) {
|
|
|
|
shuttingDown = true
|
|
|
|
}
|
2021-05-08 21:42:44 +02:00
|
|
|
|
|
|
|
func applicationDidEnterBackground(_ application: UIApplication) {
|
2024-06-08 21:12:43 +02:00
|
|
|
|
|
|
|
ArticleStringFormatter.emptyCaches()
|
|
|
|
MultilineUILabelSizer.emptyCache()
|
|
|
|
SingleLineUILabelSizer.emptyCache()
|
2021-05-08 21:42:44 +02:00
|
|
|
IconImageCache.shared.emptyCache()
|
2024-07-08 01:23:47 +02:00
|
|
|
AccountManager.shared.emptyCaches()
|
2024-06-08 21:12:43 +02:00
|
|
|
|
|
|
|
Task.detached {
|
|
|
|
await DownloadWithCacheManager.shared.cleanupCache()
|
|
|
|
}
|
2021-05-08 21:42:44 +02:00
|
|
|
}
|
2019-06-28 17:28:02 +02:00
|
|
|
|
|
|
|
// MARK: Notifications
|
|
|
|
|
|
|
|
@objc func unreadCountDidChange(_ note: Notification) {
|
|
|
|
if note.object is AccountManager {
|
2024-07-08 01:23:47 +02:00
|
|
|
unreadCount = AccountManager.shared.unreadCount
|
2019-06-28 17:28:02 +02:00
|
|
|
}
|
2018-02-05 22:29:46 +01:00
|
|
|
}
|
2019-06-19 17:26:03 +02:00
|
|
|
|
2019-06-28 17:28:02 +02:00
|
|
|
@objc func accountRefreshDidFinish(_ note: Notification) {
|
2020-07-02 04:47:45 +02:00
|
|
|
AppDefaults.shared.lastRefresh = Date()
|
2019-06-28 17:28:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - API
|
2020-03-11 21:47:00 +01:00
|
|
|
|
|
|
|
func manualRefresh(errorHandler: @escaping (Error) -> ()) {
|
2024-04-08 02:06:39 +02:00
|
|
|
|
|
|
|
let sceneDelegates = UIApplication.shared.connectedScenes.compactMap{ $0.delegate as? SceneDelegate }
|
|
|
|
for sceneDelegate in sceneDelegates {
|
|
|
|
sceneDelegate.cleanUp(conditional: true)
|
2020-03-11 21:47:00 +01:00
|
|
|
}
|
2024-03-26 07:36:27 +01:00
|
|
|
|
|
|
|
Task { @MainActor in
|
2024-07-08 01:23:47 +02:00
|
|
|
await AccountManager.shared.refreshAll(errorHandler: errorHandler)
|
2024-03-26 07:36:27 +01:00
|
|
|
}
|
2020-03-11 21:47:00 +01:00
|
|
|
}
|
|
|
|
|
2020-01-11 00:32:06 +01:00
|
|
|
func resumeDatabaseProcessingIfNecessary() {
|
2024-07-08 01:23:47 +02:00
|
|
|
if AccountManager.shared.isSuspended {
|
|
|
|
AccountManager.shared.resumeAll()
|
2020-01-11 00:32:06 +01:00
|
|
|
os_log("Application processing resumed.", log: self.log, type: .info)
|
|
|
|
}
|
|
|
|
}
|
2019-06-28 17:28:02 +02:00
|
|
|
|
|
|
|
func prepareAccountsForBackground() {
|
2020-02-09 22:08:11 +01:00
|
|
|
extensionFeedAddRequestFile.suspend()
|
2019-05-20 20:51:08 +02:00
|
|
|
syncTimer?.invalidate()
|
2019-06-19 17:26:03 +02:00
|
|
|
scheduleBackgroundFeedRefresh()
|
2019-11-01 01:20:52 +01:00
|
|
|
syncArticleStatus()
|
2019-12-01 23:51:25 +01:00
|
|
|
waitForSyncTasksToFinish()
|
2018-02-05 22:29:46 +01:00
|
|
|
}
|
2019-06-19 17:26:03 +02:00
|
|
|
|
2019-06-28 17:28:02 +02:00
|
|
|
func prepareAccountsForForeground() {
|
2020-02-09 22:08:11 +01:00
|
|
|
extensionFeedAddRequestFile.resume()
|
2020-04-22 03:23:46 +02:00
|
|
|
syncTimer?.update()
|
|
|
|
|
2024-03-26 07:36:27 +01:00
|
|
|
Task { @MainActor in
|
|
|
|
if let lastRefresh = AppDefaults.shared.lastRefresh {
|
|
|
|
if Date() > lastRefresh.addingTimeInterval(15 * 60) {
|
2024-07-08 01:23:47 +02:00
|
|
|
await AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
|
2024-03-26 07:36:27 +01:00
|
|
|
} else {
|
2024-07-08 01:23:47 +02:00
|
|
|
await AccountManager.shared.syncArticleStatusAll()
|
2024-03-26 07:06:05 +01:00
|
|
|
}
|
2024-03-26 07:36:27 +01:00
|
|
|
} else {
|
2024-07-08 01:23:47 +02:00
|
|
|
await AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
|
2019-04-26 22:24:39 +02:00
|
|
|
}
|
|
|
|
}
|
2018-02-05 22:29:46 +01:00
|
|
|
}
|
2024-03-26 07:36:27 +01:00
|
|
|
|
2024-05-04 07:35:20 +02:00
|
|
|
nonisolated func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
|
|
|
|
2024-02-26 04:02:29 +01:00
|
|
|
completionHandler([.list, .banner, .badge, .sound])
|
2019-10-03 02:42:16 +02:00
|
|
|
}
|
|
|
|
|
2024-07-01 03:14:42 +02:00
|
|
|
nonisolated func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
|
|
|
|
|
|
|
MainActor.assumeIsolated {
|
|
|
|
defer { completionHandler() }
|
|
|
|
|
|
|
|
let userInfo = response.notification.request.content.userInfo
|
|
|
|
|
|
|
|
switch response.actionIdentifier {
|
|
|
|
case "MARK_AS_READ":
|
|
|
|
handleMarkAsRead(userInfo: userInfo)
|
|
|
|
case "MARK_AS_STARRED":
|
|
|
|
handleMarkAsStarred(userInfo: userInfo)
|
|
|
|
default:
|
|
|
|
if let sceneDelegate = response.targetScene?.delegate as? SceneDelegate {
|
|
|
|
sceneDelegate.handle(response)
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
|
|
|
|
sceneDelegate.coordinator.dismissIfLaunchingFromExternalAction()
|
|
|
|
})
|
|
|
|
}
|
2020-12-23 13:15:25 +01:00
|
|
|
}
|
2019-10-03 16:53:21 +02:00
|
|
|
}
|
|
|
|
}
|
2018-02-05 22:29:46 +01:00
|
|
|
}
|
|
|
|
|
2019-09-01 23:54:07 +02:00
|
|
|
// MARK: App Initialization
|
|
|
|
|
|
|
|
private extension AppDelegate {
|
2024-04-16 07:21:17 +02:00
|
|
|
|
2019-09-01 23:54:07 +02:00
|
|
|
private func initializeHomeScreenQuickActions() {
|
|
|
|
let unreadTitle = NSLocalizedString("First Unread", comment: "First Unread")
|
2019-09-26 22:51:16 +02:00
|
|
|
let unreadIcon = UIApplicationShortcutIcon(systemImageName: "chevron.down.circle")
|
2019-09-01 23:54:07 +02:00
|
|
|
let unreadItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.FirstUnread", localizedTitle: unreadTitle, localizedSubtitle: nil, icon: unreadIcon, userInfo: nil)
|
|
|
|
|
|
|
|
let searchTitle = NSLocalizedString("Search", comment: "Search")
|
|
|
|
let searchIcon = UIApplicationShortcutIcon(systemImageName: "magnifyingglass")
|
|
|
|
let searchItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.ShowSearch", localizedTitle: searchTitle, localizedSubtitle: nil, icon: searchIcon, userInfo: nil)
|
|
|
|
|
2019-09-02 22:45:09 +02:00
|
|
|
let addTitle = NSLocalizedString("Add Feed", comment: "Add Feed")
|
2019-09-02 22:14:26 +02:00
|
|
|
let addIcon = UIApplicationShortcutIcon(systemImageName: "plus")
|
|
|
|
let addItem = UIApplicationShortcutItem(type: "com.ranchero.NetNewsWire.ShowAdd", localizedTitle: addTitle, localizedSubtitle: nil, icon: addIcon, userInfo: nil)
|
|
|
|
|
2019-09-02 23:05:55 +02:00
|
|
|
UIApplication.shared.shortcutItems = [addItem, searchItem, unreadItem]
|
2019-09-01 23:54:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2019-11-01 01:20:52 +01:00
|
|
|
// MARK: Go To Background
|
2019-12-02 21:14:35 +01:00
|
|
|
|
2019-11-01 01:20:52 +01:00
|
|
|
private extension AppDelegate {
|
|
|
|
|
2019-12-01 23:51:25 +01:00
|
|
|
func waitForSyncTasksToFinish() {
|
2019-12-06 23:16:20 +01:00
|
|
|
guard !isWaitingForSyncTasks && UIApplication.shared.applicationState == .background else { return }
|
2019-11-01 01:20:52 +01:00
|
|
|
|
2019-12-01 23:51:25 +01:00
|
|
|
isWaitingForSyncTasks = true
|
|
|
|
|
|
|
|
self.waitBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask { [weak self] in
|
|
|
|
guard let self = self else { return }
|
|
|
|
self.completeProcessing(true)
|
2019-11-01 01:20:52 +01:00
|
|
|
os_log("Accounts wait for progress terminated for running too long.", log: self.log, type: .info)
|
|
|
|
}
|
|
|
|
|
|
|
|
DispatchQueue.main.async { [weak self] in
|
2019-12-01 23:51:25 +01:00
|
|
|
self?.waitToComplete() { [weak self] suspend in
|
|
|
|
self?.completeProcessing(suspend)
|
2019-11-01 01:20:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-01 23:51:25 +01:00
|
|
|
func waitToComplete(completion: @escaping (Bool) -> Void) {
|
2019-12-06 23:16:20 +01:00
|
|
|
guard UIApplication.shared.applicationState == .background else {
|
2022-01-04 23:25:20 +01:00
|
|
|
os_log("App came back to foreground, no longer waiting.", log: self.log, type: .info)
|
2019-12-01 23:51:25 +01:00
|
|
|
completion(false)
|
2019-11-01 01:20:52 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-07-08 01:23:47 +02:00
|
|
|
if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning {
|
2019-12-01 23:51:25 +01:00
|
|
|
os_log("Waiting for sync to finish...", log: self.log, type: .info)
|
2019-11-01 01:20:52 +01:00
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
2019-12-01 23:51:25 +01:00
|
|
|
self?.waitToComplete(completion: completion)
|
2019-11-01 01:20:52 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
os_log("Refresh progress complete.", log: self.log, type: .info)
|
2019-12-01 23:51:25 +01:00
|
|
|
completion(true)
|
2019-11-01 01:20:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-01 23:51:25 +01:00
|
|
|
func completeProcessing(_ suspend: Bool) {
|
|
|
|
if suspend {
|
2019-12-02 21:14:35 +01:00
|
|
|
suspendApplication()
|
2019-12-01 23:51:25 +01:00
|
|
|
}
|
|
|
|
UIApplication.shared.endBackgroundTask(self.waitBackgroundUpdateTask)
|
|
|
|
self.waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
|
|
|
isWaitingForSyncTasks = false
|
|
|
|
}
|
|
|
|
|
2019-11-01 01:20:52 +01:00
|
|
|
func syncArticleStatus() {
|
2019-12-01 23:51:25 +01:00
|
|
|
guard !isSyncArticleStatusRunning else { return }
|
|
|
|
|
|
|
|
isSyncArticleStatusRunning = true
|
|
|
|
|
2019-11-01 01:20:52 +01:00
|
|
|
let completeProcessing = { [unowned self] in
|
2019-12-01 23:51:25 +01:00
|
|
|
self.isSyncArticleStatusRunning = false
|
2019-11-01 01:20:52 +01:00
|
|
|
UIApplication.shared.endBackgroundTask(self.syncBackgroundUpdateTask)
|
|
|
|
self.syncBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid
|
|
|
|
}
|
|
|
|
|
|
|
|
self.syncBackgroundUpdateTask = UIApplication.shared.beginBackgroundTask {
|
|
|
|
completeProcessing()
|
|
|
|
os_log("Accounts sync processing terminated for running too long.", log: self.log, type: .info)
|
|
|
|
}
|
|
|
|
|
2024-03-20 04:33:54 +01:00
|
|
|
Task { @MainActor in
|
2024-07-08 01:23:47 +02:00
|
|
|
await AccountManager.shared.syncArticleStatusAll()
|
2024-03-26 07:06:05 +01:00
|
|
|
completeProcessing()
|
2019-11-01 01:20:52 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-02 21:14:35 +01:00
|
|
|
func suspendApplication() {
|
2019-12-06 23:16:20 +01:00
|
|
|
guard UIApplication.shared.applicationState == .background else { return }
|
2019-12-02 21:44:52 +01:00
|
|
|
|
2024-07-08 01:23:47 +02:00
|
|
|
AccountManager.shared.suspendNetworkAll()
|
|
|
|
AccountManager.shared.suspendDatabaseAll()
|
2024-03-25 05:25:11 +01:00
|
|
|
ArticleThemeDownloader.cleanUp()
|
2020-02-02 21:11:39 +01:00
|
|
|
|
2019-12-02 21:14:35 +01:00
|
|
|
CoalescingQueue.standard.performCallsImmediately()
|
|
|
|
for scene in UIApplication.shared.connectedScenes {
|
|
|
|
if let sceneDelegate = scene.delegate as? SceneDelegate {
|
|
|
|
sceneDelegate.suspend()
|
|
|
|
}
|
|
|
|
}
|
2019-12-05 01:27:39 +01:00
|
|
|
|
2019-12-06 01:43:38 +01:00
|
|
|
os_log("Application processing suspended.", log: self.log, type: .info)
|
2019-12-02 21:14:35 +01:00
|
|
|
}
|
|
|
|
|
2019-11-01 01:20:52 +01:00
|
|
|
}
|
|
|
|
|
2019-09-01 23:54:07 +02:00
|
|
|
// MARK: Background Tasks
|
2019-06-19 17:26:03 +02:00
|
|
|
|
2019-04-27 20:54:52 +02:00
|
|
|
private extension AppDelegate {
|
2019-06-28 17:28:02 +02:00
|
|
|
|
2019-06-19 17:26:03 +02:00
|
|
|
/// Register all background tasks.
|
|
|
|
func registerBackgroundTasks() {
|
|
|
|
// Register background feed refresh.
|
|
|
|
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.ranchero.NetNewsWire.FeedRefresh", using: nil) { (task) in
|
|
|
|
self.performBackgroundFeedRefresh(with: task as! BGAppRefreshTask)
|
2019-04-29 16:29:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-19 17:26:03 +02:00
|
|
|
/// Schedules a background app refresh based on `AppDefaults.refreshInterval`.
|
|
|
|
func scheduleBackgroundFeedRefresh() {
|
|
|
|
let request = BGAppRefreshTaskRequest(identifier: "com.ranchero.NetNewsWire.FeedRefresh")
|
2019-11-14 00:13:06 +01:00
|
|
|
request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
|
2019-11-06 02:14:26 +01:00
|
|
|
|
2019-11-14 00:13:06 +01:00
|
|
|
// We send this to a dedicated serial queue because as of 11/05/19 on iOS 13.2 the call to the
|
2019-11-06 02:14:26 +01:00
|
|
|
// task scheduler can hang indefinitely.
|
2019-11-06 23:47:33 +01:00
|
|
|
bgTaskDispatchQueue.async {
|
2019-11-06 02:14:26 +01:00
|
|
|
do {
|
|
|
|
try BGTaskScheduler.shared.submit(request)
|
|
|
|
} catch {
|
2024-05-04 20:05:45 +02:00
|
|
|
Task { @MainActor in
|
|
|
|
os_log(.error, log: self.log, "Could not schedule app refresh: %@", error.localizedDescription)
|
|
|
|
}
|
2019-11-06 02:14:26 +01:00
|
|
|
}
|
2019-06-19 17:26:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Performs background feed refresh.
|
|
|
|
/// - Parameter task: `BGAppRefreshTask`
|
|
|
|
/// - Warning: As of Xcode 11 beta 2, when triggered from the debugger this doesn't work.
|
|
|
|
func performBackgroundFeedRefresh(with task: BGAppRefreshTask) {
|
|
|
|
|
|
|
|
scheduleBackgroundFeedRefresh() // schedule next refresh
|
|
|
|
|
2019-10-02 23:41:32 +02:00
|
|
|
os_log("Woken to perform account refresh.", log: self.log, type: .info)
|
|
|
|
|
2024-03-20 04:33:54 +01:00
|
|
|
Task { @MainActor in
|
2024-07-08 01:23:47 +02:00
|
|
|
if AccountManager.shared.isSuspended {
|
|
|
|
AccountManager.shared.resumeAll()
|
2019-12-01 23:51:25 +01:00
|
|
|
}
|
2024-07-08 01:23:47 +02:00
|
|
|
await AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log)
|
|
|
|
if !AccountManager.shared.isSuspended {
|
2024-03-26 07:36:27 +01:00
|
|
|
try? WidgetDataEncoder.shared.encodeWidgetData()
|
|
|
|
self.suspendApplication()
|
|
|
|
os_log("Account refresh operation completed.", log: self.log, type: .info)
|
|
|
|
task.setTaskCompleted(success: true)
|
2019-06-19 17:26:03 +02:00
|
|
|
}
|
|
|
|
}
|
2024-02-28 05:06:57 +01:00
|
|
|
|
2019-06-19 17:26:03 +02:00
|
|
|
// set expiration handler
|
2019-11-28 01:03:19 +01:00
|
|
|
task.expirationHandler = { [weak task] in
|
2019-12-05 00:56:09 +01:00
|
|
|
DispatchQueue.main.sync {
|
|
|
|
self.suspendApplication()
|
|
|
|
}
|
2019-06-19 17:26:03 +02:00
|
|
|
os_log("Accounts refresh processing terminated for running too long.", log: self.log, type: .info)
|
2019-11-28 01:03:19 +01:00
|
|
|
task?.setTaskCompleted(success: false)
|
2019-06-19 17:26:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2020-12-23 13:15:25 +01:00
|
|
|
|
|
|
|
// Handle Notification Actions
|
|
|
|
|
|
|
|
private extension AppDelegate {
|
|
|
|
|
2024-03-19 18:15:30 +01:00
|
|
|
@MainActor func handleMarkAsRead(userInfo: [AnyHashable: Any]) {
|
|
|
|
|
|
|
|
guard let articlePathInfo = ArticlePathInfo(userInfo: userInfo) else {
|
|
|
|
return
|
2020-12-23 13:15:25 +01:00
|
|
|
}
|
2024-03-19 18:15:30 +01:00
|
|
|
|
2020-12-23 13:15:25 +01:00
|
|
|
resumeDatabaseProcessingIfNecessary()
|
2024-03-19 18:15:30 +01:00
|
|
|
|
2024-07-08 01:23:47 +02:00
|
|
|
guard let accountID = articlePathInfo.accountID, let account = AccountManager.shared.existingAccount(with: accountID) else {
|
2020-12-23 13:15:25 +01:00
|
|
|
os_log(.debug, "No account found from notification.")
|
|
|
|
return
|
|
|
|
}
|
2024-03-21 04:54:21 +01:00
|
|
|
guard let articleID = articlePathInfo.articleID else {
|
|
|
|
os_log(.debug, "No articleID found from notification.")
|
|
|
|
return
|
|
|
|
}
|
2024-03-19 18:15:30 +01:00
|
|
|
|
|
|
|
Task { @MainActor in
|
|
|
|
guard let articles = try? await account.articles(for: .articleIDs([articleID])) else {
|
|
|
|
os_log(.debug, "No article found from search using %@", articleID)
|
|
|
|
return
|
2020-12-23 13:15:25 +01:00
|
|
|
}
|
2024-03-19 18:15:30 +01:00
|
|
|
|
2024-03-27 02:48:44 +01:00
|
|
|
try? await account.markArticles(articles, statusKey: .read, flag: true)
|
2024-03-19 18:15:30 +01:00
|
|
|
|
|
|
|
self.prepareAccountsForBackground()
|
|
|
|
|
2024-03-27 02:48:44 +01:00
|
|
|
try? await account.syncArticleStatus()
|
2024-07-08 01:23:47 +02:00
|
|
|
if !AccountManager.shared.isSuspended {
|
2024-03-27 00:50:11 +01:00
|
|
|
try? WidgetDataEncoder.shared.encodeWidgetData()
|
|
|
|
self.prepareAccountsForBackground()
|
|
|
|
self.suspendApplication()
|
|
|
|
}
|
2024-03-19 18:15:30 +01:00
|
|
|
}
|
2020-12-23 13:15:25 +01:00
|
|
|
}
|
2024-03-27 00:50:11 +01:00
|
|
|
|
2024-03-19 18:15:30 +01:00
|
|
|
@MainActor func handleMarkAsStarred(userInfo: [AnyHashable: Any]) {
|
|
|
|
|
|
|
|
guard let articlePathInfo = ArticlePathInfo(userInfo: userInfo) else {
|
|
|
|
return
|
2020-12-23 13:15:25 +01:00
|
|
|
}
|
2024-03-19 18:15:30 +01:00
|
|
|
|
2020-12-23 13:15:25 +01:00
|
|
|
resumeDatabaseProcessingIfNecessary()
|
2024-03-19 18:15:30 +01:00
|
|
|
|
2024-07-08 01:23:47 +02:00
|
|
|
guard let accountID = articlePathInfo.accountID, let account = AccountManager.shared.existingAccount(with: accountID) else {
|
2020-12-23 13:15:25 +01:00
|
|
|
os_log(.debug, "No account found from notification.")
|
|
|
|
return
|
|
|
|
}
|
2024-03-21 04:54:21 +01:00
|
|
|
guard let articleID = articlePathInfo.articleID else {
|
|
|
|
os_log(.debug, "No articleID found from notification.")
|
|
|
|
return
|
|
|
|
}
|
2024-03-19 18:15:30 +01:00
|
|
|
|
|
|
|
Task { @MainActor in
|
|
|
|
|
|
|
|
guard let articles = try? await account.articles(for: .articleIDs([articleID])) else {
|
|
|
|
os_log(.debug, "No article found from search using %@", articleID)
|
|
|
|
return
|
2020-12-23 13:15:25 +01:00
|
|
|
}
|
2024-03-19 18:15:30 +01:00
|
|
|
|
2024-03-27 02:48:44 +01:00
|
|
|
try? await account.markArticles(articles, statusKey: .starred, flag: true)
|
2024-03-19 18:15:30 +01:00
|
|
|
|
2024-03-27 00:50:11 +01:00
|
|
|
try? await account.syncArticleStatus()
|
2024-07-08 01:23:47 +02:00
|
|
|
if !AccountManager.shared.isSuspended {
|
2024-03-27 00:50:11 +01:00
|
|
|
try? WidgetDataEncoder.shared.encodeWidgetData()
|
|
|
|
self.prepareAccountsForBackground()
|
|
|
|
self.suspendApplication()
|
|
|
|
}
|
2024-03-19 18:15:30 +01:00
|
|
|
}
|
2020-12-23 13:15:25 +01:00
|
|
|
}
|
|
|
|
}
|