133 lines
4.9 KiB
Swift
133 lines
4.9 KiB
Swift
//
|
|
// 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")
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|