Move CloudKit logic to PersistenceManager (#355)

Observe updates rather than execute operations imperatively. Also refine
responsibilities of AppContext and CoreContext.
This commit is contained in:
Davide De Rosa 2023-09-10 10:34:42 +02:00 committed by GitHub
parent c645f39254
commit 0872c27fce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 151 additions and 186 deletions

View File

@ -50,7 +50,6 @@
0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */; }; 0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */; };
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */; }; 0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */; };
0E35C09A280E95BB0071FA35 /* ProviderProfileAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */; }; 0E35C09A280E95BB0071FA35 /* ProviderProfileAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */; };
0E3A3C102AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C0F2AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift */; };
0E3A3C132AAB7C480003A5F6 /* UpgradeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */; }; 0E3A3C132AAB7C480003A5F6 /* UpgradeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */; };
0E3A3C142AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */; }; 0E3A3C142AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */; };
0E3A3C162AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */; }; 0E3A3C162AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */; };
@ -344,7 +343,6 @@
0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Scene.swift"; sourceTree = "<group>"; }; 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Scene.swift"; sourceTree = "<group>"; };
0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnDemandView+SSID.swift"; sourceTree = "<group>"; }; 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnDemandView+SSID.swift"; sourceTree = "<group>"; };
0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileAvailability.swift; sourceTree = "<group>"; }; 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileAvailability.swift; sourceTree = "<group>"; };
0E3A3C0F2AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyValueStore+CloudKit.swift"; sourceTree = "<group>"; };
0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManager.swift; sourceTree = "<group>"; }; 0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManager.swift; sourceTree = "<group>"; };
0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultUpgradeManagerStrategy.swift; sourceTree = "<group>"; }; 0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultUpgradeManagerStrategy.swift; sourceTree = "<group>"; };
0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManagerStrategy.swift; sourceTree = "<group>"; }; 0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManagerStrategy.swift; sourceTree = "<group>"; };
@ -746,7 +744,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0EBC075C27EC529000208AD9 /* DebugLog+Constants.swift */, 0EBC075C27EC529000208AD9 /* DebugLog+Constants.swift */,
0E3A3C0F2AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift */,
0EB17EB927D2560300D473B5 /* PassepartoutProviders+Extensions.swift */, 0EB17EB927D2560300D473B5 /* PassepartoutProviders+Extensions.swift */,
0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */, 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */,
0E2DE71B27DCCFE80067B9E1 /* TunnelKit+Extensions.swift */, 0E2DE71B27DCCFE80067B9E1 /* TunnelKit+Extensions.swift */,
@ -1545,7 +1542,6 @@
0E53249927D26B51002565C3 /* ProductManager.swift in Sources */, 0E53249927D26B51002565C3 /* ProductManager.swift in Sources */,
0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */, 0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */,
0EB4042C27CA0E8C00378B1A /* Unlocalized.swift in Sources */, 0EB4042C27CA0E8C00378B1A /* Unlocalized.swift in Sources */,
0E3A3C102AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift in Sources */,
0EB4042E27CA136300378B1A /* AddingTextField.swift in Sources */, 0EB4042E27CA136300378B1A /* AddingTextField.swift in Sources */,
0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */, 0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */,
0EF2212B27E667EA001D0BD7 /* AddProviderView+Name.swift in Sources */, 0EF2212B27E667EA001D0BD7 /* AddProviderView+Name.swift in Sources */,

View File

@ -28,20 +28,26 @@ import PassepartoutLibrary
// safer alternative to @EnvironmentObject // safer alternative to @EnvironmentObject
extension AppContext { // MARK: App
private static let coreContext = CoreContext(store: UserDefaultsStore(defaults: .standard, key: \.key))
static let shared = AppContext(coreContext: coreContext) extension AppContext {
static let shared = AppContext(store: UserDefaultsStore(defaults: .standard, key: \.key))
}
extension UpgradeManager {
static let shared = AppContext.shared.upgradeManager
} }
extension ProductManager { extension ProductManager {
static let shared = AppContext.shared.productManager static let shared = AppContext.shared.productManager
} }
extension UpgradeManager { extension PersistenceManager {
static let shared = AppContext.shared.upgradeManager static let shared = AppContext.shared.persistenceManager
} }
// MARK: App -> Core
extension ProfileManager { extension ProfileManager {
static let shared = AppContext.shared.profileManager static let shared = AppContext.shared.profileManager
} }

