Change widget encoding to be more parallel and only run when going into the background

This commit is contained in:
Maurice Parker 2022-10-29 00:39:03 -05:00
parent c181cb1d39
commit 169b103f6d
2 changed files with 112 additions and 91 deletions

View File

@ -25,110 +25,132 @@ public final class WidgetDataEncoder {
private lazy var imageContainer = containerURL?.appendingPathComponent("widgetImages", isDirectory: true)
private lazy var dataURL = containerURL?.appendingPathComponent("widget-data.json")
private var searchWorkItem: DispatchWorkItem?
public var isRunning = false
init () {
if imageContainer != nil {
try? FileManager.default.createDirectory(at: imageContainer!, withIntermediateDirectories: true, attributes: nil)
}
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
}
func pause() {
searchWorkItem?.cancel()
}
func resume() {
dispatchWorkItem()
}
@objc func statusesDidChange(_ note: Notification) {
dispatchWorkItem()
}
func dispatchWorkItem() {
func encode() {
if #available(iOS 14, *) {
searchWorkItem?.cancel()
searchWorkItem = DispatchWorkItem { [weak self] in
self?.encodeWidgetData()
isRunning = true
flushSharedContainer()
os_log(.debug, log: log, "Starting encoding widget data.")
DispatchQueue.main.async {
self.encodeWidgetData() { latestData in
guard let latestData = latestData else {
self.isRunning = false
return
}
let encodedData = try? JSONEncoder().encode(latestData)
os_log(.debug, log: self.log, "Finished encoding widget data.")
if self.fileExists() {
try? FileManager.default.removeItem(at: self.dataURL!)
os_log(.debug, log: self.log, "Removed widget data from container.")
}
if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) {
os_log(.debug, log: self.log, "Wrote widget data to container.")
WidgetCenter.shared.reloadAllTimelines()
}
self.isRunning = false
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: searchWorkItem!)
}
}
@available(iOS 14, *)
private func encodeWidgetData() {
flushSharedContainer()
os_log(.debug, log: log, "Starting encoding widget data.")
private func encodeWidgetData(completion: @escaping (WidgetData?) -> Void) {
var dispatchGroup = DispatchGroup()
var groupError: Error? = nil
do {
let unreadArticles = Array(try AccountManager.shared.fetchArticles(.unread(fetchLimit))).sortedByDate(.orderedDescending)
let starredArticles = Array(try AccountManager.shared.fetchArticles(.starred(fetchLimit))).sortedByDate(.orderedDescending)
let todayArticles = Array(try AccountManager.shared.fetchArticles(.today(fetchLimit))).sortedByDate(.orderedDescending)
var unread = [LatestArticle]()
var today = [LatestArticle]()
var starred = [LatestArticle]()
for article in unreadArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIconPath: writeImageDataToSharedContainer(article.iconImage()?.image.dataRepresentation()),
pubDate: article.datePublished?.description ?? "")
unread.append(latestArticle)
}
for article in starredArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIconPath: writeImageDataToSharedContainer(article.iconImage()?.image.dataRepresentation()),
pubDate: article.datePublished?.description ?? "")
starred.append(latestArticle)
}
for article in todayArticles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIconPath: writeImageDataToSharedContainer(article.iconImage()?.image.dataRepresentation()),
pubDate: article.datePublished?.description ?? "")
today.append(latestArticle)
}
let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount,
currentTodayCount: SmartFeedsController.shared.todayFeed.unreadCount,
currentStarredCount: try AccountManager.shared.fetchCountForStarredArticles(),
unreadArticles: unread,
starredArticles: starred,
todayArticles:today,
lastUpdateTime: Date())
DispatchQueue.global().async { [weak self] in
guard let self = self else { return }
let encodedData = try? JSONEncoder().encode(latestData)
os_log(.debug, log: self.log, "Finished encoding widget data.")
if self.fileExists() {
try? FileManager.default.removeItem(at: self.dataURL!)
os_log(.debug, log: self.log, "Removed widget data from container.")
var unread = [LatestArticle]()
dispatchGroup.enter()
AccountManager.shared.fetchArticlesAsync(.unread(fetchLimit)) { (articleSetResult) in
switch articleSetResult {
case .success(let articles):
for article in articles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIconPath: self.writeImageDataToSharedContainer(article.iconImage()?.image.dataRepresentation()),
pubDate: article.datePublished?.description ?? "")
unread.append(latestArticle)
}
if FileManager.default.createFile(atPath: self.dataURL!.path, contents: encodedData, attributes: nil) {
os_log(.debug, log: self.log, "Wrote widget data to container.")
WidgetCenter.shared.reloadAllTimelines()
}
case .failure(let databaseError):
groupError = databaseError
}
} catch {
os_log(.error, log: log, "WidgetDataEncoder failed to write the widget data.")
dispatchGroup.leave()
}
var starred = [LatestArticle]()
dispatchGroup.enter()
AccountManager.shared.fetchArticlesAsync(.starred(fetchLimit)) { (articleSetResult) in
switch articleSetResult {
case .success(let articles):
for article in articles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIconPath: self.writeImageDataToSharedContainer(article.iconImage()?.image.dataRepresentation()),
pubDate: article.datePublished?.description ?? "")
starred.append(latestArticle)
}
case .failure(let databaseError):
groupError = databaseError
}
dispatchGroup.leave()
}
var today = [LatestArticle]()
dispatchGroup.enter()
AccountManager.shared.fetchArticlesAsync(.today(fetchLimit)) { (articleSetResult) in
switch articleSetResult {
case .success(let articles):
for article in articles {
let latestArticle = LatestArticle(id: article.sortableArticleID,
feedTitle: article.sortableName,
articleTitle: ArticleStringFormatter.truncatedTitle(article).isEmpty ? ArticleStringFormatter.truncatedSummary(article) : ArticleStringFormatter.truncatedTitle(article),
articleSummary: article.summary,
feedIconPath: self.writeImageDataToSharedContainer(article.iconImage()?.image.dataRepresentation()),
pubDate: article.datePublished?.description ?? "")
today.append(latestArticle)
}
case .failure(let databaseError):
groupError = databaseError
}
dispatchGroup.leave()
}
dispatchGroup.notify(queue: .main) {
if groupError != nil {
os_log(.error, log: self.log, "WidgetDataEncoder failed to write the widget data.")
completion(nil)
} else {
let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount,
currentTodayCount: SmartFeedsController.shared.todayFeed.unreadCount,
currentStarredCount: (try? AccountManager.shared.fetchCountForStarredArticles()) ?? 0,
unreadArticles: unread,
starredArticles: starred,
todayArticles:today,
lastUpdateTime: Date())
completion(latestData)
}
}
}
private func fileExists() -> Bool {

View File

@ -175,16 +175,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
func prepareAccountsForBackground() {
extensionFeedAddRequestFile.suspend()
widgetDataEncoder.pause()
syncTimer?.invalidate()
scheduleBackgroundFeedRefresh()
syncArticleStatus()
widgetDataEncoder.encode()
waitForSyncTasksToFinish()
}
func prepareAccountsForForeground() {
extensionFeedAddRequestFile.resume()
widgetDataEncoder.resume()
syncTimer?.update()
if let lastRefresh = AppDefaults.shared.lastRefresh {
@ -298,7 +297,7 @@ private extension AppDelegate {
return
}
if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning {
if AccountManager.shared.refreshInProgress || isSyncArticleStatusRunning || widgetDataEncoder.isRunning {
os_log("Waiting for sync to finish...", log: self.log, type: .info)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
self?.waitToComplete(completion: completion)