rastrillo / native Public

Clone
git clone https://amadan.net/rastrillo/native

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2
3/// A failure the person has to see and act on.
4public enum SyncFailure: Sendable, Equatable {
5 /// An entry is held; the app owns the wording and the action.
6 case held(Hold.Reason)
7 /// Enqueueing failed (disk full, or the store is unreadable): input pauses.
8 case cannotSaveOnDevice
9 /// The app's per-lane byte ceiling was reached: input stops, and says why.
10 case tooManyUnsent
11}
12
13/// What the save status says, computed from outbox state and never from HTTP
14/// activity. The cases are in priority order: the first that applies wins.
15///
16/// "Offline" with nothing pending is `.saved`: status stays silent and the
17/// connectivity chrome says offline, so "Offline · 0 pending" never appears.
18public enum SyncStatus: Sendable, Equatable {
19 /// 1. A held entry, or a local failure. Sticky until resolved.
20 case failed(pending: Int, reason: SyncFailure)
21 /// 2. The session ended with changes still to send. Sticky until sign-in.
22 case needsSignIn(pending: Int)
23 /// 3. Nothing to send and nothing in flight. Silent.
24 case saved
25 /// 4. A send is in flight (or about to start).
26 case saving
27 /// 5. Changes are waiting for a connection.
28 case offline(pending: Int)
29 /// 6. Backing off after a transient failure.
30 case retrying(pending: Int)
31
32 public enum SaveState: String, Sendable { case saved, saving, retrying, failed }
33 public var saveState: SaveState {
34 switch self {
35 case .saved: return .saved
36 case .saving: return .saving
37 case .offline, .retrying: return .retrying
38 case .failed, .needsSignIn: return .failed
39 }
40 }
41 public var isSticky: Bool { saveState == .failed }
42 public var isAnnounced: Bool {
43 switch self { case .offline, .needsSignIn, .failed: return true; default: return false }
44 }
45 public init(summary: OutboxSummary, sending: Bool, sessionEnded: Bool, offline: Bool, backingOff: Bool,
46 localFailure: SyncFailure? = nil, count: (OutboxSummary) -> Int = { $0.pendingLanes }) {
47 let pending = count(summary)
48 if let held = summary.lanes.sorted(by: { $0.key < $1.key }).compactMap({ $0.value.firstHold }).first {
49 self = .failed(pending: pending, reason: .held(held))
50 } else if let localFailure { self = .failed(pending: pending, reason: localFailure) }
51 else if summary.unavailable { self = .failed(pending: pending, reason: .cannotSaveOnDevice) }
52 else if sessionEnded { self = .needsSignIn(pending: pending) }
53 else if summary.isEmpty && summary.inFlight == 0 { self = .saved }
54 else if sending || summary.inFlight > 0 { self = .saving }
55 else if offline { self = .offline(pending: pending) }
56 else if backingOff { self = .retrying(pending: pending) }
57 else { self = .saving }
58 }
59}
60