View File

@ -31,34 +31,48 @@ import PassepartoutLibrary
final class AppContext { final class AppContext {
private let coreContext: CoreContext private let coreContext: CoreContext
private var lastIsCloudSyncingEnabled: Bool? let upgradeManager: UpgradeManager
let productManager: ProductManager let productManager: ProductManager
let persistenceManager: PersistenceManager
private let reviewer: Reviewer private let reviewer: Reviewer
private var cancellables: Set<AnyCancellable> = [] private var cancellables: Set<AnyCancellable> = []
init(coreContext: CoreContext) { init(store: KeyValueStore) {
self.coreContext = coreContext let logger = SwiftyBeaverLogger(
logFile: Constants.Log.App.url,
logLevel: Constants.Log.level,
logFormat: Constants.Log.App.format
)
Passepartout.shared.logger = logger
pp_log.info("Logging to: \(logger.logFile!)")
upgradeManager = UpgradeManager(
store: store,
strategy: DefaultUpgradeManagerStrategy()
)
upgradeManager.migrate(toVersion: Constants.Global.appVersionNumber)
productManager = ProductManager( productManager = ProductManager(
overriddenAppType: Constants.InApp.overriddenAppType, overriddenAppType: Constants.InApp.overriddenAppType,
buildProducts: Constants.InApp.buildProducts buildProducts: Constants.InApp.buildProducts
) )
persistenceManager = PersistenceManager(store: store)
reviewer = Reviewer() reviewer = Reviewer()
reviewer.eventCountBeforeRating = Constants.Rating.eventCount reviewer.eventCountBeforeRating = Constants.Rating.eventCount
coreContext = CoreContext(persistenceManager: persistenceManager)
// post // post
configureObjects() configureObjects()
} }
var upgradeManager: UpgradeManager {
coreContext.upgradeManager
}
var providerManager: ProviderManager { var providerManager: ProviderManager {
coreContext.providerManager coreContext.providerManager
} }
@ -116,29 +130,3 @@ private extension AppContext {
return true return true
} }
} }
// MARK: CloudKit
extension AppContext {
var shouldEnableCloudSyncing: Bool {
get {
coreContext.store.shouldEnableCloudSyncing
}
set {
coreContext.store.shouldEnableCloudSyncing = newValue
// iCloud may be externally disabled from the device settings
let isCloudSyncingEnabled = coreContext.store.isCloudSyncingEnabled
guard isCloudSyncingEnabled != lastIsCloudSyncingEnabled else {
pp_log.debug("CloudKit state did not change")
return
}
coreContext.reloadCloudKitObjects(isEnabled: isCloudSyncingEnabled)
lastIsCloudSyncingEnabled = isCloudSyncingEnabled
}
}
func eraseCloudKitStore() async {
await coreContext.eraseCloudKitStore()
}
}

View File

@ -34,12 +34,6 @@ import TunnelKitManager
final class CoreContext { final class CoreContext {
let store: KeyValueStore let store: KeyValueStore
private let persistenceManager: PersistenceManager
private(set) var vpnPersistence: VPNPersistence
let upgradeManager: UpgradeManager
let providerManager: ProviderManager let providerManager: ProviderManager
let profileManager: ProfileManager let profileManager: ProfileManager
@ -48,27 +42,11 @@ final class CoreContext {
private var cancellables: Set<AnyCancellable> = [] private var cancellables: Set<AnyCancellable> = []
init(store: KeyValueStore) { init(persistenceManager: PersistenceManager) {
self.store = store store = persistenceManager.store
let logger = SwiftyBeaverLogger( let vpnPersistence = persistenceManager.vpnPersistence(
logFile: Constants.Log.App.url, withName: Constants.Persistence.profilesContainerName
logLevel: Constants.Log.level,
logFormat: Constants.Log.App.format
)
Passepartout.shared.logger = logger
pp_log.info("Logging to: \(logger.logFile!)")
upgradeManager = UpgradeManager(
store: store,
strategy: DefaultUpgradeManagerStrategy()
)
upgradeManager.migrate(toVersion: Constants.Global.appVersionNumber)
persistenceManager = PersistenceManager(store: store)
vpnPersistence = persistenceManager.vpnPersistence(
withName: Constants.Persistence.profilesContainerName,
cloudKit: store.isCloudSyncingEnabled
) )
let providersPersistence = persistenceManager.providersPersistence( let providersPersistence = persistenceManager.providersPersistence(
withName: Constants.Persistence.providersContainerName withName: Constants.Persistence.providersContainerName
@ -119,12 +97,12 @@ final class CoreContext {
// post // post
configureObjects() configureObjects(persistenceManager: persistenceManager)
} }
} }
private extension CoreContext { private extension CoreContext {
func configureObjects() { func configureObjects(persistenceManager: PersistenceManager) {
providerManager.rateLimitMilliseconds = Constants.RateLimit.providerManager providerManager.rateLimitMilliseconds = Constants.RateLimit.providerManager
vpnManager.tunnelLogPath = Constants.Log.Tunnel.path vpnManager.tunnelLogPath = Constants.Log.Tunnel.path
vpnManager.tunnelLogFormat = Constants.Log.Tunnel.format vpnManager.tunnelLogFormat = Constants.Log.Tunnel.format
@ -133,27 +111,25 @@ private extension CoreContext {
vpnManager.observeUpdates() vpnManager.observeUpdates()
CoreConfiguration.masksPrivateData = vpnManager.masksPrivateData CoreConfiguration.masksPrivateData = vpnManager.masksPrivateData
vpnManager.didUpdatePreferences.sink { vpnManager.didUpdatePreferences
.sink {
CoreConfiguration.masksPrivateData = $0.masksPrivateData CoreConfiguration.masksPrivateData = $0.masksPrivateData
}.store(in: &cancellables) }.store(in: &cancellables)
persistenceManager.didChangePersistence
.sink { [weak self] in
self?.reloadCloudKitObjects(persistenceManager: persistenceManager)
}.store(in: &cancellables)
} }
} }
// MARK: CloudKit // MARK: CloudKit
extension CoreContext { extension CoreContext {
func reloadCloudKitObjects(isEnabled: Bool) { func reloadCloudKitObjects(persistenceManager: PersistenceManager) {
vpnPersistence = persistenceManager.vpnPersistence( let vpnPersistence = persistenceManager.vpnPersistence(
withName: Constants.Persistence.profilesContainerName, withName: Constants.Persistence.profilesContainerName
cloudKit: isEnabled
) )
profileManager.swapProfileRepository(vpnPersistence.profileRepository()) profileManager.swapProfileRepository(vpnPersistence.profileRepository())
} }
func eraseCloudKitStore() async {
await vpnPersistence.eraseCloudKitStore(
fromContainerWithId: Constants.CloudKit.containerId,
zoneId: .init(zoneName: Constants.CloudKit.coreDataZone)
)
}
} }

View File

@ -29,8 +29,6 @@ import PassepartoutLibrary
enum AppPreference: String, KeyStoreDomainLocation { enum AppPreference: String, KeyStoreDomainLocation {
case launchesOnLogin case launchesOnLogin
case shouldEnableCloudSyncing
case isShowingFavorites case isShowingFavorites
case didHandleSubreddit case didHandleSubreddit

View File

@ -1,63 +0,0 @@
//
// KeyValueStore+CloudKit.swift
// Passepartout
//
// Created by Davide De Rosa on 9/7/23.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
import PassepartoutLibrary
extension KeyValueStore {
// MARK: Support
private var cloudKitToken: Any? {
FileManager.default.ubiquityIdentityToken
}
var isCloudKitSupported: Bool {
cloudKitToken != nil
}
// MARK: Preference
var shouldEnableCloudSyncing: Bool {
get {
value(forLocation: AppPreference.shouldEnableCloudSyncing) ?? false
}
set {
setValue(newValue, forLocation: AppPreference.shouldEnableCloudSyncing)
}
}
// MARK: Computed state
var isCloudSyncingEnabled: Bool {
guard isCloudKitSupported else {
pp_log.debug("CloudKit unavailable")
return false
}
let isEnabled = shouldEnableCloudSyncing
pp_log.debug("CloudKit enabled: \(isEnabled)")
return isEnabled
}
}

View File

@ -36,7 +36,7 @@ public final class DefaultUpgradeManagerStrategy: UpgradeManagerStrategy {
let isUpgradeFromBefore_2_2_0: Bool? = store.value(forLocation: UpgradeManager.StoreKey.existingKeyBefore_2_2_0) let isUpgradeFromBefore_2_2_0: Bool? = store.value(forLocation: UpgradeManager.StoreKey.existingKeyBefore_2_2_0)
if isUpgradeFromBefore_2_2_0 != nil { if isUpgradeFromBefore_2_2_0 != nil {
pp_log.debug("Upgrading from < 2.2.0, iCloud syncing defaults to enabled") pp_log.debug("Upgrading from < 2.2.0, iCloud syncing defaults to enabled")
store.setValue(true, forLocation: AppPreference.shouldEnableCloudSyncing) store.setValue(true, forLocation: PersistenceManager.StoreKey.shouldEnableCloudSyncing)
store.removeValue(forLocation: UpgradeManager.StoreKey.existingKeyBefore_2_2_0) store.removeValue(forLocation: UpgradeManager.StoreKey.existingKeyBefore_2_2_0)
} }

View File

@ -23,15 +23,29 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
import CloudKit
import Combine
import CoreData import CoreData
import Foundation import Foundation
import PassepartoutLibrary import PassepartoutLibrary
final class PersistenceManager { final class PersistenceManager: ObservableObject {
private let store: KeyValueStore let store: KeyValueStore
private(set) var isCloudSyncingEnabled: Bool {
didSet {
pp_log.info("CloudKit enabled: \(isCloudSyncingEnabled)")
didChangePersistence.send()
}
}
@Published private(set) var isErasingCloudKitStore = false
let didChangePersistence = PassthroughSubject<Void, Never>()
init(store: KeyValueStore) { init(store: KeyValueStore) {
self.store = store self.store = store
isCloudSyncingEnabled = store.canEnableCloudSyncing
// set once // set once
if persistenceAuthor == nil { if persistenceAuthor == nil {
@ -39,8 +53,8 @@ final class PersistenceManager {
} }
} }
func vpnPersistence(withName containerName: String, cloudKit: Bool) -> VPNPersistence { func vpnPersistence(withName containerName: String) -> VPNPersistence {
VPNPersistence(withName: containerName, cloudKit: cloudKit, author: persistenceAuthor) VPNPersistence(withName: containerName, cloudKit: isCloudSyncingEnabled, author: persistenceAuthor)
} }
func providersPersistence(withName containerName: String) -> ProvidersPersistence { func providersPersistence(withName containerName: String) -> ProvidersPersistence {
@ -48,8 +62,59 @@ final class PersistenceManager {
} }
} }
// MARK: CloudKit
extension PersistenceManager {
func eraseCloudKitStore() async {
await MainActor.run {
isErasingCloudKitStore = true
}
await Self.eraseCloudKitStore(
fromContainerWithId: Constants.CloudKit.containerId,
zoneId: .init(zoneName: Constants.CloudKit.coreDataZone)
)
await MainActor.run {
isErasingCloudKitStore = false
}
}
// WARNING: this is not running on main actor
private static func eraseCloudKitStore(fromContainerWithId containerId: String, zoneId: CKRecordZone.ID) async {
do {
let container = CKContainer(identifier: containerId)
let db = container.privateCloudDatabase
try await db.deleteRecordZone(withID: zoneId)
} catch {
pp_log.error("Unable to erase CloudKit store: \(error)")
}
}
}
// MARK: KeyValueStore // MARK: KeyValueStore
private extension KeyValueStore {
private var cloudKitToken: Any? {
FileManager.default.ubiquityIdentityToken
}
private var isCloudKitSupported: Bool {
cloudKitToken != nil
}
var canEnableCloudSyncing: Bool {
isCloudKitSupported && shouldEnableCloudSyncing
}
var shouldEnableCloudSyncing: Bool {
get {
value(forLocation: PersistenceManager.StoreKey.shouldEnableCloudSyncing) ?? false
}
set {
setValue(newValue, forLocation: PersistenceManager.StoreKey.shouldEnableCloudSyncing)
}
}
}
extension PersistenceManager { extension PersistenceManager {
private(set) var persistenceAuthor: String? { private(set) var persistenceAuthor: String? {
get { get {
@ -59,12 +124,34 @@ extension PersistenceManager {
store.setValue(newValue, forLocation: StoreKey.persistenceAuthor) store.setValue(newValue, forLocation: StoreKey.persistenceAuthor)
} }
} }
var shouldEnableCloudSyncing: Bool {
get {
store.shouldEnableCloudSyncing
}
set {
objectWillChange.send()
store.shouldEnableCloudSyncing = newValue
// iCloud may be externally disabled from the device settings
let newIsCloudSyncingEnabled = store.canEnableCloudSyncing
guard newIsCloudSyncingEnabled != isCloudSyncingEnabled else {
pp_log.debug("CloudKit state did not change")
return
}
isCloudSyncingEnabled = newIsCloudSyncingEnabled
}
}
} }
private extension PersistenceManager { // TODO: iCloud, restore private after dropping migration from 2.2.0
// private extension PersistenceManager {
extension PersistenceManager {
enum StoreKey: String, KeyStoreDomainLocation { enum StoreKey: String, KeyStoreDomainLocation {
case persistenceAuthor case persistenceAuthor
case shouldEnableCloudSyncing
var domain: String { var domain: String {
"Passepartout.PersistenceManager" "Passepartout.PersistenceManager"
} }

View File

@ -31,27 +31,18 @@ struct SettingsView: View {
@ObservedObject private var productManager: ProductManager @ObservedObject private var productManager: ProductManager
@ObservedObject private var persistenceManager: PersistenceManager
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
@AppStorage(AppPreference.locksInBackground.key) private var locksInBackground = false @AppStorage(AppPreference.locksInBackground.key) private var locksInBackground = false
@Binding private var shouldEnableCloudSyncing: Bool
@State private var isErasingCloudStore = false
private let versionString = Constants.Global.appVersionString private let versionString = Constants.Global.appVersionString
init() { init() {
profileManager = .shared profileManager = .shared
productManager = .shared productManager = .shared
persistenceManager = .shared
_shouldEnableCloudSyncing = .init {
AppContext.shared.shouldEnableCloudSyncing
} set: { isEnabled in
withAnimation {
AppContext.shared.shouldEnableCloudSyncing = isEnabled
}
}
} }
var body: some View { var body: some View {
@ -82,15 +73,13 @@ private extension SettingsView {
var iCloudSection: some View { var iCloudSection: some View {
Section { Section {
Toggle(L10n.Settings.Items.ShouldEnableCloudSyncing.caption, isOn: $shouldEnableCloudSyncing) Toggle(L10n.Settings.Items.ShouldEnableCloudSyncing.caption, isOn: $persistenceManager.shouldEnableCloudSyncing.themeAnimation())
Button(L10n.Settings.Items.EraseCloudStore.caption) { Button(L10n.Settings.Items.EraseCloudStore.caption) {
isErasingCloudStore = true
Task { Task {
await AppContext.shared.eraseCloudKitStore() await persistenceManager.eraseCloudKitStore()
isErasingCloudStore = false
} }
}.withTrailingProgress(when: isErasingCloudStore) }.withTrailingProgress(when: persistenceManager.isErasingCloudKitStore)
.disabled(shouldEnableCloudSyncing || isErasingCloudStore) .disabled(persistenceManager.shouldEnableCloudSyncing || persistenceManager.isErasingCloudKitStore)
} header: { } header: {
Text(Unlocalized.Other.iCloud) Text(Unlocalized.Other.iCloud)
} footer: { } footer: {

View File

@ -43,8 +43,8 @@ extension LaunchOnLoginItem {
guard SMLoginItemSetEnabled(Constants.Mac.appLauncherId as CFString, newValue) else { guard SMLoginItemSetEnabled(Constants.Mac.appLauncherId as CFString, newValue) else {
return return
} }
launchesOnLogin = newValue
objectWillChange.send() objectWillChange.send()
launchesOnLogin = newValue
} }
} }

View File

@ -23,7 +23,6 @@
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>. // along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
// //
import CloudKit
import CoreData import CoreData
import Foundation import Foundation
import PassepartoutCore import PassepartoutCore
@ -55,15 +54,4 @@ public final class VPNPersistence {
public func profileRepository() -> ProfileRepository { public func profileRepository() -> ProfileRepository {
CDProfileRepository(store.context) CDProfileRepository(store.context)
} }
// WARNING: this is not running on main actor
public func eraseCloudKitStore(fromContainerWithId containerId: String, zoneId: CKRecordZone.ID) async {
do {
let container = CKContainer(identifier: containerId)
let db = container.privateCloudDatabase
try await db.deleteRecordZone(withID: zoneId)
} catch {
pp_log.error("Unable to erase CloudKit store: \(error)")
}
}
} }