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:
Davide 2024-10-10 16:20:36 +02:00 committed by GitHub
parent 0aac8cd9f3
commit d589f1162d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 195 additions and 279 deletions

View File

@ -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;

View File

@ -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)
}
}

View File

@ -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: [

View File

@ -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)
}
}

View File

@ -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
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)
}

View File

@ -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 resetFilter() async throws {
assertionFailure("Unused by ProfileManager")
}
public func saveEntities(_ entities: [Profile]) async throws {
for profile in entities {
public func saveProfile(_ profile: Profile) async throws {
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)
}
}
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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())
}
}

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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,9 +69,52 @@ 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)
@ -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)
}
}

View File

@ -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
}
}

View File

@ -70,6 +70,8 @@ extension Registry {
}
}
// MARK: -
extension TunnelEnvironment where Self == AppGroupEnvironment {
static var shared: Self {
AppGroupEnvironment(