frenzy-ios/Reader/BackgroundManager.swift

134 lines
5.0 KiB
Swift
Raw Normal View History

2022-06-22 20:05:06 +00:00
//
// BackgroundManager.swift
// Reader
//
// Created by Shadowfacts on 6/20/22.
//
import Foundation
import BackgroundTasks
import OSLog
import Persistence
import Fervor
private let logger = Logger(subsystem: "net.shadowfacts.Reader", category: "BackgroundManager")
class BackgroundManager: NSObject {
static let shared = BackgroundManager()
private override init() {}
static let refreshIdentifier = "net.shadowfacts.Reader.refresh"
private var refreshTask: BGAppRefreshTask?
private var completedRefreshRequests = 0
private var receivedData: [Int: Data] = [:]
func scheduleRefresh() {
// we schedule refreshes from sceneDidEnterBackground, but there may be multiple scenes, and we only want one refresh task
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: BackgroundManager.refreshIdentifier)
logger.debug("Scheduling background refresh task")
let request = BGAppRefreshTaskRequest(identifier: BackgroundManager.refreshIdentifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: 2 * 60 * 60)
// request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
do {
try BGTaskScheduler.shared.submit(request)
} catch {
logger.error("Unable to schedule app refresh: \(String(describing: error), privacy: .public)")
}
}
func handleBackgroundRefresh(task: BGAppRefreshTask) {
logger.debug("Handling background refresh task")
scheduleRefresh()
guard !LocalData.accounts.isEmpty else {
task.setTaskCompleted(success: true)
return
}
self.refreshTask = task
self.completedRefreshRequests = 0
doRefresh()
task.expirationHandler = {
task.setTaskCompleted(success: self.completedRefreshRequests == LocalData.accounts.count)
}
}
func doRefresh() {
let config = URLSessionConfiguration.background(withIdentifier: BackgroundManager.refreshIdentifier)
config.sessionSendsLaunchEvents = true
let backgroundSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
Task {
for account in LocalData.accounts {
let fervorController = await FervorController(account: account, session: backgroundSession)
let itemsRequest = await fervorController.client.itemsRequest(limit: 32)
let task = backgroundSession.dataTask(with: itemsRequest)
task.taskDescription = account.id.base64EncodedString()
task.resume()
}
}
}
private func receivedData(_ data: Data, for task: URLSessionDataTask) {
guard let taskDescription = task.taskDescription,
let id = Data(base64Encoded: taskDescription),
let account = LocalData.account(with: id) else {
logger.error("Could not find account to handle request")
return
}
Task { @MainActor in
defer {
if let refreshTask {
refreshTask.setTaskCompleted(success: completedRefreshRequests == LocalData.accounts.count)
}
}
let items: [Fervor.Item]
do {
items = try FervorClient.decoder.decode([Fervor.Item].self, from: data)
} catch {
logger.error("Unable to decode items: \(String(describing: error), privacy: .public)")
return
}
let fervorController = await FervorController(account: account)
do {
try await fervorController.persistentContainer.upsertItems(items)
logger.info("Upserted \(items.count) items during background refresh")
2022-09-08 02:57:48 +00:00
logger.info("Most recent unread item: \(items.first(where: { $0.read == false })!.title!, privacy: .public)")
2022-06-22 20:05:06 +00:00
await WidgetHelper.updateWidgetData(fervorController: fervorController)
completedRefreshRequests += 1
} catch {
logger.error("Unable to upsert items: \(String(describing: error), privacy: .public)")
}
}
}
}
extension BackgroundManager: URLSessionDataDelegate {
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
if !receivedData.keys.contains(dataTask.taskIdentifier) {
receivedData[dataTask.taskIdentifier] = Data(capacity: Int(dataTask.countOfBytesExpectedToReceive))
}
receivedData[dataTask.taskIdentifier]!.append(data)
logger.debug("Received chunk of \(data.count) bytes")
if dataTask.countOfBytesReceived == dataTask.countOfBytesExpectedToReceive {
logger.info("Received all data for task, handling update")
receivedData(receivedData.removeValue(forKey: dataTask.taskIdentifier)!, for: dataTask)
}
}
}