mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-16 12:30:29 +00:00
177 lines
5.9 KiB
Swift
177 lines
5.9 KiB
Swift
//
|
|
// ContinuedProcessingManager.swift
|
|
// StikJIT
|
|
//
|
|
// Created by Codex on 11/20/24.
|
|
//
|
|
|
|
import Foundation
|
|
import BackgroundTasks
|
|
|
|
final class ContinuedProcessingManager {
|
|
static let shared = ContinuedProcessingManager()
|
|
private let handler: ContinuedProcessingHandling
|
|
|
|
private init() {
|
|
if #available(iOS 26.0, *) {
|
|
handler = ModernContinuedProcessingHandler()
|
|
} else {
|
|
handler = NoopContinuedProcessingHandler()
|
|
}
|
|
}
|
|
|
|
var isSupported: Bool { handler.isSupported }
|
|
|
|
func configureIfNeeded() {
|
|
handler.configureIfNeeded()
|
|
}
|
|
|
|
func cancelPendingTasks() {
|
|
handler.cancelPendingTasks()
|
|
}
|
|
|
|
func begin(title: String, subtitle: String) {
|
|
handler.begin(title: title, subtitle: subtitle)
|
|
}
|
|
|
|
func updateProgress(_ fraction: Double) {
|
|
handler.updateProgress(fraction)
|
|
}
|
|
|
|
func finish(success: Bool) {
|
|
handler.finish(success: success)
|
|
}
|
|
}
|
|
|
|
private protocol ContinuedProcessingHandling: AnyObject {
|
|
var isSupported: Bool { get }
|
|
func configureIfNeeded()
|
|
func cancelPendingTasks()
|
|
func begin(title: String, subtitle: String)
|
|
func updateProgress(_ fraction: Double)
|
|
func finish(success: Bool)
|
|
}
|
|
|
|
private final class NoopContinuedProcessingHandler: ContinuedProcessingHandling {
|
|
var isSupported: Bool { false }
|
|
func configureIfNeeded() {}
|
|
func cancelPendingTasks() {}
|
|
func begin(title: String, subtitle: String) {}
|
|
func updateProgress(_ fraction: Double) {}
|
|
func finish(success: Bool) {}
|
|
}
|
|
|
|
@available(iOS 26.0, *)
|
|
private final class ModernContinuedProcessingHandler: ContinuedProcessingHandling {
|
|
private let scheduler = BGTaskScheduler.shared
|
|
private let taskIdentifier: String
|
|
private var didRegister = false
|
|
private var activeTask: BGContinuedProcessingTask?
|
|
private let queue = DispatchQueue(label: "com.stikdebug.continuedProcessing",
|
|
qos: .utility)
|
|
private var pendingMetadata: (title: String, subtitle: String)?
|
|
|
|
init() {
|
|
let bundleID = Bundle.main.bundleIdentifier ?? "com.stik.sj"
|
|
taskIdentifier = "\(bundleID).continuedProcessingTask.script"
|
|
}
|
|
|
|
var isSupported: Bool { true }
|
|
|
|
func configureIfNeeded() {
|
|
guard !didRegister else { return }
|
|
scheduler.register(forTaskWithIdentifier: taskIdentifier, using: nil) { [weak self] task in
|
|
guard let continuedTask = task as? BGContinuedProcessingTask else {
|
|
task.setTaskCompleted(success: false)
|
|
return
|
|
}
|
|
self?.handle(task: continuedTask)
|
|
}
|
|
didRegister = true
|
|
}
|
|
|
|
func begin(title: String, subtitle: String) {
|
|
guard UserDefaults.standard.bool(forKey: UserDefaults.Keys.enableContinuedProcessing) else { return }
|
|
configureIfNeeded()
|
|
var reserved = false
|
|
queue.sync {
|
|
if activeTask == nil && pendingMetadata == nil {
|
|
pendingMetadata = (title: title, subtitle: subtitle)
|
|
reserved = true
|
|
}
|
|
}
|
|
guard reserved else { return }
|
|
// Clear any stale request that might block new submissions.
|
|
scheduler.cancel(taskRequestWithIdentifier: taskIdentifier)
|
|
let request = BGContinuedProcessingTaskRequest(identifier: taskIdentifier,
|
|
title: title,
|
|
subtitle: subtitle)
|
|
request.strategy = .queue
|
|
do {
|
|
try scheduler.submit(request)
|
|
LogManager.shared.addInfoLog("Requested continued processing: \(title)")
|
|
} catch {
|
|
LogManager.shared.addWarningLog("Unable to request continued processing: \(error.localizedDescription)")
|
|
queue.async { [weak self] in
|
|
self?.pendingMetadata = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func cancelPendingTasks() {
|
|
queue.async { [weak self] in
|
|
guard let self else { return }
|
|
if let task = activeTask {
|
|
task.setTaskCompleted(success: false)
|
|
activeTask = nil
|
|
}
|
|
pendingMetadata = nil
|
|
scheduler.cancel(taskRequestWithIdentifier: taskIdentifier)
|
|
}
|
|
}
|
|
|
|
func updateProgress(_ fraction: Double) {
|
|
queue.async { [weak self] in
|
|
guard let task = self?.activeTask else { return }
|
|
let clamped = max(0.0, min(1.0, fraction))
|
|
task.progress.totalUnitCount = max(task.progress.totalUnitCount, 100)
|
|
task.progress.completedUnitCount = Int64(Double(task.progress.totalUnitCount) * clamped)
|
|
}
|
|
}
|
|
|
|
func finish(success: Bool) {
|
|
queue.async { [weak self] in
|
|
guard let self else { return }
|
|
if let task = self.activeTask {
|
|
task.progress.completedUnitCount = task.progress.totalUnitCount
|
|
task.setTaskCompleted(success: success)
|
|
self.activeTask = nil
|
|
} else if pendingMetadata != nil {
|
|
scheduler.cancel(taskRequestWithIdentifier: taskIdentifier)
|
|
}
|
|
pendingMetadata = nil
|
|
}
|
|
}
|
|
|
|
private func handle(task: BGContinuedProcessingTask) {
|
|
queue.async { [weak self] in
|
|
guard let self else { return }
|
|
activeTask = task
|
|
if let metadata = pendingMetadata {
|
|
task.updateTitle(metadata.title, subtitle: metadata.subtitle)
|
|
}
|
|
if task.progress.totalUnitCount == 0 {
|
|
task.progress.totalUnitCount = 100
|
|
}
|
|
task.progress.completedUnitCount = 1
|
|
task.expirationHandler = { [weak self] in
|
|
self?.handleExpiration()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func handleExpiration() {
|
|
LogManager.shared.addWarningLog("Continued processing expired early")
|
|
finish(success: false)
|
|
}
|
|
}
|