Skip to content

Commit baf35e7

Browse files
committed
chore: save current iOS app and widget changes
1 parent bc5fc01 commit baf35e7

15 files changed

Lines changed: 775 additions & 21 deletions

ios/VaultSync/App/AppDelegate.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
1212
) -> Bool {
1313
application.registerForRemoteNotifications()
1414
logger.info("Registered for remote notifications")
15+
logger.debug("Custom URL routing is handled by VaultSyncApp.onOpenURL; AppDelegate remains dedicated to push and background delivery")
1516
Task { await refreshNotificationAuthorizationState() }
1617
return true
1718
}

ios/VaultSync/App/VaultSyncApp.swift

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,24 @@ struct VaultSyncApp: App {
2424

2525
var body: some Scene {
2626
WindowGroup {
27-
if hasCompletedOnboarding {
28-
ContentView(
29-
syncthingManager: syncthingManager,
30-
vaultManager: vaultManager,
31-
subscriptionManager: subscriptionManager
32-
)
33-
} else {
34-
OnboardingView(
35-
hasCompletedOnboarding: $hasCompletedOnboarding,
36-
syncthingManager: syncthingManager,
37-
vaultManager: vaultManager,
38-
subscriptionManager: subscriptionManager
39-
)
27+
Group {
28+
if hasCompletedOnboarding {
29+
ContentView(
30+
syncthingManager: syncthingManager,
31+
vaultManager: vaultManager,
32+
subscriptionManager: subscriptionManager
33+
)
34+
} else {
35+
OnboardingView(
36+
hasCompletedOnboarding: $hasCompletedOnboarding,
37+
syncthingManager: syncthingManager,
38+
vaultManager: vaultManager,
39+
subscriptionManager: subscriptionManager
40+
)
41+
}
42+
}
43+
.onOpenURL { url in
44+
handleIncomingURL(url)
4045
}
4146
}
4247
.onChange(of: scenePhase) { _, newPhase in
@@ -87,4 +92,22 @@ struct VaultSyncApp: App {
8792
}
8893
}
8994
}
95+
96+
private func handleIncomingURL(_ url: URL) {
97+
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
98+
components.scheme?.caseInsensitiveCompare("vaultsync") == .orderedSame,
99+
components.host?.caseInsensitiveCompare("sync") == .orderedSame else {
100+
logger.debug("Ignoring unsupported incoming URL: \(url.absoluteString, privacy: .public)")
101+
return
102+
}
103+
104+
let folderID = components.queryItems?
105+
.first(where: { $0.name == "folder" })?
106+
.value?
107+
.trimmingCharacters(in: .whitespacesAndNewlines)
108+
let normalizedFolderID = folderID.flatMap { $0.isEmpty ? nil : $0 }
109+
110+
logger.info("Handling sync URL request (folder=\(normalizedFolderID ?? "all", privacy: .public))")
111+
syncthingManager.triggerForegroundSync(folderID: normalizedFolderID)
112+
}
90113
}

ios/VaultSync/Services/BackgroundSyncService.swift

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,9 @@ enum BackgroundSyncService {
302302
) async -> SyncResult {
303303
logger.info("Background sync starting (reason=\(reason))")
304304
trace("Starting background sync (reason=\(reason), maxDuration=\(Int(maxDuration))s).")
305+
let syncStartedAt = Date()
305306
var telemetryEventCursor = latestBridgeEventID()
307+
let syncStartEventCursor = telemetryEventCursor
306308

307309
// Silent pushes arrive after iOS has suspended the process. The Go
308310
// bridge's cached state still reports isRunning=true, but the TCP
@@ -336,7 +338,9 @@ enum BackgroundSyncService {
336338
return completeSync(
337339
reason: reason,
338340
result: .noBookmarkAccess,
339-
detail: L10n.tr("No security-scoped bookmark access was available.")
341+
detail: L10n.tr("No security-scoped bookmark access was available."),
342+
startedAt: syncStartedAt,
343+
initialEventCursor: syncStartEventCursor
340344
)
341345
}
342346
trace("Restored bookmark access for \(managedURLs.count) URL(s).")
@@ -352,7 +356,9 @@ enum BackgroundSyncService {
352356
return completeSync(
353357
reason: reason,
354358
result: .bridgeStartFailed,
355-
detail: err
359+
detail: err,
360+
startedAt: syncStartedAt,
361+
initialEventCursor: syncStartEventCursor
356362
)
357363
}
358364
trace("Bridge start requested for background-owned sync.")
@@ -368,7 +374,9 @@ enum BackgroundSyncService {
368374
return completeSync(
369375
reason: reason,
370376
result: .noFoldersConfigured,
371-
detail: L10n.tr("No folders were available for background sync.")
377+
detail: L10n.tr("No folders were available for background sync."),
378+
startedAt: syncStartedAt,
379+
initialEventCursor: syncStartEventCursor
372380
)
373381
}
374382

@@ -397,7 +405,9 @@ enum BackgroundSyncService {
397405
return completeSync(
398406
reason: reason,
399407
result: .bridgeStartFailed,
400-
detail: restart.errorDetail ?? L10n.tr("Forced silent-push restart failed.")
408+
detail: restart.errorDetail ?? L10n.tr("Forced silent-push restart failed."),
409+
startedAt: syncStartedAt,
410+
initialEventCursor: syncStartEventCursor
401411
)
402412
}
403413

@@ -423,7 +433,9 @@ enum BackgroundSyncService {
423433
return completeSync(
424434
reason: reason,
425435
result: .noFoldersConfigured,
426-
detail: L10n.tr("No folders were available after forced silent-push restart.")
436+
detail: L10n.tr("No folders were available after forced silent-push restart."),
437+
startedAt: syncStartedAt,
438+
initialEventCursor: syncStartEventCursor
427439
)
428440
}
429441

@@ -443,7 +455,13 @@ enum BackgroundSyncService {
443455
if ownsLifecycle {
444456
cleanupBackgroundManaged(managedURLs)
445457
}
446-
return completeSync(reason: reason, result: .alreadyIdle, detail: nil)
458+
return completeSync(
459+
reason: reason,
460+
result: .alreadyIdle,
461+
detail: nil,
462+
startedAt: syncStartedAt,
463+
initialEventCursor: syncStartEventCursor
464+
)
447465
}
448466

449467
let deadline = Date(timeIntervalSinceNow: maxDuration)
@@ -495,7 +513,13 @@ enum BackgroundSyncService {
495513
result = .notIdleBeforeDeadline
496514
detail = L10n.fmt("Sync did not reach idle before %ds deadline.", Int(maxDuration))
497515
}
498-
return completeSync(reason: reason, result: result, detail: detail)
516+
return completeSync(
517+
reason: reason,
518+
result: result,
519+
detail: detail,
520+
startedAt: syncStartedAt,
521+
initialEventCursor: syncStartEventCursor
522+
)
499523
}
500524

501525
// MARK: - BGAppRefreshTask Handler
@@ -603,7 +627,9 @@ enum BackgroundSyncService {
603627
private static func completeSync(
604628
reason: String,
605629
result: SyncResult,
606-
detail: String?
630+
detail: String?,
631+
startedAt: Date,
632+
initialEventCursor: Int
607633
) -> SyncResult {
608634
let outcome = SyncOutcome(
609635
timestamp: Date(),
@@ -612,6 +638,15 @@ enum BackgroundSyncService {
612638
detail: detail
613639
)
614640
persistSyncOutcome(outcome)
641+
WidgetSnapshotStore.write(
642+
snapshot: WidgetSnapshotStore.Snapshot(
643+
lastSyncTime: WidgetSnapshotStore.iso8601String(from: outcome.timestamp),
644+
lastSyncDuration: max(0, outcome.timestamp.timeIntervalSince(startedAt)),
645+
status: result.isSuccessful ? "idle" : "error",
646+
filesSynced: syncedFileCount(since: initialEventCursor),
647+
folderCount: currentFolderCount()
648+
)
649+
)
615650
if let detail, !detail.isEmpty {
616651
trace("Completed with result=\(result.rawValue): \(detail)")
617652
} else {
@@ -679,6 +714,26 @@ enum BackgroundSyncService {
679714
return total / Double(folders.count)
680715
}
681716

717+
private static func currentFolderCount() -> Int {
718+
let json = SyncBridgeService.getFoldersJSON()
719+
guard let data = json.data(using: .utf8),
720+
let folders = try? JSONDecoder().decode([FolderStub].self, from: data) else {
721+
return 0
722+
}
723+
return folders.count
724+
}
725+
726+
private static func syncedFileCount(since lastEventID: Int) -> Int {
727+
let events = decodeBridgeEvents(from: SyncBridgeService.getEventsSince(lastID: lastEventID))
728+
return events.reduce(into: 0) { count, event in
729+
guard event.type == "ItemFinished" else { return }
730+
let error = event.data?["error"] ?? ""
731+
if error.isEmpty {
732+
count += 1
733+
}
734+
}
735+
}
736+
682737
private static func notifyConflictsIfAny() async {
683738
let json = SyncBridgeService.getFoldersJSON()
684739
guard let data = json.data(using: .utf8),

0 commit comments

Comments
 (0)