Write widget data when article status changes happen. Fixes #3567

This commit is contained in:
Maurice Parker 2022-07-28 17:15:36 -05:00
parent 7683a96de0
commit 141ed4f915
3 changed files with 37 additions and 25 deletions

View File

@ -20,21 +20,48 @@ public final class WidgetDataEncoder {
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application") private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Application")
private let fetchLimit = 7 private let fetchLimit = 7
private var backgroundTaskID: UIBackgroundTaskIdentifier!
private lazy var appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String private lazy var appGroup = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
private lazy var containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) private lazy var containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup)
private lazy var imageContainer = containerURL?.appendingPathComponent("widgetImages", isDirectory: true) private lazy var imageContainer = containerURL?.appendingPathComponent("widgetImages", isDirectory: true)
private lazy var dataURL = containerURL?.appendingPathComponent("widget-data.json") private lazy var dataURL = containerURL?.appendingPathComponent("widget-data.json")
static let shared = WidgetDataEncoder() private let encodeWidgetDataQueue = CoalescingQueue(name: "Encode the Widget Data", interval: 5.0)
private init () {
init () {
if imageContainer != nil { if imageContainer != nil {
try? FileManager.default.createDirectory(at: imageContainer!, withIntermediateDirectories: true, attributes: nil) try? FileManager.default.createDirectory(at: imageContainer!, withIntermediateDirectories: true, attributes: nil)
} }
if #available(iOS 14, *) {
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
}
}
func encodeIfNecessary() {
encodeWidgetDataQueue.performCallsImmediately()
}
@available(iOS 14, *)
@objc func statusesDidChange(_ note: Notification) {
encodeWidgetDataQueue.add(self, #selector(performEncodeWidgetData))
}
@available(iOS 14, *)
@objc private func performEncodeWidgetData() {
// We will be on the Main Thread when the encodeIfNecessary function is called. We want
// block the main thread in that case so that the widget data is encoded. If it is on
// a background Thread, it was called by the CoalescingQueue. In that case we need to
// move it to the Main Thread and want to execute it async.
if Thread.isMainThread {
encodeWidgetData()
} else {
DispatchQueue.main.async {
self.encodeWidgetData()
}
}
} }
@available(iOS 14, *) @available(iOS 14, *)
func encodeWidgetData() throws { private func encodeWidgetData() {
flushSharedContainer() flushSharedContainer()
os_log(.debug, log: log, "Starting encoding widget data.") os_log(.debug, log: log, "Starting encoding widget data.")
@ -89,10 +116,6 @@ public final class WidgetDataEncoder {
DispatchQueue.global().async { [weak self] in DispatchQueue.global().async { [weak self] in
guard let self = self else { return } guard let self = self else { return }
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask (withName: "com.ranchero.NetNewsWire.Encode") {
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid
}
let encodedData = try? JSONEncoder().encode(latestData) let encodedData = try? JSONEncoder().encode(latestData)
os_log(.debug, log: self.log, "Finished encoding widget data.") os_log(.debug, log: self.log, "Finished encoding widget data.")
@ -104,14 +127,11 @@ public final class WidgetDataEncoder {
if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) { if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) {
os_log(.debug, log: self.log, "Wrote widget data to container.") os_log(.debug, log: self.log, "Wrote widget data to container.")
WidgetCenter.shared.reloadAllTimelines() WidgetCenter.shared.reloadAllTimelines()
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid
} else {
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = .invalid
} }
} }
} catch {
os_log(.error, log: log, "WidgetDataEncoder failed to write the widget data.")
} }
} }

View File

@ -45,6 +45,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
var webFeedIconDownloader: WebFeedIconDownloader! var webFeedIconDownloader: WebFeedIconDownloader!
var extensionContainersFile: ExtensionContainersFile! var extensionContainersFile: ExtensionContainersFile!
var extensionFeedAddRequestFile: ExtensionFeedAddRequestFile! var extensionFeedAddRequestFile: ExtensionFeedAddRequestFile!
var widgetDataEncoder: WidgetDataEncoder!
var unreadCount = 0 { var unreadCount = 0 {
didSet { didSet {
@ -114,6 +115,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
extensionContainersFile = ExtensionContainersFile() extensionContainersFile = ExtensionContainersFile()
extensionFeedAddRequestFile = ExtensionFeedAddRequestFile() extensionFeedAddRequestFile = ExtensionFeedAddRequestFile()
widgetDataEncoder = WidgetDataEncoder()
syncTimer = ArticleStatusSyncTimer() syncTimer = ArticleStatusSyncTimer()
#if DEBUG #if DEBUG
@ -172,6 +175,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func prepareAccountsForBackground() { func prepareAccountsForBackground() {
extensionFeedAddRequestFile.suspend() extensionFeedAddRequestFile.suspend()
widgetDataEncoder.encodeIfNecessary()
syncTimer?.invalidate() syncTimer?.invalidate()
scheduleBackgroundFeedRefresh() scheduleBackgroundFeedRefresh()
syncArticleStatus() syncArticleStatus()
@ -398,9 +402,6 @@ private extension AppDelegate {
} }
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) { [unowned self] in AccountManager.shared.refreshAll(errorHandler: ErrorHandler.log) { [unowned self] in
if !AccountManager.shared.isSuspended { if !AccountManager.shared.isSuspended {
if #available(iOS 14, *) {
try? WidgetDataEncoder.shared.encodeWidgetData()
}
self.suspendApplication() self.suspendApplication()
os_log("Account refresh operation completed.", log: self.log, type: .info) os_log("Account refresh operation completed.", log: self.log, type: .info)
task.setTaskCompleted(success: true) task.setTaskCompleted(success: true)
@ -445,9 +446,6 @@ private extension AppDelegate {
self.prepareAccountsForBackground() self.prepareAccountsForBackground()
account!.syncArticleStatus(completion: { [weak self] _ in account!.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended { if !AccountManager.shared.isSuspended {
if #available(iOS 14, *) {
try? WidgetDataEncoder.shared.encodeWidgetData()
}
self?.prepareAccountsForBackground() self?.prepareAccountsForBackground()
self?.suspendApplication() self?.suspendApplication()
} }
@ -474,9 +472,6 @@ private extension AppDelegate {
account!.markArticles(article!, statusKey: .starred, flag: true) { _ in } account!.markArticles(article!, statusKey: .starred, flag: true) { _ in }
account!.syncArticleStatus(completion: { [weak self] _ in account!.syncArticleStatus(completion: { [weak self] _ in
if !AccountManager.shared.isSuspended { if !AccountManager.shared.isSuspended {
if #available(iOS 14, *) {
try? WidgetDataEncoder.shared.encodeWidgetData()
}
self?.prepareAccountsForBackground() self?.prepareAccountsForBackground()
self?.suspendApplication() self?.suspendApplication()
} }

View File

@ -66,9 +66,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
} }
func sceneDidEnterBackground(_ scene: UIScene) { func sceneDidEnterBackground(_ scene: UIScene) {
if #available(iOS 14, *) {
try? WidgetDataEncoder.shared.encodeWidgetData()
}
ArticleStringFormatter.emptyCaches() ArticleStringFormatter.emptyCaches()
appDelegate.prepareAccountsForBackground() appDelegate.prepareAccountsForBackground()
} }