Improve concurrency model for iOS
This commit is contained in:
@@ -10,7 +10,7 @@ class SyncService: ObservableObject {
|
||||
@Published var isConnected = false
|
||||
@Published var isTesting = false
|
||||
@Published var isOfflineMode = false
|
||||
|
||||
|
||||
@Published var providerType: SyncProviderType = .server {
|
||||
didSet {
|
||||
updateActiveProvider()
|
||||
@@ -23,8 +23,6 @@ class SyncService: ObservableObject {
|
||||
private let userDefaults = UserDefaults.standard
|
||||
private let logTag = "SyncService"
|
||||
private var syncTask: Task<Void, Never>?
|
||||
private var pendingChanges = false
|
||||
private let syncDebounceDelay: TimeInterval = 2.0
|
||||
|
||||
private enum Keys {
|
||||
static let serverURL = "sync_server_url"
|
||||
@@ -39,7 +37,7 @@ class SyncService: ObservableObject {
|
||||
// Legacy properties for compatibility with SettingsView
|
||||
var serverURL: String {
|
||||
get { userDefaults.string(forKey: Keys.serverURL) ?? "" }
|
||||
set {
|
||||
set {
|
||||
userDefaults.set(newValue, forKey: Keys.serverURL)
|
||||
// If active provider is server, it will pick up the change from UserDefaults
|
||||
}
|
||||
@@ -66,28 +64,28 @@ class SyncService: ObservableObject {
|
||||
isConnected = userDefaults.bool(forKey: Keys.isConnected)
|
||||
isAutoSyncEnabled = userDefaults.object(forKey: Keys.autoSyncEnabled) as? Bool ?? true
|
||||
isOfflineMode = userDefaults.bool(forKey: Keys.offlineMode)
|
||||
|
||||
|
||||
if let savedType = userDefaults.string(forKey: Keys.providerType),
|
||||
let type = SyncProviderType(rawValue: savedType) {
|
||||
self.providerType = type
|
||||
} else {
|
||||
self.providerType = .server // Default
|
||||
}
|
||||
|
||||
|
||||
updateActiveProvider()
|
||||
}
|
||||
|
||||
|
||||
private func updateActiveProvider() {
|
||||
switch providerType {
|
||||
case .server:
|
||||
activeProvider = ServerSyncProvider()
|
||||
case .iCloud:
|
||||
// Placeholder for iCloud provider
|
||||
activeProvider = nil
|
||||
activeProvider = nil
|
||||
case .none:
|
||||
activeProvider = nil
|
||||
}
|
||||
|
||||
|
||||
// Update status based on new provider
|
||||
if let provider = activeProvider {
|
||||
isConnected = provider.isConnected
|
||||
@@ -101,7 +99,7 @@ class SyncService: ObservableObject {
|
||||
AppLogger.info("Sync skipped: Offline mode is enabled.", tag: logTag)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let provider = activeProvider else {
|
||||
if providerType == .none {
|
||||
return
|
||||
@@ -127,7 +125,7 @@ class SyncService: ObservableObject {
|
||||
|
||||
do {
|
||||
try await provider.sync(dataManager: dataManager)
|
||||
|
||||
|
||||
// Update last sync time
|
||||
// Provider might have updated it in UserDefaults, reload it
|
||||
if let lastSync = userDefaults.object(forKey: Keys.lastSyncTime) as? Date {
|
||||
@@ -144,12 +142,12 @@ class SyncService: ObservableObject {
|
||||
AppLogger.error("Test connection failed: No active provider", tag: logTag)
|
||||
throw SyncError.notConfigured
|
||||
}
|
||||
|
||||
|
||||
isTesting = true
|
||||
defer { isTesting = false }
|
||||
|
||||
|
||||
try await provider.testConnection()
|
||||
|
||||
|
||||
isConnected = provider.isConnected
|
||||
userDefaults.set(isConnected, forKey: Keys.isConnected)
|
||||
}
|
||||
@@ -162,34 +160,19 @@ class SyncService: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
if isSyncing {
|
||||
pendingChanges = true
|
||||
return
|
||||
}
|
||||
guard !isSyncing else { return }
|
||||
|
||||
syncTask?.cancel()
|
||||
|
||||
syncTask = Task {
|
||||
try? await Task.sleep(nanoseconds: UInt64(syncDebounceDelay * 1_000_000_000))
|
||||
|
||||
try? await Task.sleep(for: .seconds(2))
|
||||
guard !Task.isCancelled else { return }
|
||||
|
||||
repeat {
|
||||
pendingChanges = false
|
||||
|
||||
do {
|
||||
try await syncWithServer(dataManager: dataManager)
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
self.isSyncing = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if pendingChanges {
|
||||
try? await Task.sleep(nanoseconds: UInt64(syncDebounceDelay * 1_000_000_000))
|
||||
}
|
||||
} while pendingChanges && !Task.isCancelled
|
||||
do {
|
||||
try await syncWithServer(dataManager: dataManager)
|
||||
} catch {
|
||||
self.isSyncing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,30 +181,26 @@ class SyncService: ObservableObject {
|
||||
|
||||
syncTask?.cancel()
|
||||
syncTask = nil
|
||||
pendingChanges = false
|
||||
|
||||
Task {
|
||||
do {
|
||||
try await syncWithServer(dataManager: dataManager)
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
self.isSyncing = false
|
||||
}
|
||||
self.isSyncing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
activeProvider?.disconnect()
|
||||
|
||||
|
||||
syncTask?.cancel()
|
||||
syncTask = nil
|
||||
pendingChanges = false
|
||||
isSyncing = false
|
||||
isConnected = false
|
||||
lastSyncTime = nil
|
||||
syncError = nil
|
||||
|
||||
|
||||
// These are shared keys, so clearing them affects all providers if they use them
|
||||
// But disconnect() is usually user initiated action
|
||||
userDefaults.set(false, forKey: Keys.isConnected)
|
||||
@@ -239,8 +218,7 @@ class SyncService: ObservableObject {
|
||||
userDefaults.removeObject(forKey: Keys.autoSyncEnabled)
|
||||
syncTask?.cancel()
|
||||
syncTask = nil
|
||||
pendingChanges = false
|
||||
|
||||
|
||||
activeProvider?.disconnect()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user