Reorganize shared objects (#716)
Mainly: - Aggregate shared/mock entities in less scattered files - Review package dependencies Also: - Decouple ProfileRepository from Core Data Repository in UtilsLibrary (filters done by ProfileManager)
This commit is contained in:
parent
0aac8cd9f3
commit
d589f1162d
|
@ -16,11 +16,10 @@
|
|||
0EC066D12C7DC47600D88A94 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0EC066D02C7DC47600D88A94 /* LaunchScreen.storyboard */; platformFilter = ios; };
|
||||
0EC332CA2B8A1808000B9C2F /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0EC332C92B8A1808000B9C2F /* NetworkExtension.framework */; };
|
||||
0EC332D22B8A1808000B9C2F /* PassepartoutTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0EC332C82B8A1808000B9C2F /* PassepartoutTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
0EC797422B9378E000C093B7 /* Shared+AppUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797402B9378E000C093B7 /* Shared+AppUI.swift */; };
|
||||
0EC797422B9378E000C093B7 /* Shared+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797402B9378E000C093B7 /* Shared+App.swift */; };
|
||||
0EC797432B9378E000C093B7 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797412B9378E000C093B7 /* Shared.swift */; };
|
||||
0EC797442B93790600C093B7 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC797412B9378E000C093B7 /* Shared.swift */; };
|
||||
0EC9C0232CA5BD0B00C52954 /* AppUI in Frameworks */ = {isa = PBXBuildFile; productRef = 0EC9C0222CA5BD0B00C52954 /* AppUI */; };
|
||||
0EC9C0282CA5C04500C52954 /* Shared+AppLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EC9C0272CA5C04500C52954 /* Shared+AppLibrary.swift */; };
|
||||
0EDE56EA2CABE40D0082D21C /* Intents.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0EDE56E62CABE40D0082D21C /* Intents.plist */; };
|
||||
0EDE56FA2CABE42E0082D21C /* PassepartoutIntents.appex in Embed ExtensionKit Extensions */ = {isa = PBXBuildFile; fileRef = 0EDE56F02CABE42E0082D21C /* PassepartoutIntents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
0EDE57002CABE4B50082D21C /* IntentsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE56E72CABE40D0082D21C /* IntentsExtension.swift */; };
|
||||
|
@ -84,9 +83,8 @@
|
|||
0EC066D02C7DC47600D88A94 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
0EC332C82B8A1808000B9C2F /* PassepartoutTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PassepartoutTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0EC332C92B8A1808000B9C2F /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
||||
0EC797402B9378E000C093B7 /* Shared+AppUI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Shared+AppUI.swift"; sourceTree = "<group>"; };
|
||||
0EC797402B9378E000C093B7 /* Shared+App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Shared+App.swift"; sourceTree = "<group>"; };
|
||||
0EC797412B9378E000C093B7 /* Shared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = "<group>"; };
|
||||
0EC9C0272CA5C04500C52954 /* Shared+AppLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shared+AppLibrary.swift"; sourceTree = "<group>"; };
|
||||
0ED1EFDA2C33059600CBD9BD /* App.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = App.plist; sourceTree = "<group>"; };
|
||||
0EDE56E52CABE40D0082D21C /* Intents.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Intents.entitlements; sourceTree = "<group>"; };
|
||||
0EDE56E62CABE40D0082D21C /* Intents.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Intents.plist; sourceTree = "<group>"; };
|
||||
|
@ -181,8 +179,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
0EC797412B9378E000C093B7 /* Shared.swift */,
|
||||
0EC9C0272CA5C04500C52954 /* Shared+AppLibrary.swift */,
|
||||
0EC797402B9378E000C093B7 /* Shared+AppUI.swift */,
|
||||
0EC797402B9378E000C093B7 /* Shared+App.swift */,
|
||||
);
|
||||
path = Shared;
|
||||
sourceTree = "<group>";
|
||||
|
@ -370,9 +367,8 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0E7C3CCD2C9AF44600B72E69 /* AppDelegate.swift in Sources */,
|
||||
0EC9C0282CA5C04500C52954 /* Shared+AppLibrary.swift in Sources */,
|
||||
0E7E3D6B2B9345FD002BBDB4 /* PassepartoutApp.swift in Sources */,
|
||||
0EC797422B9378E000C093B7 /* Shared+AppUI.swift in Sources */,
|
||||
0EC797422B9378E000C093B7 /* Shared+App.swift in Sources */,
|
||||
0EC797432B9378E000C093B7 /* Shared.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
@ -78,8 +78,6 @@ private extension PassepartoutApp {
|
|||
AppUI.configure(with: context)
|
||||
}
|
||||
.themeLockScreen()
|
||||
.environmentObject(theme)
|
||||
.environmentObject(context.iapManager)
|
||||
.environmentObject(context.connectionObserver)
|
||||
.withEnvironment(from: context, theme: theme)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,7 @@ let package = Package(
|
|||
),
|
||||
.library(
|
||||
name: "AppUI",
|
||||
targets: [
|
||||
"AppDataProfiles",
|
||||
"AppUI"
|
||||
]
|
||||
targets: ["AppUI"]
|
||||
),
|
||||
.library(
|
||||
name: "TunnelLibrary",
|
||||
|
@ -46,17 +43,14 @@ let package = Package(
|
|||
// Targets can depend on other targets in this package and products from dependencies.
|
||||
.target(
|
||||
name: "AppData",
|
||||
dependencies: [
|
||||
"AppLibrary",
|
||||
.product(name: "PassepartoutKit", package: "passepartoutkit-source")
|
||||
]
|
||||
dependencies: []
|
||||
),
|
||||
.target(
|
||||
name: "AppDataProfiles",
|
||||
dependencies: [
|
||||
"AppData",
|
||||
"UtilsLibrary",
|
||||
.product(name: "PassepartoutKit", package: "passepartoutkit-source")
|
||||
"AppLibrary",
|
||||
"UtilsLibrary"
|
||||
],
|
||||
resources: [
|
||||
.process("Profiles.xcdatamodeld")
|
||||
|
@ -64,16 +58,17 @@ let package = Package(
|
|||
),
|
||||
.target(
|
||||
name: "AppLibrary",
|
||||
dependencies: [
|
||||
"CommonLibrary",
|
||||
"Kvitto",
|
||||
"LegacyV2",
|
||||
"UtilsLibrary"
|
||||
]
|
||||
dependencies: ["CommonLibrary"]
|
||||
),
|
||||
.target(
|
||||
name: "AppUI",
|
||||
dependencies: ["AppLibrary"],
|
||||
dependencies: [
|
||||
"AppDataProfiles",
|
||||
"AppLibrary",
|
||||
"Kvitto",
|
||||
"LegacyV2",
|
||||
"UtilsLibrary"
|
||||
],
|
||||
resources: [
|
||||
.process("Resources")
|
||||
]
|
||||
|
@ -92,6 +87,7 @@ let package = Package(
|
|||
.target(
|
||||
name: "LegacyV2",
|
||||
dependencies: [
|
||||
"UtilsLibrary",
|
||||
.product(name: "PassepartoutKit", package: "passepartoutkit-source")
|
||||
],
|
||||
resources: [
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
import AppData
|
||||
import AppLibrary
|
||||
import Combine
|
||||
import CoreData
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
|
@ -37,7 +38,7 @@ extension AppData {
|
|||
context: NSManagedObjectContext,
|
||||
observingResults: Bool,
|
||||
onResultError: ((Error) -> CoreDataResultAction)?
|
||||
) -> any ProfileRepository {
|
||||
) -> ProfileRepository {
|
||||
let repository = CoreDataRepository<CDProfileV3, Profile>(
|
||||
context: context,
|
||||
observingResults: observingResults
|
||||
|
@ -68,8 +69,29 @@ extension AppData {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Specialization
|
||||
|
||||
extension CDProfileV3: CoreDataUniqueEntity {
|
||||
}
|
||||
|
||||
extension CoreDataRepository: ProfileRepository where T == Profile {
|
||||
extension Profile: UniqueEntity {
|
||||
public var uuid: UUID? {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
extension CoreDataRepository: ProfileRepository where T == Profile {
|
||||
public nonisolated var profilesPublisher: AnyPublisher<[Profile], Never> {
|
||||
entitiesPublisher
|
||||
.map(\.entities)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public func saveProfile(_ profile: Profile) async throws {
|
||||
try await saveEntities([profile])
|
||||
}
|
||||
|
||||
public func removeProfiles(withIds profileIds: [Profile.ID]) async throws {
|
||||
try await removeEntities(withIds: profileIds)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,63 +26,36 @@
|
|||
import Combine
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
import UtilsLibrary
|
||||
|
||||
public final class InMemoryProfileRepository: ProfileRepository {
|
||||
private var profiles: [Profile] {
|
||||
didSet {
|
||||
profilesSubject.send(EntitiesResult(profiles, isFiltering: false))
|
||||
profilesSubject.send(profiles)
|
||||
}
|
||||
}
|
||||
|
||||
private let profilesSubject: CurrentValueSubject<EntitiesResult<Profile>, Never>
|
||||
private let profilesSubject: CurrentValueSubject<[Profile], Never>
|
||||
|
||||
public init(profiles: [Profile] = []) {
|
||||
self.profiles = profiles
|
||||
profilesSubject = CurrentValueSubject(EntitiesResult(profiles, isFiltering: false))
|
||||
profilesSubject = CurrentValueSubject(profiles)
|
||||
}
|
||||
|
||||
public var entitiesPublisher: AnyPublisher<EntitiesResult<Profile>, Never> {
|
||||
profilesSubject
|
||||
.map {
|
||||
EntitiesResult($0.entities.sorted {
|
||||
$0.name < $1.name
|
||||
}, isFiltering: $0.isFiltering)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
public var profilesPublisher: AnyPublisher<[Profile], Never> {
|
||||
profilesSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public func filter(byFormat format: String, arguments: [Any]?) {
|
||||
print("Filter by format '\(format)' with \(arguments ?? [])")
|
||||
guard let nameSearch = arguments?.first as? String, !nameSearch.isEmpty else {
|
||||
profilesSubject.send(EntitiesResult(profiles, isFiltering: false))
|
||||
return
|
||||
}
|
||||
let match = nameSearch.lowercased()
|
||||
let filtered = profiles.filter {
|
||||
$0.name.lowercased().contains(match)
|
||||
}
|
||||
profilesSubject.send(EntitiesResult(filtered, isFiltering: true))
|
||||
}
|
||||
|
||||
public func resetFilter() {
|
||||
print("Reset filter")
|
||||
profilesSubject.send(EntitiesResult(profiles, isFiltering: false))
|
||||
}
|
||||
|
||||
public func saveEntities(_ entities: [Profile]) {
|
||||
print("Save entities: \(entities.map(\.id))")
|
||||
entities.forEach { profile in
|
||||
if let index = profiles.firstIndex(where: { $0.id == profile.id }) {
|
||||
profiles[index] = profile
|
||||
} else {
|
||||
profiles.append(profile)
|
||||
}
|
||||
public func saveProfile(_ profile: Profile) async throws {
|
||||
print("Save profile: \(profile.id))")
|
||||
if let index = profiles.firstIndex(where: { $0.id == profile.id }) {
|
||||
profiles[index] = profile
|
||||
} else {
|
||||
profiles.append(profile)
|
||||
}
|
||||
}
|
||||
|
||||
public func removeEntities(withIds ids: [UUID]) {
|
||||
print("Remove entities: \(ids)")
|
||||
public func removeProfiles(withIds ids: [Profile.ID]) async throws {
|
||||
print("Remove profiles: \(ids)")
|
||||
profiles = profiles.filter {
|
||||
!ids.contains($0.id)
|
||||
}
|
||||
|
|
|
@ -24,9 +24,9 @@
|
|||
//
|
||||
|
||||
import Combine
|
||||
import CommonLibrary
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
import UtilsLibrary
|
||||
|
||||
public final class NEProfileRepository: ProfileRepository {
|
||||
private let repository: NETunnelManagerRepository
|
||||
|
@ -57,31 +57,17 @@ public final class NEProfileRepository: ProfileRepository {
|
|||
}
|
||||
}
|
||||
|
||||
public var entitiesPublisher: AnyPublisher<EntitiesResult<Profile>, Never> {
|
||||
profilesSubject
|
||||
.map {
|
||||
EntitiesResult($0, isFiltering: false)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
public var profilesPublisher: AnyPublisher<[Profile], Never> {
|
||||
profilesSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public func filter(byFormat format: String, arguments: [Any]?) async throws {
|
||||
assertionFailure("Unused by ProfileManager")
|
||||
public func saveProfile(_ profile: Profile) async throws {
|
||||
try await repository.save(profile, connect: false, title: \.name)
|
||||
}
|
||||
|
||||
public func resetFilter() async throws {
|
||||
assertionFailure("Unused by ProfileManager")
|
||||
}
|
||||
|
||||
public func saveEntities(_ entities: [Profile]) async throws {
|
||||
for profile in entities {
|
||||
try await repository.save(profile, connect: false, title: \.name)
|
||||
}
|
||||
}
|
||||
|
||||
public func removeEntities(withIds ids: [UUID]) async throws {
|
||||
for profileId in ids {
|
||||
try await repository.remove(profileId: profileId)
|
||||
public func removeProfiles(withIds profileIds: [Profile.ID]) async throws {
|
||||
for id in profileIds {
|
||||
try await repository.remove(profileId: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
import Combine
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
import UtilsLibrary
|
||||
|
||||
@MainActor
|
||||
public final class ProfileManager: ObservableObject {
|
||||
|
@ -117,7 +116,7 @@ extension ProfileManager {
|
|||
do {
|
||||
let existingProfile = allProfiles[profile.id]
|
||||
if existingProfile == nil || profile != existingProfile {
|
||||
try await repository.saveEntities([profile])
|
||||
try await repository.saveProfile(profile)
|
||||
|
||||
allProfiles[profile.id] = profile
|
||||
didChange.send(.save(profile))
|
||||
|
@ -132,10 +131,10 @@ extension ProfileManager {
|
|||
if let isShared, let remoteRepository {
|
||||
if isShared {
|
||||
pp_log(.app, .notice, "Enable remote sharing of profile \(profile.id)...")
|
||||
try await remoteRepository.saveEntities([profile])
|
||||
try await remoteRepository.saveProfile(profile)
|
||||
} else {
|
||||
pp_log(.app, .notice, "Disable remote sharing of profile \(profile.id)...")
|
||||
try await remoteRepository.removeEntities(withIds: [profile.id])
|
||||
try await remoteRepository.removeProfiles(withIds: [profile.id])
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
@ -153,13 +152,13 @@ extension ProfileManager {
|
|||
do {
|
||||
// remove local profiles
|
||||
var newAllProfiles = allProfiles
|
||||
try await repository.removeEntities(withIds: profileIds)
|
||||
try await repository.removeProfiles(withIds: profileIds)
|
||||
profileIds.forEach {
|
||||
newAllProfiles.removeValue(forKey: $0)
|
||||
}
|
||||
|
||||
// remove remote counterpart too
|
||||
try? await remoteRepository?.removeEntities(withIds: profileIds)
|
||||
try? await remoteRepository?.removeProfiles(withIds: profileIds)
|
||||
profileIds.forEach {
|
||||
allRemoteProfiles.removeValue(forKey: $0)
|
||||
}
|
||||
|
@ -186,7 +185,7 @@ extension ProfileManager {
|
|||
|
||||
public func eraseRemotelySharedProfiles() async throws {
|
||||
pp_log(.app, .notice, "Erase remotely shared profiles...")
|
||||
try await remoteRepository?.removeEntities(withIds: Array(allRemoteProfiles.keys))
|
||||
try await remoteRepository?.removeProfiles(withIds: Array(allRemoteProfiles.keys))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,7 +236,7 @@ private extension ProfileManager {
|
|||
extension ProfileManager {
|
||||
public func observeObjects(searchDebounce: Int = 200) {
|
||||
repository
|
||||
.entitiesPublisher
|
||||
.profilesPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] in
|
||||
self?.reloadLocalProfiles($0)
|
||||
|
@ -245,7 +244,7 @@ extension ProfileManager {
|
|||
.store(in: &subscriptions)
|
||||
|
||||
remoteRepository?
|
||||
.entitiesPublisher
|
||||
.profilesPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] in
|
||||
self?.reloadRemoteProfiles($0)
|
||||
|
@ -253,7 +252,7 @@ extension ProfileManager {
|
|||
.store(in: &subscriptions)
|
||||
|
||||
remoteRepository?
|
||||
.entitiesPublisher
|
||||
.profilesPublisher
|
||||
.dropFirst()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] in
|
||||
|
@ -271,25 +270,25 @@ extension ProfileManager {
|
|||
}
|
||||
|
||||
private extension ProfileManager {
|
||||
func reloadLocalProfiles(_ result: EntitiesResult<Profile>) {
|
||||
pp_log(.app, .info, "Reload local profiles: \(result.entities.map(\.id))")
|
||||
allProfiles = result.entities.reduce(into: [:]) {
|
||||
func reloadLocalProfiles(_ result: [Profile]) {
|
||||
pp_log(.app, .info, "Reload local profiles: \(result.map(\.id))")
|
||||
allProfiles = result.reduce(into: [:]) {
|
||||
$0[$1.id] = $1
|
||||
}
|
||||
}
|
||||
|
||||
func reloadRemoteProfiles(_ result: EntitiesResult<Profile>) {
|
||||
pp_log(.app, .info, "Reload remote profiles: \(result.entities.map(\.id))")
|
||||
allRemoteProfiles = result.entities.reduce(into: [:]) {
|
||||
func reloadRemoteProfiles(_ result: [Profile]) {
|
||||
pp_log(.app, .info, "Reload remote profiles: \(result.map(\.id))")
|
||||
allRemoteProfiles = result.reduce(into: [:]) {
|
||||
$0[$1.id] = $1
|
||||
}
|
||||
objectWillChange.send()
|
||||
}
|
||||
|
||||
// pull remote updates into local profiles (best-effort)
|
||||
func importRemoteProfiles(_ result: EntitiesResult<Profile>) {
|
||||
let profilesToImport = result.entities
|
||||
pp_log(.app, .info, "Try to import remote profiles: \(result.entities.map(\.id))")
|
||||
func importRemoteProfiles(_ result: [Profile]) {
|
||||
let profilesToImport = result
|
||||
pp_log(.app, .info, "Try to import remote profiles: \(result.map(\.id))")
|
||||
|
||||
Task.detached { [weak self] in
|
||||
for remoteProfile in profilesToImport {
|
||||
|
|
|
@ -23,21 +23,14 @@
|
|||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
import UtilsLibrary
|
||||
|
||||
extension Profile: UniqueEntity {
|
||||
public var uuid: UUID? {
|
||||
id
|
||||
}
|
||||
}
|
||||
public protocol ProfileRepository {
|
||||
var profilesPublisher: AnyPublisher<[Profile], Never> { get }
|
||||
|
||||
public protocol ProfileRepository: Repository where Entity == Profile {
|
||||
}
|
||||
func saveProfile(_ profile: Profile) async throws
|
||||
|
||||
extension ProfileRepository {
|
||||
public func filter(byName name: String) async throws {
|
||||
try await filter(byFormat: "name CONTAINS[cd] %@", arguments: [name])
|
||||
}
|
||||
func removeProfiles(withIds profileIds: [Profile.ID]) async throws
|
||||
}
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
//
|
||||
// Shared.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 8/11/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
|
||||
import PassepartoutWireGuardGo
|
||||
import UtilsLibrary
|
||||
|
||||
extension LoggerDestination {
|
||||
public static let app = Self(category: "app")
|
||||
}
|
||||
|
||||
extension WireGuard.Configuration.Builder {
|
||||
public static var `default`: Self {
|
||||
.init(keyGenerator: StandardWireGuardKeyGenerator())
|
||||
}
|
||||
}
|
||||
|
||||
extension CoreDataPersistentStoreLogger where Self == DefaultCoreDataPersistentStoreLogger {
|
||||
public static var `default`: CoreDataPersistentStoreLogger {
|
||||
DefaultCoreDataPersistentStoreLogger()
|
||||
}
|
||||
}
|
||||
|
||||
public struct DefaultCoreDataPersistentStoreLogger: CoreDataPersistentStoreLogger {
|
||||
public func debug(_ msg: String) {
|
||||
pp_log(.app, .info, msg)
|
||||
}
|
||||
|
||||
public func warning(_ msg: String) {
|
||||
pp_log(.app, .error, msg)
|
||||
}
|
||||
}
|
|
@ -27,9 +27,13 @@ import SwiftUI
|
|||
|
||||
@MainActor
|
||||
extension View {
|
||||
func withMockEnvironment() -> some View {
|
||||
environmentObject(Theme())
|
||||
.environmentObject(IAPManager.mock)
|
||||
.environmentObject(ConnectionObserver.mock)
|
||||
public func withEnvironment(from context: AppContext, theme: Theme) -> some View {
|
||||
environmentObject(theme)
|
||||
.environmentObject(context.iapManager)
|
||||
.environmentObject(context.connectionObserver)
|
||||
}
|
||||
|
||||
public func withMockEnvironment() -> some View {
|
||||
withEnvironment(from: .mock, theme: Theme())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import PassepartoutKit
|
|||
|
||||
extension PassepartoutConfiguration {
|
||||
public func configureLogging(to url: URL, parameters: Constants.Log, logsPrivateData: Bool) {
|
||||
pp_log(.common, .debug, "Log to: \(url)")
|
||||
pp_log(.app, .debug, "Log to: \(url)")
|
||||
|
||||
setLocalLogger(options: .init(
|
||||
url: url,
|
||||
|
|
|
@ -26,10 +26,6 @@
|
|||
import Foundation
|
||||
import PassepartoutKit
|
||||
|
||||
extension Constants {
|
||||
public static let shared = Bundle.module.unsafeDecode(Constants.self, filename: "Constants")
|
||||
}
|
||||
|
||||
public struct Constants: Decodable, Sendable {
|
||||
public struct Websites: Decodable, Sendable {
|
||||
public let home: URL
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Shared.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 9/26/24.
|
||||
// Created by Davide De Rosa on 8/11/24.
|
||||
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
|
@ -25,11 +25,27 @@
|
|||
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
import PassepartoutWireGuardGo
|
||||
|
||||
extension LoggerDestination {
|
||||
static let common = Self(category: "common")
|
||||
public static let app = Self(category: "app")
|
||||
}
|
||||
|
||||
extension WireGuard.Configuration.Builder {
|
||||
public static var `default`: Self {
|
||||
.init(keyGenerator: StandardWireGuardKeyGenerator())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: #716, move to Environment
|
||||
extension Constants {
|
||||
public static let shared = Bundle.module.unsafeDecode(Constants.self, filename: "Constants")
|
||||
}
|
||||
|
||||
// TODO: #716, move to Environment?
|
||||
// BundleConfiguration.shared
|
||||
|
||||
// TODO: #716, move to Environment
|
||||
extension UserDefaults {
|
||||
public static let appGroup: UserDefaults = {
|
||||
let appGroup = BundleConfiguration.mainString(for: .groupId)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//
|
||||
// Shared+AppLibrary.swift
|
||||
// Shared+App.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 9/26/24.
|
||||
// Created by Davide De Rosa on 2/24/24.
|
||||
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
|
@ -26,12 +26,24 @@
|
|||
import AppData
|
||||
import AppDataProfiles
|
||||
import AppLibrary
|
||||
import CommonLibrary
|
||||
import CoreData
|
||||
import AppUI
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
import UtilsLibrary
|
||||
|
||||
extension AppContext {
|
||||
static let shared = AppContext(
|
||||
iapManager: .shared,
|
||||
profileManager: .shared,
|
||||
tunnel: .shared,
|
||||
tunnelEnvironment: .shared,
|
||||
registry: .shared,
|
||||
constants: .shared
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension ProfileManager {
|
||||
static let shared: ProfileManager = {
|
||||
let repository = localProfileRepository
|
||||
|
@ -39,7 +51,7 @@ extension ProfileManager {
|
|||
let remoteStore = CoreDataPersistentStore(
|
||||
logger: .default,
|
||||
containerName: BundleConfiguration.mainString(for: .remoteProfilesContainerName),
|
||||
model: coreDataModel,
|
||||
model: AppData.cdProfilesModel,
|
||||
cloudKitIdentifier: BundleConfiguration.mainString(for: .cloudKitId),
|
||||
author: nil
|
||||
)
|
||||
|
@ -57,10 +69,53 @@ extension ProfileManager {
|
|||
}()
|
||||
}
|
||||
|
||||
private var coreDataModel: NSManagedObjectModel {
|
||||
AppData.cdProfilesModel
|
||||
// MARK: -
|
||||
|
||||
extension IAPManager {
|
||||
static let shared = IAPManager(
|
||||
customUserLevel: customUserLevel,
|
||||
receiptReader: KvittoReceiptReader(),
|
||||
// FIXME: #662, omit unrestrictedFeatures on release!
|
||||
unrestrictedFeatures: [.interactiveLogin, .sharing],
|
||||
productsAtBuild: productsAtBuild
|
||||
)
|
||||
|
||||
private static var customUserLevel: AppUserLevel? {
|
||||
if let envString = ProcessInfo.processInfo.environment["CUSTOM_USER_LEVEL"],
|
||||
let envValue = Int(envString),
|
||||
let testAppType = AppUserLevel(rawValue: envValue) {
|
||||
|
||||
return testAppType
|
||||
}
|
||||
if let infoValue = BundleConfiguration.mainIntegerIfPresent(for: .customUserLevel),
|
||||
let testAppType = AppUserLevel(rawValue: infoValue) {
|
||||
|
||||
return testAppType
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private static let productsAtBuild: BuildProducts<AppProduct> = {
|
||||
#if os(iOS)
|
||||
if $0 <= 2016 {
|
||||
return [.Full.iOS]
|
||||
} else if $0 <= 3000 {
|
||||
return [.Features.networkSettings]
|
||||
}
|
||||
return []
|
||||
#elseif os(macOS)
|
||||
if $0 <= 3000 {
|
||||
return [.Features.networkSettings]
|
||||
}
|
||||
return []
|
||||
#else
|
||||
return []
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
|
||||
extension Tunnel {
|
||||
|
@ -109,3 +164,21 @@ private var neRepository: NETunnelManagerRepository {
|
|||
}
|
||||
|
||||
#endif
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension CoreDataPersistentStoreLogger where Self == DefaultCoreDataPersistentStoreLogger {
|
||||
static var `default`: CoreDataPersistentStoreLogger {
|
||||
DefaultCoreDataPersistentStoreLogger()
|
||||
}
|
||||
}
|
||||
|
||||
private struct DefaultCoreDataPersistentStoreLogger: CoreDataPersistentStoreLogger {
|
||||
func debug(_ msg: String) {
|
||||
pp_log(.app, .info, msg)
|
||||
}
|
||||
|
||||
func warning(_ msg: String) {
|
||||
pp_log(.app, .error, msg)
|
||||
}
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
//
|
||||
// Shared+AppUI.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 2/24/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 AppUI
|
||||
import Foundation
|
||||
import PassepartoutKit
|
||||
import UtilsLibrary
|
||||
|
||||
extension AppContext {
|
||||
static let shared = AppContext(
|
||||
iapManager: .shared,
|
||||
profileManager: .shared,
|
||||
tunnel: .shared,
|
||||
tunnelEnvironment: .shared,
|
||||
registry: .shared,
|
||||
constants: .shared
|
||||
)
|
||||
}
|
||||
|
||||
extension IAPManager {
|
||||
static let shared = IAPManager(
|
||||
customUserLevel: customUserLevel,
|
||||
receiptReader: KvittoReceiptReader(),
|
||||
// FIXME: #662, omit unrestrictedFeatures on release!
|
||||
unrestrictedFeatures: [.interactiveLogin, .sharing],
|
||||
productsAtBuild: productsAtBuild
|
||||
)
|
||||
|
||||
private static var customUserLevel: AppUserLevel? {
|
||||
if let envString = ProcessInfo.processInfo.environment["CUSTOM_USER_LEVEL"],
|
||||
let envValue = Int(envString),
|
||||
let testAppType = AppUserLevel(rawValue: envValue) {
|
||||
|
||||
return testAppType
|
||||
}
|
||||
if let infoValue = BundleConfiguration.mainIntegerIfPresent(for: .customUserLevel),
|
||||
let testAppType = AppUserLevel(rawValue: infoValue) {
|
||||
|
||||
return testAppType
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private static let productsAtBuild: BuildProducts<AppProduct> = {
|
||||
#if os(iOS)
|
||||
if $0 <= 2016 {
|
||||
return [.Full.iOS]
|
||||
} else if $0 <= 3000 {
|
||||
return [.Features.networkSettings]
|
||||
}
|
||||
return []
|
||||
#elseif os(macOS)
|
||||
if $0 <= 3000 {
|
||||
return [.Features.networkSettings]
|
||||
}
|
||||
return []
|
||||
#else
|
||||
return []
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -70,6 +70,8 @@ extension Registry {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension TunnelEnvironment where Self == AppGroupEnvironment {
|
||||
static var shared: Self {
|
||||
AppGroupEnvironment(
|
||||
|
|
Loading…
Reference in New Issue