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:
parent
c645f39254
commit
0872c27fce
|
@ -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 */,
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue