Persist managers state to generic key-value store
Move all persisted state out of AppManager to where it really belongs. To do that, inject a shared KeyValueStore object into managers that need to persist part of their state in a strongly typed manner. Below are persisted states: - PersistenceManager - persistenceAuthor - ProfileManager - activeProfileId - UpgradeManager (formerly AppManager) - didMigrateToV2 (migrate former value) - VPNManager - tunnelLogFormat - masksPrivateData A similar approach is used for app-specific preferences, by using a strongly typed enum (AppPreference) together with SwiftUI @AppStorage property wrapper. Worth moving logging logic into a specific LogManager. Finally, drop any former view dependency on AppManager, as states are now accessed through specific managers.
This commit is contained in:
parent
127ba28a8c
commit
14b42fbea5
|
@ -16,6 +16,7 @@
|
||||||
0E0BD27927B2EBE500583AC5 /* ShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27827B2EBE500583AC5 /* ShortcutsView.swift */; };
|
0E0BD27927B2EBE500583AC5 /* ShortcutsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E0BD27827B2EBE500583AC5 /* ShortcutsView.swift */; };
|
||||||
0E0C0729236087A100155AAC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E0C072B236087A100155AAC /* InfoPlist.strings */; };
|
0E0C0729236087A100155AAC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E0C072B236087A100155AAC /* InfoPlist.strings */; };
|
||||||
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12BC8E27F62C8500B2F912 /* Validators.swift */; };
|
0E12BC8F27F62C8600B2F912 /* Validators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12BC8E27F62C8500B2F912 /* Validators.swift */; };
|
||||||
|
0E293851285A70AC002A6E0E /* AppPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E293850285A70AC002A6E0E /* AppPreference.swift */; };
|
||||||
0E2A8D4927ADF87F00207D04 /* PassepartoutApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */; };
|
0E2A8D4927ADF87F00207D04 /* PassepartoutApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */; };
|
||||||
0E2A8D4F27B04BBA00207D04 /* OrganizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */; };
|
0E2A8D4F27B04BBA00207D04 /* OrganizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */; };
|
||||||
0E2AC24522EC3AC10037B4B0 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */; };
|
0E2AC24522EC3AC10037B4B0 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 0E2AC24422EC3AC10037B4B0 /* Settings.bundle */; };
|
||||||
|
@ -47,7 +48,6 @@
|
||||||
0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5349BD27C16A4500C71BB3 /* StyledPicker.swift */; };
|
0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5349BD27C16A4500C71BB3 /* StyledPicker.swift */; };
|
||||||
0E5349C627C176C200C71BB3 /* EndpointView+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5349C527C176C200C71BB3 /* EndpointView+OpenVPN.swift */; };
|
0E5349C627C176C200C71BB3 /* EndpointView+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5349C527C176C200C71BB3 /* EndpointView+OpenVPN.swift */; };
|
||||||
0E5349C827C176D100C71BB3 /* EndpointView+WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5349C727C176D100C71BB3 /* EndpointView+WireGuard.swift */; };
|
0E5349C827C176D100C71BB3 /* EndpointView+WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5349C727C176D100C71BB3 /* EndpointView+WireGuard.swift */; };
|
||||||
0E53E63727E34FE2001D4902 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E53E63627E34FE2001D4902 /* AppContext.swift */; };
|
|
||||||
0E5683B927C2825D00EAF1CD /* DiagnosticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5683B827C2825D00EAF1CD /* DiagnosticsView.swift */; };
|
0E5683B927C2825D00EAF1CD /* DiagnosticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E5683B827C2825D00EAF1CD /* DiagnosticsView.swift */; };
|
||||||
0E6059CB27FCC5DE003F4063 /* Flags.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E6059C827FCC5DD003F4063 /* Flags.xcassets */; };
|
0E6059CB27FCC5DE003F4063 /* Flags.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E6059C827FCC5DD003F4063 /* Flags.xcassets */; };
|
||||||
0E6059CC27FCC5DE003F4063 /* Providers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E6059C927FCC5DE003F4063 /* Providers.xcassets */; };
|
0E6059CC27FCC5DE003F4063 /* Providers.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E6059C927FCC5DE003F4063 /* Providers.xcassets */; };
|
||||||
|
@ -123,6 +123,7 @@
|
||||||
0EF2212D27E66EB5001D0BD7 /* AddProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2212C27E66EB5001D0BD7 /* AddProviderView.swift */; };
|
0EF2212D27E66EB5001D0BD7 /* AddProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2212C27E66EB5001D0BD7 /* AddProviderView.swift */; };
|
||||||
0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2212E27E66F60001D0BD7 /* AddProfileView.swift */; };
|
0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2212E27E66F60001D0BD7 /* AddProfileView.swift */; };
|
||||||
0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2213027E674BD001D0BD7 /* AddProviderViewModel.swift */; };
|
0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2213027E674BD001D0BD7 /* AddProviderViewModel.swift */; };
|
||||||
|
0EF2CC03285AFED800E501D5 /* AppContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF2CC02285AFED800E501D5 /* AppContext.swift */; };
|
||||||
0EF8C5A828213C510053CE89 /* OrganizerView+Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF8C5A728213C510053CE89 /* OrganizerView+Profiles.swift */; };
|
0EF8C5A828213C510053CE89 /* OrganizerView+Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EF8C5A728213C510053CE89 /* OrganizerView+Profiles.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
@ -202,6 +203,7 @@
|
||||||
0E12BC8E27F62C8500B2F912 /* Validators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validators.swift; sourceTree = "<group>"; };
|
0E12BC8E27F62C8500B2F912 /* Validators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Validators.swift; sourceTree = "<group>"; };
|
||||||
0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
0E1C0A52238FFF97009FC087 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
0E23B4A12298559800304C30 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
0E23B4A12298559800304C30 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
||||||
|
0E293850285A70AC002A6E0E /* AppPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPreference.swift; sourceTree = "<group>"; };
|
||||||
0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassepartoutApp.swift; sourceTree = "<group>"; };
|
0E2A8D4727ADF87F00207D04 /* PassepartoutApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassepartoutApp.swift; sourceTree = "<group>"; };
|
||||||
0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizerView.swift; sourceTree = "<group>"; };
|
0E2A8D4E27B04BB900207D04 /* OrganizerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizerView.swift; sourceTree = "<group>"; };
|
||||||
0E2AC24422EC3AC10037B4B0 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
0E2AC24422EC3AC10037B4B0 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = "<group>"; };
|
||||||
|
@ -232,7 +234,6 @@
|
||||||
0E5349BD27C16A4500C71BB3 /* StyledPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyledPicker.swift; sourceTree = "<group>"; };
|
0E5349BD27C16A4500C71BB3 /* StyledPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyledPicker.swift; sourceTree = "<group>"; };
|
||||||
0E5349C527C176C200C71BB3 /* EndpointView+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointView+OpenVPN.swift"; sourceTree = "<group>"; };
|
0E5349C527C176C200C71BB3 /* EndpointView+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointView+OpenVPN.swift"; sourceTree = "<group>"; };
|
||||||
0E5349C727C176D100C71BB3 /* EndpointView+WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointView+WireGuard.swift"; sourceTree = "<group>"; };
|
0E5349C727C176D100C71BB3 /* EndpointView+WireGuard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EndpointView+WireGuard.swift"; sourceTree = "<group>"; };
|
||||||
0E53E63627E34FE2001D4902 /* AppContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = "<group>"; };
|
|
||||||
0E5683B827C2825D00EAF1CD /* DiagnosticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticsView.swift; sourceTree = "<group>"; };
|
0E5683B827C2825D00EAF1CD /* DiagnosticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiagnosticsView.swift; sourceTree = "<group>"; };
|
||||||
0E57F63820C83FC5008323CF /* Passepartout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Passepartout.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
0E57F63820C83FC5008323CF /* Passepartout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Passepartout.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
0E57F64720C83FC7008323CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
0E57F64720C83FC7008323CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
@ -341,6 +342,7 @@
|
||||||
0EF2212C27E66EB5001D0BD7 /* AddProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProviderView.swift; sourceTree = "<group>"; };
|
0EF2212C27E66EB5001D0BD7 /* AddProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProviderView.swift; sourceTree = "<group>"; };
|
||||||
0EF2212E27E66F60001D0BD7 /* AddProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProfileView.swift; sourceTree = "<group>"; };
|
0EF2212E27E66F60001D0BD7 /* AddProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProfileView.swift; sourceTree = "<group>"; };
|
||||||
0EF2213027E674BD001D0BD7 /* AddProviderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProviderViewModel.swift; sourceTree = "<group>"; };
|
0EF2213027E674BD001D0BD7 /* AddProviderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddProviderViewModel.swift; sourceTree = "<group>"; };
|
||||||
|
0EF2CC02285AFED800E501D5 /* AppContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = "<group>"; };
|
||||||
0EF8C5A728213C510053CE89 /* OrganizerView+Profiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Profiles.swift"; sourceTree = "<group>"; };
|
0EF8C5A728213C510053CE89 /* OrganizerView+Profiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Profiles.swift"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
@ -565,7 +567,8 @@
|
||||||
0EB17EA027D2263700D473B5 /* Constants */ = {
|
0EB17EA027D2263700D473B5 /* Constants */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0E53E63627E34FE2001D4902 /* AppContext.swift */,
|
0EF2CC02285AFED800E501D5 /* AppContext.swift */,
|
||||||
|
0E293850285A70AC002A6E0E /* AppPreference.swift */,
|
||||||
0EB17EA527D2263700D473B5 /* Constants+Extensions.swift */,
|
0EB17EA527D2263700D473B5 /* Constants+Extensions.swift */,
|
||||||
0E6059CE27FCC618003F4063 /* SwiftGen+Assets.swift */,
|
0E6059CE27FCC618003F4063 /* SwiftGen+Assets.swift */,
|
||||||
0EBC075F27EC587900208AD9 /* SwiftGen+Strings.swift */,
|
0EBC075F27EC587900208AD9 /* SwiftGen+Strings.swift */,
|
||||||
|
@ -907,7 +910,6 @@
|
||||||
0EBC075D27EC529000208AD9 /* DebugLog+Constants.swift in Sources */,
|
0EBC075D27EC529000208AD9 /* DebugLog+Constants.swift in Sources */,
|
||||||
0E3CD47F280DA14B007075C0 /* AddProfileMenu.swift in Sources */,
|
0E3CD47F280DA14B007075C0 /* AddProfileMenu.swift in Sources */,
|
||||||
0EB17EAA27D226C900D473B5 /* Constants+Extensions.swift in Sources */,
|
0EB17EAA27D226C900D473B5 /* Constants+Extensions.swift in Sources */,
|
||||||
0E53E63727E34FE2001D4902 /* AppContext.swift in Sources */,
|
|
||||||
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */,
|
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */,
|
||||||
0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */,
|
0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */,
|
||||||
0EBE880F281B18DE0090D9E6 /* ProfileRow.swift in Sources */,
|
0EBE880F281B18DE0090D9E6 /* ProfileRow.swift in Sources */,
|
||||||
|
@ -930,8 +932,10 @@
|
||||||
0E71ACEF27C106B500F85C4B /* ProviderPresetView.swift in Sources */,
|
0E71ACEF27C106B500F85C4B /* ProviderPresetView.swift in Sources */,
|
||||||
0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */,
|
0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */,
|
||||||
0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */,
|
0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */,
|
||||||
|
0E293851285A70AC002A6E0E /* AppPreference.swift in Sources */,
|
||||||
0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */,
|
0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */,
|
||||||
0E2C172B27CB63F9007E8488 /* Reviewer.swift in Sources */,
|
0E2C172B27CB63F9007E8488 /* Reviewer.swift in Sources */,
|
||||||
|
0EF2CC03285AFED800E501D5 /* AppContext.swift in Sources */,
|
||||||
0E71ACDD27C0295C00F85C4B /* View+Extensions.swift in Sources */,
|
0E71ACDD27C0295C00F85C4B /* View+Extensions.swift in Sources */,
|
||||||
0E34A2B627CAA8CC00C73B67 /* Core+L10n.swift in Sources */,
|
0E34A2B627CAA8CC00C73B67 /* Core+L10n.swift in Sources */,
|
||||||
0E7577DF2817E22C00081CBE /* VPNToggle.swift in Sources */,
|
0E7577DF2817E22C00081CBE /* VPNToggle.swift in Sources */,
|
||||||
|
|
|
@ -33,6 +33,8 @@ import PassepartoutServices
|
||||||
class AppContext {
|
class AppContext {
|
||||||
static let shared = AppContext()
|
static let shared = AppContext()
|
||||||
|
|
||||||
|
private let logManager: LogManager
|
||||||
|
|
||||||
private let profilesPersistence: Persistence
|
private let profilesPersistence: Persistence
|
||||||
|
|
||||||
private let providersPersistence: Persistence
|
private let providersPersistence: Persistence
|
||||||
|
@ -45,7 +47,7 @@ class AppContext {
|
||||||
providersPersistence.containerURLs
|
providersPersistence.containerURLs
|
||||||
}
|
}
|
||||||
|
|
||||||
let appManager: AppManager
|
let upgradeManager: UpgradeManager
|
||||||
|
|
||||||
let providerManager: ProviderManager
|
let providerManager: ProviderManager
|
||||||
|
|
||||||
|
@ -62,18 +64,15 @@ class AppContext {
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
|
let store = UserDefaultsStore(defaults: .standard)
|
||||||
|
|
||||||
// core
|
logManager = LogManager(logFile: Constants.Log.appFileURL)
|
||||||
|
logManager.logLevel = Constants.Log.logLevel
|
||||||
|
logManager.logFormat = Constants.Log.appLogFormat
|
||||||
|
logManager.configureLogging()
|
||||||
|
pp_log.info("Logging to: \(logManager.logFile!)")
|
||||||
|
|
||||||
appManager = AppManager()
|
let persistenceManager = PersistenceManager(store: store)
|
||||||
appManager.logLevel = Constants.Log.logLevel
|
|
||||||
appManager.logFile = Constants.Log.appFileURL
|
|
||||||
appManager.logFormat = Constants.Log.appLogFormat
|
|
||||||
appManager.tunnelLogFormat = Constants.Log.tunnelLogFormat
|
|
||||||
appManager.configureLogging()
|
|
||||||
pp_log.info("Logging to: \(appManager.logFile!)")
|
|
||||||
|
|
||||||
let persistenceManager = PersistenceManager(author: appManager.persistenceAuthor)
|
|
||||||
profilesPersistence = persistenceManager.profilesPersistence(
|
profilesPersistence = persistenceManager.profilesPersistence(
|
||||||
withName: Constants.Persistence.profilesContainerName
|
withName: Constants.Persistence.profilesContainerName
|
||||||
)
|
)
|
||||||
|
@ -81,6 +80,8 @@ class AppContext {
|
||||||
withName: Constants.Persistence.providersContainerName
|
withName: Constants.Persistence.providersContainerName
|
||||||
)
|
)
|
||||||
|
|
||||||
|
upgradeManager = UpgradeManager(store: store)
|
||||||
|
|
||||||
providerManager = ProviderManager(
|
providerManager = ProviderManager(
|
||||||
appBuild: Constants.Global.appBuildNumber,
|
appBuild: Constants.Global.appBuildNumber,
|
||||||
bundleServices: DefaultWebServices.bundledServices(
|
bundleServices: DefaultWebServices.bundledServices(
|
||||||
|
@ -95,6 +96,7 @@ class AppContext {
|
||||||
)
|
)
|
||||||
|
|
||||||
profileManager = ProfileManager(
|
profileManager = ProfileManager(
|
||||||
|
store: store,
|
||||||
providerManager: providerManager,
|
providerManager: providerManager,
|
||||||
appGroup: Constants.App.appGroupId,
|
appGroup: Constants.App.appGroupId,
|
||||||
keychainLabel: Unlocalized.Keychain.passwordLabel,
|
keychainLabel: Unlocalized.Keychain.passwordLabel,
|
||||||
|
@ -112,7 +114,7 @@ class AppContext {
|
||||||
)
|
)
|
||||||
#endif
|
#endif
|
||||||
vpnManager = VPNManager(
|
vpnManager = VPNManager(
|
||||||
appManager: appManager,
|
store: store,
|
||||||
profileManager: profileManager,
|
profileManager: profileManager,
|
||||||
providerManager: providerManager,
|
providerManager: providerManager,
|
||||||
strategy: strategy
|
strategy: strategy
|
||||||
|
@ -137,14 +139,11 @@ class AppContext {
|
||||||
// core
|
// core
|
||||||
|
|
||||||
providerManager.rateLimitMilliseconds = Constants.RateLimit.providerManager
|
providerManager.rateLimitMilliseconds = Constants.RateLimit.providerManager
|
||||||
|
vpnManager.tunnelLogFormat = Constants.Log.tunnelLogFormat
|
||||||
vpnManager.isOnDemandRulesSupported = {
|
vpnManager.isOnDemandRulesSupported = {
|
||||||
self.isEligibleForOnDemandRules()
|
self.isEligibleForOnDemandRules()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let activeProfileId = appManager.activeProfileId {
|
|
||||||
profileManager.setActiveProfileId(activeProfileId)
|
|
||||||
}
|
|
||||||
|
|
||||||
profileManager.observeUpdates()
|
profileManager.observeUpdates()
|
||||||
vpnManager.observeUpdates()
|
vpnManager.observeUpdates()
|
||||||
|
|
||||||
|
@ -180,8 +179,8 @@ class AppContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AppManager {
|
extension UpgradeManager {
|
||||||
static let shared = AppContext.shared.appManager
|
static let shared = AppContext.shared.upgradeManager
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProfileManager {
|
extension ProfileManager {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// AppPreference.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 6/15/22.
|
||||||
|
// Copyright (c) 2022 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 PassepartoutUtils
|
||||||
|
|
||||||
|
enum AppPreference: String, KeyStoreDomainLocation {
|
||||||
|
case isShowingFavorites
|
||||||
|
|
||||||
|
case didHandleSubreddit
|
||||||
|
|
||||||
|
var domain: String {
|
||||||
|
"App"
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,8 +37,6 @@ extension DiagnosticsView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ObservedObject private var appManager: AppManager
|
|
||||||
|
|
||||||
@ObservedObject private var providerManager: ProviderManager
|
@ObservedObject private var providerManager: ProviderManager
|
||||||
|
|
||||||
@ObservedObject private var vpnManager: VPNManager
|
@ObservedObject private var vpnManager: VPNManager
|
||||||
|
@ -62,7 +60,6 @@ extension DiagnosticsView {
|
||||||
private let logUpdateInterval = Constants.Log.tunnelLogRefreshInterval
|
private let logUpdateInterval = Constants.Log.tunnelLogRefreshInterval
|
||||||
|
|
||||||
init(providerName: ProviderName?) {
|
init(providerName: ProviderName?) {
|
||||||
appManager = .shared
|
|
||||||
providerManager = .shared
|
providerManager = .shared
|
||||||
vpnManager = .shared
|
vpnManager = .shared
|
||||||
currentVPNState = .shared
|
currentVPNState = .shared
|
||||||
|
@ -120,7 +117,7 @@ extension DiagnosticsView {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.disabled(url == nil)
|
}.disabled(url == nil)
|
||||||
Toggle(L10n.Diagnostics.Items.MasksPrivateData.caption, isOn: $appManager.masksPrivateData)
|
Toggle(L10n.Diagnostics.Items.MasksPrivateData.caption, isOn: $vpnManager.masksPrivateData)
|
||||||
} footer: {
|
} footer: {
|
||||||
Text(L10n.Diagnostics.Sections.DebugLog.footer)
|
Text(L10n.Diagnostics.Sections.DebugLog.footer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ extension OrganizerView {
|
||||||
|
|
||||||
private func performMigrationsIfNeeded() {
|
private func performMigrationsIfNeeded() {
|
||||||
Task {
|
Task {
|
||||||
AppManager.shared.doMigrations(profileManager)
|
UpgradeManager.shared.doMigrations(profileManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ struct OrganizerView: View {
|
||||||
|
|
||||||
@State private var isHostFileImporterPresented = false
|
@State private var isHostFileImporterPresented = false
|
||||||
|
|
||||||
@AppStorage(AppManager.DefaultKey.didHandleSubreddit.rawValue) var didHandleSubreddit = false
|
@AppStorage(AppPreference.didHandleSubreddit.key) private var didHandleSubreddit = false
|
||||||
|
|
||||||
private let hostFileTypes = Constants.URLs.filetypes
|
private let hostFileTypes = Constants.URLs.filetypes
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,6 @@ import PassepartoutCore
|
||||||
struct ProviderLocationView: View, ProviderProfileAvailability {
|
struct ProviderLocationView: View, ProviderProfileAvailability {
|
||||||
@ObservedObject var providerManager: ProviderManager
|
@ObservedObject var providerManager: ProviderManager
|
||||||
|
|
||||||
@ObservedObject private var appManager: AppManager
|
|
||||||
|
|
||||||
@ObservedObject private var currentProfile: ObservableProfile
|
@ObservedObject private var currentProfile: ObservableProfile
|
||||||
|
|
||||||
var profile: Profile {
|
var profile: Profile {
|
||||||
|
@ -55,7 +53,7 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
|
||||||
|
|
||||||
@Binding private var favoriteLocationIds: Set<String>?
|
@Binding private var favoriteLocationIds: Set<String>?
|
||||||
|
|
||||||
@AppStorage(AppManager.DefaultKey.isShowingFavorites.rawValue) private var isShowingFavorites = false
|
@AppStorage(AppPreference.isShowingFavorites.key) private var isShowingFavorites = false
|
||||||
|
|
||||||
private var isShowingEmptyFavorites: Bool {
|
private var isShowingEmptyFavorites: Bool {
|
||||||
guard isShowingFavorites else {
|
guard isShowingFavorites else {
|
||||||
|
@ -68,7 +66,6 @@ struct ProviderLocationView: View, ProviderProfileAvailability {
|
||||||
init(currentProfile: ObservableProfile, isEditable: Bool, isPresented: Binding<Bool>) {
|
init(currentProfile: ObservableProfile, isEditable: Bool, isPresented: Binding<Bool>) {
|
||||||
let providerManager: ProviderManager = .shared
|
let providerManager: ProviderManager = .shared
|
||||||
|
|
||||||
appManager = .shared
|
|
||||||
self.providerManager = providerManager
|
self.providerManager = providerManager
|
||||||
self.currentProfile = currentProfile
|
self.currentProfile = currentProfile
|
||||||
self.isEditable = isEditable
|
self.isEditable = isEditable
|
||||||
|
|
|
@ -27,8 +27,6 @@ import SwiftUI
|
||||||
import PassepartoutCore
|
import PassepartoutCore
|
||||||
|
|
||||||
struct VPNToggle: View {
|
struct VPNToggle: View {
|
||||||
@ObservedObject private var appManager: AppManager
|
|
||||||
|
|
||||||
@ObservedObject private var profileManager: ProfileManager
|
@ObservedObject private var profileManager: ProfileManager
|
||||||
|
|
||||||
@ObservedObject private var vpnManager: VPNManager
|
@ObservedObject private var vpnManager: VPNManager
|
||||||
|
@ -64,7 +62,6 @@ struct VPNToggle: View {
|
||||||
@State private var canToggle = true
|
@State private var canToggle = true
|
||||||
|
|
||||||
init(profileId: UUID, rateLimit: Int) {
|
init(profileId: UUID, rateLimit: Int) {
|
||||||
appManager = .shared
|
|
||||||
profileManager = .shared
|
profileManager = .shared
|
||||||
vpnManager = .shared
|
vpnManager = .shared
|
||||||
currentVPNState = .shared
|
currentVPNState = .shared
|
||||||
|
@ -87,10 +84,6 @@ struct VPNToggle: View {
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
let profile = try await vpnManager.connect(with: profileId)
|
let profile = try await vpnManager.connect(with: profileId)
|
||||||
|
|
||||||
// IMPORTANT: save immediately to keep in sync with VPN status
|
|
||||||
appManager.activeProfileId = profileId
|
|
||||||
|
|
||||||
donateIntents(withProfile: profile)
|
donateIntents(withProfile: profile)
|
||||||
} catch {
|
} catch {
|
||||||
pp_log.warning("Unable to connect to profile \(profileId): \(error)")
|
pp_log.warning("Unable to connect to profile \(profileId): \(error)")
|
||||||
|
|
|
@ -1,221 +0,0 @@
|
||||||
//
|
|
||||||
// AppManager.swift
|
|
||||||
// Passepartout
|
|
||||||
//
|
|
||||||
// Created by Davide De Rosa on 2/8/22.
|
|
||||||
// Copyright (c) 2022 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 CoreData
|
|
||||||
import SwiftyBeaver
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
public class AppManager: ObservableObject {
|
|
||||||
public enum DefaultKey: String {
|
|
||||||
case activeProfileId
|
|
||||||
|
|
||||||
case launchesOnLogin
|
|
||||||
|
|
||||||
case isShowingFavorites
|
|
||||||
|
|
||||||
case confirmsQuit
|
|
||||||
|
|
||||||
case logFormat
|
|
||||||
|
|
||||||
case tunnelLogFormat
|
|
||||||
|
|
||||||
case masksPrivateData
|
|
||||||
|
|
||||||
case didHandleSubreddit
|
|
||||||
|
|
||||||
// internal use
|
|
||||||
|
|
||||||
case persistenceAuthor
|
|
||||||
|
|
||||||
case didMigrateToV2
|
|
||||||
}
|
|
||||||
|
|
||||||
private let defaults: UserDefaults = .standard
|
|
||||||
|
|
||||||
public var logLevel: SwiftyBeaver.Level = .info
|
|
||||||
|
|
||||||
public var logFile: URL?
|
|
||||||
|
|
||||||
// MARK: State
|
|
||||||
|
|
||||||
@Published public private(set) var isDoingMigrations = true
|
|
||||||
|
|
||||||
public init() {
|
|
||||||
defaults.register(keyedDefaults: [
|
|
||||||
.activeProfileId: nil,
|
|
||||||
.launchesOnLogin: false,
|
|
||||||
.isShowingFavorites: false,
|
|
||||||
.confirmsQuit: true,
|
|
||||||
.logFormat: nil,
|
|
||||||
.tunnelLogFormat: nil,
|
|
||||||
.masksPrivateData: true,
|
|
||||||
.didHandleSubreddit: false,
|
|
||||||
//
|
|
||||||
.didMigrateToV2: false
|
|
||||||
])
|
|
||||||
|
|
||||||
// set once
|
|
||||||
if persistenceAuthor == nil {
|
|
||||||
persistenceAuthor = UUID().uuidString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func configureLogging() {
|
|
||||||
let console = ConsoleDestination()
|
|
||||||
console.minLevel = logLevel
|
|
||||||
// console.useNSLog = true
|
|
||||||
if let logFormat = logFormat {
|
|
||||||
console.format = logFormat
|
|
||||||
}
|
|
||||||
SwiftyBeaver.addDestination(console)
|
|
||||||
|
|
||||||
if let fileURL = logFile {
|
|
||||||
let file = FileDestination()
|
|
||||||
file.minLevel = logLevel
|
|
||||||
file.logFileURL = fileURL
|
|
||||||
if let logFormat = logFormat {
|
|
||||||
file.format = logFormat
|
|
||||||
}
|
|
||||||
_ = file.deleteLogFile()
|
|
||||||
SwiftyBeaver.addDestination(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
CoreConfiguration.masksPrivateData = masksPrivateData
|
|
||||||
}
|
|
||||||
|
|
||||||
public func doMigrations(_ profileManager: ProfileManager) {
|
|
||||||
// profileManager.removeAllProfiles()
|
|
||||||
guard didMigrateToV2 else {
|
|
||||||
isDoingMigrations = true
|
|
||||||
let migrated = doMigrateToV2()
|
|
||||||
if !migrated.isEmpty {
|
|
||||||
pp_log.info("Migrating \(migrated.count) profiles")
|
|
||||||
migrated.forEach {
|
|
||||||
var profile = $0
|
|
||||||
if profileManager.isExistingProfile(withName: profile.header.name) {
|
|
||||||
profile = profile.renamedUniquely(withLastUpdate: true)
|
|
||||||
}
|
|
||||||
profileManager.saveProfile(profile, isActive: nil)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pp_log.info("Nothing to migrate!")
|
|
||||||
}
|
|
||||||
isDoingMigrations = false
|
|
||||||
|
|
||||||
didMigrateToV2 = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isDoingMigrations = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Current state
|
|
||||||
|
|
||||||
public var preferences: AppPreferences {
|
|
||||||
return DefaultAppPreferences(
|
|
||||||
activeProfileId: activeProfileId,
|
|
||||||
logFormat: logFormat,
|
|
||||||
tunnelLogFormat: tunnelLogFormat,
|
|
||||||
masksPrivateData: masksPrivateData
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppManager: AppPreferences {
|
|
||||||
public var activeProfileId: UUID? {
|
|
||||||
get {
|
|
||||||
guard let uuidString = defaults.string(forKey: DefaultKey.activeProfileId.rawValue) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return UUID(uuidString: uuidString)
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
defaults.set(newValue?.uuidString, forKey: DefaultKey.activeProfileId.rawValue)
|
|
||||||
defaults.synchronize()
|
|
||||||
objectWillChange.send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var logFormat: String? {
|
|
||||||
get {
|
|
||||||
defaults.string(forKey: DefaultKey.logFormat.rawValue)
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
defaults.set(newValue, forKey: DefaultKey.logFormat.rawValue)
|
|
||||||
objectWillChange.send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var tunnelLogFormat: String? {
|
|
||||||
get {
|
|
||||||
defaults.string(forKey: DefaultKey.tunnelLogFormat.rawValue)
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
defaults.set(newValue, forKey: DefaultKey.tunnelLogFormat.rawValue)
|
|
||||||
objectWillChange.send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public var masksPrivateData: Bool {
|
|
||||||
get {
|
|
||||||
defaults.bool(forKey: DefaultKey.masksPrivateData.rawValue)
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
defaults.set(newValue, forKey: DefaultKey.masksPrivateData.rawValue)
|
|
||||||
CoreConfiguration.masksPrivateData = newValue
|
|
||||||
|
|
||||||
objectWillChange.send()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Internal use (readonly)
|
|
||||||
|
|
||||||
public private(set) var persistenceAuthor: String? {
|
|
||||||
get {
|
|
||||||
defaults.string(forKey: DefaultKey.persistenceAuthor.rawValue)
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
defaults.set(newValue, forKey: DefaultKey.persistenceAuthor.rawValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public internal(set) var didMigrateToV2: Bool {
|
|
||||||
get {
|
|
||||||
defaults.bool(forKey: DefaultKey.didMigrateToV2.rawValue)
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
defaults.set(newValue, forKey: DefaultKey.didMigrateToV2.rawValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension UserDefaults {
|
|
||||||
func register(keyedDefaults: [AppManager.DefaultKey: Any?]) {
|
|
||||||
let mapped = keyedDefaults.reduce(into: [String: Any]()) {
|
|
||||||
$0[$1.key.rawValue] = $1.value
|
|
||||||
}
|
|
||||||
register(defaults: mapped)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
//
|
||||||
|
// LogManager.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 6/15/22.
|
||||||
|
// Copyright (c) 2022 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 SwiftyBeaver
|
||||||
|
|
||||||
|
public class LogManager {
|
||||||
|
public let logFile: URL?
|
||||||
|
|
||||||
|
public var logLevel: SwiftyBeaver.Level = .info
|
||||||
|
|
||||||
|
public var logFormat: String?
|
||||||
|
|
||||||
|
public init(logFile: URL?) {
|
||||||
|
self.logFile = logFile
|
||||||
|
}
|
||||||
|
|
||||||
|
public func configureLogging() {
|
||||||
|
let console = ConsoleDestination()
|
||||||
|
console.minLevel = logLevel
|
||||||
|
// console.useNSLog = true
|
||||||
|
if let logFormat = logFormat {
|
||||||
|
console.format = logFormat
|
||||||
|
}
|
||||||
|
SwiftyBeaver.addDestination(console)
|
||||||
|
|
||||||
|
if let fileURL = logFile {
|
||||||
|
let file = FileDestination()
|
||||||
|
file.minLevel = logLevel
|
||||||
|
file.logFileURL = fileURL
|
||||||
|
if let logFormat = logFormat {
|
||||||
|
file.format = logFormat
|
||||||
|
}
|
||||||
|
_ = file.deleteLogFile()
|
||||||
|
SwiftyBeaver.addDestination(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,21 +25,50 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreData
|
import CoreData
|
||||||
|
import PassepartoutUtils
|
||||||
|
|
||||||
public class PersistenceManager {
|
public class PersistenceManager {
|
||||||
private let author: String?
|
private let store: KeyValueStore
|
||||||
|
|
||||||
public init(author: String?) {
|
public init(store: KeyValueStore) {
|
||||||
self.author = author
|
self.store = store
|
||||||
|
|
||||||
|
// set once
|
||||||
|
if persistenceAuthor == nil {
|
||||||
|
persistenceAuthor = UUID().uuidString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func profilesPersistence(withName containerName: String) -> Persistence {
|
public func profilesPersistence(withName containerName: String) -> Persistence {
|
||||||
let model = PassepartoutDataModels.profiles
|
let model = PassepartoutDataModels.profiles
|
||||||
return Persistence(withCloudKitName: containerName, model: model, author: author)
|
return Persistence(withCloudKitName: containerName, model: model, author: persistenceAuthor)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func providersPersistence(withName containerName: String) -> Persistence {
|
public func providersPersistence(withName containerName: String) -> Persistence {
|
||||||
let model = PassepartoutDataModels.providers
|
let model = PassepartoutDataModels.providers
|
||||||
return Persistence(withLocalName: containerName, model: model, author: author)
|
return Persistence(withLocalName: containerName, model: model, author: persistenceAuthor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: KeyValueStore
|
||||||
|
|
||||||
|
extension PersistenceManager {
|
||||||
|
public private(set) var persistenceAuthor: String? {
|
||||||
|
get {
|
||||||
|
store.value(forLocation: StoreKey.persistenceAuthor)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
store.setValue(newValue, forLocation: StoreKey.persistenceAuthor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension PersistenceManager {
|
||||||
|
private enum StoreKey: String, KeyStoreDomainLocation {
|
||||||
|
case persistenceAuthor
|
||||||
|
|
||||||
|
var domain: String {
|
||||||
|
"PersistenceManager"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// AppManager+Migrations.swift
|
// UpgradeManager+Migrations.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 3/20/22.
|
// Created by Davide De Rosa on 3/20/22.
|
||||||
|
@ -28,12 +28,34 @@ import GenericJSON
|
||||||
import TunnelKitCore
|
import TunnelKitCore
|
||||||
import TunnelKitOpenVPNCore
|
import TunnelKitOpenVPNCore
|
||||||
import TunnelKitManager
|
import TunnelKitManager
|
||||||
|
import PassepartoutUtils
|
||||||
|
|
||||||
private typealias Map = [String: Any]
|
private typealias Map = [String: Any]
|
||||||
|
|
||||||
|
// MARK: Migrate old store
|
||||||
|
|
||||||
|
extension UpgradeManager {
|
||||||
|
fileprivate enum LegacyStoreKey: String, KeyStoreLocation {
|
||||||
|
case didMigrateToV2
|
||||||
|
|
||||||
|
var key: String {
|
||||||
|
rawValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doMigrateStore(_ store: KeyValueStore) {
|
||||||
|
if !didMigrateToV2 {
|
||||||
|
guard let legacyDidMigrateToV2: Bool = store.value(forLocation: LegacyStoreKey.didMigrateToV2) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
didMigrateToV2 = legacyDidMigrateToV2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: Migrate to version 2
|
// MARK: Migrate to version 2
|
||||||
|
|
||||||
extension AppManager {
|
extension UpgradeManager {
|
||||||
fileprivate enum MigrationError: Error {
|
fileprivate enum MigrationError: Error {
|
||||||
case json
|
case json
|
||||||
|
|
||||||
|
@ -321,7 +343,7 @@ private extension URL {
|
||||||
func asJSON() throws -> Map {
|
func asJSON() throws -> Map {
|
||||||
let data = try Data(contentsOf: self)
|
let data = try Data(contentsOf: self)
|
||||||
guard let json = try JSONSerialization.jsonObject(with: data) as? Map else {
|
guard let json = try JSONSerialization.jsonObject(with: data) as? Map else {
|
||||||
throw AppManager.MigrationError.json
|
throw UpgradeManager.MigrationError.json
|
||||||
}
|
}
|
||||||
return json
|
return json
|
||||||
}
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
//
|
||||||
|
// UpgradeManager.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 2/8/22.
|
||||||
|
// Copyright (c) 2022 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 CoreData
|
||||||
|
import SwiftyBeaver
|
||||||
|
import PassepartoutUtils
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
public class UpgradeManager: ObservableObject {
|
||||||
|
|
||||||
|
// MARK: Initialization
|
||||||
|
|
||||||
|
private let store: KeyValueStore
|
||||||
|
|
||||||
|
// MARK: State
|
||||||
|
|
||||||
|
@Published public private(set) var isDoingMigrations = true
|
||||||
|
|
||||||
|
public init(store: KeyValueStore) {
|
||||||
|
self.store = store
|
||||||
|
}
|
||||||
|
|
||||||
|
public func doMigrations(_ profileManager: ProfileManager) {
|
||||||
|
doMigrateStore(store)
|
||||||
|
|
||||||
|
// profileManager.removeAllProfiles()
|
||||||
|
guard didMigrateToV2 else {
|
||||||
|
isDoingMigrations = true
|
||||||
|
let migrated = doMigrateToV2()
|
||||||
|
if !migrated.isEmpty {
|
||||||
|
pp_log.info("Migrating \(migrated.count) profiles")
|
||||||
|
migrated.forEach {
|
||||||
|
var profile = $0
|
||||||
|
if profileManager.isExistingProfile(withName: profile.header.name) {
|
||||||
|
profile = profile.renamedUniquely(withLastUpdate: true)
|
||||||
|
}
|
||||||
|
profileManager.saveProfile(profile, isActive: nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pp_log.info("Nothing to migrate!")
|
||||||
|
}
|
||||||
|
isDoingMigrations = false
|
||||||
|
|
||||||
|
didMigrateToV2 = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isDoingMigrations = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: KeyValueStore
|
||||||
|
|
||||||
|
extension UpgradeManager {
|
||||||
|
public internal(set) var didMigrateToV2: Bool {
|
||||||
|
get {
|
||||||
|
store.value(forLocation: StoreKey.didMigrateToV2) ?? false
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
store.setValue(newValue, forLocation: StoreKey.didMigrateToV2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension UpgradeManager {
|
||||||
|
private enum StoreKey: String, KeyStoreDomainLocation {
|
||||||
|
case didMigrateToV2
|
||||||
|
|
||||||
|
var domain: String {
|
||||||
|
"UpgradeManager"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,13 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension VPNManager {
|
extension VPNManager {
|
||||||
|
private var vpnPreferences: VPNPreferences {
|
||||||
|
DefaultVPNPreferences(
|
||||||
|
tunnelLogFormat: tunnelLogFormat,
|
||||||
|
masksPrivateData: masksPrivateData
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func vpnConfigurationWithCurrentProfile() -> VPNConfiguration? {
|
func vpnConfigurationWithCurrentProfile() -> VPNConfiguration? {
|
||||||
do {
|
do {
|
||||||
guard profileManager.isCurrentProfileActive() else {
|
guard profileManager.isCurrentProfileActive() else {
|
||||||
|
@ -52,7 +59,7 @@ extension VPNManager {
|
||||||
let parameters = VPNConfigurationParameters(
|
let parameters = VPNConfigurationParameters(
|
||||||
profile,
|
profile,
|
||||||
appGroup: profileManager.appGroup,
|
appGroup: profileManager.appGroup,
|
||||||
preferences: appManager.preferences,
|
preferences: vpnPreferences,
|
||||||
passwordReference: profileManager.passwordReference(forProfile: profile),
|
passwordReference: profileManager.passwordReference(forProfile: profile),
|
||||||
withNetworkSettings: isNetworkSettingsSupported(),
|
withNetworkSettings: isNetworkSettingsSupported(),
|
||||||
withCustomRules: isOnDemandRulesSupported()
|
withCustomRules: isOnDemandRulesSupported()
|
||||||
|
|
|
@ -25,8 +25,9 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
import TunnelKitCore
|
||||||
import TunnelKitManager
|
import TunnelKitManager
|
||||||
import TunnelKitOpenVPNManager
|
import PassepartoutUtils
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public class VPNManager: ObservableObject {
|
public class VPNManager: ObservableObject {
|
||||||
|
@ -34,7 +35,7 @@ public class VPNManager: ObservableObject {
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
let appManager: AppManager
|
private let store: KeyValueStore
|
||||||
|
|
||||||
let profileManager: ProfileManager
|
let profileManager: ProfileManager
|
||||||
|
|
||||||
|
@ -68,12 +69,12 @@ public class VPNManager: ObservableObject {
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
appManager: AppManager,
|
store: KeyValueStore,
|
||||||
profileManager: ProfileManager,
|
profileManager: ProfileManager,
|
||||||
providerManager: ProviderManager,
|
providerManager: ProviderManager,
|
||||||
strategy: VPNManagerStrategy
|
strategy: VPNManagerStrategy
|
||||||
) {
|
) {
|
||||||
self.appManager = appManager
|
self.store = store
|
||||||
self.profileManager = profileManager
|
self.profileManager = profileManager
|
||||||
self.providerManager = providerManager
|
self.providerManager = providerManager
|
||||||
self.strategy = strategy
|
self.strategy = strategy
|
||||||
|
@ -81,6 +82,8 @@ public class VPNManager: ObservableObject {
|
||||||
isOnDemandRulesSupported = { true }
|
isOnDemandRulesSupported = { true }
|
||||||
|
|
||||||
currentState = ObservableState()
|
currentState = ObservableState()
|
||||||
|
|
||||||
|
CoreConfiguration.masksPrivateData = masksPrivateData
|
||||||
}
|
}
|
||||||
|
|
||||||
public func toggle() -> Bool {
|
public func toggle() -> Bool {
|
||||||
|
@ -152,7 +155,7 @@ extension VPNManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func observeProfileManager() {
|
private func observeProfileManager() {
|
||||||
profileManager.$activeProfileId
|
profileManager.activeProfileIdPublisher
|
||||||
.dropFirst()
|
.dropFirst()
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.sink { newId in
|
.sink { newId in
|
||||||
|
@ -252,3 +255,39 @@ extension VPNManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: KeyValueStore
|
||||||
|
|
||||||
|
extension VPNManager {
|
||||||
|
public var tunnelLogFormat: String? {
|
||||||
|
get {
|
||||||
|
store.value(forLocation: StoreKey.tunnelLogFormat)
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
store.setValue(newValue, forLocation: StoreKey.tunnelLogFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public var masksPrivateData: Bool {
|
||||||
|
get {
|
||||||
|
store.value(forLocation: StoreKey.masksPrivateData) ?? true
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
store.setValue(newValue, forLocation: StoreKey.masksPrivateData)
|
||||||
|
|
||||||
|
CoreConfiguration.masksPrivateData = masksPrivateData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension VPNManager {
|
||||||
|
private enum StoreKey: String, KeyStoreDomainLocation {
|
||||||
|
case tunnelLogFormat
|
||||||
|
|
||||||
|
case masksPrivateData
|
||||||
|
|
||||||
|
var domain: String {
|
||||||
|
"VPNManager"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ struct VPNConfigurationParameters {
|
||||||
|
|
||||||
let appGroup: String
|
let appGroup: String
|
||||||
|
|
||||||
let preferences: AppPreferences
|
let preferences: VPNPreferences
|
||||||
|
|
||||||
let networkSettings: Profile.NetworkSettings
|
let networkSettings: Profile.NetworkSettings
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ struct VPNConfigurationParameters {
|
||||||
init(
|
init(
|
||||||
_ profile: Profile,
|
_ profile: Profile,
|
||||||
appGroup: String,
|
appGroup: String,
|
||||||
preferences: AppPreferences,
|
preferences: VPNPreferences,
|
||||||
passwordReference: Data?,
|
passwordReference: Data?,
|
||||||
withNetworkSettings: Bool,
|
withNetworkSettings: Bool,
|
||||||
withCustomRules: Bool
|
withCustomRules: Bool
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
//
|
//
|
||||||
// AppPreferences.swift
|
// VPNPreferences.swift
|
||||||
// Passepartout
|
// Passepartout
|
||||||
//
|
//
|
||||||
// Created by Davide De Rosa on 2/10/22.
|
// Created by Davide De Rosa on 6/6/22.
|
||||||
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
|
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
|
||||||
//
|
//
|
||||||
// https://github.com/passepartoutvpn
|
// https://github.com/passepartoutvpn
|
||||||
|
@ -25,23 +25,14 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@MainActor
|
public protocol VPNPreferences {
|
||||||
public protocol AppPreferences {
|
|
||||||
var activeProfileId: UUID? { get }
|
|
||||||
|
|
||||||
var logFormat: String? { get }
|
|
||||||
|
|
||||||
var tunnelLogFormat: String? { get }
|
var tunnelLogFormat: String? { get }
|
||||||
|
|
||||||
var masksPrivateData: Bool { get }
|
var masksPrivateData: Bool { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct DefaultAppPreferences: AppPreferences {
|
struct DefaultVPNPreferences: VPNPreferences {
|
||||||
public let activeProfileId: UUID?
|
let tunnelLogFormat: String?
|
||||||
|
|
||||||
public let logFormat: String?
|
let masksPrivateData: Bool
|
||||||
|
|
||||||
public let tunnelLogFormat: String?
|
|
||||||
|
|
||||||
public let masksPrivateData: Bool
|
|
||||||
}
|
}
|
|
@ -35,6 +35,8 @@ public class ProfileManager: ObservableObject {
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
|
|
||||||
|
private let store: KeyValueStore
|
||||||
|
|
||||||
private let providerManager: ProviderManager
|
private let providerManager: ProviderManager
|
||||||
|
|
||||||
public let appGroup: String
|
public let appGroup: String
|
||||||
|
@ -47,7 +49,7 @@ public class ProfileManager: ObservableObject {
|
||||||
|
|
||||||
// MARK: Observables
|
// MARK: Observables
|
||||||
|
|
||||||
@Published public private(set) var activeProfileId: UUID? {
|
@Published private var internalActiveProfileId: UUID? {
|
||||||
willSet {
|
willSet {
|
||||||
pp_log.debug("Setting active profile: \(newValue?.uuidString ?? "nil")")
|
pp_log.debug("Setting active profile: \(newValue?.uuidString ?? "nil")")
|
||||||
}
|
}
|
||||||
|
@ -59,6 +61,10 @@ public class ProfileManager: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var activeProfileIdPublisher: Published<UUID?>.Publisher {
|
||||||
|
$internalActiveProfileId
|
||||||
|
}
|
||||||
|
|
||||||
public var currentProfileId: UUID? {
|
public var currentProfileId: UUID? {
|
||||||
get {
|
get {
|
||||||
internalCurrentProfileId
|
internalCurrentProfileId
|
||||||
|
@ -83,6 +89,7 @@ public class ProfileManager: ObservableObject {
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
store: KeyValueStore,
|
||||||
providerManager: ProviderManager,
|
providerManager: ProviderManager,
|
||||||
appGroup: String,
|
appGroup: String,
|
||||||
keychainLabel: @escaping (String, VPNProtocolType) -> String,
|
keychainLabel: @escaping (String, VPNProtocolType) -> String,
|
||||||
|
@ -91,6 +98,7 @@ public class ProfileManager: ObservableObject {
|
||||||
guard let _ = UserDefaults(suiteName: appGroup) else {
|
guard let _ = UserDefaults(suiteName: appGroup) else {
|
||||||
fatalError("No entitlements for group '\(appGroup)'")
|
fatalError("No entitlements for group '\(appGroup)'")
|
||||||
}
|
}
|
||||||
|
self.store = store
|
||||||
self.providerManager = providerManager
|
self.providerManager = providerManager
|
||||||
self.appGroup = appGroup
|
self.appGroup = appGroup
|
||||||
self.keychainLabel = keychainLabel
|
self.keychainLabel = keychainLabel
|
||||||
|
@ -99,14 +107,6 @@ public class ProfileManager: ObservableObject {
|
||||||
|
|
||||||
currentProfile = ObservableProfile()
|
currentProfile = ObservableProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setActiveProfileId(_ id: UUID) {
|
|
||||||
guard isExistingProfile(withId: id) else {
|
|
||||||
pp_log.warning("Active profile \(id) does not exist, ignoring")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
activeProfileId = id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Index
|
// MARK: Index
|
||||||
|
@ -450,3 +450,41 @@ extension ProfileManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: KeyValueStore
|
||||||
|
|
||||||
|
extension ProfileManager {
|
||||||
|
public private(set) var activeProfileId: UUID? {
|
||||||
|
get {
|
||||||
|
guard let idString: String = store.value(forLocation: StoreKey.activeProfileId) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let id = UUID(uuidString: idString) else {
|
||||||
|
pp_log.warning("Active profile id is malformed, ignoring")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard isExistingProfile(withId: id) else {
|
||||||
|
pp_log.warning("Active profile \(id) does not exist, ignoring")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
|
||||||
|
// trigger publisher
|
||||||
|
internalActiveProfileId = newValue
|
||||||
|
|
||||||
|
store.setValue(newValue?.uuidString, forLocation: StoreKey.activeProfileId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension ProfileManager {
|
||||||
|
private enum StoreKey: String, KeyStoreDomainLocation {
|
||||||
|
case activeProfileId
|
||||||
|
|
||||||
|
var domain: String {
|
||||||
|
"ProfileManager"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// KeyValueStore.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 6/15/22.
|
||||||
|
// Copyright (c) 2022 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
|
||||||
|
|
||||||
|
public protocol KeyStoreLocation: RawRepresentable where RawValue == String {
|
||||||
|
var key: String { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol KeyStoreDomainLocation: KeyStoreLocation {
|
||||||
|
var domain: String { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
extension KeyStoreDomainLocation {
|
||||||
|
public var key: String {
|
||||||
|
"\(domain).\(rawValue)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol KeyValueStore {
|
||||||
|
func setValue<L: KeyStoreLocation, V>(_ value: V, forLocation location: L)
|
||||||
|
|
||||||
|
func value<L: KeyStoreLocation, V>(forLocation location: L) -> V?
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// UserDefaultsStore.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 6/15/22.
|
||||||
|
// Copyright (c) 2022 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
|
||||||
|
|
||||||
|
public struct UserDefaultsStore: KeyValueStore {
|
||||||
|
private let defaults: UserDefaults
|
||||||
|
|
||||||
|
public init(defaults: UserDefaults) {
|
||||||
|
self.defaults = defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setValue<L: KeyStoreLocation, V>(_ value: V, forLocation location: L) {
|
||||||
|
defaults.setValue(value, forKey: location.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func value<L: KeyStoreLocation, V>(forLocation location: L) -> V? {
|
||||||
|
defaults.value(forKey: location.key) as? V
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue