Track module preferences history in Core Data (#994)
Restore CDModulePreferencesV3 to track the history of module prefrences. This way, excluded endpoints may be saved globally to Core Data as a starting point. Then in Profile.userInfo we only save the relevant exclusions for the current configuration. The .excludedEndpoints relationship is therefore moved out of CDProviderPreferencesV3. Further refactoring: - ModuleViewParameters now includes a ModulePreferences observable that module views can observe - Tunnel doesn't need access to PreferencesManager anymore (exclusions are in Profile.userInfo)
This commit is contained in:
parent
aeec943c58
commit
6f9c78b257
|
@ -33,5 +33,5 @@ final class CDExcludedEndpoint: NSManagedObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NSManaged var endpoint: String?
|
@NSManaged var endpoint: String?
|
||||||
@NSManaged var providerPreferences: CDProviderPreferencesV3?
|
@NSManaged var modulePreferences: CDModulePreferencesV3?
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// CDModulePreferencesV3.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 12/10/24.
|
||||||
|
// Copyright (c) 2024 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 CoreData
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
@objc(CDModulePreferencesV3)
|
||||||
|
final class CDModulePreferencesV3: NSManagedObject {
|
||||||
|
@nonobjc static func fetchRequest() -> NSFetchRequest<CDModulePreferencesV3> {
|
||||||
|
NSFetchRequest<CDModulePreferencesV3>(entityName: "CDModulePreferencesV3")
|
||||||
|
}
|
||||||
|
|
||||||
|
@NSManaged var moduleId: UUID?
|
||||||
|
@NSManaged var lastUpdate: Date?
|
||||||
|
@NSManaged var excludedEndpoints: Set<CDExcludedEndpoint>?
|
||||||
|
}
|
|
@ -35,5 +35,4 @@ final class CDProviderPreferencesV3: NSManagedObject {
|
||||||
@NSManaged var providerId: String?
|
@NSManaged var providerId: String?
|
||||||
@NSManaged var lastUpdate: Date?
|
@NSManaged var lastUpdate: Date?
|
||||||
@NSManaged var favoriteServerIds: Data?
|
@NSManaged var favoriteServerIds: Data?
|
||||||
@NSManaged var excludedEndpoints: Set<CDExcludedEndpoint>?
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,16 @@
|
||||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23507" systemVersion="23H222" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23507" systemVersion="23H222" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="CDExcludedEndpoint" representedClassName="CDExcludedEndpoint" syncable="YES">
|
<entity name="CDExcludedEndpoint" representedClassName="CDExcludedEndpoint" syncable="YES">
|
||||||
<attribute name="endpoint" optional="YES" attributeType="String"/>
|
<attribute name="endpoint" optional="YES" attributeType="String"/>
|
||||||
<relationship name="providerPreferences" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CDProviderPreferencesV3" inverseName="excludedEndpoints" inverseEntity="CDProviderPreferencesV3"/>
|
<relationship name="modulePreferences" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CDModulePreferencesV3" inverseName="excludedEndpoints" inverseEntity="CDModulePreferencesV3"/>
|
||||||
|
</entity>
|
||||||
|
<entity name="CDModulePreferencesV3" representedClassName="CDModulePreferencesV3" syncable="YES">
|
||||||
|
<attribute name="lastUpdate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
|
<attribute name="moduleId" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||||
|
<relationship name="excludedEndpoints" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="CDExcludedEndpoint" inverseName="modulePreferences" inverseEntity="CDExcludedEndpoint"/>
|
||||||
</entity>
|
</entity>
|
||||||
<entity name="CDProviderPreferencesV3" representedClassName="CDProviderPreferencesV3" syncable="YES">
|
<entity name="CDProviderPreferencesV3" representedClassName="CDProviderPreferencesV3" syncable="YES">
|
||||||
<attribute name="favoriteServerIds" optional="YES" attributeType="Binary"/>
|
<attribute name="favoriteServerIds" optional="YES" attributeType="Binary"/>
|
||||||
<attribute name="lastUpdate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
<attribute name="lastUpdate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||||
<attribute name="providerId" optional="YES" attributeType="String"/>
|
<attribute name="providerId" optional="YES" attributeType="String"/>
|
||||||
<relationship name="excludedEndpoints" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="CDExcludedEndpoint" inverseName="providerPreferences" inverseEntity="CDExcludedEndpoint"/>
|
|
||||||
</entity>
|
</entity>
|
||||||
</model>
|
</model>
|
|
@ -0,0 +1,120 @@
|
||||||
|
//
|
||||||
|
// CDModulePreferencesRepositoryV3.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 12/5/24.
|
||||||
|
// Copyright (c) 2024 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 AppData
|
||||||
|
import CommonLibrary
|
||||||
|
import CoreData
|
||||||
|
import Foundation
|
||||||
|
import PassepartoutKit
|
||||||
|
|
||||||
|
extension AppData {
|
||||||
|
public static func cdModulePreferencesRepositoryV3(context: NSManagedObjectContext, moduleId: UUID) throws -> ModulePreferencesRepository {
|
||||||
|
try CDModulePreferencesRepositoryV3(context: context, moduleId: moduleId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class CDModulePreferencesRepositoryV3: ModulePreferencesRepository {
|
||||||
|
private nonisolated let context: NSManagedObjectContext
|
||||||
|
|
||||||
|
private let entity: CDModulePreferencesV3
|
||||||
|
|
||||||
|
init(context: NSManagedObjectContext, moduleId: UUID) throws {
|
||||||
|
self.context = context
|
||||||
|
|
||||||
|
entity = try context.performAndWait {
|
||||||
|
let request = CDModulePreferencesV3.fetchRequest()
|
||||||
|
request.predicate = NSPredicate(format: "moduleId == %@", moduleId.uuidString)
|
||||||
|
request.sortDescriptors = [.init(key: "lastUpdate", ascending: false)]
|
||||||
|
do {
|
||||||
|
let entities = try request.execute()
|
||||||
|
|
||||||
|
// dedup by lastUpdate
|
||||||
|
entities.enumerated().forEach {
|
||||||
|
guard $0.offset > 0 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$0.element.excludedEndpoints?.forEach(context.delete(_:))
|
||||||
|
context.delete($0.element)
|
||||||
|
}
|
||||||
|
|
||||||
|
let entity = entities.first ?? CDModulePreferencesV3(context: context)
|
||||||
|
entity.moduleId = moduleId
|
||||||
|
entity.lastUpdate = Date()
|
||||||
|
return entity
|
||||||
|
} catch {
|
||||||
|
pp_log(.app, .error, "Unable to load preferences for module \(moduleId): \(error)")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isExcludedEndpoint(_ endpoint: ExtendedEndpoint) -> Bool {
|
||||||
|
context.performAndWait {
|
||||||
|
entity.excludedEndpoints?.contains {
|
||||||
|
$0.endpoint == endpoint.rawValue
|
||||||
|
} ?? false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addExcludedEndpoint(_ endpoint: ExtendedEndpoint) {
|
||||||
|
context.performAndWait {
|
||||||
|
guard entity.excludedEndpoints?.contains(where: {
|
||||||
|
$0.endpoint == endpoint.rawValue
|
||||||
|
}) != true else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let mapper = CoreDataMapper(context: context)
|
||||||
|
let cdEndpoint = mapper.cdExcludedEndpoint(from: endpoint)
|
||||||
|
cdEndpoint.modulePreferences = entity
|
||||||
|
entity.excludedEndpoints?.insert(cdEndpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeExcludedEndpoint(_ endpoint: ExtendedEndpoint) {
|
||||||
|
context.performAndWait {
|
||||||
|
guard let found = entity.excludedEndpoints?.first(where: {
|
||||||
|
$0.endpoint == endpoint.rawValue
|
||||||
|
}) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entity.excludedEndpoints?.remove(found)
|
||||||
|
context.delete(found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func save() throws {
|
||||||
|
try context.performAndWait {
|
||||||
|
guard context.hasChanges else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
try context.save()
|
||||||
|
} catch {
|
||||||
|
context.rollback()
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,7 +55,6 @@ private final class CDProviderPreferencesRepositoryV3: ProviderPreferencesReposi
|
||||||
guard $0.offset > 0 else {
|
guard $0.offset > 0 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
$0.element.excludedEndpoints?.forEach(context.delete(_:))
|
|
||||||
context.delete($0.element)
|
context.delete($0.element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,35 +94,6 @@ private final class CDProviderPreferencesRepositoryV3: ProviderPreferencesReposi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isExcludedEndpoint(_ endpoint: ExtendedEndpoint) -> Bool {
|
|
||||||
context.performAndWait {
|
|
||||||
entity.excludedEndpoints?.contains {
|
|
||||||
$0.endpoint == endpoint.rawValue
|
|
||||||
} ?? false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addExcludedEndpoint(_ endpoint: ExtendedEndpoint) {
|
|
||||||
context.performAndWait {
|
|
||||||
let mapper = CoreDataMapper(context: context)
|
|
||||||
let cdEndpoint = mapper.cdExcludedEndpoint(from: endpoint)
|
|
||||||
cdEndpoint.providerPreferences = entity
|
|
||||||
entity.excludedEndpoints?.insert(cdEndpoint)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeExcludedEndpoint(_ endpoint: ExtendedEndpoint) {
|
|
||||||
context.performAndWait {
|
|
||||||
guard let found = entity.excludedEndpoints?.first(where: {
|
|
||||||
$0.endpoint == endpoint.rawValue
|
|
||||||
}) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
entity.excludedEndpoints?.remove(found)
|
|
||||||
context.delete(found)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func save() throws {
|
func save() throws {
|
||||||
try context.performAndWait {
|
try context.performAndWait {
|
||||||
guard context.hasChanges else {
|
guard context.hasChanges else {
|
||||||
|
|
|
@ -34,9 +34,6 @@ public struct AppCoordinator: View, AppCoordinatorConforming, SizeClassProviding
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
public var iapManager: IAPManager
|
public var iapManager: IAPManager
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
public var preferencesManager: PreferencesManager
|
|
||||||
|
|
||||||
@Environment(\.isUITesting)
|
@Environment(\.isUITesting)
|
||||||
private var isUITesting
|
private var isUITesting
|
||||||
|
|
||||||
|
|
|
@ -242,6 +242,7 @@ private extension OnDemandView {
|
||||||
module: $0,
|
module: $0,
|
||||||
parameters: .init(
|
parameters: .init(
|
||||||
editor: $1,
|
editor: $1,
|
||||||
|
preferences: ModulePreferences(),
|
||||||
impl: nil
|
impl: nil
|
||||||
),
|
),
|
||||||
observer: MockWifi()
|
observer: MockWifi()
|
||||||
|
|
|
@ -30,9 +30,6 @@ import SwiftUI
|
||||||
|
|
||||||
struct OpenVPNView: View, ModuleDraftEditing {
|
struct OpenVPNView: View, ModuleDraftEditing {
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var preferencesManager: PreferencesManager
|
|
||||||
|
|
||||||
@Environment(\.navigationPath)
|
@Environment(\.navigationPath)
|
||||||
private var path
|
private var path
|
||||||
|
|
||||||
|
@ -41,6 +38,9 @@ struct OpenVPNView: View, ModuleDraftEditing {
|
||||||
@ObservedObject
|
@ObservedObject
|
||||||
var editor: ProfileEditor
|
var editor: ProfileEditor
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var modulePreferences: ModulePreferences
|
||||||
|
|
||||||
let impl: OpenVPNModule.Implementation?
|
let impl: OpenVPNModule.Implementation?
|
||||||
|
|
||||||
private let isServerPushed: Bool
|
private let isServerPushed: Bool
|
||||||
|
@ -51,15 +51,13 @@ struct OpenVPNView: View, ModuleDraftEditing {
|
||||||
@State
|
@State
|
||||||
private var paywallReason: PaywallReason?
|
private var paywallReason: PaywallReason?
|
||||||
|
|
||||||
@StateObject
|
|
||||||
private var providerPreferences = ProviderPreferences()
|
|
||||||
|
|
||||||
@StateObject
|
@StateObject
|
||||||
private var errorHandler: ErrorHandler = .default()
|
private var errorHandler: ErrorHandler = .default()
|
||||||
|
|
||||||
init(serverConfiguration: OpenVPN.Configuration) {
|
init(serverConfiguration: OpenVPN.Configuration) {
|
||||||
module = OpenVPNModule.Builder(configurationBuilder: serverConfiguration.builder())
|
module = OpenVPNModule.Builder(configurationBuilder: serverConfiguration.builder())
|
||||||
editor = ProfileEditor(modules: [module])
|
editor = ProfileEditor(modules: [module])
|
||||||
|
modulePreferences = ModulePreferences()
|
||||||
assert(module.configurationBuilder != nil, "isServerPushed must imply module.configurationBuilder != nil")
|
assert(module.configurationBuilder != nil, "isServerPushed must imply module.configurationBuilder != nil")
|
||||||
impl = nil
|
impl = nil
|
||||||
isServerPushed = true
|
isServerPushed = true
|
||||||
|
@ -68,6 +66,7 @@ struct OpenVPNView: View, ModuleDraftEditing {
|
||||||
init(module: OpenVPNModule.Builder, parameters: ModuleViewParameters) {
|
init(module: OpenVPNModule.Builder, parameters: ModuleViewParameters) {
|
||||||
self.module = module
|
self.module = module
|
||||||
editor = parameters.editor
|
editor = parameters.editor
|
||||||
|
modulePreferences = parameters.preferences
|
||||||
impl = parameters.impl as? OpenVPNModule.Implementation
|
impl = parameters.impl as? OpenVPNModule.Implementation
|
||||||
isServerPushed = false
|
isServerPushed = false
|
||||||
}
|
}
|
||||||
|
@ -129,7 +128,7 @@ private extension OpenVPNView {
|
||||||
var providerModifier: some ViewModifier {
|
var providerModifier: some ViewModifier {
|
||||||
VPNProviderContentModifier(
|
VPNProviderContentModifier(
|
||||||
providerId: providerId,
|
providerId: providerId,
|
||||||
providerPreferences: providerPreferences,
|
providerPreferences: nil,
|
||||||
selectedEntity: providerEntity,
|
selectedEntity: providerEntity,
|
||||||
paywallReason: $paywallReason,
|
paywallReason: $paywallReason,
|
||||||
entityDestination: Subroute.providerServer,
|
entityDestination: Subroute.providerServer,
|
||||||
|
@ -200,17 +199,32 @@ private extension OpenVPNView {
|
||||||
|
|
||||||
private extension OpenVPNView {
|
private extension OpenVPNView {
|
||||||
var excludedEndpoints: ObservableList<ExtendedEndpoint> {
|
var excludedEndpoints: ObservableList<ExtendedEndpoint> {
|
||||||
if draft.wrappedValue.providerSelection != nil {
|
editor.excludedEndpoints(for: module.id, preferences: modulePreferences)
|
||||||
return providerPreferences.excludedEndpoints()
|
|
||||||
} else {
|
|
||||||
return editor.excludedEndpoints(for: module.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func onSelectServer(server: VPNServer, preset: VPNPreset<OpenVPN.Configuration>) {
|
func onSelectServer(server: VPNServer, preset: VPNPreset<OpenVPN.Configuration>) {
|
||||||
draft.wrappedValue.providerEntity = VPNEntity(server: server, preset: preset)
|
draft.wrappedValue.providerEntity = VPNEntity(server: server, preset: preset)
|
||||||
|
resetExcludedEndpointsWithCurrentProviderEntity()
|
||||||
path.wrappedValue.removeLast()
|
path.wrappedValue.removeLast()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filter out exclusions unrelated to current server
|
||||||
|
func resetExcludedEndpointsWithCurrentProviderEntity() {
|
||||||
|
do {
|
||||||
|
let cfg = try draft.wrappedValue.providerSelection?.configuration()
|
||||||
|
editor.profile.attributes.editPreferences(inModule: module.id) {
|
||||||
|
if let cfg {
|
||||||
|
$0.excludedEndpoints = Set(cfg.remotes?.filter {
|
||||||
|
modulePreferences.isExcludedEndpoint($0)
|
||||||
|
} ?? [])
|
||||||
|
} else {
|
||||||
|
$0.excludedEndpoints = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
pp_log(.app, .error, "Unable to build provider configuration for excluded endpoints: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
|
@ -28,12 +28,19 @@ import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ModuleDetailView: View {
|
struct ModuleDetailView: View {
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var preferencesManager: PreferencesManager
|
||||||
|
|
||||||
let profileEditor: ProfileEditor
|
let profileEditor: ProfileEditor
|
||||||
|
|
||||||
let moduleId: UUID?
|
let moduleId: UUID?
|
||||||
|
|
||||||
let moduleViewFactory: any ModuleViewFactory
|
let moduleViewFactory: any ModuleViewFactory
|
||||||
|
|
||||||
|
@StateObject
|
||||||
|
private var preferences = ModulePreferences()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
debugChanges()
|
debugChanges()
|
||||||
return Group {
|
return Group {
|
||||||
|
@ -52,8 +59,24 @@ private extension ModuleDetailView {
|
||||||
func editorView(forModuleWithId moduleId: UUID) -> some View {
|
func editorView(forModuleWithId moduleId: UUID) -> some View {
|
||||||
AnyView(moduleViewFactory.view(
|
AnyView(moduleViewFactory.view(
|
||||||
with: profileEditor,
|
with: profileEditor,
|
||||||
|
preferences: preferences,
|
||||||
moduleId: moduleId
|
moduleId: moduleId
|
||||||
))
|
))
|
||||||
|
.onLoad {
|
||||||
|
do {
|
||||||
|
let repository = try preferencesManager.preferencesRepository(forModuleWithId: moduleId)
|
||||||
|
preferences.setRepository(repository)
|
||||||
|
} catch {
|
||||||
|
pp_log(.app, .error, "Unable to load preferences for module \(moduleId): \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
do {
|
||||||
|
try preferences.save()
|
||||||
|
} catch {
|
||||||
|
pp_log(.app, .error, "Unable to save preferences for module \(moduleId): \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var emptyView: some View {
|
var emptyView: some View {
|
||||||
|
|
|
@ -43,9 +43,6 @@ struct ProfileCoordinator: View {
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
private var iapManager: IAPManager
|
private var iapManager: IAPManager
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
private var preferencesManager: PreferencesManager
|
|
||||||
|
|
||||||
let profileManager: ProfileManager
|
let profileManager: ProfileManager
|
||||||
|
|
||||||
let profileEditor: ProfileEditor
|
let profileEditor: ProfileEditor
|
||||||
|
|
|
@ -188,13 +188,14 @@ private extension ProviderContentModifier {
|
||||||
if let providerId {
|
if let providerId {
|
||||||
do {
|
do {
|
||||||
pp_log(.app, .debug, "Load preferences for provider \(providerId)")
|
pp_log(.app, .debug, "Load preferences for provider \(providerId)")
|
||||||
providerPreferences.repository = try preferencesManager.preferencesRepository(forProviderWithId: providerId)
|
let repository = try preferencesManager.preferencesRepository(forProviderWithId: providerId)
|
||||||
|
providerPreferences.setRepository(repository)
|
||||||
} catch {
|
} catch {
|
||||||
pp_log(.app, .error, "Unable to load preferences for provider \(providerId): \(error)")
|
pp_log(.app, .error, "Unable to load preferences for provider \(providerId): \(error)")
|
||||||
providerPreferences.repository = nil
|
providerPreferences.setRepository(nil)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
providerPreferences.repository = nil
|
providerPreferences.setRepository(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -164,7 +164,8 @@ private extension VPNProviderServerView {
|
||||||
private extension VPNProviderServerView {
|
private extension VPNProviderServerView {
|
||||||
func loadInitialServers() async {
|
func loadInitialServers() async {
|
||||||
do {
|
do {
|
||||||
providerPreferences.repository = try preferencesManager.preferencesRepository(forProviderWithId: providerId)
|
let repository = try preferencesManager.preferencesRepository(forProviderWithId: providerId)
|
||||||
|
providerPreferences.setRepository(repository)
|
||||||
} catch {
|
} catch {
|
||||||
pp_log(.app, .error, "Unable to load preferences for provider \(providerId): \(error)")
|
pp_log(.app, .error, "Unable to load preferences for provider \(providerId): \(error)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
//
|
||||||
|
// ModulePreferences.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 12/10/24.
|
||||||
|
// Copyright (c) 2024 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 CommonUtils
|
||||||
|
import Foundation
|
||||||
|
import PassepartoutKit
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
public final class ModulePreferences: ObservableObject {
|
||||||
|
private var repository: ModulePreferencesRepository?
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setRepository(_ repository: ModulePreferencesRepository?) {
|
||||||
|
self.repository = repository
|
||||||
|
}
|
||||||
|
|
||||||
|
public func isExcludedEndpoint(_ endpoint: ExtendedEndpoint) -> Bool {
|
||||||
|
repository?.isExcludedEndpoint(endpoint) ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func addExcludedEndpoint(_ endpoint: ExtendedEndpoint) {
|
||||||
|
repository?.addExcludedEndpoint(endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func removeExcludedEndpoint(_ endpoint: ExtendedEndpoint) {
|
||||||
|
repository?.removeExcludedEndpoint(endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func save() throws {
|
||||||
|
try repository?.save()
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,11 +28,17 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
public final class PreferencesManager: ObservableObject, Sendable {
|
public final class PreferencesManager: ObservableObject, Sendable {
|
||||||
|
private let modulesFactory: @Sendable (UUID) throws -> ModulePreferencesRepository
|
||||||
|
|
||||||
private let providersFactory: @Sendable (ProviderID) throws -> ProviderPreferencesRepository
|
private let providersFactory: @Sendable (ProviderID) throws -> ProviderPreferencesRepository
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
modulesFactory: (@Sendable (UUID) throws -> ModulePreferencesRepository)? = nil,
|
||||||
providersFactory: (@Sendable (ProviderID) throws -> ProviderPreferencesRepository)? = nil
|
providersFactory: (@Sendable (ProviderID) throws -> ProviderPreferencesRepository)? = nil
|
||||||
) {
|
) {
|
||||||
|
self.modulesFactory = modulesFactory ?? { _ in
|
||||||
|
DummyModulePreferencesRepository()
|
||||||
|
}
|
||||||
self.providersFactory = providersFactory ?? { _ in
|
self.providersFactory = providersFactory ?? { _ in
|
||||||
DummyProviderPreferencesRepository()
|
DummyProviderPreferencesRepository()
|
||||||
}
|
}
|
||||||
|
@ -40,25 +46,18 @@ public final class PreferencesManager: ObservableObject, Sendable {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PreferencesManager {
|
extension PreferencesManager {
|
||||||
|
public func preferencesRepository(forModuleWithId moduleId: UUID) throws -> ModulePreferencesRepository {
|
||||||
|
try modulesFactory(moduleId)
|
||||||
|
}
|
||||||
|
|
||||||
public func preferencesRepository(forProviderWithId providerId: ProviderID) throws -> ProviderPreferencesRepository {
|
public func preferencesRepository(forProviderWithId providerId: ProviderID) throws -> ProviderPreferencesRepository {
|
||||||
try providersFactory(providerId)
|
try providersFactory(providerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
|
||||||
extension PreferencesManager {
|
|
||||||
public func preferences(forProviderWithId providerId: ProviderID) throws -> ProviderPreferences {
|
|
||||||
let object = ProviderPreferences()
|
|
||||||
object.repository = try providersFactory(providerId)
|
|
||||||
return object
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Dummy
|
// MARK: - Dummy
|
||||||
|
|
||||||
private final class DummyProviderPreferencesRepository: ProviderPreferencesRepository {
|
private final class DummyModulePreferencesRepository: ModulePreferencesRepository {
|
||||||
var favoriteServers: Set<String> = []
|
|
||||||
|
|
||||||
func isExcludedEndpoint(_ endpoint: ExtendedEndpoint) -> Bool {
|
func isExcludedEndpoint(_ endpoint: ExtendedEndpoint) -> Bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -72,3 +71,10 @@ private final class DummyProviderPreferencesRepository: ProviderPreferencesRepos
|
||||||
func save() throws {
|
func save() throws {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class DummyProviderPreferencesRepository: ProviderPreferencesRepository {
|
||||||
|
var favoriteServers: Set<String> = []
|
||||||
|
|
||||||
|
func save() throws {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,11 +29,15 @@ import PassepartoutKit
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class ProviderPreferences: ObservableObject {
|
public final class ProviderPreferences: ObservableObject {
|
||||||
public var repository: ProviderPreferencesRepository?
|
private var repository: ProviderPreferencesRepository?
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func setRepository(_ repository: ProviderPreferencesRepository?) {
|
||||||
|
self.repository = repository
|
||||||
|
}
|
||||||
|
|
||||||
public var favoriteServers: Set<String> {
|
public var favoriteServers: Set<String> {
|
||||||
get {
|
get {
|
||||||
repository?.favoriteServers ?? []
|
repository?.favoriteServers ?? []
|
||||||
|
@ -44,16 +48,6 @@ public final class ProviderPreferences: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func excludedEndpoints() -> ObservableList<ExtendedEndpoint> {
|
|
||||||
ObservableList { [weak self] in
|
|
||||||
self?.repository?.isExcludedEndpoint($0) == true
|
|
||||||
} add: { [weak self] in
|
|
||||||
self?.repository?.addExcludedEndpoint($0)
|
|
||||||
} remove: { [weak self] in
|
|
||||||
self?.repository?.removeExcludedEndpoint($0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func save() throws {
|
public func save() throws {
|
||||||
try repository?.save()
|
try repository?.save()
|
||||||
}
|
}
|
|
@ -40,17 +40,26 @@ extension ProfileAttributes {
|
||||||
self.userInfo = userInfo ?? [:]
|
self.userInfo = userInfo ?? [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var excludedEndpoints: Set<ExtendedEndpoint> {
|
||||||
|
get {
|
||||||
|
Set(rawExcludedEndpoints.compactMap(ExtendedEndpoint.init(rawValue:)))
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
rawExcludedEndpoints = newValue.map(\.rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func isExcludedEndpoint(_ endpoint: ExtendedEndpoint) -> Bool {
|
public func isExcludedEndpoint(_ endpoint: ExtendedEndpoint) -> Bool {
|
||||||
excludedEndpoints.contains(endpoint.rawValue)
|
rawExcludedEndpoints.contains(endpoint.rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
public mutating func addExcludedEndpoint(_ endpoint: ExtendedEndpoint) {
|
public mutating func addExcludedEndpoint(_ endpoint: ExtendedEndpoint) {
|
||||||
excludedEndpoints.append(endpoint.rawValue)
|
rawExcludedEndpoints.append(endpoint.rawValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
public mutating func removeExcludedEndpoint(_ endpoint: ExtendedEndpoint) {
|
public mutating func removeExcludedEndpoint(_ endpoint: ExtendedEndpoint) {
|
||||||
let rawValue = endpoint.rawValue
|
let rawValue = endpoint.rawValue
|
||||||
excludedEndpoints.removeAll {
|
rawExcludedEndpoints.removeAll {
|
||||||
$0 == rawValue
|
$0 == rawValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +67,7 @@ extension ProfileAttributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProfileAttributes.ModulePreferences {
|
extension ProfileAttributes.ModulePreferences {
|
||||||
var excludedEndpoints: [String] {
|
var rawExcludedEndpoints: [String] {
|
||||||
get {
|
get {
|
||||||
userInfo[Key.excludedEndpoints.rawValue] as? [String] ?? []
|
userInfo[Key.excludedEndpoints.rawValue] as? [String] ?? []
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
//
|
||||||
|
// ModulePreferencesRepository.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 12/10/24.
|
||||||
|
// Copyright (c) 2024 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 PassepartoutKit
|
||||||
|
|
||||||
|
public protocol ModulePreferencesRepository {
|
||||||
|
func isExcludedEndpoint(_ endpoint: ExtendedEndpoint) -> Bool
|
||||||
|
|
||||||
|
func addExcludedEndpoint(_ endpoint: ExtendedEndpoint)
|
||||||
|
|
||||||
|
func removeExcludedEndpoint(_ endpoint: ExtendedEndpoint)
|
||||||
|
|
||||||
|
func save() throws
|
||||||
|
}
|
|
@ -29,11 +29,5 @@ import PassepartoutKit
|
||||||
public protocol ProviderPreferencesRepository {
|
public protocol ProviderPreferencesRepository {
|
||||||
var favoriteServers: Set<String> { get set }
|
var favoriteServers: Set<String> { get set }
|
||||||
|
|
||||||
func isExcludedEndpoint(_ endpoint: ExtendedEndpoint) -> Bool
|
|
||||||
|
|
||||||
func addExcludedEndpoint(_ endpoint: ExtendedEndpoint)
|
|
||||||
|
|
||||||
func removeExcludedEndpoint(_ endpoint: ExtendedEndpoint)
|
|
||||||
|
|
||||||
func save() throws
|
func save() throws
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import CommonLibrary
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ extension ModuleBuilder where Self: ModuleViewProviding {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
moduleView(with: .init(
|
moduleView(with: .init(
|
||||||
editor: ProfileEditor(modules: [self]),
|
editor: ProfileEditor(modules: [self]),
|
||||||
|
preferences: ModulePreferences(),
|
||||||
impl: nil
|
impl: nil
|
||||||
))
|
))
|
||||||
.navigationTitle(title)
|
.navigationTitle(title)
|
||||||
|
|
|
@ -47,7 +47,7 @@ extension ProfileEditor {
|
||||||
// MARK: - ModulePreferences
|
// MARK: - ModulePreferences
|
||||||
|
|
||||||
extension ProfileEditor {
|
extension ProfileEditor {
|
||||||
public func excludedEndpoints(for moduleId: UUID) -> ObservableList<ExtendedEndpoint> {
|
public func excludedEndpoints(for moduleId: UUID, preferences: ModulePreferences) -> ObservableList<ExtendedEndpoint> {
|
||||||
ObservableList { [weak self] endpoint in
|
ObservableList { [weak self] endpoint in
|
||||||
self?.profile.attributes.preference(inModule: moduleId) {
|
self?.profile.attributes.preference(inModule: moduleId) {
|
||||||
$0.isExcludedEndpoint(endpoint)
|
$0.isExcludedEndpoint(endpoint)
|
||||||
|
@ -56,10 +56,12 @@ extension ProfileEditor {
|
||||||
self?.profile.attributes.editPreferences(inModule: moduleId) {
|
self?.profile.attributes.editPreferences(inModule: moduleId) {
|
||||||
$0.addExcludedEndpoint(endpoint)
|
$0.addExcludedEndpoint(endpoint)
|
||||||
}
|
}
|
||||||
|
preferences.addExcludedEndpoint(endpoint)
|
||||||
} remove: { [weak self] endpoint in
|
} remove: { [weak self] endpoint in
|
||||||
self?.profile.attributes.editPreferences(inModule: moduleId) {
|
self?.profile.attributes.editPreferences(inModule: moduleId) {
|
||||||
$0.removeExcludedEndpoint(endpoint)
|
$0.removeExcludedEndpoint(endpoint)
|
||||||
}
|
}
|
||||||
|
preferences.removeExcludedEndpoint(endpoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,11 +36,12 @@ public final class DefaultModuleViewFactory: ModuleViewFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
public func view(with editor: ProfileEditor, moduleId: UUID) -> some View {
|
public func view(with editor: ProfileEditor, preferences: ModulePreferences, moduleId: UUID) -> some View {
|
||||||
let result = editor.moduleViewProvider(withId: moduleId, registry: registry)
|
let result = editor.moduleViewProvider(withId: moduleId, registry: registry)
|
||||||
if let result {
|
if let result {
|
||||||
AnyView(result.provider.moduleView(with: .init(
|
AnyView(result.provider.moduleView(with: .init(
|
||||||
editor: editor,
|
editor: editor,
|
||||||
|
preferences: preferences,
|
||||||
impl: result.impl
|
impl: result.impl
|
||||||
)))
|
)))
|
||||||
.navigationTitle(result.title)
|
.navigationTitle(result.title)
|
||||||
|
|
|
@ -31,5 +31,5 @@ public protocol ModuleViewFactory: AnyObject {
|
||||||
associatedtype Content: View
|
associatedtype Content: View
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func view(with editor: ProfileEditor, moduleId: UUID) -> Content
|
func view(with editor: ProfileEditor, preferences: ModulePreferences, moduleId: UUID) -> Content
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,14 +37,18 @@ public protocol ModuleViewProviding {
|
||||||
public struct ModuleViewParameters {
|
public struct ModuleViewParameters {
|
||||||
public let editor: ProfileEditor
|
public let editor: ProfileEditor
|
||||||
|
|
||||||
|
public let preferences: ModulePreferences
|
||||||
|
|
||||||
public let impl: (any ModuleImplementation)?
|
public let impl: (any ModuleImplementation)?
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public init(
|
public init(
|
||||||
editor: ProfileEditor,
|
editor: ProfileEditor,
|
||||||
|
preferences: ModulePreferences,
|
||||||
impl: (any ModuleImplementation)?
|
impl: (any ModuleImplementation)?
|
||||||
) {
|
) {
|
||||||
self.editor = editor
|
self.editor = editor
|
||||||
|
self.preferences = preferences
|
||||||
self.impl = impl
|
self.impl = impl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ final class ProfileAttributesTests: XCTestCase {
|
||||||
let excludedEndpoints: [String] = [
|
let excludedEndpoints: [String] = [
|
||||||
"1.1.1.1:UDP6:1000",
|
"1.1.1.1:UDP6:1000",
|
||||||
"2.2.2.2:TCP4:2000",
|
"2.2.2.2:TCP4:2000",
|
||||||
"3.3.3.3:TCP:3000",
|
"3.3.3.3:TCP:3000"
|
||||||
]
|
]
|
||||||
let moduleUserInfo: [String: AnyHashable] = [
|
let moduleUserInfo: [String: AnyHashable] = [
|
||||||
"excludedEndpoints": excludedEndpoints
|
"excludedEndpoints": excludedEndpoints
|
||||||
|
@ -89,7 +89,7 @@ final class ProfileAttributesTests: XCTestCase {
|
||||||
for moduleId in [moduleId1, moduleId2] {
|
for moduleId in [moduleId1, moduleId2] {
|
||||||
let module = sut.preferences(inModule: moduleId)
|
let module = sut.preferences(inModule: moduleId)
|
||||||
XCTAssertEqual(module.userInfo, moduleUserInfo)
|
XCTAssertEqual(module.userInfo, moduleUserInfo)
|
||||||
XCTAssertEqual(module.excludedEndpoints, excludedEndpoints)
|
XCTAssertEqual(module.rawExcludedEndpoints, excludedEndpoints)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ final class ProfileAttributesTests: XCTestCase {
|
||||||
let excludedEndpoints: [String] = [
|
let excludedEndpoints: [String] = [
|
||||||
"1.1.1.1:UDP6:1000",
|
"1.1.1.1:UDP6:1000",
|
||||||
"2.2.2.2:TCP4:2000",
|
"2.2.2.2:TCP4:2000",
|
||||||
"3.3.3.3:TCP:3000",
|
"3.3.3.3:TCP:3000"
|
||||||
]
|
]
|
||||||
let moduleUserInfo: [String: AnyHashable] = [
|
let moduleUserInfo: [String: AnyHashable] = [
|
||||||
"excludedEndpoints": excludedEndpoints
|
"excludedEndpoints": excludedEndpoints
|
||||||
|
@ -114,7 +114,7 @@ final class ProfileAttributesTests: XCTestCase {
|
||||||
var sut = ProfileAttributes(userInfo: nil)
|
var sut = ProfileAttributes(userInfo: nil)
|
||||||
for moduleId in [moduleId1, moduleId2] {
|
for moduleId in [moduleId1, moduleId2] {
|
||||||
var module = sut.preferences(inModule: moduleId1)
|
var module = sut.preferences(inModule: moduleId1)
|
||||||
module.excludedEndpoints = excludedEndpoints
|
module.rawExcludedEndpoints = excludedEndpoints
|
||||||
XCTAssertEqual(module.userInfo, moduleUserInfo)
|
XCTAssertEqual(module.userInfo, moduleUserInfo)
|
||||||
sut.setPreferences(module, inModule: moduleId)
|
sut.setPreferences(module, inModule: moduleId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,6 @@
|
||||||
0E8DFD532D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD4C2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift */; };
|
0E8DFD532D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD4C2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift */; };
|
||||||
0E8DFD542D05FE5A00531CDE /* Dependencies+IAPManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD4B2D05FE5A00531CDE /* Dependencies+IAPManager.swift */; };
|
0E8DFD542D05FE5A00531CDE /* Dependencies+IAPManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD4B2D05FE5A00531CDE /* Dependencies+IAPManager.swift */; };
|
||||||
0E8DFD562D05FE5A00531CDE /* Dependencies+CoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD492D05FE5A00531CDE /* Dependencies+CoreData.swift */; };
|
0E8DFD562D05FE5A00531CDE /* Dependencies+CoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD492D05FE5A00531CDE /* Dependencies+CoreData.swift */; };
|
||||||
0E8DFD592D05FF0400531CDE /* Dependencies+PreferencesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD582D05FF0400531CDE /* Dependencies+PreferencesManager.swift */; };
|
|
||||||
0E8DFD5A2D05FF0400531CDE /* Dependencies+PreferencesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8DFD582D05FF0400531CDE /* Dependencies+PreferencesManager.swift */; };
|
|
||||||
0E916B782CF80FD60072921A /* ProfileEditorScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */; };
|
0E916B782CF80FD60072921A /* ProfileEditorScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */; };
|
||||||
0E916B7C2CF811EB0072921A /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */; };
|
0E916B7C2CF811EB0072921A /* XCUIElement+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */; };
|
||||||
0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */; };
|
0E94EE582B93554B00588243 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7E3D672B9345FD002BBDB4 /* PacketTunnelProvider.swift */; };
|
||||||
|
@ -170,7 +168,6 @@
|
||||||
0E8DFD4B2D05FE5A00531CDE /* Dependencies+IAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+IAPManager.swift"; sourceTree = "<group>"; };
|
0E8DFD4B2D05FE5A00531CDE /* Dependencies+IAPManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+IAPManager.swift"; sourceTree = "<group>"; };
|
||||||
0E8DFD4C2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+PassepartoutKit.swift"; sourceTree = "<group>"; };
|
0E8DFD4C2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+PassepartoutKit.swift"; sourceTree = "<group>"; };
|
||||||
0E8DFD4D2D05FE5A00531CDE /* Dependencies+Processors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+Processors.swift"; sourceTree = "<group>"; };
|
0E8DFD4D2D05FE5A00531CDE /* Dependencies+Processors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+Processors.swift"; sourceTree = "<group>"; };
|
||||||
0E8DFD582D05FF0400531CDE /* Dependencies+PreferencesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dependencies+PreferencesManager.swift"; sourceTree = "<group>"; };
|
|
||||||
0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditorScreen.swift; sourceTree = "<group>"; };
|
0E916B772CF80FD60072921A /* ProfileEditorScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditorScreen.swift; sourceTree = "<group>"; };
|
||||||
0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = "<group>"; };
|
0E916B7B2CF811EB0072921A /* XCUIElement+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCUIElement+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
0E94EE5C2B93570600588243 /* Tunnel.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Tunnel.plist; sourceTree = "<group>"; };
|
0E94EE5C2B93570600588243 /* Tunnel.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Tunnel.plist; sourceTree = "<group>"; };
|
||||||
|
@ -350,7 +347,6 @@
|
||||||
0E8DFD492D05FE5A00531CDE /* Dependencies+CoreData.swift */,
|
0E8DFD492D05FE5A00531CDE /* Dependencies+CoreData.swift */,
|
||||||
0E8DFD4B2D05FE5A00531CDE /* Dependencies+IAPManager.swift */,
|
0E8DFD4B2D05FE5A00531CDE /* Dependencies+IAPManager.swift */,
|
||||||
0E8DFD4C2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift */,
|
0E8DFD4C2D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift */,
|
||||||
0E8DFD582D05FF0400531CDE /* Dependencies+PreferencesManager.swift */,
|
|
||||||
);
|
);
|
||||||
path = Shared;
|
path = Shared;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -720,7 +716,6 @@
|
||||||
0E81955A2CFDA75200CC8FFD /* Dependencies.swift in Sources */,
|
0E81955A2CFDA75200CC8FFD /* Dependencies.swift in Sources */,
|
||||||
0E6EEEE32CF8CABA0076E2B0 /* AppContext+Testing.swift in Sources */,
|
0E6EEEE32CF8CABA0076E2B0 /* AppContext+Testing.swift in Sources */,
|
||||||
0E6EEEE42CF8CABA0076E2B0 /* ProfileManager+Testing.swift in Sources */,
|
0E6EEEE42CF8CABA0076E2B0 /* ProfileManager+Testing.swift in Sources */,
|
||||||
0E8DFD592D05FF0400531CDE /* Dependencies+PreferencesManager.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -769,7 +764,6 @@
|
||||||
0E8DFD532D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift in Sources */,
|
0E8DFD532D05FE5A00531CDE /* Dependencies+PassepartoutKit.swift in Sources */,
|
||||||
0E8DFD542D05FE5A00531CDE /* Dependencies+IAPManager.swift in Sources */,
|
0E8DFD542D05FE5A00531CDE /* Dependencies+IAPManager.swift in Sources */,
|
||||||
0E8DFD562D05FE5A00531CDE /* Dependencies+CoreData.swift in Sources */,
|
0E8DFD562D05FE5A00531CDE /* Dependencies+CoreData.swift in Sources */,
|
||||||
0E8DFD5A2D05FF0400531CDE /* Dependencies+PreferencesManager.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import AppData
|
import AppData
|
||||||
|
import AppDataPreferences
|
||||||
import AppDataProfiles
|
import AppDataProfiles
|
||||||
import AppDataProviders
|
import AppDataProviders
|
||||||
import CommonLibrary
|
import CommonLibrary
|
||||||
|
@ -122,7 +123,30 @@ extension AppContext {
|
||||||
return MigrationManager(profileStrategy: profileStrategy, simulation: migrationSimulation)
|
return MigrationManager(profileStrategy: profileStrategy, simulation: migrationSimulation)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let preferencesManager = dependencies.preferencesManager(withCloudKit: true)
|
let preferencesManager: PreferencesManager = {
|
||||||
|
let preferencesStore = CoreDataPersistentStore(
|
||||||
|
logger: dependencies.coreDataLogger(),
|
||||||
|
containerName: Constants.shared.containers.preferences,
|
||||||
|
baseURL: BundleConfiguration.urlForGroupDocuments,
|
||||||
|
model: AppData.cdPreferencesModel,
|
||||||
|
cloudKitIdentifier: BundleConfiguration.mainString(for: .cloudKitPreferencesId),
|
||||||
|
author: nil
|
||||||
|
)
|
||||||
|
return PreferencesManager(
|
||||||
|
modulesFactory: {
|
||||||
|
try AppData.cdModulePreferencesRepositoryV3(
|
||||||
|
context: preferencesStore.context,
|
||||||
|
moduleId: $0
|
||||||
|
)
|
||||||
|
},
|
||||||
|
providersFactory: {
|
||||||
|
try AppData.cdProviderPreferencesRepositoryV3(
|
||||||
|
context: preferencesStore.context,
|
||||||
|
providerId: $0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}()
|
||||||
|
|
||||||
return AppContext(
|
return AppContext(
|
||||||
iapManager: iapManager,
|
iapManager: iapManager,
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
//
|
|
||||||
// Dependencies+PreferencesManager.swift
|
|
||||||
// Passepartout
|
|
||||||
//
|
|
||||||
// Created by Davide De Rosa on 12/2/24.
|
|
||||||
// Copyright (c) 2024 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 AppData
|
|
||||||
import AppDataPreferences
|
|
||||||
import CommonLibrary
|
|
||||||
import CommonUtils
|
|
||||||
import Foundation
|
|
||||||
import PassepartoutKit
|
|
||||||
|
|
||||||
extension Dependencies {
|
|
||||||
func preferencesManager(withCloudKit: Bool) -> PreferencesManager {
|
|
||||||
let preferencesStore = CoreDataPersistentStore(
|
|
||||||
logger: coreDataLogger(),
|
|
||||||
containerName: Constants.shared.containers.preferences,
|
|
||||||
baseURL: BundleConfiguration.urlForGroupDocuments,
|
|
||||||
model: AppData.cdPreferencesModel,
|
|
||||||
cloudKitIdentifier: withCloudKit ? BundleConfiguration.mainString(for: .cloudKitPreferencesId) : nil,
|
|
||||||
author: nil
|
|
||||||
)
|
|
||||||
return PreferencesManager(
|
|
||||||
providersFactory: {
|
|
||||||
try AppData.cdProviderPreferencesRepositoryV3(
|
|
||||||
context: preferencesStore.context,
|
|
||||||
providerId: $0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -28,10 +28,7 @@ import Foundation
|
||||||
import PassepartoutKit
|
import PassepartoutKit
|
||||||
|
|
||||||
final class DefaultTunnelProcessor: Sendable {
|
final class DefaultTunnelProcessor: Sendable {
|
||||||
private let preferencesManager: PreferencesManager
|
init() {
|
||||||
|
|
||||||
init(preferencesManager: PreferencesManager) {
|
|
||||||
self.preferencesManager = preferencesManager
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,13 +46,6 @@ extension DefaultTunnelProcessor: PacketTunnelProcessor {
|
||||||
preferences.isExcludedEndpoint($0)
|
preferences.isExcludedEndpoint($0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let providerId = moduleBuilder.providerId {
|
|
||||||
let providerPreferences = try preferencesManager.preferencesRepository(forProviderWithId: providerId)
|
|
||||||
moduleBuilder.configurationBuilder?.remotes?.removeAll {
|
|
||||||
providerPreferences.isExcludedEndpoint($0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let module = try moduleBuilder.tryBuild()
|
let module = try moduleBuilder.tryBuild()
|
||||||
builder.saveModule(module)
|
builder.saveModule(module)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,10 +37,7 @@ extension TunnelContext {
|
||||||
betaChecker: dependencies.betaChecker(),
|
betaChecker: dependencies.betaChecker(),
|
||||||
productsAtBuild: dependencies.productsAtBuild()
|
productsAtBuild: dependencies.productsAtBuild()
|
||||||
)
|
)
|
||||||
let processor: PacketTunnelProcessor = {
|
let processor = DefaultTunnelProcessor()
|
||||||
let preferencesManager = dependencies.preferencesManager(withCloudKit: false)
|
|
||||||
return DefaultTunnelProcessor(preferencesManager: preferencesManager)
|
|
||||||
}()
|
|
||||||
return TunnelContext(
|
return TunnelContext(
|
||||||
iapManager: iapManager,
|
iapManager: iapManager,
|
||||||
processor: processor
|
processor: processor
|
||||||
|
|
Loading…
Reference in New Issue