eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1import BackgroundTasks
2import Foundation
3import LChatCore
4
5// 🤖 Background App Refresh: iOS periodically wakes the app (cadence is the
6// system's call — it learns from usage) and BackgroundSync pulls + decrypts +
7// files new messages into the local store off the main thread. Together with
8// the NSE's per-push ingest this keeps chats caught up before they're opened.
9//
10// The identifier must be listed in Info.plist's
11// BGTaskSchedulerPermittedIdentifiers (project.yml), and registration must
12// happen before didFinishLaunching returns (PushManager wires it).
13enum BackgroundRefresh {
14 static var taskID: String {
15 (Bundle.main.bundleIdentifier ?? "com.elevenmessenger.messenger") + ".refresh"
16 }
17
18 static func register() {
19 BGTaskScheduler.shared.register(forTaskWithIdentifier: taskID, using: nil) { task in
20 guard let refresh = task as? BGAppRefreshTask else {
21 task.setTaskCompleted(success: false)
22 return
23 }
24 handle(refresh)
25 }
26 }
27
28 /// (Re)request the next wake. Cheap and idempotent — a duplicate submit
29 /// for a pending request just replaces it. Called when the app backgrounds
30 /// and from the handler itself, so the chain never breaks.
31 static func schedule() {
32 let req = BGAppRefreshTaskRequest(identifier: taskID)
33 req.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
34 try? BGTaskScheduler.shared.submit(req)
35 }
36
37 private static func handle(_ task: BGAppRefreshTask) {
38 schedule()
39 // completed-once guard: the sync finishing and the expiration handler
40 // can race; BGTaskScheduler wants exactly one completion call.
41 let done = OneShot()
42 let work = Task {
43 // The app's background close gated the store (MessageStore.suspend);
44 // this pass is a legitimate write window, so open the bracket first.
45 await MessageStore.shared.resume()
46 await BackgroundSync.run(instances: InstancesStore.load())
47 // Release the shared store's connection BEFORE declaring done —
48 // the process suspends any moment after, and a held app-group
49 // file lock at suspension is the 0xdead10cc kill.
50 await MessageStore.shared.suspend()
51 if done.claim() { task.setTaskCompleted(success: true) }
52 }
53 task.expirationHandler = {
54 work.cancel()
55 Task {
56 await MessageStore.shared.suspend()
57 if done.claim() { task.setTaskCompleted(success: false) }
58 }
59 }
60 }
61
62 private final class OneShot: @unchecked Sendable {
63 private let lock = NSLock()
64 private var used = false
65 func claim() -> Bool {
66 lock.lock()
67 defer { lock.unlock() }
68 if used { return false }
69 used = true
70 return true
71 }
72 }
73}
74