From 7ccb10febc2c04b915fa5ff5449c5837efda2c21 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Wed, 24 May 2023 18:19:47 +0200 Subject: [PATCH] Rethink library architecture (#301) --- .gitmodules | 2 +- API | 2 +- Passepartout.xcodeproj/project.pbxproj | 38 +- .../xcschemes/Passepartout.xcscheme | 48 ++- .../xcschemes/PassepartoutTests.xcscheme | 30 -- Passepartout.xctestplan | 71 ++++ Passepartout/App/AppDelegate.swift | 2 +- .../App/Constants/Constants+App.swift | 4 +- .../App/Context/AppContext+Shared.swift | 4 - Passepartout/App/Context/AppContext.swift | 10 +- Passepartout/App/InApp/ProductManager.swift | 2 +- .../App/Intents/IntentDispatcher.swift | 2 +- Passepartout/App/Intents/IntentsManager.swift | 2 +- Passepartout/App/Mac/MacBundle.swift | 2 +- Passepartout/App/Mac/MacBundleDelegate.swift | 2 +- .../Models/DefaultLightProfileManager.swift | 4 +- .../Models/DefaultLightProviderManager.swift | 8 +- .../App/Mac/Models/DefaultLightUtils.swift | 2 +- .../Mac/Models/DefaultLightVPNManager.swift | 3 +- .../App}/Pickers/Picker+Network.swift | 8 +- .../App}/Pickers/Picker+OpenVPN.swift | 8 +- Passepartout/App/Reusable/LockableView.swift | 2 +- .../App/Reusable/MailComposerView.swift | 2 +- Passepartout/App/Reusable/Reviewer.swift | 2 +- Passepartout/App/SceneDelegate.swift | 2 +- .../App/Views/AddProviderViewModel.swift | 2 +- .../App/Views/DiagnosticsView+OpenVPN.swift | 2 +- .../App/Views/DiagnosticsView+WireGuard.swift | 2 +- .../App/Views/OnDemandView+SSID.swift | 5 +- Passepartout/App/Views/View+Extensions.swift | 3 +- .../App/fastlane/ios/metadata/copyright.txt | 2 +- .../Context/CoreContext+Shared.swift | 2 +- .../AppShared/Context/CoreContext.swift | 47 ++- .../Context}/PersistenceManager.swift | 19 +- Passepartout/AppShared/L10n/Unlocalized.swift | 20 +- Passepartout/Launcher/AppDelegate.swift | 2 +- Passepartout/Mac/Mac/DefaultMacMenu.swift | 2 +- Passepartout/Mac/Mac/DefaultMacUtils.swift | 2 +- .../Mac/Menu/HostProfileItem+ViewModel.swift | 2 +- .../Menu/LaunchOnLoginItem+ViewModel.swift | 2 +- .../Menu/PassepartoutMenu+StatusButton.swift | 2 +- Passepartout/Mac/Menu/PassepartoutMenu.swift | 2 +- .../Menu/ProviderLocationItem+ViewModel.swift | 2 +- .../Menu/ProviderProfileItem+ViewModel.swift | 2 +- .../Menu/ProviderServerItem+ViewModel.swift | 2 +- .../Mac/Menu/VPNItemGroup+ViewModel.swift | 2 +- .../Mac/Menu/VisibilityItem+ViewModel.swift | 2 +- Passepartout/Mac/PassepartoutMac.swift | 2 +- .../ObservableProcessTransformer.swift | 2 +- .../Mac/Reusable/TextItem+ViewModel.swift | 2 +- .../Tunnel/OpenVPN/PacketTunnelProvider.swift | 2 +- .../WireGuard/PacketTunnelProvider.swift | 2 +- .../.swiftpm/PassepartoutLibrary.xctestplan | 45 +++ .../xcschemes/OpenVPNAppExtension.xcscheme | 66 ++++ .../xcschemes/PassepartoutCoreTests.xcscheme | 53 +++ .../PassepartoutLibrary-Package.xcscheme | 332 ++++++++++++++++++ .../xcschemes/PassepartoutLibrary.xcscheme | 71 ++++ .../PassepartoutProvidersTests.xcscheme | 53 +++ .../xcschemes/PassepartoutVPNTests.xcscheme | 53 +++ .../xcschemes/WireGuardAppExtension.xcscheme | 66 ++++ PassepartoutLibrary/Package.swift | 99 +++--- .../{Models => Domain}/VPNProtocolType.swift | 13 +- .../Sources/PassepartoutCore/Exports.swift | 3 + .../Extensions/Profile+Extensions.swift | 57 --- ...epartoutError.swift => Passepartout.swift} | 15 +- .../Reusable/CoreDataPersistentStore.swift} | 36 +- .../Reusable/CoreLocationWifiObserver.swift} | 31 +- .../Reusable/FetchedValueHolder.swift | 2 +- .../Reusable/GenericWebEndpoint.swift | 0 .../Reusable/GenericWebParser.swift | 2 +- .../Reusable/GenericWebResponse.swift | 0 .../Reusable/GenericWebServices.swift | 2 +- .../Reusable/InApp.swift | 2 +- .../Reusable/JSON+Codable.swift} | 2 +- .../Reusable/KeyValueStore.swift | 16 +- .../Reusable/KeyedCache.swift | 14 +- .../PassepartoutCore/Reusable/Logger.swift | 102 ++++++ .../Reusable/Mapper.swift | 0 .../Reusable/RateLimited.swift | 0 .../Reusable/StrippableContent.swift | 0 .../Reusable/ValueHolder.swift | 0 .../PassepartoutCore/Reusable/Wifi.swift | 43 +++ .../Utils/Utils+Async.swift | 0 .../Utils/Utils+CoreData.swift | 0 .../Utils/Utils+Dates.swift | 0 .../Utils/Utils+FileManager.swift | 0 .../Utils/Utils+Logging.swift | 0 .../Utils/Utils+Network.swift | 0 .../Utils/Utils+Strings.swift | 0 .../Utils/Utils+TestFlight.swift | 0 .../Utils/Utils+URL.swift | 0 .../Utils/Utils.swift | 0 .../Sources/PassepartoutLibrary/Exports.swift | 7 +- .../CoreDataProfileManagerStrategy.swift | 69 ---- .../Managers/ProfileManager+Keychain.swift | 119 ------- .../CDInfrastructure+CoreDataProperties.swift | 46 --- ...structureCategory+CoreDataProperties.swift | 80 ----- ...rastructurePreset+CoreDataProperties.swift | 47 --- ...rastructureServer+CoreDataProperties.swift | 35 -- .../Domain}/CredentialsPurpose.swift | 0 .../Domain}/ProviderCategory.swift | 1 + .../Domain}/ProviderLocation.swift | 1 + .../Domain}/ProviderMetadata.swift | 1 + .../Domain}/ProviderName.swift | 0 .../Domain}/ProviderServer.swift | 5 +- ...fiable.swift => Domain+Identifiable.swift} | 6 +- ...ers+Logging.swift => Domain+Logging.swift} | 5 +- .../Extensions/ProviderName+Providers.swift} | 4 +- .../Managers/ProviderManager.swift | 188 ++-------- .../Strategies/InfrastructureRepository.swift | 33 ++ .../LocalProvidersRepository.swift} | 7 +- .../Strategies/ProviderRepository.swift} | 8 +- .../Strategies/RemoteProvidersStrategy.swift | 51 +++ .../Strategies/ServerRepository.swift | 41 +++ .../API | 0 .../CDInfrastructure+CoreDataClass.swift | 2 +- .../CDInfrastructure+CoreDataProperties.swift | 46 +++ ...InfrastructureCategory+CoreDataClass.swift | 2 +- ...structureCategory+CoreDataProperties.swift | 80 +++++ ...ructureDefaultSettings+CoreDataClass.swift | 2 +- ...reDefaultSettings+CoreDataProperties.swift | 8 +- ...InfrastructureLocation+CoreDataClass.swift | 2 +- ...structureLocation+CoreDataProperties.swift | 16 +- ...CDInfrastructurePreset+CoreDataClass.swift | 2 +- ...rastructurePreset+CoreDataProperties.swift | 47 +++ ...CDInfrastructureServer+CoreDataClass.swift | 2 +- ...rastructureServer+CoreDataProperties.swift | 35 ++ .../Data}/CDProvider+CoreDataClass.swift | 2 +- .../Data}/CDProvider+CoreDataProperties.swift | 18 +- .../Data/Persistence.swift} | 27 +- .../Model.xcdatamodel/contents | 0 .../APIRemoteProvidersStrategy.swift | 171 +++++++++ .../Strategies/APIWebServices.swift} | 42 ++- ...alProvidersRepository+Infrastructure.swift | 85 +++++ ...CDLocalProvidersRepository+Provider.swift} | 74 +--- .../CDLocalProvidersRepository+Server.swift} | 13 +- .../CDLocalProvidersRepository.swift} | 12 +- .../Strategies/CDWebServicesRepository.swift} | 104 +++--- .../Strategies}/CategoryMapper.swift | 2 +- .../Strategies}/DefaultSettingsMapper.swift | 2 +- .../Strategies}/InfrastructureMapper.swift | 2 +- .../Strategies}/LocationMapper.swift | 2 +- .../Strategies}/PresetMapper.swift | 2 +- .../Strategies}/ProviderMapper.swift | 2 +- .../Strategies}/ServerMapper.swift | 2 +- .../WSProviderCategory.swift | 0 .../WSProviderInfrastructure.swift | 0 .../WSProviderLocation.swift | 0 .../WSProviderName.swift | 0 .../WSProviderPreset.swift | 0 .../WSProviderServer.swift | 0 .../WSProvidersIndex.swift | 0 .../WSVPNProtocol.swift | 0 .../{ => Strategies}/WebServices.swift | 27 +- .../Strategies/WebServicesRepository.swift | 38 ++ .../Sources/PassepartoutUtils/Exports.swift | 3 - .../Reusable/LogManager.swift | 61 ---- .../Domain}/DebugLog.swift | 0 .../Domain/Errors.swift} | 8 +- .../Domain}/Network.swift | 0 .../Domain}/Profile+Account.swift | 0 .../Domain}/Profile+Header.swift | 1 + .../Domain}/Profile+Host.swift | 0 .../Domain}/Profile+NetworkSettings.swift | 0 .../Domain}/Profile+OnDemand.swift | 0 .../Domain}/Profile+OpenVPNSettings.swift | 1 + .../Domain}/Profile+Provider.swift | 2 + .../Domain}/Profile+WireGuardSettings.swift | 1 + .../Domain}/Profile.swift | 1 + .../Domain/VPNConfiguration.swift} | 11 +- .../VPNConfigurationParameters.swift} | 39 +- .../{Models => Domain}/VPNPreferences.swift | 16 +- .../PassepartoutVPN/Domain/VPNState.swift | 38 ++ .../Extensions/DebugLog+Extensions.swift | 0 .../Extensions/Domain+Logging.swift} | 3 +- .../Extensions/Host+Extensions.swift | 1 + .../NetworkSettings+Extensions.swift | 0 .../Extensions/OnDemand+Extensions.swift | 0 .../Extensions/PassepartoutError+VPN.swift | 31 -- .../Extensions/Profile+Extensions.swift | 42 ++- .../Extensions/Provider+Extensions.swift | 2 + .../Managers}/ObservableProfile.swift | 3 +- .../ObservableVPNState.swift | 48 ++- .../Managers}/ProfileManager+Extensions.swift | 5 - .../Managers/ProfileManager.swift | 111 ++++-- .../Managers/UpgradeManager.swift | 17 +- .../Managers/VPNManager+Configuration.swift | 117 ------ ...ions.swift => VPNManager+Extensions.swift} | 3 +- .../PassepartoutVPN/Managers/VPNManager.swift | 74 +++- .../Strategies/ProfileRepository.swift} | 18 +- .../Strategies/SecretRepository.swift | 43 +++ .../Strategies/UpgradeStrategy.swift} | 10 +- .../VPNManagerStrategy.swift | 8 +- .../Data}/CDProfile+CoreDataClass.swift | 2 +- .../Data}/CDProfile+CoreDataProperties.swift | 12 +- .../Data/Persistence.swift} | 21 +- .../Model.xcdatamodel/contents | 0 .../Extensions/OnDemand+Rules.swift | 10 +- .../Extensions/OpenVPNSettings+Network.swift | 1 + .../OpenVPNSettings+TunnelKit.swift} | 10 +- .../PassepartoutProviders+TunnelKit.swift | 2 +- .../PassepartoutVPN+StrippableContent.swift} | 4 +- .../Extensions/Profile+ProviderManager.swift} | 13 +- .../ProfileManager+TunnelKit.swift} | 9 +- .../ProviderManager+Extensions.swift | 1 + .../VPNProtocolType+Extensions.swift | 20 +- .../WireGuardSettings+Network.swift | 1 + .../WireGuardSettings+TunnelKit.swift} | 10 +- .../Strategies/CDProfileRepository.swift} | 72 ++-- .../Strategies/DefaultUpgradeStrategy.swift} | 26 +- .../Strategies/KeychainSecretRepository.swift | 87 +++++ .../Strategies}/ProfileMapper.swift | 2 +- .../Strategies/SwiftyBeaverLogger.swift | 96 +++++ .../TunnelKitVPNManagerStrategy.swift | 58 ++- .../Strategies}/UserDefaultsStore.swift | 20 +- .../PassepartoutCoreTests/CoreTests.swift | 37 +- .../Resources/Debug.log | 0 .../LibraryTests.swift | 36 -- .../ProvidersTests.swift | 32 +- .../ServicesTests.swift | 12 +- .../PassepartoutUtilsTests/UtilsTests.swift | 72 ---- .../ProfilesTests.swift | 6 +- .../Tests/PassepartoutVPNTests/VPNTests.swift | 2 +- 223 files changed, 2948 insertions(+), 1647 deletions(-) create mode 100644 Passepartout.xctestplan rename {PassepartoutLibrary/Sources/PassepartoutProfiles => Passepartout/App}/Pickers/Picker+Network.swift (81%) rename {PassepartoutLibrary/Sources/PassepartoutProfiles => Passepartout/App}/Pickers/Picker+OpenVPN.swift (85%) rename {PassepartoutLibrary/Sources/PassepartoutProfiles/Managers => Passepartout/AppShared/Context}/PersistenceManager.swift (71%) create mode 100644 PassepartoutLibrary/.swiftpm/PassepartoutLibrary.xctestplan create mode 100644 PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/OpenVPNAppExtension.xcscheme create mode 100644 PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCoreTests.xcscheme create mode 100644 PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutLibrary-Package.xcscheme create mode 100644 PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutLibrary.xcscheme create mode 100644 PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutProvidersTests.xcscheme create mode 100644 PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutVPNTests.xcscheme create mode 100644 PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/WireGuardAppExtension.xcscheme rename PassepartoutLibrary/Sources/PassepartoutCore/{Models => Domain}/VPNProtocolType.swift (82%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutCore/Exports.swift delete mode 100644 PassepartoutLibrary/Sources/PassepartoutCore/Extensions/Profile+Extensions.swift rename PassepartoutLibrary/Sources/PassepartoutCore/{PassepartoutError.swift => Passepartout.swift} (78%) rename PassepartoutLibrary/Sources/{PassepartoutUtils/Reusable/Persistence.swift => PassepartoutCore/Reusable/CoreDataPersistentStore.swift} (89%) rename PassepartoutLibrary/Sources/{PassepartoutUtils/Reusable/SSIDReader.swift => PassepartoutCore/Reusable/CoreLocationWifiObserver.swift} (74%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/FetchedValueHolder.swift (96%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/GenericWebEndpoint.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/GenericWebParser.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/GenericWebResponse.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/GenericWebServices.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/InApp.swift (99%) rename PassepartoutLibrary/Sources/{PassepartoutUtils/Utils/Utils+Codable.swift => PassepartoutCore/Reusable/JSON+Codable.swift} (98%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/KeyValueStore.swift (83%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/KeyedCache.swift (83%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutCore/Reusable/Logger.swift rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/Mapper.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/RateLimited.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/StrippableContent.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Reusable/ValueHolder.swift (100%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutCore/Reusable/Wifi.swift rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Utils/Utils+Async.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Utils/Utils+CoreData.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Utils/Utils+Dates.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Utils/Utils+FileManager.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Utils/Utils+Logging.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Utils/Utils+Network.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Utils/Utils+Strings.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Utils/Utils+TestFlight.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Utils/Utils+URL.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutUtils => PassepartoutCore}/Utils/Utils.swift (100%) delete mode 100644 PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/CoreDataProfileManagerStrategy.swift delete mode 100644 PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager+Keychain.swift delete mode 100644 PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataProperties.swift delete mode 100644 PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataProperties.swift delete mode 100644 PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataProperties.swift delete mode 100644 PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataProperties.swift rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutProviders/Domain}/CredentialsPurpose.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutProviders/Domain}/ProviderCategory.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutProviders/Domain}/ProviderLocation.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutProviders/Domain}/ProviderMetadata.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutProviders/Domain}/ProviderName.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutProviders/Domain}/ProviderServer.swift (97%) rename PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/{PassepartoutProviders+Identifiable.swift => Domain+Identifiable.swift} (95%) rename PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/{PassepartoutProviders+Logging.swift => Domain+Logging.swift} (90%) rename PassepartoutLibrary/Sources/{PassepartoutServices/Extensions/WSProviderName+Extensions.swift => PassepartoutProviders/Extensions/ProviderName+Providers.swift} (95%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/InfrastructureRepository.swift rename PassepartoutLibrary/Sources/{PassepartoutVPN/Models/VPNConfigurationError.swift => PassepartoutProviders/Strategies/LocalProvidersRepository.swift} (82%) rename PassepartoutLibrary/Sources/{PassepartoutUtils/Reusable/Repository.swift => PassepartoutProviders/Strategies/ProviderRepository.swift} (83%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/RemoteProvidersStrategy.swift create mode 100644 PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/ServerRepository.swift rename PassepartoutLibrary/Sources/{PassepartoutServices => PassepartoutProvidersImpl}/API (100%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/DataModels => PassepartoutProvidersImpl/Data}/CDInfrastructure+CoreDataClass.swift (82%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructure+CoreDataProperties.swift rename PassepartoutLibrary/Sources/{PassepartoutProviders/DataModels => PassepartoutProvidersImpl/Data}/CDInfrastructureCategory+CoreDataClass.swift (81%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureCategory+CoreDataProperties.swift rename PassepartoutLibrary/Sources/{PassepartoutProviders/DataModels => PassepartoutProvidersImpl/Data}/CDInfrastructureDefaultSettings+CoreDataClass.swift (80%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/DataModels => PassepartoutProvidersImpl/Data}/CDInfrastructureDefaultSettings+CoreDataProperties.swift (63%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/DataModels => PassepartoutProvidersImpl/Data}/CDInfrastructureLocation+CoreDataClass.swift (81%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/DataModels => PassepartoutProvidersImpl/Data}/CDInfrastructureLocation+CoreDataProperties.swift (54%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/DataModels => PassepartoutProvidersImpl/Data}/CDInfrastructurePreset+CoreDataClass.swift (81%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructurePreset+CoreDataProperties.swift rename PassepartoutLibrary/Sources/{PassepartoutProviders/DataModels => PassepartoutProvidersImpl/Data}/CDInfrastructureServer+CoreDataClass.swift (81%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureServer+CoreDataProperties.swift rename PassepartoutLibrary/Sources/{PassepartoutProviders/DataModels => PassepartoutProvidersImpl/Data}/CDProvider+CoreDataClass.swift (83%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/DataModels => PassepartoutProvidersImpl/Data}/CDProvider+CoreDataProperties.swift (51%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Extensions/PassepartoutDataModels+Providers.swift => PassepartoutProvidersImpl/Data/Persistence.swift} (56%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/DataModels => PassepartoutProvidersImpl/Data}/Providers.xcdatamodeld/Model.xcdatamodel/contents (100%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/APIRemoteProvidersStrategy.swift rename PassepartoutLibrary/Sources/{PassepartoutServices/DefaultWebServices.swift => PassepartoutProvidersImpl/Strategies/APIWebServices.swift} (79%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Infrastructure.swift rename PassepartoutLibrary/Sources/{PassepartoutProviders/Repositories/ProviderRepository.swift => PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Provider.swift} (51%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Repositories/ServerRepository.swift => PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Server.swift} (96%) rename PassepartoutLibrary/Sources/{PassepartoutCore/PassepartoutDataModels.swift => PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository.swift} (76%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Repositories/InfrastructureRepository.swift => PassepartoutProvidersImpl/Strategies/CDWebServicesRepository.swift} (63%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Repositories => PassepartoutProvidersImpl/Strategies}/CategoryMapper.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Repositories => PassepartoutProvidersImpl/Strategies}/DefaultSettingsMapper.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Repositories => PassepartoutProvidersImpl/Strategies}/InfrastructureMapper.swift (99%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Repositories => PassepartoutProvidersImpl/Strategies}/LocationMapper.swift (99%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Repositories => PassepartoutProvidersImpl/Strategies}/PresetMapper.swift (99%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Repositories => PassepartoutProvidersImpl/Strategies}/ProviderMapper.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Repositories => PassepartoutProvidersImpl/Strategies}/ServerMapper.swift (99%) rename PassepartoutLibrary/Sources/PassepartoutServices/{DataModels => Domain}/WSProviderCategory.swift (100%) rename PassepartoutLibrary/Sources/PassepartoutServices/{DataModels => Domain}/WSProviderInfrastructure.swift (100%) rename PassepartoutLibrary/Sources/PassepartoutServices/{DataModels => Domain}/WSProviderLocation.swift (100%) rename PassepartoutLibrary/Sources/PassepartoutServices/{DataModels => Domain}/WSProviderName.swift (100%) rename PassepartoutLibrary/Sources/PassepartoutServices/{DataModels => Domain}/WSProviderPreset.swift (100%) rename PassepartoutLibrary/Sources/PassepartoutServices/{DataModels => Domain}/WSProviderServer.swift (100%) rename PassepartoutLibrary/Sources/PassepartoutServices/{DataModels => Domain}/WSProvidersIndex.swift (100%) rename PassepartoutLibrary/Sources/PassepartoutServices/{DataModels => Domain}/WSVPNProtocol.swift (100%) rename PassepartoutLibrary/Sources/PassepartoutServices/{ => Strategies}/WebServices.swift (71%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutServices/Strategies/WebServicesRepository.swift delete mode 100644 PassepartoutLibrary/Sources/PassepartoutUtils/Exports.swift delete mode 100644 PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/LogManager.swift rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutVPN/Domain}/DebugLog.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Extensions/PassepartoutError+Providers.swift => PassepartoutVPN/Domain/Errors.swift} (83%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutVPN/Domain}/Network.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutVPN/Domain}/Profile+Account.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutVPN/Domain}/Profile+Header.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutVPN/Domain}/Profile+Host.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutVPN/Domain}/Profile+NetworkSettings.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutVPN/Domain}/Profile+OnDemand.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutVPN/Domain}/Profile+OpenVPNSettings.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutVPN/Domain}/Profile+Provider.swift (96%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutVPN/Domain}/Profile+WireGuardSettings.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutCore/Models => PassepartoutVPN/Domain}/Profile.swift (99%) rename PassepartoutLibrary/Sources/{PassepartoutProviders/Managers/ProviderManagerFetchPriority.swift => PassepartoutVPN/Domain/VPNConfiguration.swift} (84%) rename PassepartoutLibrary/Sources/PassepartoutVPN/{Models/VPNConfiguration.swift => Domain/VPNConfigurationParameters.swift} (59%) rename PassepartoutLibrary/Sources/PassepartoutVPN/{Models => Domain}/VPNPreferences.swift (75%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNState.swift rename PassepartoutLibrary/Sources/{PassepartoutLibrary => PassepartoutVPN}/Extensions/DebugLog+Extensions.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/Extensions/PassepartoutProfiles+Logging.swift => PassepartoutVPN/Extensions/Domain+Logging.swift} (94%) rename PassepartoutLibrary/Sources/{PassepartoutCore => PassepartoutVPN}/Extensions/Host+Extensions.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutCore => PassepartoutVPN}/Extensions/NetworkSettings+Extensions.swift (100%) rename PassepartoutLibrary/Sources/{PassepartoutCore => PassepartoutVPN}/Extensions/OnDemand+Extensions.swift (100%) delete mode 100644 PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/PassepartoutError+VPN.swift rename PassepartoutLibrary/Sources/{PassepartoutProfiles => PassepartoutVPN}/Extensions/Profile+Extensions.swift (69%) rename PassepartoutLibrary/Sources/{PassepartoutCore => PassepartoutVPN}/Extensions/Provider+Extensions.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/Models => PassepartoutVPN/Managers}/ObservableProfile.swift (93%) rename PassepartoutLibrary/Sources/PassepartoutVPN/{Models => Managers}/ObservableVPNState.swift (64%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/Extensions => PassepartoutVPN/Managers}/ProfileManager+Extensions.swift (87%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles => PassepartoutVPN}/Managers/ProfileManager.swift (82%) rename PassepartoutLibrary/Sources/{PassepartoutLibrary => PassepartoutVPN}/Managers/UpgradeManager.swift (87%) delete mode 100644 PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Configuration.swift rename PassepartoutLibrary/Sources/PassepartoutVPN/Managers/{VPNManager+Actions.swift => VPNManager+Extensions.swift} (98%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/Managers/ProfileManagerStrategy.swift => PassepartoutVPN/Strategies/ProfileRepository.swift} (73%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/SecretRepository.swift rename PassepartoutLibrary/Sources/{PassepartoutProfiles/Extensions/PassepartoutError+Profiles.swift => PassepartoutVPN/Strategies/UpgradeStrategy.swift} (79%) rename PassepartoutLibrary/Sources/PassepartoutVPN/{Managers => Strategies}/VPNManagerStrategy.swift (85%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/DataModels => PassepartoutVPNImpl/Data}/CDProfile+CoreDataClass.swift (83%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/DataModels => PassepartoutVPNImpl/Data}/CDProfile+CoreDataProperties.swift (54%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/Extensions/PassepartoutDataModels+Profiles.swift => PassepartoutVPNImpl/Data/Persistence.swift} (64%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/DataModels => PassepartoutVPNImpl/Data}/Profiles.xcdatamodeld/Model.xcdatamodel/contents (100%) rename PassepartoutLibrary/Sources/{PassepartoutVPN => PassepartoutVPNImpl}/Extensions/OnDemand+Rules.swift (91%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles => PassepartoutVPNImpl}/Extensions/OpenVPNSettings+Network.swift (99%) rename PassepartoutLibrary/Sources/{PassepartoutVPN/Extensions/OpenVPNSettings+VPNConfiguration.swift => PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift} (94%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles => PassepartoutVPNImpl}/Extensions/PassepartoutProviders+TunnelKit.swift (99%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/Extensions/PassepartoutProfiles+StrippableContent.swift => PassepartoutVPNImpl/Extensions/PassepartoutVPN+StrippableContent.swift} (96%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/Extensions/PassepartoutProfiles+Subtype.swift => PassepartoutVPNImpl/Extensions/Profile+ProviderManager.swift} (89%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/Managers/ProfileManager+Processing.swift => PassepartoutVPNImpl/Extensions/ProfileManager+TunnelKit.swift} (82%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles => PassepartoutVPNImpl}/Extensions/ProviderManager+Extensions.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutVPN => PassepartoutVPNImpl}/Extensions/VPNProtocolType+Extensions.swift (82%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles => PassepartoutVPNImpl}/Extensions/WireGuardSettings+Network.swift (98%) rename PassepartoutLibrary/Sources/{PassepartoutVPN/Extensions/WireGuardSettings+VPNConfiguration.swift => PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift} (93%) rename PassepartoutLibrary/Sources/{PassepartoutProfiles/Repositories/ProfileRepository.swift => PassepartoutVPNImpl/Strategies/CDProfileRepository.swift} (85%) rename PassepartoutLibrary/Sources/{PassepartoutLibrary/Managers/UpgradeManager+Migrations.swift => PassepartoutVPNImpl/Strategies/DefaultUpgradeStrategy.swift} (95%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/KeychainSecretRepository.swift rename PassepartoutLibrary/Sources/{PassepartoutProfiles/Repositories => PassepartoutVPNImpl/Strategies}/ProfileMapper.swift (99%) create mode 100644 PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/SwiftyBeaverLogger.swift rename PassepartoutLibrary/Sources/{PassepartoutVPN/Managers => PassepartoutVPNImpl/Strategies}/TunnelKitVPNManagerStrategy.swift (81%) rename PassepartoutLibrary/Sources/{PassepartoutUtils/Reusable => PassepartoutVPNImpl/Strategies}/UserDefaultsStore.swift (62%) rename PassepartoutLibrary/Tests/{PassepartoutUtilsTests => PassepartoutCoreTests}/Resources/Debug.log (100%) delete mode 100644 PassepartoutLibrary/Tests/PassepartoutLibraryTests/LibraryTests.swift rename PassepartoutLibrary/Tests/{PassepartoutServicesTests => PassepartoutProvidersTests}/ServicesTests.swift (87%) delete mode 100644 PassepartoutLibrary/Tests/PassepartoutUtilsTests/UtilsTests.swift rename PassepartoutLibrary/Tests/{PassepartoutProfilesTests => PassepartoutVPNTests}/ProfilesTests.swift (89%) diff --git a/.gitmodules b/.gitmodules index 7ce4464a..960d6f33 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "PassepartoutLibrary/Sources/PassepartoutServices/API"] - path = PassepartoutLibrary/Sources/PassepartoutServices/API + path = PassepartoutLibrary/Sources/PassepartoutProvidersImpl/API url = https://github.com/passepartoutvpn/api diff --git a/API b/API index 82aba07e..a8ad7531 120000 --- a/API +++ b/API @@ -1 +1 @@ -PassepartoutLibrary/Sources/PassepartoutServices/API/ \ No newline at end of file +PassepartoutLibrary/Sources/PassepartoutProvidersImpl/API/ \ No newline at end of file diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj index 9f351699..0c84cbf5 100644 --- a/Passepartout.xcodeproj/project.pbxproj +++ b/Passepartout.xcodeproj/project.pbxproj @@ -103,6 +103,11 @@ 0E71ACFD27C1321A00F85C4B /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E71ACFC27C1321A00F85C4B /* ActivityView.swift */; }; 0E7577D72816A3B200081CBE /* DestructiveButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7577D62816A3B200081CBE /* DestructiveButton.swift */; }; 0E7577DF2817E22C00081CBE /* VPNToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7577DE2817E22C00081CBE /* VPNToggle.swift */; }; + 0E7A8C0A2A1D410500780F4B /* PersistenceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7A8C092A1D410400780F4B /* PersistenceManager.swift */; }; + 0E7A8C0C2A1D4A6100780F4B /* PassepartoutLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0E7A8C0B2A1D4A6100780F4B /* PassepartoutLibrary */; }; + 0E7A8C0E2A1D4A6E00780F4B /* PassepartoutLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0E7A8C0D2A1D4A6E00780F4B /* PassepartoutLibrary */; }; + 0E7A8C0F2A1D54DE00780F4B /* Picker+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7A8C072A1D40BA00780F4B /* Picker+OpenVPN.swift */; }; + 0E7A8C102A1D54DE00780F4B /* Picker+Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E7A8C082A1D40BA00780F4B /* Picker+Network.swift */; }; 0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */; }; 0E92D7C627F103300033CB7B /* ProfileView+Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */; }; 0E92D7C927F1042A0033CB7B /* ProfileView+Extra.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */; }; @@ -138,7 +143,6 @@ 0EA1D84728805EAE00F3CA48 /* Flags.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0EA1D84628805EAE00F3CA48 /* Flags.xcassets */; }; 0EA591162733DDDA0096F796 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 0EA591142733DDDA0096F796 /* Intents.intentdefinition */; }; 0EA9030B287045F70087BC73 /* SystemMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA9030A287045F70087BC73 /* SystemMenu.swift */; }; - 0EB13BBE290E835A003CB654 /* PassepartoutLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0EB13BBD290E835A003CB654 /* PassepartoutLibrary */; }; 0EB13BC0290E8C8D003CB654 /* PassepartoutTestsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB13BBF290E8C8D003CB654 /* PassepartoutTestsApp.swift */; }; 0EB17EA727D226B400D473B5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA127D2263700D473B5 /* Constants.swift */; }; 0EB17EA927D226C900D473B5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA127D2263700D473B5 /* Constants.swift */; }; @@ -159,7 +163,6 @@ 0EBC076027EC587900208AD9 /* SwiftGen+Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBC075F27EC587900208AD9 /* SwiftGen+Strings.swift */; }; 0EBE880F281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBE880E281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift */; }; 0ECB78E9285F5DE300B0E460 /* PassepartoutMac.bundle in Embed Plugins */ = {isa = PBXBuildFile; fileRef = 0ECB78DA285F52F700B0E460 /* PassepartoutMac.bundle */; platformFilter = maccatalyst; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 0ECB78EC2863A21600B0E460 /* PassepartoutLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 0ECB78EB2863A21600B0E460 /* PassepartoutLibrary */; }; 0ECF71EE27B6A99300CDB528 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ECF71ED27B6A99300CDB528 /* AccountView.swift */; }; 0ED1D6DC27DBA41700983466 /* DiagnosticsView+OpenVPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED1D6DB27DBA41700983466 /* DiagnosticsView+OpenVPN.swift */; }; 0ED1D6DE27DBA42100983466 /* DiagnosticsView+WireGuard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED1D6DD27DBA42100983466 /* DiagnosticsView+WireGuard.swift */; }; @@ -295,6 +298,7 @@ 0E021D9B284E68580077EF5D /* AppContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppContext.swift; sourceTree = ""; }; 0E0392762818732D00827C10 /* BuildProducts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildProducts.swift; sourceTree = ""; }; 0E039278281890B100827C10 /* AddHostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddHostView.swift; sourceTree = ""; }; + 0E0480372A1A4AE000462F2F /* Passepartout.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Passepartout.xctestplan; sourceTree = ""; }; 0E04F0052883462E00BFCE1C /* LightUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightUtils.swift; sourceTree = ""; }; 0E04F0082883466500BFCE1C /* DefaultLightUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultLightUtils.swift; sourceTree = ""; }; 0E065F102813269500062CAF /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; @@ -390,6 +394,9 @@ 0E71ACFC27C1321A00F85C4B /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; 0E7577D62816A3B200081CBE /* DestructiveButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestructiveButton.swift; sourceTree = ""; }; 0E7577DE2817E22C00081CBE /* VPNToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNToggle.swift; sourceTree = ""; }; + 0E7A8C072A1D40BA00780F4B /* Picker+OpenVPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Picker+OpenVPN.swift"; sourceTree = ""; }; + 0E7A8C082A1D40BA00780F4B /* Picker+Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Picker+Network.swift"; sourceTree = ""; }; + 0E7A8C092A1D410400780F4B /* PersistenceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceManager.swift; sourceTree = ""; }; 0E90DFE527BACC1500EF5078 /* AddHostViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddHostViewModel.swift; sourceTree = ""; }; 0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Configuration.swift"; sourceTree = ""; }; 0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+Extra.swift"; sourceTree = ""; }; @@ -532,7 +539,7 @@ files = ( 0E9C3B6F27FC573E00D0F02E /* CloudKit.framework in Frameworks */, 0E53249D27D28FC7002565C3 /* Kvitto in Frameworks */, - 0ECB78EC2863A21600B0E460 /* PassepartoutLibrary in Frameworks */, + 0E7A8C0C2A1D4A6100780F4B /* PassepartoutLibrary in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -540,7 +547,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 0EB13BBE290E835A003CB654 /* PassepartoutLibrary in Frameworks */, + 0E7A8C0E2A1D4A6E00780F4B /* PassepartoutLibrary in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -577,6 +584,7 @@ children = ( 0E021D9A284E68580077EF5D /* CoreContext.swift */, 0E29385B285A8B30002A6E0E /* CoreContext+Shared.swift */, + 0E7A8C092A1D410400780F4B /* PersistenceManager.swift */, ); path = Context; sourceTree = ""; @@ -792,6 +800,7 @@ children = ( 0EE315DB2733104700F5D461 /* Packages */, 0E23B4A12298559800304C30 /* Config.xcconfig */, + 0E0480372A1A4AE000462F2F /* Passepartout.xctestplan */, 0E9AA982259F7674003FAFF1 /* Passepartout */, 0EB13BAD290E825E003CB654 /* PassepartoutTests */, 0E57F63920C83FC5008323CF /* Products */, @@ -812,6 +821,15 @@ name = Products; sourceTree = ""; }; + 0E7A8C062A1D40BA00780F4B /* Pickers */ = { + isa = PBXGroup; + children = ( + 0E7A8C072A1D40BA00780F4B /* Picker+OpenVPN.swift */, + 0E7A8C082A1D40BA00780F4B /* Picker+Network.swift */, + ); + path = Pickers; + sourceTree = ""; + }; 0E92781227E7CD530057BB81 /* InApp */ = { isa = PBXGroup; children = ( @@ -875,6 +893,7 @@ 0E92781227E7CD530057BB81 /* InApp */, 0EA591112733DD4E0096F796 /* Intents */, 0E5467F12867A52B00F74D1C /* Mac */, + 0E7A8C062A1D40BA00780F4B /* Pickers */, 0E2C171C27CB6307007E8488 /* Reusable */, 0E35C0AE280EF8A80071FA35 /* Views */, 0E6059CA27FCC5DE003F4063 /* Assets.xcassets */, @@ -1076,7 +1095,7 @@ name = Passepartout; packageProductDependencies = ( 0E53249C27D28FC7002565C3 /* Kvitto */, - 0ECB78EB2863A21600B0E460 /* PassepartoutLibrary */, + 0E7A8C0B2A1D4A6100780F4B /* PassepartoutLibrary */, ); productName = Passepartout; productReference = 0E57F63820C83FC5008323CF /* Passepartout.app */; @@ -1097,7 +1116,7 @@ ); name = PassepartoutTests; packageProductDependencies = ( - 0EB13BBD290E835A003CB654 /* PassepartoutLibrary */, + 0E7A8C0D2A1D4A6E00780F4B /* PassepartoutLibrary */, ); productName = PassepartoutTests; productReference = 0EB13BAC290E825E003CB654 /* PassepartoutTests.app */; @@ -1435,6 +1454,7 @@ 0E04F0092883466500BFCE1C /* DefaultLightUtils.swift in Sources */, 0E5349C827C176D100C71BB3 /* EndpointView+WireGuard.swift in Sources */, 0EBC076027EC587900208AD9 /* SwiftGen+Strings.swift in Sources */, + 0E7A8C0F2A1D54DE00780F4B /* Picker+OpenVPN.swift in Sources */, 0E0392772818732D00827C10 /* BuildProducts.swift in Sources */, 0E0F4C5C29C76B790022E884 /* SceneDelegate+Shortcuts.swift in Sources */, 0E5683B927C2825D00EAF1CD /* DiagnosticsView.swift in Sources */, @@ -1487,11 +1507,13 @@ 0EF2212F27E66F60001D0BD7 /* AddProfileView.swift in Sources */, 0E96D2FC2871D94E005EFBCF /* DefaultLightProfileManager.swift in Sources */, 0EF0FAF627DD0211007EB181 /* PaywallView.swift in Sources */, + 0E7A8C102A1D54DE00780F4B /* Picker+Network.swift in Sources */, 0E293851285A70AC002A6E0E /* AppPreference.swift in Sources */, 0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */, 0E2C172B27CB63F9007E8488 /* Reviewer.swift in Sources */, 0E71ACDD27C0295C00F85C4B /* View+Extensions.swift in Sources */, 0E021D9C284E68580077EF5D /* CoreContext.swift in Sources */, + 0E7A8C0A2A1D410500780F4B /* PersistenceManager.swift in Sources */, A38D607728AFCFD20005C271 /* SettingsView.swift in Sources */, 0E34A2B627CAA8CC00C73B67 /* Core+L10n.swift in Sources */, 0E7577DF2817E22C00081CBE /* VPNToggle.swift in Sources */, @@ -2254,11 +2276,11 @@ package = 0E53249B27D28FC7002565C3 /* XCRemoteSwiftPackageReference "Kvitto" */; productName = Kvitto; }; - 0EB13BBD290E835A003CB654 /* PassepartoutLibrary */ = { + 0E7A8C0B2A1D4A6100780F4B /* PassepartoutLibrary */ = { isa = XCSwiftPackageProductDependency; productName = PassepartoutLibrary; }; - 0ECB78EB2863A21600B0E460 /* PassepartoutLibrary */ = { + 0E7A8C0D2A1D4A6E00780F4B /* PassepartoutLibrary */ = { isa = XCSwiftPackageProductDependency; productName = PassepartoutLibrary; }; diff --git a/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme b/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme index f7fd0cda..132a1c6c 100644 --- a/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme +++ b/Passepartout.xcodeproj/xcshareddata/xcschemes/Passepartout.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> @@ -78,7 +78,53 @@ ReferencedContainer = "container:Passepartout.xcodeproj"> + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - Bool { diff --git a/Passepartout/App/Constants/Constants+App.swift b/Passepartout/App/Constants/Constants+App.swift index 4d3112e8..0f799ebe 100644 --- a/Passepartout/App/Constants/Constants+App.swift +++ b/Passepartout/App/Constants/Constants+App.swift @@ -24,7 +24,7 @@ // import Foundation -import SwiftyBeaver +import PassepartoutLibrary import UniformTypeIdentifiers extension Constants { @@ -154,7 +154,7 @@ extension Constants { private static let parentPath = "Library/Caches" - static let level: SwiftyBeaver.Level = { + static let level: LoggerLevel = { guard let levelString = ProcessInfo.processInfo.environment["LOG_LEVEL"], let levelNum = Int(levelString) else { return .info } diff --git a/Passepartout/App/Context/AppContext+Shared.swift b/Passepartout/App/Context/AppContext+Shared.swift index ec4ae2c3..b9bf07c3 100644 --- a/Passepartout/App/Context/AppContext+Shared.swift +++ b/Passepartout/App/Context/AppContext+Shared.swift @@ -33,7 +33,3 @@ extension AppContext { extension ProductManager { static let shared = AppContext.shared.productManager } - -extension LogManager { - static let shared = AppContext.shared.logManager -} diff --git a/Passepartout/App/Context/AppContext.swift b/Passepartout/App/Context/AppContext.swift index e8c51f72..e8d36595 100644 --- a/Passepartout/App/Context/AppContext.swift +++ b/Passepartout/App/Context/AppContext.swift @@ -28,9 +28,7 @@ import Foundation import PassepartoutLibrary @MainActor -class AppContext { - let logManager: LogManager - +final class AppContext { let productManager: ProductManager private let reviewer: Reviewer @@ -38,12 +36,6 @@ class AppContext { private var cancellables: Set = [] init(coreContext: CoreContext) { - logManager = LogManager(logFile: Constants.Log.App.url) - logManager.logLevel = Constants.Log.level - logManager.logFormat = Constants.Log.App.format - logManager.configureLogging() - pp_log.info("Logging to: \(logManager.logFile!)") - productManager = ProductManager( appType: Constants.InApp.appType, buildProducts: Constants.InApp.buildProducts diff --git a/Passepartout/App/InApp/ProductManager.swift b/Passepartout/App/InApp/ProductManager.swift index 0c95b7ac..182a8e99 100644 --- a/Passepartout/App/InApp/ProductManager.swift +++ b/Passepartout/App/InApp/ProductManager.swift @@ -35,7 +35,7 @@ enum ProductError: Error { case beta } -class ProductManager: NSObject, ObservableObject { +final class ProductManager: NSObject, ObservableObject { enum AppType: Int { case freemium = 0 diff --git a/Passepartout/App/Intents/IntentDispatcher.swift b/Passepartout/App/Intents/IntentDispatcher.swift index e94991d3..cfce6bc2 100644 --- a/Passepartout/App/Intents/IntentDispatcher.swift +++ b/Passepartout/App/Intents/IntentDispatcher.swift @@ -27,7 +27,7 @@ import Foundation import Intents import PassepartoutLibrary -class IntentDispatcher { +final class IntentDispatcher { private struct Groups { static let vpn = "VPN" diff --git a/Passepartout/App/Intents/IntentsManager.swift b/Passepartout/App/Intents/IntentsManager.swift index 7a685562..59eab287 100644 --- a/Passepartout/App/Intents/IntentsManager.swift +++ b/Passepartout/App/Intents/IntentsManager.swift @@ -30,7 +30,7 @@ import IntentsUI import PassepartoutLibrary @MainActor -class IntentsManager: NSObject, ObservableObject { +final class IntentsManager: NSObject, ObservableObject { @Published private(set) var isReloadingShortcuts = false @Published private(set) var shortcuts: [UUID: Shortcut] = [:] diff --git a/Passepartout/App/Mac/MacBundle.swift b/Passepartout/App/Mac/MacBundle.swift index 13e1c4f7..95a7e07b 100644 --- a/Passepartout/App/Mac/MacBundle.swift +++ b/Passepartout/App/Mac/MacBundle.swift @@ -25,7 +25,7 @@ import Foundation -class MacBundle { +final class MacBundle { static let shared = MacBundle() private var bridge: MacBridge! diff --git a/Passepartout/App/Mac/MacBundleDelegate.swift b/Passepartout/App/Mac/MacBundleDelegate.swift index 72e1e139..0116784a 100644 --- a/Passepartout/App/Mac/MacBundleDelegate.swift +++ b/Passepartout/App/Mac/MacBundleDelegate.swift @@ -25,7 +25,7 @@ import Foundation -class MacBundleDelegate: MacMenuDelegate { +final class MacBundleDelegate: MacMenuDelegate { private weak var bundle: MacBundle? @MainActor diff --git a/Passepartout/App/Mac/Models/DefaultLightProfileManager.swift b/Passepartout/App/Mac/Models/DefaultLightProfileManager.swift index 68addfd8..c9b8a352 100644 --- a/Passepartout/App/Mac/Models/DefaultLightProfileManager.swift +++ b/Passepartout/App/Mac/Models/DefaultLightProfileManager.swift @@ -27,7 +27,7 @@ import Combine import Foundation import PassepartoutLibrary -class DefaultLightProfile: LightProfile { +final class DefaultLightProfile: LightProfile { let id: UUID let name: String @@ -50,7 +50,7 @@ class DefaultLightProfile: LightProfile { } } -class DefaultLightProfileManager: LightProfileManager { +final class DefaultLightProfileManager: LightProfileManager { private let profileManager = ProfileManager.shared private let providerManager = ProviderManager.shared diff --git a/Passepartout/App/Mac/Models/DefaultLightProviderManager.swift b/Passepartout/App/Mac/Models/DefaultLightProviderManager.swift index 1e920d34..96f18c26 100644 --- a/Passepartout/App/Mac/Models/DefaultLightProviderManager.swift +++ b/Passepartout/App/Mac/Models/DefaultLightProviderManager.swift @@ -27,7 +27,7 @@ import Combine import Foundation import PassepartoutLibrary -class DefaultLightProviderCategory: LightProviderCategory { +final class DefaultLightProviderCategory: LightProviderCategory { let name: String var locations: [LightProviderLocation] @@ -40,7 +40,7 @@ class DefaultLightProviderCategory: LightProviderCategory { } } -class DefaultLightProviderLocation: LightProviderLocation { +final class DefaultLightProviderLocation: LightProviderLocation { let description: String let id: String @@ -59,7 +59,7 @@ class DefaultLightProviderLocation: LightProviderLocation { } } -class DefaultLightProviderServer: LightProviderServer { +final class DefaultLightProviderServer: LightProviderServer { let description: String let longDescription: String @@ -79,7 +79,7 @@ class DefaultLightProviderServer: LightProviderServer { } } -class DefaultLightProviderManager: LightProviderManager { +final class DefaultLightProviderManager: LightProviderManager { private let providerManager = ProviderManager.shared private var subscriptions: Set = [] diff --git a/Passepartout/App/Mac/Models/DefaultLightUtils.swift b/Passepartout/App/Mac/Models/DefaultLightUtils.swift index de47b660..0f9e2437 100644 --- a/Passepartout/App/Mac/Models/DefaultLightUtils.swift +++ b/Passepartout/App/Mac/Models/DefaultLightUtils.swift @@ -26,7 +26,7 @@ import Foundation import SwiftUI -class DefaultLightUtils: LightUtils { +final class DefaultLightUtils: LightUtils { private let app: UIApplication init() { diff --git a/Passepartout/App/Mac/Models/DefaultLightVPNManager.swift b/Passepartout/App/Mac/Models/DefaultLightVPNManager.swift index 516d6508..bf59ff2a 100644 --- a/Passepartout/App/Mac/Models/DefaultLightVPNManager.swift +++ b/Passepartout/App/Mac/Models/DefaultLightVPNManager.swift @@ -26,8 +26,9 @@ import Combine import Foundation import PassepartoutLibrary +import TunnelKitManager -class DefaultLightVPNManager: LightVPNManager { +final class DefaultLightVPNManager: LightVPNManager { private let vpnManager = VPNManager.shared private var subscriptions: Set = [] diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Pickers/Picker+Network.swift b/Passepartout/App/Pickers/Picker+Network.swift similarity index 81% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Pickers/Picker+Network.swift rename to Passepartout/App/Pickers/Picker+Network.swift index 26ac6291..a7be62ac 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Pickers/Picker+Network.swift +++ b/Passepartout/App/Pickers/Picker+Network.swift @@ -24,11 +24,11 @@ // import Foundation -import PassepartoutCore +import PassepartoutLibrary import TunnelKitCore extension Network.DNSSettings { - public static func availableConfigurationTypes(forVPNProtocol vpnProtocol: VPNProtocolType) -> [ConfigurationType] { + static func availableConfigurationTypes(forVPNProtocol vpnProtocol: VPNProtocolType) -> [ConfigurationType] { switch vpnProtocol { case .openVPN: return [.plain, .https, .tls, .disabled] @@ -40,7 +40,7 @@ extension Network.DNSSettings { } extension Network.ProxySettings { - public static let availableConfigurationTypes: [ConfigurationType] = [ + static let availableConfigurationTypes: [ConfigurationType] = [ .manual, .pac, .disabled @@ -48,5 +48,5 @@ extension Network.ProxySettings { } extension Network.MTUSettings { - public static let availableBytes: [Int] = [0, 1500, 1400, 1300, 1200] + static let availableBytes: [Int] = [0, 1500, 1400, 1300, 1200] } diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Pickers/Picker+OpenVPN.swift b/Passepartout/App/Pickers/Picker+OpenVPN.swift similarity index 85% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Pickers/Picker+OpenVPN.swift rename to Passepartout/App/Pickers/Picker+OpenVPN.swift index 12734ef0..7fa6e64a 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Pickers/Picker+OpenVPN.swift +++ b/Passepartout/App/Pickers/Picker+OpenVPN.swift @@ -27,7 +27,7 @@ import Foundation import TunnelKitOpenVPN extension OpenVPN.Cipher { - public static let available: [OpenVPN.Cipher] = [ + static let available: [OpenVPN.Cipher] = [ .aes256gcm, .aes192gcm, .aes128gcm, @@ -38,7 +38,7 @@ extension OpenVPN.Cipher { } extension OpenVPN.Digest { - public static let available: [OpenVPN.Digest] = [ + static let available: [OpenVPN.Digest] = [ .sha1, .sha224, .sha256, @@ -48,7 +48,7 @@ extension OpenVPN.Digest { } extension OpenVPN.CompressionFraming { - public static let available: [OpenVPN.CompressionFraming] = [ + static let available: [OpenVPN.CompressionFraming] = [ .disabled, .compLZO, .compress @@ -56,7 +56,7 @@ extension OpenVPN.CompressionFraming { } extension OpenVPN.CompressionAlgorithm { - public static let available: [OpenVPN.CompressionAlgorithm] = [ + static let available: [OpenVPN.CompressionAlgorithm] = [ .disabled, .LZO ] diff --git a/Passepartout/App/Reusable/LockableView.swift b/Passepartout/App/Reusable/LockableView.swift index 0208662f..99a6843e 100644 --- a/Passepartout/App/Reusable/LockableView.swift +++ b/Passepartout/App/Reusable/LockableView.swift @@ -115,7 +115,7 @@ struct LockableView: View { } } -private class Lock: ObservableObject { +private final class Lock: ObservableObject { enum State { case none diff --git a/Passepartout/App/Reusable/MailComposerView.swift b/Passepartout/App/Reusable/MailComposerView.swift index e40707b2..218c48a9 100644 --- a/Passepartout/App/Reusable/MailComposerView.swift +++ b/Passepartout/App/Reusable/MailComposerView.swift @@ -27,7 +27,7 @@ import MessageUI import SwiftUI struct MailComposerView: UIViewControllerRepresentable { - class Coordinator: NSObject, MFMailComposeViewControllerDelegate { + final class Coordinator: NSObject, MFMailComposeViewControllerDelegate { @Binding private var isPresented: Bool init(_ view: MailComposerView) { diff --git a/Passepartout/App/Reusable/Reviewer.swift b/Passepartout/App/Reusable/Reviewer.swift index 3f3d55db..e95d796d 100644 --- a/Passepartout/App/Reusable/Reviewer.swift +++ b/Passepartout/App/Reusable/Reviewer.swift @@ -26,7 +26,7 @@ import StoreKit import UIKit -public class Reviewer: ObservableObject { +public final class Reviewer: ObservableObject { private struct Keys { static let eventCount = "Reviewer.EventCount" diff --git a/Passepartout/App/SceneDelegate.swift b/Passepartout/App/SceneDelegate.swift index f1a9e2ba..dcf41094 100644 --- a/Passepartout/App/SceneDelegate.swift +++ b/Passepartout/App/SceneDelegate.swift @@ -26,7 +26,7 @@ import PassepartoutLibrary import SwiftUI -class SceneDelegate: UIResponder, UIWindowSceneDelegate { +final class SceneDelegate: UIResponder, UIWindowSceneDelegate { func sceneDidEnterBackground(_ scene: UIScene) { ProfileManager.shared.persist() #if targetEnvironment(macCatalyst) diff --git a/Passepartout/App/Views/AddProviderViewModel.swift b/Passepartout/App/Views/AddProviderViewModel.swift index 76103ab7..44390849 100644 --- a/Passepartout/App/Views/AddProviderViewModel.swift +++ b/Passepartout/App/Views/AddProviderViewModel.swift @@ -27,7 +27,7 @@ import Foundation import PassepartoutLibrary extension AddProviderView { - class ViewModel: ObservableObject { + final class ViewModel: ObservableObject { enum PendingOperation { case index diff --git a/Passepartout/App/Views/DiagnosticsView+OpenVPN.swift b/Passepartout/App/Views/DiagnosticsView+OpenVPN.swift index ccaeea96..8672fa89 100644 --- a/Passepartout/App/Views/DiagnosticsView+OpenVPN.swift +++ b/Passepartout/App/Views/DiagnosticsView+OpenVPN.swift @@ -154,7 +154,7 @@ extension DiagnosticsView.OpenVPNView { } private var appLogURL: URL? { - LogManager.shared.logFile + Passepartout.shared.logger.logFile } private var tunnelLogURL: URL? { diff --git a/Passepartout/App/Views/DiagnosticsView+WireGuard.swift b/Passepartout/App/Views/DiagnosticsView+WireGuard.swift index e9c5b6ae..f3bca660 100644 --- a/Passepartout/App/Views/DiagnosticsView+WireGuard.swift +++ b/Passepartout/App/Views/DiagnosticsView+WireGuard.swift @@ -52,7 +52,7 @@ extension DiagnosticsView { extension DiagnosticsView.WireGuardView { private var appLogURL: URL? { - LogManager.shared.logFile + Passepartout.shared.logger.logFile } private var tunnelLogURL: URL? { diff --git a/Passepartout/App/Views/OnDemandView+SSID.swift b/Passepartout/App/Views/OnDemandView+SSID.swift index 8514b9eb..68d3b1dc 100644 --- a/Passepartout/App/Views/OnDemandView+SSID.swift +++ b/Passepartout/App/Views/OnDemandView+SSID.swift @@ -30,7 +30,8 @@ extension OnDemandView { struct SSIDList: View { @Binding var withSSIDs: [String: Bool] - @StateObject private var reader = SSIDReader() + // FIXME: arch, this is a candidate for DI + @StateObject private var reader = Wifi(observer: CoreLocationWifiObserver()) var body: some View { EditableTextList(elements: allSSIDs, allowsDuplicates: false, mapping: mapElements) { text in @@ -73,7 +74,7 @@ extension OnDemandView { private func requestSSID(_ text: Binding) { Task { @MainActor in - let ssid = try await reader.requestCurrentSSID() + let ssid = try await reader.currentSSID() if !withSSIDs.keys.contains(ssid) { text.wrappedValue = ssid } diff --git a/Passepartout/App/Views/View+Extensions.swift b/Passepartout/App/Views/View+Extensions.swift index 94d558d5..578e4fc0 100644 --- a/Passepartout/App/Views/View+Extensions.swift +++ b/Passepartout/App/Views/View+Extensions.swift @@ -25,7 +25,6 @@ import PassepartoutLibrary import SwiftUI -import SwiftyBeaver extension View { func withoutTitleBar() -> some View { @@ -106,7 +105,7 @@ extension View { extension View { func debugChanges() { - if SwiftyBeaver.destinations.first?.minLevel == .verbose { + if Passepartout.shared.logger.logLevel == .verbose { Self._printChanges() } } diff --git a/Passepartout/App/fastlane/ios/metadata/copyright.txt b/Passepartout/App/fastlane/ios/metadata/copyright.txt index 9c81b778..75c2d123 100644 --- a/Passepartout/App/fastlane/ios/metadata/copyright.txt +++ b/Passepartout/App/fastlane/ios/metadata/copyright.txt @@ -1 +1 @@ -2022 Davide De Rosa +2023 Davide De Rosa diff --git a/Passepartout/AppShared/Context/CoreContext+Shared.swift b/Passepartout/AppShared/Context/CoreContext+Shared.swift index b470f794..83d29712 100644 --- a/Passepartout/AppShared/Context/CoreContext+Shared.swift +++ b/Passepartout/AppShared/Context/CoreContext+Shared.swift @@ -27,7 +27,7 @@ import Foundation import PassepartoutLibrary extension CoreContext { - static let shared = CoreContext(store: UserDefaultsStore(defaults: .standard)) + static let shared = CoreContext(store: UserDefaultsStore(defaults: .standard, key: \.key)) } extension UpgradeManager { diff --git a/Passepartout/AppShared/Context/CoreContext.swift b/Passepartout/AppShared/Context/CoreContext.swift index e320a00b..cf3022e0 100644 --- a/Passepartout/AppShared/Context/CoreContext.swift +++ b/Passepartout/AppShared/Context/CoreContext.swift @@ -26,14 +26,16 @@ import Combine import Foundation import PassepartoutLibrary +import TunnelKitCore +import TunnelKitManager @MainActor -class CoreContext { +final class CoreContext { let store: KeyValueStore - private let profilesPersistence: Persistence + private let profilesPersistence: CoreDataPersistentStore - private let providersPersistence: Persistence + private let providersPersistence: CoreDataPersistentStore var urlsForProfiles: [URL]? { profilesPersistence.containerURLs @@ -56,6 +58,14 @@ class CoreContext { init(store: KeyValueStore) { self.store = store + let logger = SwiftyBeaverLogger( + logFile: Constants.Log.App.url, + logLevel: Constants.Log.level, + logFormat: Constants.Log.App.format + ) + Passepartout.shared.logger = logger + pp_log.info("Logging to: \(logger.logFile!)") + let persistenceManager = PersistenceManager(store: store) profilesPersistence = persistenceManager.profilesPersistence( withName: Constants.Persistence.profilesContainerName @@ -64,29 +74,35 @@ class CoreContext { withName: Constants.Persistence.providersContainerName ) - upgradeManager = UpgradeManager(store: store) + upgradeManager = UpgradeManager( + store: store, + strategy: DefaultUpgradeStrategy() + ) - providerManager = ProviderManager( + let remoteProvidersStrategy = APIRemoteProvidersStrategy( appBuild: Constants.Global.appBuildNumber, - bundleServices: DefaultWebServices.bundledServices( + bundleServices: APIWebServices.bundledServices( withVersion: Constants.Services.version ), - webServices: DefaultWebServices( + remoteServices: APIWebServices( Constants.Services.version, Constants.Repos.api, timeout: Constants.Services.connectivityTimeout ), - persistence: providersPersistence + webServicesRepository: PassepartoutPersistence.webServicesRepository(providersPersistence) + ) + providerManager = ProviderManager( + localProvidersRepository: PassepartoutPersistence.localProvidersRepository(providersPersistence), + remoteProvidersStrategy: remoteProvidersStrategy ) profileManager = ProfileManager( store: store, providerManager: providerManager, - appGroup: Constants.App.appGroupId, - keychainLabel: Unlocalized.Keychain.passwordLabel, - strategy: CoreDataProfileManagerStrategy( - persistence: profilesPersistence - ) + profileRepository: PassepartoutPersistence.profileRepository(profilesPersistence), + keychain: KeychainSecretRepository(appGroup: Constants.App.appGroupId), + keychainEntry: Unlocalized.Keychain.passwordEntry, + keychainLabel: Unlocalized.Keychain.passwordLabel ) #if targetEnvironment(simulator) @@ -94,17 +110,16 @@ class CoreContext { #else let vpn = NetworkExtensionVPN() #endif - let strategy = TunnelKitVPNManagerStrategy( + let vpnManagerStrategy = TunnelKitVPNManagerStrategy( appGroup: Constants.App.appGroupId, tunnelBundleIdentifier: Constants.App.tunnelBundleId, vpn: vpn ) vpnManager = VPNManager( - appGroup: Constants.App.appGroupId, store: store, profileManager: profileManager, providerManager: providerManager, - strategy: strategy + strategy: vpnManagerStrategy ) // post diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/PersistenceManager.swift b/Passepartout/AppShared/Context/PersistenceManager.swift similarity index 71% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/PersistenceManager.swift rename to Passepartout/AppShared/Context/PersistenceManager.swift index 5d50ddcd..605cf1bb 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/PersistenceManager.swift +++ b/Passepartout/AppShared/Context/PersistenceManager.swift @@ -25,13 +25,12 @@ import CoreData import Foundation -import PassepartoutCore -import PassepartoutUtils +import PassepartoutLibrary -public final class PersistenceManager { +final class PersistenceManager { private let store: KeyValueStore - public init(store: KeyValueStore) { + init(store: KeyValueStore) { self.store = store // set once @@ -40,21 +39,19 @@ public final class PersistenceManager { } } - public func profilesPersistence(withName containerName: String) -> Persistence { - let model = PassepartoutDataModels.profiles - return Persistence(withCloudKitName: containerName, model: model, author: persistenceAuthor) + func profilesPersistence(withName containerName: String) -> CoreDataPersistentStore { + PassepartoutPersistence.profilesStore(withName: containerName, cloudKit: true, author: persistenceAuthor) } - public func providersPersistence(withName containerName: String) -> Persistence { - let model = PassepartoutDataModels.providers - return Persistence(withLocalName: containerName, model: model, author: persistenceAuthor) + func providersPersistence(withName containerName: String) -> CoreDataPersistentStore { + PassepartoutPersistence.providersStore(withName: containerName, cloudKit: false, author: persistenceAuthor) } } // MARK: KeyValueStore extension PersistenceManager { - public private(set) var persistenceAuthor: String? { + private(set) var persistenceAuthor: String? { get { store.value(forLocation: StoreKey.persistenceAuthor) } diff --git a/Passepartout/AppShared/L10n/Unlocalized.swift b/Passepartout/AppShared/L10n/Unlocalized.swift index afc4d72e..7decd06b 100644 --- a/Passepartout/AppShared/L10n/Unlocalized.swift +++ b/Passepartout/AppShared/L10n/Unlocalized.swift @@ -56,8 +56,12 @@ enum Unlocalized { } enum Keychain { - static func passwordLabel(_ profileName: String, vpnProtocol: VPNProtocolType) -> String { - "\(Constants.Global.appName): \(profileName) (\(vpnProtocol.description))" + static func passwordEntry(_ profile: Profile) -> String { + "\(profile.id.uuidString):\(profile.currentVPNProtocol.keychainEntry):\(profile.account.username)" + } + + static func passwordLabel(_ profile: Profile) -> String { + "\(Constants.Global.appName): \(profile.header.name) (\(profile.currentVPNProtocol.keychainEntry))" } } @@ -252,3 +256,15 @@ enum Unlocalized { static let totp = "TOTP" } } + +private extension VPNProtocolType { + var keychainEntry: String { + switch self { + case .openVPN: + return "OpenVPN" + + case .wireGuard: + return "WireGuard" + } + } +} diff --git a/Passepartout/Launcher/AppDelegate.swift b/Passepartout/Launcher/AppDelegate.swift index ef51e814..3caaf29c 100644 --- a/Passepartout/Launcher/AppDelegate.swift +++ b/Passepartout/Launcher/AppDelegate.swift @@ -26,7 +26,7 @@ import AppKit import Foundation -class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { +final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { private let appURL = Constants.Launcher.appURL private var isAppRunning: Bool { diff --git a/Passepartout/Mac/Mac/DefaultMacMenu.swift b/Passepartout/Mac/Mac/DefaultMacMenu.swift index 27c567f6..18a53d75 100644 --- a/Passepartout/Mac/Mac/DefaultMacMenu.swift +++ b/Passepartout/Mac/Mac/DefaultMacMenu.swift @@ -25,7 +25,7 @@ import Foundation -class DefaultMacMenu: MacMenu { +final class DefaultMacMenu: MacMenu { weak var delegate: MacMenuDelegate? private lazy var menu: PassepartoutMenu = { diff --git a/Passepartout/Mac/Mac/DefaultMacUtils.swift b/Passepartout/Mac/Mac/DefaultMacUtils.swift index f0a7b3a0..f8a0f1dd 100644 --- a/Passepartout/Mac/Mac/DefaultMacUtils.swift +++ b/Passepartout/Mac/Mac/DefaultMacUtils.swift @@ -26,7 +26,7 @@ import AppKit import Foundation -class DefaultMacUtils: MacUtils { +final class DefaultMacUtils: MacUtils { private(set) lazy var isStartedByLauncher = NSApp.isHidden private let transformer = ObservableProcessTransformer.shared diff --git a/Passepartout/Mac/Menu/HostProfileItem+ViewModel.swift b/Passepartout/Mac/Menu/HostProfileItem+ViewModel.swift index 59b2b163..b8364889 100644 --- a/Passepartout/Mac/Menu/HostProfileItem+ViewModel.swift +++ b/Passepartout/Mac/Menu/HostProfileItem+ViewModel.swift @@ -28,7 +28,7 @@ import Foundation extension HostProfileItem { @MainActor - class ViewModel { + final class ViewModel { let profile: LightProfile private let vpnManager: LightVPNManager diff --git a/Passepartout/Mac/Menu/LaunchOnLoginItem+ViewModel.swift b/Passepartout/Mac/Menu/LaunchOnLoginItem+ViewModel.swift index a817f6c6..0dc149fb 100644 --- a/Passepartout/Mac/Menu/LaunchOnLoginItem+ViewModel.swift +++ b/Passepartout/Mac/Menu/LaunchOnLoginItem+ViewModel.swift @@ -30,7 +30,7 @@ import ServiceManagement extension LaunchOnLoginItem { @MainActor - class ViewModel: ObservableObject { + final class ViewModel: ObservableObject { let title: String let utils: LightUtils diff --git a/Passepartout/Mac/Menu/PassepartoutMenu+StatusButton.swift b/Passepartout/Mac/Menu/PassepartoutMenu+StatusButton.swift index 2c6366bc..ef64dc91 100644 --- a/Passepartout/Mac/Menu/PassepartoutMenu+StatusButton.swift +++ b/Passepartout/Mac/Menu/PassepartoutMenu+StatusButton.swift @@ -29,7 +29,7 @@ import Foundation extension PassepartoutMenu { @MainActor - class StatusButton { + final class StatusButton { private lazy var statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) private lazy var statusButton: NSStatusBarButton = { diff --git a/Passepartout/Mac/Menu/PassepartoutMenu.swift b/Passepartout/Mac/Menu/PassepartoutMenu.swift index d76dda4d..b5be51a4 100644 --- a/Passepartout/Mac/Menu/PassepartoutMenu.swift +++ b/Passepartout/Mac/Menu/PassepartoutMenu.swift @@ -27,7 +27,7 @@ import AppKit import Foundation @MainActor -class PassepartoutMenu { +final class PassepartoutMenu { private let macMenuDelegate: MacMenuDelegate private let profileManager: LightProfileManager diff --git a/Passepartout/Mac/Menu/ProviderLocationItem+ViewModel.swift b/Passepartout/Mac/Menu/ProviderLocationItem+ViewModel.swift index a91227f0..9656762f 100644 --- a/Passepartout/Mac/Menu/ProviderLocationItem+ViewModel.swift +++ b/Passepartout/Mac/Menu/ProviderLocationItem+ViewModel.swift @@ -28,7 +28,7 @@ import Foundation extension ProviderLocationItem { @MainActor - class ViewModel { + final class ViewModel { private let profile: LightProfile let location: LightProviderLocation diff --git a/Passepartout/Mac/Menu/ProviderProfileItem+ViewModel.swift b/Passepartout/Mac/Menu/ProviderProfileItem+ViewModel.swift index a4be6719..07623f04 100644 --- a/Passepartout/Mac/Menu/ProviderProfileItem+ViewModel.swift +++ b/Passepartout/Mac/Menu/ProviderProfileItem+ViewModel.swift @@ -28,7 +28,7 @@ import Foundation extension ProviderProfileItem { @MainActor - class ViewModel { + final class ViewModel { let profile: LightProfile private let providerManager: LightProviderManager diff --git a/Passepartout/Mac/Menu/ProviderServerItem+ViewModel.swift b/Passepartout/Mac/Menu/ProviderServerItem+ViewModel.swift index b3ab1c9d..1c4151f6 100644 --- a/Passepartout/Mac/Menu/ProviderServerItem+ViewModel.swift +++ b/Passepartout/Mac/Menu/ProviderServerItem+ViewModel.swift @@ -28,7 +28,7 @@ import Foundation extension ProviderServerItem { @MainActor - class ViewModel { + final class ViewModel { private let profile: LightProfile let server: LightProviderServer diff --git a/Passepartout/Mac/Menu/VPNItemGroup+ViewModel.swift b/Passepartout/Mac/Menu/VPNItemGroup+ViewModel.swift index 72d2c6f4..ccbb751b 100644 --- a/Passepartout/Mac/Menu/VPNItemGroup+ViewModel.swift +++ b/Passepartout/Mac/Menu/VPNItemGroup+ViewModel.swift @@ -29,7 +29,7 @@ import Foundation extension VPNItemGroup { @MainActor - class ViewModel { + final class ViewModel { private let vpnManager: LightVPNManager private let toggleTitleBlock: (Bool) -> String diff --git a/Passepartout/Mac/Menu/VisibilityItem+ViewModel.swift b/Passepartout/Mac/Menu/VisibilityItem+ViewModel.swift index 70c29e74..cb00f634 100644 --- a/Passepartout/Mac/Menu/VisibilityItem+ViewModel.swift +++ b/Passepartout/Mac/Menu/VisibilityItem+ViewModel.swift @@ -29,7 +29,7 @@ import Foundation extension VisibilityItem { @MainActor - class ViewModel { + final class ViewModel { private let transformer: ObservableProcessTransformer private let utils: LightUtils diff --git a/Passepartout/Mac/PassepartoutMac.swift b/Passepartout/Mac/PassepartoutMac.swift index e9eea107..b3a71df0 100644 --- a/Passepartout/Mac/PassepartoutMac.swift +++ b/Passepartout/Mac/PassepartoutMac.swift @@ -25,7 +25,7 @@ import Foundation -class PassepartoutMac: NSObject, MacBridge { +final class PassepartoutMac: NSObject, MacBridge { required override init() { super.init() } diff --git a/Passepartout/Mac/Reusable/ObservableProcessTransformer.swift b/Passepartout/Mac/Reusable/ObservableProcessTransformer.swift index bc7eb9eb..34a58103 100644 --- a/Passepartout/Mac/Reusable/ObservableProcessTransformer.swift +++ b/Passepartout/Mac/Reusable/ObservableProcessTransformer.swift @@ -26,7 +26,7 @@ import Combine import Foundation -class ObservableProcessTransformer: ObservableObject { +final class ObservableProcessTransformer: ObservableObject { static let shared = ObservableProcessTransformer() private let transformer = ProcessTransformer() diff --git a/Passepartout/Mac/Reusable/TextItem+ViewModel.swift b/Passepartout/Mac/Reusable/TextItem+ViewModel.swift index ec4e7a2d..6ec75ff5 100644 --- a/Passepartout/Mac/Reusable/TextItem+ViewModel.swift +++ b/Passepartout/Mac/Reusable/TextItem+ViewModel.swift @@ -27,7 +27,7 @@ import Combine import Foundation extension TextItem { - class ViewModel { + final class ViewModel { let title: CurrentValueSubject let state: CurrentValueSubject diff --git a/Passepartout/Tunnel/OpenVPN/PacketTunnelProvider.swift b/Passepartout/Tunnel/OpenVPN/PacketTunnelProvider.swift index 769fac58..aeff33b7 100644 --- a/Passepartout/Tunnel/OpenVPN/PacketTunnelProvider.swift +++ b/Passepartout/Tunnel/OpenVPN/PacketTunnelProvider.swift @@ -26,7 +26,7 @@ import Foundation import OpenVPNAppExtension -class PacketTunnelProvider: OpenVPNTunnelProvider { +final class PacketTunnelProvider: OpenVPNTunnelProvider { override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { appVersion = "\(Constants.Global.appName) \(Constants.Global.appVersionString)" dnsTimeout = Constants.OpenVPNTunnel.dnsTimeout diff --git a/Passepartout/Tunnel/WireGuard/PacketTunnelProvider.swift b/Passepartout/Tunnel/WireGuard/PacketTunnelProvider.swift index ac344f5d..eaf935d3 100644 --- a/Passepartout/Tunnel/WireGuard/PacketTunnelProvider.swift +++ b/Passepartout/Tunnel/WireGuard/PacketTunnelProvider.swift @@ -26,5 +26,5 @@ import Foundation import WireGuardAppExtension -class PacketTunnelProvider: WireGuardTunnelProvider { +final class PacketTunnelProvider: WireGuardTunnelProvider { } diff --git a/PassepartoutLibrary/.swiftpm/PassepartoutLibrary.xctestplan b/PassepartoutLibrary/.swiftpm/PassepartoutLibrary.xctestplan new file mode 100644 index 00000000..f95d91d0 --- /dev/null +++ b/PassepartoutLibrary/.swiftpm/PassepartoutLibrary.xctestplan @@ -0,0 +1,45 @@ +{ + "configurations" : [ + { + "id" : "2A4B150B-39A2-417B-98FD-1C5C74C26E3E", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:", + "identifier" : "PassepartoutCoreTests", + "name" : "PassepartoutCoreTests" + } + }, + { + "target" : { + "containerPath" : "container:", + "identifier" : "PassepartoutProvidersTests", + "name" : "PassepartoutProvidersTests" + } + }, + { + "target" : { + "containerPath" : "container:", + "identifier" : "PassepartoutServicesTests", + "name" : "PassepartoutServicesTests" + } + }, + { + "target" : { + "containerPath" : "container:", + "identifier" : "PassepartoutVPNTests", + "name" : "PassepartoutVPNTests" + } + } + ], + "version" : 1 +} diff --git a/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/OpenVPNAppExtension.xcscheme b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/OpenVPNAppExtension.xcscheme new file mode 100644 index 00000000..7c2cb106 --- /dev/null +++ b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/OpenVPNAppExtension.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCoreTests.xcscheme b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCoreTests.xcscheme new file mode 100644 index 00000000..b1df5d8d --- /dev/null +++ b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutCoreTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutLibrary-Package.xcscheme b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutLibrary-Package.xcscheme new file mode 100644 index 00000000..abc851a7 --- /dev/null +++ b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutLibrary-Package.xcscheme @@ -0,0 +1,332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutLibrary.xcscheme b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutLibrary.xcscheme new file mode 100644 index 00000000..2836c84b --- /dev/null +++ b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutLibrary.xcscheme @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutProvidersTests.xcscheme b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutProvidersTests.xcscheme new file mode 100644 index 00000000..c76514e2 --- /dev/null +++ b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutProvidersTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutVPNTests.xcscheme b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutVPNTests.xcscheme new file mode 100644 index 00000000..2560f992 --- /dev/null +++ b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/PassepartoutVPNTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/WireGuardAppExtension.xcscheme b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/WireGuardAppExtension.xcscheme new file mode 100644 index 00000000..97fe4962 --- /dev/null +++ b/PassepartoutLibrary/.swiftpm/xcode/xcshareddata/xcschemes/WireGuardAppExtension.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PassepartoutLibrary/Package.swift b/PassepartoutLibrary/Package.swift index 144248f4..7c90df34 100644 --- a/PassepartoutLibrary/Package.swift +++ b/PassepartoutLibrary/Package.swift @@ -32,58 +32,64 @@ let package = Package( targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. + + // MARK: Implementations + .target( name: "PassepartoutLibrary", dependencies: [ - "PassepartoutVPN" + "PassepartoutVPNImpl", + "PassepartoutProvidersImpl" ]), + .target( + name: "PassepartoutVPNImpl", + dependencies: [ + "PassepartoutVPN", + "SwiftyBeaver", + .product(name: "TunnelKitLZO", package: "TunnelKit") + ], + resources: [ + .process("Data/Profiles.xcdatamodeld") + ]), + .target( + name: "PassepartoutProvidersImpl", + dependencies: [ + "PassepartoutProviders", + "PassepartoutServices" + ], + resources: [ + .copy("API"), + .process("Data/Providers.xcdatamodeld") + ]), + + // MARK: Interfaces + .target( name: "PassepartoutVPN", dependencies: [ - "PassepartoutProfiles", - .product(name: "TunnelKitLZO", package: "TunnelKit") - ]), - .target( - name: "PassepartoutProfiles", - dependencies: [ - "PassepartoutProviders" - ], - resources: [ - .process("DataModels/Profiles.xcdatamodeld") + "PassepartoutProviders", + .product(name: "TunnelKit", package: "TunnelKit"), + .product(name: "TunnelKitOpenVPN", package: "TunnelKit"), // FIXME: arch, drop this + .product(name: "TunnelKitWireGuard", package: "TunnelKit"), // FIXME: arch, drop this ]), .target( name: "PassepartoutProviders", dependencies: [ - "PassepartoutCore", - "PassepartoutServices" - ], - resources: [ - .process("DataModels/Providers.xcdatamodeld") + "PassepartoutCore" + ]), + .target( + name: "PassepartoutServices", + dependencies: [ + "PassepartoutCore" ]), .target( name: "PassepartoutCore", dependencies: [ - .product(name: "TunnelKit", package: "TunnelKit"), - .product(name: "TunnelKitOpenVPN", package: "TunnelKit"), - .product(name: "TunnelKitWireGuard", package: "TunnelKit"), - .product(name: "GenericJSON", package: "generic-json-swift") + .product(name: "GenericJSON", package: "generic-json-swift") // FIXME: arch, drop this ]), - // - .target( - name: "PassepartoutServices", - dependencies: [ - "PassepartoutUtils" - ], - resources: [ - .copy("API") - ]), - .target( - name: "PassepartoutUtils", - dependencies: [ - .product(name: "GenericJSON", package: "generic-json-swift"), - "SwiftyBeaver" - ]), - // + + // MARK: App extensions + .target( name: "OpenVPNAppExtension", dependencies: [ @@ -95,27 +101,18 @@ let package = Package( dependencies: [ .product(name: "TunnelKitWireGuardAppExtension", package: "TunnelKit") ]), - .testTarget( - name: "PassepartoutLibraryTests", - dependencies: ["PassepartoutLibrary"]), + + // MARK: Tests + .testTarget( name: "PassepartoutVPNTests", - dependencies: ["PassepartoutVPN"]), - .testTarget( - name: "PassepartoutProfilesTests", - dependencies: ["PassepartoutProfiles"]), + dependencies: ["PassepartoutVPNImpl"]), .testTarget( name: "PassepartoutProvidersTests", - dependencies: ["PassepartoutProviders"]), + dependencies: ["PassepartoutProvidersImpl"]), .testTarget( name: "PassepartoutCoreTests", - dependencies: ["PassepartoutCore"]), - .testTarget( - name: "PassepartoutServicesTests", - dependencies: ["PassepartoutServices"]), - .testTarget( - name: "PassepartoutUtilsTests", - dependencies: ["PassepartoutUtils"], + dependencies: ["PassepartoutCore"], resources: [ .process("Resources") ]) diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/VPNProtocolType.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Domain/VPNProtocolType.swift similarity index 82% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/VPNProtocolType.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Domain/VPNProtocolType.swift index 4feb65e6..b867b8d8 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Models/VPNProtocolType.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Domain/VPNProtocolType.swift @@ -25,6 +25,7 @@ import Foundation +// FIXME: arch, provide a well-defined serialization property rather than .rawValue public enum VPNProtocolType: String, CaseIterable, Codable { case openVPN = "ovpn" @@ -34,15 +35,3 @@ public enum VPNProtocolType: String, CaseIterable, Codable { public protocol VPNProtocolProviding { var vpnProtocol: VPNProtocolType { get } } - -extension VPNProtocolType: CustomStringConvertible { - public var description: String { - switch self { - case .openVPN: - return "OpenVPN" - - case .wireGuard: - return "WireGuard" - } - } -} diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Exports.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Exports.swift new file mode 100644 index 00000000..d6e914ac --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Exports.swift @@ -0,0 +1,3 @@ +public var pp_log: Logger { + Passepartout.shared.logger +} diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Extensions/Profile+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Extensions/Profile+Extensions.swift deleted file mode 100644 index 41f517f1..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Extensions/Profile+Extensions.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Profile+Extensions.swift -// Passepartout -// -// Created by Davide De Rosa on 3/13/22. -// Copyright (c) 2023 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation - -extension Profile { - public var isProvider: Bool { - provider != nil - } - - public var vpnProtocols: [VPNProtocolType] { - if isProvider { - return provider?.vpnProtocols ?? [] - } else { - return host?.vpnProtocols ?? [] - } - } - - public var account: Profile.Account { - get { - if isProvider { - return providerAccount ?? .init() - } else { - return hostAccount ?? .init() - } - } - set { - if isProvider { - providerAccount = newValue - } else { - hostAccount = newValue - } - } - } -} diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/PassepartoutError.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Passepartout.swift similarity index 78% rename from PassepartoutLibrary/Sources/PassepartoutCore/PassepartoutError.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Passepartout.swift index 79f0d982..0776b22e 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/PassepartoutError.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Passepartout.swift @@ -1,5 +1,5 @@ // -// PassepartoutError.swift +// Passepartout.swift // Passepartout // // Created by Davide De Rosa on 6/12/18. @@ -25,6 +25,19 @@ import Foundation +// FIXME: arch, global variables here are not extensible, use dependency injection instead +public class Passepartout { + public static let shared = Passepartout() + + private init() { + } + + public var logger: Logger = DefaultLogger() +} + +public enum PassepartoutPersistence { +} + public struct PassepartoutError: Error, Equatable { private let string: String diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/Persistence.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/CoreDataPersistentStore.swift similarity index 89% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/Persistence.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/CoreDataPersistentStore.swift index d7afd6bf..1e4eddaa 100644 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/Persistence.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/CoreDataPersistentStore.swift @@ -1,5 +1,5 @@ // -// Persistence.swift +// CoreDataPersistentStore.swift // Passepartout // // Created by Davide De Rosa on 3/14/22. @@ -27,24 +27,16 @@ import Combine import CoreData import Foundation -public class Persistence { +public final class CoreDataPersistentStore { private let container: NSPersistentContainer - public var context: NSManagedObjectContext { - container.viewContext - } - - public var coordinator: NSPersistentStoreCoordinator { - container.persistentStoreCoordinator - } - - public convenience init(withLocalName containerName: String, model: NSManagedObjectModel, author: String?) { - let container = NSPersistentContainer(name: containerName, managedObjectModel: model) - self.init(withContainer: container, author: author) - } - - public convenience init(withCloudKitName containerName: String, model: NSManagedObjectModel, author: String?) { - let container = NSPersistentCloudKitContainer(name: containerName, managedObjectModel: model) + public convenience init(withName containerName: String, model: NSManagedObjectModel, cloudKit: Bool, author: String?) { + let container: NSPersistentContainer + if cloudKit { + container = NSPersistentCloudKitContainer(name: containerName, managedObjectModel: model) + } else { + container = NSPersistentContainer(name: containerName, managedObjectModel: model) + } self.init(withContainer: container, author: author) } @@ -112,3 +104,13 @@ public class Persistence { } } } + +extension CoreDataPersistentStore { + public var context: NSManagedObjectContext { + container.viewContext + } + + public var coordinator: NSPersistentStoreCoordinator { + container.persistentStoreCoordinator + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/SSIDReader.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/CoreLocationWifiObserver.swift similarity index 74% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/SSIDReader.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/CoreLocationWifiObserver.swift index 02c60216..80d2e0ce 100644 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/SSIDReader.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/CoreLocationWifiObserver.swift @@ -1,8 +1,8 @@ // -// SSIDReader.swift +// CoreLocationWifiObserver.swift // Passepartout // -// Created by Davide De Rosa on 2/24/22. +// Created by Davide De Rosa on 5/21/23. // Copyright (c) 2023 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -26,38 +26,37 @@ import CoreLocation import Foundation -@MainActor -public class SSIDReader: NSObject, ObservableObject { - private let manager = CLLocationManager() +public final class CoreLocationWifiObserver: NSObject, WifiObserver { + private let locationManager = CLLocationManager() private var continuation: CheckedContinuation? - private func currentSSID() async -> String { - await Utils.currentWifiSSID() ?? "" - } - - public func requestCurrentSSID() async throws -> String { - switch manager.authorizationStatus { + public func currentSSID() async throws -> String { + switch locationManager.authorizationStatus { case .authorizedAlways, .authorizedWhenInUse, .denied: - return await currentSSID() + return await currentSSIDWithoutAuthorization() default: return try await withCheckedThrowingContinuation { continuation in self.continuation = continuation - manager.delegate = self - manager.requestWhenInUseAuthorization() + locationManager.delegate = self + locationManager.requestWhenInUseAuthorization() } } } + + private func currentSSIDWithoutAuthorization() async -> String { + await Utils.currentWifiSSID() ?? "" + } } -extension SSIDReader: CLLocationManagerDelegate { +extension CoreLocationWifiObserver: CLLocationManagerDelegate { public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { switch manager.authorizationStatus { case .authorizedWhenInUse, .authorizedAlways, .denied: Task { - continuation?.resume(returning: await currentSSID()) + continuation?.resume(returning: await currentSSIDWithoutAuthorization()) continuation = nil } diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/FetchedValueHolder.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/FetchedValueHolder.swift similarity index 96% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/FetchedValueHolder.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/FetchedValueHolder.swift index 689a4c15..658d172c 100644 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/FetchedValueHolder.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/FetchedValueHolder.swift @@ -27,7 +27,7 @@ import Combine import CoreData import Foundation -public class FetchedValueHolder: NSObject, ValueHolder, NSFetchedResultsControllerDelegate { +public final class FetchedValueHolder: NSObject, ValueHolder, NSFetchedResultsControllerDelegate { @Published public var value: V private let controller: NSFetchedResultsController diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/GenericWebEndpoint.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/GenericWebEndpoint.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/GenericWebEndpoint.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/GenericWebEndpoint.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/GenericWebParser.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/GenericWebParser.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/GenericWebParser.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/GenericWebParser.swift index 78f1a092..ae140519 100644 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/GenericWebParser.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/GenericWebParser.swift @@ -25,7 +25,7 @@ import Foundation -public class GenericWebParser { +public final class GenericWebParser { private static let lmFormatter: DateFormatter = { let fmt = DateFormatter() fmt.locale = Locale(identifier: "en") diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/GenericWebResponse.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/GenericWebResponse.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/GenericWebResponse.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/GenericWebResponse.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/GenericWebServices.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/GenericWebServices.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/GenericWebServices.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/GenericWebServices.swift index c231435e..4606f041 100644 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/GenericWebServices.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/GenericWebServices.swift @@ -32,7 +32,7 @@ public protocol GenericWebServicesError: Error { static var unknown: Self { get } } -public class GenericWebServices { +public final class GenericWebServices { private let version: String? private let root: URL diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/InApp.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/InApp.swift similarity index 99% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/InApp.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/InApp.swift index 294f77a9..36b7eef9 100644 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/InApp.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/InApp.swift @@ -36,7 +36,7 @@ public enum InAppError: Error { case unknown } -public class InApp: NSObject, +public final class InApp: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver where PID.RawValue == String { diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Codable.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/JSON+Codable.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Codable.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/JSON+Codable.swift index 0b2e6a50..aeb2e6ca 100644 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Codable.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/JSON+Codable.swift @@ -1,5 +1,5 @@ // -// Utils+Codable.swift +// JSON+Codable.swift // Passepartout // // Created by Davide De Rosa on 3/13/22. diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/KeyValueStore.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/KeyValueStore.swift similarity index 83% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/KeyValueStore.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/KeyValueStore.swift index 0e86e212..61a58155 100644 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/KeyValueStore.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/KeyValueStore.swift @@ -33,16 +33,16 @@ public protocol KeyStoreDomainLocation: KeyStoreLocation { var domain: String { get } } +public protocol KeyValueStore { + func setValue(_ value: V?, forLocation location: L) where L: KeyStoreLocation + + func value(forLocation location: L) -> V? where L: KeyStoreLocation + + func removeValue(forLocation location: L) where L: KeyStoreLocation +} + extension KeyStoreDomainLocation { public var key: String { "\(domain).\(rawValue)" } } - -public protocol KeyValueStore { - func setValue(_ value: V?, forLocation location: L) - - func value(forLocation location: L) -> V? - - func removeValue(forLocation location: L) -} diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/KeyedCache.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/KeyedCache.swift similarity index 83% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/KeyedCache.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/KeyedCache.swift index 4d608aea..8dca23fb 100644 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/KeyedCache.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/KeyedCache.swift @@ -25,7 +25,7 @@ import Foundation -public class KeyedCache { +public struct KeyedCache { private let query: String private var store: [K: V] = [:] @@ -42,15 +42,15 @@ public class KeyedCache { self.query = query } - public func set(_ store: [K: V]) { + public mutating func set(_ store: [K: V]) { self.store = store } - public func put(_ key: K, value: V) { + public mutating func put(_ key: K, value: V) { store[key] = value } - public func put(_ key: K, valueBlock: (K) -> V?) -> V? { + public mutating func put(_ key: K, valueBlock: (K) -> V?) -> V? { if let cachedValue = store[key] { return cachedValue } @@ -62,18 +62,18 @@ public class KeyedCache { return value } - public func forget(where condition: (K) -> Bool) { + public mutating func forget(where condition: (K) -> Bool) { let removedKeys = store.keys.filter(condition) removedKeys.forEach { store.removeValue(forKey: $0) } } - public func forget(_ key: K) { + public mutating func forget(_ key: K) { store.removeValue(forKey: key) } - public func clear() { + public mutating func clear() { store.removeAll() } } diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/Logger.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/Logger.swift new file mode 100644 index 00000000..8be39b00 --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/Logger.swift @@ -0,0 +1,102 @@ +// +// Logger.swift +// Passepartout +// +// Created by Davide De Rosa on 6/15/22. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public enum LoggerLevel: Int { + case verbose + + case debug + + case info + + case warning + + case error +} + +public protocol Logger { + var logFile: URL? { get } + + var logLevel: LoggerLevel { get set } + + func verbose(_ message: Any) + + func debug(_ message: Any) + + func info(_ message: Any) + + func warning(_ message: Any) + + func error(_ message: Any) +} + +final class DefaultLogger: Logger { + let logFile: URL? = nil + + var logLevel: LoggerLevel = .debug + + func verbose(_ message: Any) { + guard logLevel.rawValue >= LoggerLevel.verbose.rawValue else { + return + } + logMessage(message) + } + + func debug(_ message: Any) { + guard logLevel.rawValue >= LoggerLevel.debug.rawValue else { + return + } + logMessage(message) + } + + func info(_ message: Any) { + guard logLevel.rawValue >= LoggerLevel.info.rawValue else { + return + } + logMessage(message) + } + + func warning(_ message: Any) { + guard logLevel.rawValue >= LoggerLevel.warning.rawValue else { + return + } + logMessage(message) + } + + func error(_ message: Any) { + guard logLevel.rawValue >= LoggerLevel.error.rawValue else { + return + } + logMessage(message) + } + + private func logMessage(_ message: Any) { + guard let string = message as? String else { + return + } + NSLog(string) + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/Mapper.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/Mapper.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/Mapper.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/Mapper.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/RateLimited.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/RateLimited.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/RateLimited.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/RateLimited.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/StrippableContent.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/StrippableContent.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/StrippableContent.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/StrippableContent.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/ValueHolder.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/ValueHolder.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/ValueHolder.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Reusable/ValueHolder.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/Wifi.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/Wifi.swift new file mode 100644 index 00000000..9cadcf32 --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Reusable/Wifi.swift @@ -0,0 +1,43 @@ +// +// Wifi.swift +// Passepartout +// +// Created by Davide De Rosa on 2/24/22. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +@MainActor +public final class Wifi: ObservableObject { + private let observer: WifiObserver + + public init(observer: WifiObserver) { + self.observer = observer + } + + public func currentSSID() async throws -> String { + try await observer.currentSSID() + } +} + +public protocol WifiObserver { + func currentSSID() async throws -> String +} diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Async.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Async.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Async.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Async.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+CoreData.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+CoreData.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+CoreData.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+CoreData.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Dates.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Dates.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Dates.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Dates.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+FileManager.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+FileManager.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+FileManager.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+FileManager.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Logging.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Logging.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Logging.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Logging.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Network.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Network.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Network.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Network.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Strings.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Strings.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+Strings.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+Strings.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+TestFlight.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+TestFlight.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+TestFlight.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+TestFlight.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+URL.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+URL.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils+URL.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils+URL.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Utils/Utils.swift rename to PassepartoutLibrary/Sources/PassepartoutCore/Utils/Utils.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutLibrary/Exports.swift b/PassepartoutLibrary/Sources/PassepartoutLibrary/Exports.swift index 654742ea..af3adf00 100644 --- a/PassepartoutLibrary/Sources/PassepartoutLibrary/Exports.swift +++ b/PassepartoutLibrary/Sources/PassepartoutLibrary/Exports.swift @@ -1,8 +1,5 @@ @_exported import PassepartoutCore -@_exported import PassepartoutProfiles @_exported import PassepartoutProviders -@_exported import PassepartoutServices -@_exported import PassepartoutUtils +@_exported import PassepartoutProvidersImpl @_exported import PassepartoutVPN -@_exported import TunnelKit -@_exported import TunnelKitCore +@_exported import PassepartoutVPNImpl diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/CoreDataProfileManagerStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/CoreDataProfileManagerStrategy.swift deleted file mode 100644 index 1f579073..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/CoreDataProfileManagerStrategy.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// CoreDataProfileManagerStrategy.swift -// Passepartout -// -// Created by Davide De Rosa on 4/9/22. -// Copyright (c) 2023 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Combine -import Foundation -import PassepartoutCore -import PassepartoutUtils - -public class CoreDataProfileManagerStrategy: ProfileManagerStrategy { - private let profileRepository: ProfileRepository - - private let fetchedProfiles: FetchedValueHolder<[UUID: Profile]> - - public init(persistence: Persistence) { - profileRepository = ProfileRepository(persistence.context) - fetchedProfiles = profileRepository.fetchedProfiles() - } - - public var allProfiles: [UUID: Profile] { - fetchedProfiles.value - } - - public func profiles() -> [Profile] { - profileRepository.profiles() - } - - public func profile(withId id: UUID) -> Profile? { - profileRepository.profile(withId: id) - } - - public func saveProfiles(_ profiles: [Profile]) { - do { - try profileRepository.saveProfiles(profiles) - } catch { - pp_log.error("Unable to save profile: \(error)") - } - } - - public func removeProfiles(withIds ids: [UUID]) { - profileRepository.removeProfiles(withIds: ids) - } - - public func willUpdateProfiles() -> AnyPublisher<[UUID: Profile], Never> { - fetchedProfiles.$value - .eraseToAnyPublisher() - } -} diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager+Keychain.swift b/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager+Keychain.swift deleted file mode 100644 index fc2a983e..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager+Keychain.swift +++ /dev/null @@ -1,119 +0,0 @@ -// -// ProfileManager+Keychain.swift -// Passepartout -// -// Created by Davide De Rosa on 4/8/22. -// Copyright (c) 2023 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import PassepartoutCore -import PassepartoutUtils -import TunnelKitManager - -extension ProfileManager { - public func savePassword(forProfile profile: Profile, newPassword: String? = nil) { - guard !profile.isPlaceholder else { - assertionFailure("Placeholder") - return - } - guard let keychainEntry = profile.keychainEntry else { - return - } - let password = newPassword ?? profile.account.password - guard !password.isEmpty else { - keychain.removePassword( - for: keychainEntry, - context: appGroup, - userDefined: profile.id.uuidString - ) - return - } - do { - try keychain.set( - password: password, - for: keychainEntry, - context: appGroup, - userDefined: profile.id.uuidString, - label: keychainLabel(profile.header.name, profile.currentVPNProtocol) - ) - } catch { - pp_log.error("Unable to save password to keychain: \(error)") - } - } - - public func passwordReference(forProfile profile: Profile) -> Data? { - guard !profile.isPlaceholder else { - assertionFailure("Placeholder") - return nil - } - guard let keychainEntry = profile.keychainEntry else { - return nil - } - do { - return try keychain.passwordReference( - for: keychainEntry, - context: appGroup, - userDefined: profile.id.uuidString - ) - } catch { - pp_log.debug("Unable to load password reference from keychain: \(error)") - return nil - } - } -} - -private extension Profile { - var keychainEntry: String? { - "\(id.uuidString):\(currentVPNProtocol.description):\(account.username)" - } -} - -extension Keychain { - func debugAllPasswords(matching id: UUID, context: String) { - var query = allPasswordsQuery(id, context) - query[kSecReturnAttributes as String] = true - - var list: CFTypeRef? - switch SecItemCopyMatching(query as CFDictionary, &list) { - case errSecSuccess: - break - - default: - return - } - guard let list = list else { - pp_log.debug("Keychain items: none") - return - } - pp_log.debug("Keychain items: \(list)") - } - - func removeAllPasswords(matching id: UUID, context: String) { - _ = SecItemDelete(allPasswordsQuery(id, context) as CFDictionary) - } - - private func allPasswordsQuery(_ id: UUID, _ context: String) -> [String: Any] { - var query = [String: Any]() - setScope(query: &query, context: context, userDefined: id.uuidString) - query[kSecClass as String] = kSecClassGenericPassword - return query - } -} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataProperties.swift deleted file mode 100644 index a025fb17..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataProperties.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// CDInfrastructure+CoreDataProperties.swift -// -// -// Created by Davide De Rosa on 27/03/22. -// -// This file was automatically generated and should not be edited. -// - -import CoreData -import Foundation - -extension CDInfrastructure { - - @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: "CDInfrastructure") - } - - @NSManaged public var lastUpdate: Date? - @NSManaged public var vpnProtocol: String? - @NSManaged public var categories: NSSet? - @NSManaged public var defaults: CDInfrastructureDefaultSettings? - @NSManaged public var provider: CDProvider? - -} - -// MARK: Generated accessors for categories -extension CDInfrastructure { - - @objc(addCategoriesObject:) - @NSManaged public func addToCategories(_ value: CDInfrastructureCategory) - - @objc(removeCategoriesObject:) - @NSManaged public func removeFromCategories(_ value: CDInfrastructureCategory) - - @objc(addCategories:) - @NSManaged public func addToCategories(_ values: NSSet) - - @objc(removeCategories:) - @NSManaged public func removeFromCategories(_ values: NSSet) - -} - -extension CDInfrastructure: Identifiable { - -} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataProperties.swift deleted file mode 100644 index 1aa8e6ca..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataProperties.swift +++ /dev/null @@ -1,80 +0,0 @@ -// -// CDInfrastructureCategory+CoreDataProperties.swift -// -// -// Created by Davide De Rosa on 27/03/22. -// -// This file was automatically generated and should not be edited. -// - -import CoreData -import Foundation - -extension CDInfrastructureCategory { - - @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: "CDInfrastructureCategory") - } - - @NSManaged public var name: String? - @NSManaged public var infrastructure: CDInfrastructure? - @NSManaged public var locations: NSSet? - @NSManaged public var presets: NSSet? - @NSManaged public var servers: NSSet? - -} - -// MARK: Generated accessors for locations -extension CDInfrastructureCategory { - - @objc(addLocationsObject:) - @NSManaged public func addToLocations(_ value: CDInfrastructureLocation) - - @objc(removeLocationsObject:) - @NSManaged public func removeFromLocations(_ value: CDInfrastructureLocation) - - @objc(addLocations:) - @NSManaged public func addToLocations(_ values: NSSet) - - @objc(removeLocations:) - @NSManaged public func removeFromLocations(_ values: NSSet) - -} - -// MARK: Generated accessors for presets -extension CDInfrastructureCategory { - - @objc(addPresetsObject:) - @NSManaged public func addToPresets(_ value: CDInfrastructurePreset) - - @objc(removePresetsObject:) - @NSManaged public func removeFromPresets(_ value: CDInfrastructurePreset) - - @objc(addPresets:) - @NSManaged public func addToPresets(_ values: NSSet) - - @objc(removePresets:) - @NSManaged public func removeFromPresets(_ values: NSSet) - -} - -// MARK: Generated accessors for servers -extension CDInfrastructureCategory { - - @objc(addServersObject:) - @NSManaged public func addToServers(_ value: CDInfrastructureServer) - - @objc(removeServersObject:) - @NSManaged public func removeFromServers(_ value: CDInfrastructureServer) - - @objc(addServers:) - @NSManaged public func addToServers(_ values: NSSet) - - @objc(removeServers:) - @NSManaged public func removeFromServers(_ values: NSSet) - -} - -extension CDInfrastructureCategory: Identifiable { - -} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataProperties.swift deleted file mode 100644 index 55b6b985..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataProperties.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// CDInfrastructurePreset+CoreDataProperties.swift -// -// -// Created by Davide De Rosa on 27/03/22. -// -// This file was automatically generated and should not be edited. -// - -import CoreData -import Foundation - -extension CDInfrastructurePreset { - - @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: "CDInfrastructurePreset") - } - - @NSManaged public var comment: String? - @NSManaged public var id: String? - @NSManaged public var name: String? - @NSManaged public var vpnConfiguration: Data? - @NSManaged public var vpnProtocol: String? - @NSManaged public var category: NSSet? - -} - -// MARK: Generated accessors for category -extension CDInfrastructurePreset { - - @objc(addCategoryObject:) - @NSManaged public func addToCategory(_ value: CDInfrastructureCategory) - - @objc(removeCategoryObject:) - @NSManaged public func removeFromCategory(_ value: CDInfrastructureCategory) - - @objc(addCategory:) - @NSManaged public func addToCategory(_ values: NSSet) - - @objc(removeCategory:) - @NSManaged public func removeFromCategory(_ values: NSSet) - -} - -extension CDInfrastructurePreset: Identifiable { - -} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataProperties.swift deleted file mode 100644 index 06c00f66..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataProperties.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// CDInfrastructureServer+CoreDataProperties.swift -// -// -// Created by Davide De Rosa on 27/03/22. -// -// This file was automatically generated and should not be edited. -// - -import CoreData -import Foundation - -extension CDInfrastructureServer { - - @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: "CDInfrastructureServer") - } - - @NSManaged public var area: String? - @NSManaged public var countryCode: String? - @NSManaged public var extraCountryCodes: String? - @NSManaged public var hostname: String? - @NSManaged public var ipAddresses: String? - @NSManaged public var apiId: String? - @NSManaged public var serverIndex: Int16 - @NSManaged public var tags: String? - @NSManaged public var uniqueId: String? - @NSManaged public var category: CDInfrastructureCategory? - @NSManaged public var location: CDInfrastructureLocation? - -} - -extension CDInfrastructureServer: Identifiable { - -} diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/CredentialsPurpose.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/CredentialsPurpose.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/CredentialsPurpose.swift rename to PassepartoutLibrary/Sources/PassepartoutProviders/Domain/CredentialsPurpose.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderCategory.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderCategory.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderCategory.swift rename to PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderCategory.swift index f24d9189..e95a36c6 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderCategory.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderCategory.swift @@ -24,6 +24,7 @@ // import Foundation +import PassepartoutCore public struct ProviderCategory { public let providerMetadata: ProviderMetadata diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderLocation.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderLocation.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderLocation.swift rename to PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderLocation.swift index 6f45a5d9..cb4132db 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderLocation.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderLocation.swift @@ -24,6 +24,7 @@ // import Foundation +import PassepartoutCore public struct ProviderLocation { public let providerMetadata: ProviderMetadata diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderMetadata.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderMetadata.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderMetadata.swift rename to PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderMetadata.swift index cf3db03b..3aa66cf8 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderMetadata.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderMetadata.swift @@ -24,6 +24,7 @@ // import Foundation +import PassepartoutCore public struct ProviderMetadata { public let name: String diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderName.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderName.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderName.swift rename to PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderName.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderServer.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderServer.swift similarity index 97% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderServer.swift rename to PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderServer.swift index dddff794..6541fddb 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Models/ProviderServer.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Domain/ProviderServer.swift @@ -25,8 +25,9 @@ import Foundation import GenericJSON +import PassepartoutCore -public struct ProviderServer: Identifiable { +public struct ProviderServer { public struct Preset { public let id: String @@ -87,7 +88,9 @@ public struct ProviderServer: Identifiable { self.ipAddresses = ipAddresses self.presetIds = presetIds } +} +extension ProviderServer { public func preset(withId presetId: String) -> Preset? { presets?.first { $0.id == presetId diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Identifiable.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/Domain+Identifiable.swift similarity index 95% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Identifiable.swift rename to PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/Domain+Identifiable.swift index 00b0fdc0..b7385c24 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Identifiable.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/Domain+Identifiable.swift @@ -1,5 +1,5 @@ // -// PassepartoutProviders+Identifiable.swift +// Domain+Identifiable.swift // Passepartout // // Created by Davide De Rosa on 3/25/22. @@ -41,7 +41,7 @@ extension ProviderLocation: Identifiable { } } -extension ProviderServer { +extension ProviderServer: Identifiable { public var locationId: String { "\(providerMetadata.name):\(categoryName):\(countryCode)" } @@ -55,9 +55,7 @@ extension ProviderServer { servers: nil ) } -} -extension ProviderServer { public static func id(withName providerName: ProviderName, vpnProtocol: VPNProtocolType, apiId: String) -> String? { let idSource = "\(providerName):\(vpnProtocol.rawValue):\(apiId)" guard let data = idSource.data(using: .utf8) else { diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Logging.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/Domain+Logging.swift similarity index 90% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Logging.swift rename to PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/Domain+Logging.swift index 691e9a24..3dabd0f6 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutProviders+Logging.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/Domain+Logging.swift @@ -1,8 +1,8 @@ // -// PassepartoutProviders+Logging.swift +// Domain+Logging.swift // Passepartout // -// Created by Davide De Rosa on 3/25/22. +// Created by Davide De Rosa on 4/7/22. // Copyright (c) 2023 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -24,7 +24,6 @@ // import Foundation -import PassepartoutCore extension ProviderServer { public var logDescription: String { diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/Extensions/WSProviderName+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/ProviderName+Providers.swift similarity index 95% rename from PassepartoutLibrary/Sources/PassepartoutServices/Extensions/WSProviderName+Extensions.swift rename to PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/ProviderName+Providers.swift index f928d7c9..3b7d7dd7 100644 --- a/PassepartoutLibrary/Sources/PassepartoutServices/Extensions/WSProviderName+Extensions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/ProviderName+Providers.swift @@ -1,5 +1,5 @@ // -// WSProviderName+Extensions.swift +// ProviderName+Providers.swift // Passepartout // // Created by Davide De Rosa on 11/24/19. @@ -25,7 +25,7 @@ import Foundation -extension WSProviderName { +extension ProviderName { public static let hideme = "hideme" public static let mullvad = "mullvad" diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Managers/ProviderManager.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Managers/ProviderManager.swift index 960e5b86..5b4e8e89 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Managers/ProviderManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Managers/ProviderManager.swift @@ -26,34 +26,20 @@ import Combine import Foundation import PassepartoutCore -import PassepartoutServices -import PassepartoutUtils public final class ProviderManager: ObservableObject, RateLimited { - private let appBuild: Int + private let localProvidersRepository: LocalProvidersRepository - private let bundleServices: WebServices - - private let webServices: WebServices - - private let persistence: Persistence - - private let providerRepository: ProviderRepository - - private let infrastructureRepository: InfrastructureRepository - - private let serverRepository: ServerRepository + private let remoteProvidersStrategy: RemoteProvidersStrategy public let didUpdateProviders = PassthroughSubject() - public init(appBuild: Int, bundleServices: WebServices, webServices: WebServices, persistence: Persistence) { - self.appBuild = appBuild - self.bundleServices = bundleServices - self.webServices = webServices - self.persistence = persistence - providerRepository = ProviderRepository(persistence.context) - infrastructureRepository = InfrastructureRepository(persistence.context) - serverRepository = ServerRepository(persistence.context) + public init( + localProvidersRepository: LocalProvidersRepository, + remoteProvidersStrategy: RemoteProvidersStrategy + ) { + self.localProvidersRepository = localProvidersRepository + self.remoteProvidersStrategy = remoteProvidersStrategy _ = allProviders() } @@ -61,133 +47,85 @@ public final class ProviderManager: ObservableObject, RateLimited { // MARK: Queries public func allProviders() -> [ProviderMetadata] { - providerRepository.allProviders() + localProvidersRepository.allProviders() } public func provider(withName name: ProviderName) -> ProviderMetadata? { - providerRepository.provider(withName: name) + localProvidersRepository.provider(withName: name) } public func isAvailable(_ name: ProviderName, vpnProtocol: VPNProtocolType) -> Bool { - infrastructureRepository.lastInfrastructureUpdate(withName: name, vpnProtocol: vpnProtocol) != nil + localProvidersRepository.lastInfrastructureUpdate(withName: name, vpnProtocol: vpnProtocol) != nil } public func defaultUsername(_ name: ProviderName, vpnProtocol: VPNProtocolType) -> String? { - infrastructureRepository.defaultUsername(forProviderWithName: name, vpnProtocol: vpnProtocol) + localProvidersRepository.defaultUsername(forProviderWithName: name, vpnProtocol: vpnProtocol) } public func lastUpdate(_ name: ProviderName, vpnProtocol: VPNProtocolType) -> Date? { - infrastructureRepository.lastInfrastructureUpdate(withName: name, vpnProtocol: vpnProtocol) + localProvidersRepository.lastInfrastructureUpdate(withName: name, vpnProtocol: vpnProtocol) } public func categories(_ name: ProviderName, vpnProtocol: VPNProtocolType) -> [ProviderCategory] { - serverRepository.categories(forProviderWithName: name, vpnProtocol: vpnProtocol) + localProvidersRepository.categories(forProviderWithName: name, vpnProtocol: vpnProtocol) } public func servers(forLocation location: ProviderLocation) -> [ProviderServer] { - serverRepository.servers(forLocation: location) + localProvidersRepository.servers(forLocation: location) } public func server(_ name: ProviderName, vpnProtocol: VPNProtocolType, apiId: String) -> ProviderServer? { - serverRepository.server(forProviderWithName: name, vpnProtocol: vpnProtocol, apiId: apiId) + localProvidersRepository.server(forProviderWithName: name, vpnProtocol: vpnProtocol, apiId: apiId) } public func anyDefaultServer(_ name: ProviderName, vpnProtocol: VPNProtocolType) -> ProviderServer? { - serverRepository.anyDefaultServer(forProviderWithName: name, vpnProtocol: vpnProtocol) + localProvidersRepository.anyDefaultServer(forProviderWithName: name, vpnProtocol: vpnProtocol) } public func server(withId id: String) -> ProviderServer? { - serverRepository.server(withId: id) + localProvidersRepository.server(withId: id) } // MARK: Modification - public func fetchProvidersIndexPublisher(priority: ProviderManagerFetchPriority) -> AnyPublisher { + public func fetchProvidersIndexPublisher(priority: RemoteProvidersPriority) -> AnyPublisher { guard !isRateLimited(indexActionName) else { return Just(()) .setFailureType(to: Error.self) .eraseToAnyPublisher() } - let publisher = priority.publisher(remote: { - self.webServices.providersIndex() - }, bundle: { - self.bundleServices.providersIndex() - }) - - return publisher - .receive(on: DispatchQueue.main) - .tryMap { index in - self.saveLastAction(self.indexActionName) - try self.providerRepository.mergeIndex(index) - + let savePublisher = remoteProvidersStrategy.saveIndex(priority: priority) { + self.saveLastAction(self.indexActionName) + } + return savePublisher + .map { self.didUpdateProviders.send() }.eraseToAnyPublisher() } - public func fetchProviderPublisher(withName providerName: ProviderName, vpnProtocol: VPNProtocolType, priority: ProviderManagerFetchPriority) -> AnyPublisher { + public func fetchProviderPublisher(withName providerName: ProviderName, vpnProtocol: VPNProtocolType, priority: RemoteProvidersPriority) -> AnyPublisher { guard !isRateLimited(providerName) else { return Just(()) .setFailureType(to: Error.self) .eraseToAnyPublisher() } - let publisher = priority.publisher(remote: { - let ifModifiedSince = self.infrastructureRepository.lastInfrastructureUpdate(withName: providerName, vpnProtocol: vpnProtocol) - return self.webServices.providerNetwork( - with: providerName.asWSProviderName, - vpnProtocol: vpnProtocol.asWSVPNProtocol, - ifModifiedSince: ifModifiedSince - ) - }, bundle: { - self.bundleServices.providerNetwork( - with: providerName.asWSProviderName, - vpnProtocol: vpnProtocol.asWSVPNProtocol, - ifModifiedSince: nil - ) - }) - - return publisher - .receive(on: DispatchQueue.main) - .flatMap { pub -> AnyPublisher in - self.saveLastAction(providerName) - - // ignores empty responses (e.g. HTTP 304) - guard let infrastructure = pub.value else { - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - - guard self.appBuild >= infrastructure.build else { - pp_log.error("Infrastructure requires app build >= \(infrastructure.build) (app is \(self.appBuild))") - return Fail(error: ProviderManagerError.outdatedBuild(self.appBuild, infrastructure.build)) - .eraseToAnyPublisher() - } - - do { - try self.infrastructureRepository.saveInfrastructure( - infrastructure, - vpnProtocol: vpnProtocol, - lastUpdate: pub.lastModified ?? Date() - ) - - self.didUpdateProviders.send() - } catch { - pp_log.error("Unable to persist \(providerName) infrastructure (\(vpnProtocol)): \(error)") - } - return Just(()) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() + let lastUpdate = localProvidersRepository.lastInfrastructureUpdate(withName: providerName, vpnProtocol: vpnProtocol) + let savePublisher = remoteProvidersStrategy.saveProvider( + withName: providerName, + vpnProtocol: vpnProtocol, + lastUpdate: lastUpdate, + priority: priority + ) { + self.saveLastAction(providerName) + } + return savePublisher + .map { + self.didUpdateProviders.send() }.eraseToAnyPublisher() } - public func reset() { - persistence.truncate() - - didUpdateProviders.send() - } - // MARK: RateLimited private let indexActionName = "" @@ -196,55 +134,3 @@ public final class ProviderManager: ObservableObject, RateLimited { public var rateLimitMilliseconds: Int? } - -private enum ProviderManagerError: LocalizedError { - case outdatedBuild(Int, Int) - - var errorDescription: String? { - switch self { - case .outdatedBuild(let current, let min): - return "Build is outdated (found \(current), required \(min))" - } - } -} - -private extension ProviderManagerFetchPriority { - func publisher( - remote: @escaping () -> AnyPublisher, - bundle: @escaping () -> AnyPublisher - ) -> AnyPublisher { - switch self { - case .bundle: - return bundle() - - case .remote: - return remote() - - case .remoteThenBundle: - return remote() - .catch { error -> AnyPublisher in - pp_log.warning("Unable to fetch remotely: \(error)") - pp_log.warning("Falling back to bundle") - return bundle() - }.eraseToAnyPublisher() - } - } -} - -private extension ProviderName { - var asWSProviderName: WSProviderName { - self - } -} - -private extension VPNProtocolType { - var asWSVPNProtocol: WSVPNProtocol { - switch self { - case .openVPN: - return .openVPN - - case .wireGuard: - return .wireGuard - } - } -} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/InfrastructureRepository.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/InfrastructureRepository.swift new file mode 100644 index 00000000..cf6ae6d6 --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/InfrastructureRepository.swift @@ -0,0 +1,33 @@ +// +// InfrastructureRepository.swift +// Passepartout +// +// Created by Davide De Rosa on 3/16/22. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore + +public protocol InfrastructureRepository { + func defaultUsername(forProviderWithName name: ProviderName, vpnProtocol: VPNProtocolType) -> String? + + func lastInfrastructureUpdate(withName name: ProviderName, vpnProtocol: VPNProtocolType) -> Date? +} diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Models/VPNConfigurationError.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/LocalProvidersRepository.swift similarity index 82% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Models/VPNConfigurationError.swift rename to PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/LocalProvidersRepository.swift index 0ed4cd6e..69fb37e7 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Models/VPNConfigurationError.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/LocalProvidersRepository.swift @@ -1,8 +1,8 @@ // -// VPNConfigurationError.swift +// LocalProvidersRepository.swift // Passepartout // -// Created by Davide De Rosa on 3/7/22. +// Created by Davide De Rosa on 5/24/23. // Copyright (c) 2023 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -24,6 +24,5 @@ // import Foundation -import PassepartoutCore -public typealias VPNConfigurationError = (profile: Profile, error: Error) +public typealias LocalProvidersRepository = ProviderRepository & InfrastructureRepository & ServerRepository diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/Repository.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/ProviderRepository.swift similarity index 83% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/Repository.swift rename to PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/ProviderRepository.swift index 08a4f6a8..35e26a6a 100644 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/Repository.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/ProviderRepository.swift @@ -1,5 +1,5 @@ // -// Repository.swift +// ProviderRepository.swift // Passepartout // // Created by Davide De Rosa on 3/15/22. @@ -25,8 +25,8 @@ import Foundation -public protocol Repository { - associatedtype Context +public protocol ProviderRepository { + func allProviders() -> [ProviderMetadata] - init(_ context: Context) + func provider(withName name: ProviderName) -> ProviderMetadata? } diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/RemoteProvidersStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/RemoteProvidersStrategy.swift new file mode 100644 index 00000000..0d40342f --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/RemoteProvidersStrategy.swift @@ -0,0 +1,51 @@ +// +// RemoteProvidersStrategy.swift +// Passepartout +// +// Created by Davide De Rosa on 5/24/23. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Combine +import Foundation +import PassepartoutCore + +public enum RemoteProvidersPriority { + case bundle + + case remote + + case remoteThenBundle +} + +public protocol RemoteProvidersStrategy { + func saveIndex( + priority: RemoteProvidersPriority, + onFetch: @escaping () -> Void + ) -> AnyPublisher + + func saveProvider( + withName providerName: ProviderName, + vpnProtocol: VPNProtocolType, + lastUpdate: Date?, + priority: RemoteProvidersPriority, + onFetch: @escaping () -> Void + ) -> AnyPublisher +} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/ServerRepository.swift b/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/ServerRepository.swift new file mode 100644 index 00000000..fd7e76bf --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutProviders/Strategies/ServerRepository.swift @@ -0,0 +1,41 @@ +// +// ServerRepository.swift +// Passepartout +// +// Created by Davide De Rosa on 3/15/22. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore + +public protocol ServerRepository { + func categories(forProviderWithName name: ProviderName, vpnProtocol: VPNProtocolType) -> [ProviderCategory] + + func servers(forLocation location: ProviderLocation) -> [ProviderServer] + + func server(forProviderWithName providerName: ProviderName, vpnProtocol: VPNProtocolType, apiId: String) -> ProviderServer? + + func anyServer(forProviderWithName providerName: ProviderName, vpnProtocol: VPNProtocolType, countryCode: String) -> ProviderServer? + + func anyDefaultServer(forProviderWithName providerName: ProviderName, vpnProtocol: VPNProtocolType) -> ProviderServer? + + func server(withId id: String) -> ProviderServer? +} diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/API b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/API similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutServices/API rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/API diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataClass.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructure+CoreDataClass.swift similarity index 82% rename from PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataClass.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructure+CoreDataClass.swift index 7075ec72..7791e2a8 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructure+CoreDataClass.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructure+CoreDataClass.swift @@ -11,6 +11,6 @@ import CoreData import Foundation @objc(CDInfrastructure) -public class CDInfrastructure: NSManagedObject { +class CDInfrastructure: NSManagedObject { } diff --git a/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructure+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructure+CoreDataProperties.swift new file mode 100644 index 00000000..f4c2d32b --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructure+CoreDataProperties.swift @@ -0,0 +1,46 @@ +// +// CDInfrastructure+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import CoreData +import Foundation + +extension CDInfrastructure { + + @nonobjc class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDInfrastructure") + } + + @NSManaged var lastUpdate: Date? + @NSManaged var vpnProtocol: String? + @NSManaged var categories: NSSet? + @NSManaged var defaults: CDInfrastructureDefaultSettings? + @NSManaged var provider: CDProvider? + +} + +// MARK: Generated accessors for categories +extension CDInfrastructure { + + @objc(addCategoriesObject:) + @NSManaged func addToCategories(_ value: CDInfrastructureCategory) + + @objc(removeCategoriesObject:) + @NSManaged func removeFromCategories(_ value: CDInfrastructureCategory) + + @objc(addCategories:) + @NSManaged func addToCategories(_ values: NSSet) + + @objc(removeCategories:) + @NSManaged func removeFromCategories(_ values: NSSet) + +} + +extension CDInfrastructure: Identifiable { + +} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataClass.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureCategory+CoreDataClass.swift similarity index 81% rename from PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataClass.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureCategory+CoreDataClass.swift index 3b48f711..f5be5c22 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureCategory+CoreDataClass.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureCategory+CoreDataClass.swift @@ -11,6 +11,6 @@ import CoreData import Foundation @objc(CDInfrastructureCategory) -public class CDInfrastructureCategory: NSManagedObject { +class CDInfrastructureCategory: NSManagedObject { } diff --git a/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureCategory+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureCategory+CoreDataProperties.swift new file mode 100644 index 00000000..7b56bbda --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureCategory+CoreDataProperties.swift @@ -0,0 +1,80 @@ +// +// CDInfrastructureCategory+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import CoreData +import Foundation + +extension CDInfrastructureCategory { + + @nonobjc class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDInfrastructureCategory") + } + + @NSManaged var name: String? + @NSManaged var infrastructure: CDInfrastructure? + @NSManaged var locations: NSSet? + @NSManaged var presets: NSSet? + @NSManaged var servers: NSSet? + +} + +// MARK: Generated accessors for locations +extension CDInfrastructureCategory { + + @objc(addLocationsObject:) + @NSManaged func addToLocations(_ value: CDInfrastructureLocation) + + @objc(removeLocationsObject:) + @NSManaged func removeFromLocations(_ value: CDInfrastructureLocation) + + @objc(addLocations:) + @NSManaged func addToLocations(_ values: NSSet) + + @objc(removeLocations:) + @NSManaged func removeFromLocations(_ values: NSSet) + +} + +// MARK: Generated accessors for presets +extension CDInfrastructureCategory { + + @objc(addPresetsObject:) + @NSManaged func addToPresets(_ value: CDInfrastructurePreset) + + @objc(removePresetsObject:) + @NSManaged func removeFromPresets(_ value: CDInfrastructurePreset) + + @objc(addPresets:) + @NSManaged func addToPresets(_ values: NSSet) + + @objc(removePresets:) + @NSManaged func removeFromPresets(_ values: NSSet) + +} + +// MARK: Generated accessors for servers +extension CDInfrastructureCategory { + + @objc(addServersObject:) + @NSManaged func addToServers(_ value: CDInfrastructureServer) + + @objc(removeServersObject:) + @NSManaged func removeFromServers(_ value: CDInfrastructureServer) + + @objc(addServers:) + @NSManaged func addToServers(_ values: NSSet) + + @objc(removeServers:) + @NSManaged func removeFromServers(_ values: NSSet) + +} + +extension CDInfrastructureCategory: Identifiable { + +} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataClass.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureDefaultSettings+CoreDataClass.swift similarity index 80% rename from PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataClass.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureDefaultSettings+CoreDataClass.swift index 7f4d18db..bc6895d6 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataClass.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureDefaultSettings+CoreDataClass.swift @@ -11,6 +11,6 @@ import CoreData import Foundation @objc(CDInfrastructureDefaultSettings) -public class CDInfrastructureDefaultSettings: NSManagedObject { +class CDInfrastructureDefaultSettings: NSManagedObject { } diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureDefaultSettings+CoreDataProperties.swift similarity index 63% rename from PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataProperties.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureDefaultSettings+CoreDataProperties.swift index a2b08884..69817246 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureDefaultSettings+CoreDataProperties.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureDefaultSettings+CoreDataProperties.swift @@ -12,13 +12,13 @@ import Foundation extension CDInfrastructureDefaultSettings { - @nonobjc public class func fetchRequest() -> NSFetchRequest { + @nonobjc class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "CDInfrastructureDefaultSettings") } - @NSManaged public var countryCode: String? - @NSManaged public var usernamePlaceholder: String? - @NSManaged public var infrastructure: CDInfrastructure? + @NSManaged var countryCode: String? + @NSManaged var usernamePlaceholder: String? + @NSManaged var infrastructure: CDInfrastructure? } diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataClass.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureLocation+CoreDataClass.swift similarity index 81% rename from PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataClass.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureLocation+CoreDataClass.swift index 9b2c8391..a7659d1a 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataClass.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureLocation+CoreDataClass.swift @@ -11,6 +11,6 @@ import CoreData import Foundation @objc(CDInfrastructureLocation) -public class CDInfrastructureLocation: NSManagedObject { +class CDInfrastructureLocation: NSManagedObject { } diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureLocation+CoreDataProperties.swift similarity index 54% rename from PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataProperties.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureLocation+CoreDataProperties.swift index a4ac1d27..9e70eab2 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureLocation+CoreDataProperties.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureLocation+CoreDataProperties.swift @@ -12,13 +12,13 @@ import Foundation extension CDInfrastructureLocation { - @nonobjc public class func fetchRequest() -> NSFetchRequest { + @nonobjc class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "CDInfrastructureLocation") } - @NSManaged public var countryCode: String? - @NSManaged public var category: CDInfrastructureCategory? - @NSManaged public var servers: NSSet? + @NSManaged var countryCode: String? + @NSManaged var category: CDInfrastructureCategory? + @NSManaged var servers: NSSet? } @@ -26,16 +26,16 @@ extension CDInfrastructureLocation { extension CDInfrastructureLocation { @objc(addServersObject:) - @NSManaged public func addToServers(_ value: CDInfrastructureServer) + @NSManaged func addToServers(_ value: CDInfrastructureServer) @objc(removeServersObject:) - @NSManaged public func removeFromServers(_ value: CDInfrastructureServer) + @NSManaged func removeFromServers(_ value: CDInfrastructureServer) @objc(addServers:) - @NSManaged public func addToServers(_ values: NSSet) + @NSManaged func addToServers(_ values: NSSet) @objc(removeServers:) - @NSManaged public func removeFromServers(_ values: NSSet) + @NSManaged func removeFromServers(_ values: NSSet) } diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataClass.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructurePreset+CoreDataClass.swift similarity index 81% rename from PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataClass.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructurePreset+CoreDataClass.swift index 930a8d0a..517ec77a 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructurePreset+CoreDataClass.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructurePreset+CoreDataClass.swift @@ -11,6 +11,6 @@ import CoreData import Foundation @objc(CDInfrastructurePreset) -public class CDInfrastructurePreset: NSManagedObject { +class CDInfrastructurePreset: NSManagedObject { } diff --git a/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructurePreset+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructurePreset+CoreDataProperties.swift new file mode 100644 index 00000000..18fa10b5 --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructurePreset+CoreDataProperties.swift @@ -0,0 +1,47 @@ +// +// CDInfrastructurePreset+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import CoreData +import Foundation + +extension CDInfrastructurePreset { + + @nonobjc class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDInfrastructurePreset") + } + + @NSManaged var comment: String? + @NSManaged var id: String? + @NSManaged var name: String? + @NSManaged var vpnConfiguration: Data? + @NSManaged var vpnProtocol: String? + @NSManaged var category: NSSet? + +} + +// MARK: Generated accessors for category +extension CDInfrastructurePreset { + + @objc(addCategoryObject:) + @NSManaged func addToCategory(_ value: CDInfrastructureCategory) + + @objc(removeCategoryObject:) + @NSManaged func removeFromCategory(_ value: CDInfrastructureCategory) + + @objc(addCategory:) + @NSManaged func addToCategory(_ values: NSSet) + + @objc(removeCategory:) + @NSManaged func removeFromCategory(_ values: NSSet) + +} + +extension CDInfrastructurePreset: Identifiable { + +} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataClass.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureServer+CoreDataClass.swift similarity index 81% rename from PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataClass.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureServer+CoreDataClass.swift index 3442f541..1d71c0eb 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDInfrastructureServer+CoreDataClass.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureServer+CoreDataClass.swift @@ -11,6 +11,6 @@ import CoreData import Foundation @objc(CDInfrastructureServer) -public class CDInfrastructureServer: NSManagedObject { +class CDInfrastructureServer: NSManagedObject { } diff --git a/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureServer+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureServer+CoreDataProperties.swift new file mode 100644 index 00000000..8bb9f44d --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDInfrastructureServer+CoreDataProperties.swift @@ -0,0 +1,35 @@ +// +// CDInfrastructureServer+CoreDataProperties.swift +// +// +// Created by Davide De Rosa on 27/03/22. +// +// This file was automatically generated and should not be edited. +// + +import CoreData +import Foundation + +extension CDInfrastructureServer { + + @nonobjc class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "CDInfrastructureServer") + } + + @NSManaged var area: String? + @NSManaged var countryCode: String? + @NSManaged var extraCountryCodes: String? + @NSManaged var hostname: String? + @NSManaged var ipAddresses: String? + @NSManaged var apiId: String? + @NSManaged var serverIndex: Int16 + @NSManaged var tags: String? + @NSManaged var uniqueId: String? + @NSManaged var category: CDInfrastructureCategory? + @NSManaged var location: CDInfrastructureLocation? + +} + +extension CDInfrastructureServer: Identifiable { + +} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataClass.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDProvider+CoreDataClass.swift similarity index 83% rename from PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataClass.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDProvider+CoreDataClass.swift index 65a25e19..d3642fe9 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataClass.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDProvider+CoreDataClass.swift @@ -11,6 +11,6 @@ import CoreData import Foundation @objc(CDProvider) -public class CDProvider: NSManagedObject { +class CDProvider: NSManagedObject { } diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDProvider+CoreDataProperties.swift similarity index 51% rename from PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataProperties.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDProvider+CoreDataProperties.swift index fc16c6e5..2e7e2f83 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/CDProvider+CoreDataProperties.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/CDProvider+CoreDataProperties.swift @@ -12,14 +12,14 @@ import Foundation extension CDProvider { - @nonobjc public class func fetchRequest() -> NSFetchRequest { + @nonobjc class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "CDProvider") } - @NSManaged public var fullName: String? - @NSManaged public var name: String? - @NSManaged public var infrastructures: NSSet? - @NSManaged public var lastUpdate: Date? + @NSManaged var fullName: String? + @NSManaged var name: String? + @NSManaged var infrastructures: NSSet? + @NSManaged var lastUpdate: Date? } @@ -27,16 +27,16 @@ extension CDProvider { extension CDProvider { @objc(addInfrastructuresObject:) - @NSManaged public func addToInfrastructures(_ value: CDInfrastructure) + @NSManaged func addToInfrastructures(_ value: CDInfrastructure) @objc(removeInfrastructuresObject:) - @NSManaged public func removeFromInfrastructures(_ value: CDInfrastructure) + @NSManaged func removeFromInfrastructures(_ value: CDInfrastructure) @objc(addInfrastructures:) - @NSManaged public func addToInfrastructures(_ values: NSSet) + @NSManaged func addToInfrastructures(_ values: NSSet) @objc(removeInfrastructures:) - @NSManaged public func removeFromInfrastructures(_ values: NSSet) + @NSManaged func removeFromInfrastructures(_ values: NSSet) } diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutDataModels+Providers.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/Persistence.swift similarity index 56% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutDataModels+Providers.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/Persistence.swift index 2059d896..f3283982 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutDataModels+Providers.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/Persistence.swift @@ -1,5 +1,5 @@ // -// PassepartoutDataModels+Providers.swift +// Persistence.swift // Passepartout // // Created by Davide De Rosa on 4/7/22. @@ -26,12 +26,33 @@ import CoreData import Foundation import PassepartoutCore +import PassepartoutProviders +import PassepartoutServices -extension PassepartoutDataModels { - public static let providers: NSManagedObjectModel = { +extension PassepartoutPersistence { + private static let providersDataModel: NSManagedObjectModel = { guard let model = NSManagedObjectModel.mergedModel(from: [.module]) else { fatalError("Could not load PassepartoutProviders model") } return model }() + + public static func providersStore(withName containerName: String, cloudKit: Bool, author: String?) -> CoreDataPersistentStore { + .init( + withName: containerName, + model: providersDataModel, + cloudKit: cloudKit, + author: author + ) + } +} + +extension PassepartoutPersistence { + public static func webServicesRepository(_ store: CoreDataPersistentStore) -> WebServicesRepository { + CDWebServicesRepository(store.context) + } + + public static func localProvidersRepository(_ store: CoreDataPersistentStore) -> LocalProvidersRepository { + CDLocalProvidersRepository(store.context) + } } diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/Providers.xcdatamodeld/Model.xcdatamodel/contents b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/Providers.xcdatamodeld/Model.xcdatamodel/contents similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutProviders/DataModels/Providers.xcdatamodeld/Model.xcdatamodel/contents rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Data/Providers.xcdatamodeld/Model.xcdatamodel/contents diff --git a/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/APIRemoteProvidersStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/APIRemoteProvidersStrategy.swift new file mode 100644 index 00000000..05a8d6a6 --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/APIRemoteProvidersStrategy.swift @@ -0,0 +1,171 @@ +// +// APIRemoteProvidersStrategy.swift +// Passepartout +// +// Created by Davide De Rosa on 5/24/23. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Combine +import CoreData +import Foundation +import PassepartoutCore +import PassepartoutProviders +import PassepartoutServices + +public final class APIRemoteProvidersStrategy: RemoteProvidersStrategy { + private let appBuild: Int + + private let bundleServices: WebServices + + private let remoteServices: WebServices + + private let webServicesRepository: WebServicesRepository + + public init(appBuild: Int, bundleServices: WebServices, remoteServices: WebServices, webServicesRepository: WebServicesRepository) { + self.appBuild = appBuild + self.bundleServices = bundleServices + self.remoteServices = remoteServices + self.webServicesRepository = webServicesRepository + } + + public func saveIndex( + priority: RemoteProvidersPriority, + onFetch: @escaping () -> Void + ) -> AnyPublisher { + let publisher = priority.publisher(remote: { + self.remoteServices.providersIndex() + }, bundle: { + self.bundleServices.providersIndex() + }) + + return publisher + .receive(on: DispatchQueue.main) + .tryMap { index in + onFetch() + try self.webServicesRepository.mergeIndex(index) + }.eraseToAnyPublisher() + } + + public func saveProvider( + withName providerName: ProviderName, + vpnProtocol: VPNProtocolType, + lastUpdate: Date?, + priority: RemoteProvidersPriority, + onFetch: @escaping () -> Void + ) -> AnyPublisher { + let publisher = priority.publisher(remote: { + self.remoteServices.providerNetwork( + with: providerName.asWSProviderName, + vpnProtocol: vpnProtocol.asWSVPNProtocol, + ifModifiedSince: lastUpdate + ) + }, bundle: { + self.bundleServices.providerNetwork( + with: providerName.asWSProviderName, + vpnProtocol: vpnProtocol.asWSVPNProtocol, + ifModifiedSince: nil + ) + }) + + return publisher + .receive(on: DispatchQueue.main) + .flatMap { pub -> AnyPublisher in + onFetch() + + // ignores empty responses (e.g. HTTP 304) + guard let infrastructure = pub.value else { + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } + + guard self.appBuild >= infrastructure.build else { + pp_log.error("Infrastructure requires app build >= \(infrastructure.build) (app is \(self.appBuild))") + return Fail(error: APIRemoteProvidersStrategyError.outdatedBuild(self.appBuild, infrastructure.build)) + .eraseToAnyPublisher() + } + + do { + try self.webServicesRepository.saveInfrastructure( + infrastructure, + vpnProtocol: vpnProtocol, + lastUpdate: pub.lastModified ?? Date() + ) + } catch { + pp_log.error("Unable to persist \(providerName) infrastructure (\(vpnProtocol)): \(error)") + } + return Just(()) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + }.eraseToAnyPublisher() + } +} + +private extension RemoteProvidersPriority { + func publisher( + remote: @escaping () -> AnyPublisher, + bundle: @escaping () -> AnyPublisher + ) -> AnyPublisher { + switch self { + case .bundle: + return bundle() + + case .remote: + return remote() + + case .remoteThenBundle: + return remote() + .catch { error -> AnyPublisher in + pp_log.warning("Unable to fetch remotely: \(error)") + pp_log.warning("Falling back to bundle") + return bundle() + }.eraseToAnyPublisher() + } + } +} + +private extension ProviderName { + var asWSProviderName: WSProviderName { + self + } +} + +private extension VPNProtocolType { + var asWSVPNProtocol: WSVPNProtocol { + switch self { + case .openVPN: + return .openVPN + + case .wireGuard: + return .wireGuard + } + } +} +private enum APIRemoteProvidersStrategyError: LocalizedError { + case outdatedBuild(Int, Int) + + var errorDescription: String? { + switch self { + case .outdatedBuild(let current, let min): + return "Build is outdated (found \(current), required \(min))" + } + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/DefaultWebServices.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/APIWebServices.swift similarity index 79% rename from PassepartoutLibrary/Sources/PassepartoutServices/DefaultWebServices.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/APIWebServices.swift index ba755e16..60300ff8 100644 --- a/PassepartoutLibrary/Sources/PassepartoutServices/DefaultWebServices.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/APIWebServices.swift @@ -1,5 +1,5 @@ // -// DefaultWebServices.swift +// APIWebServices.swift // Passepartout // // Created by Davide De Rosa on 9/14/18. @@ -25,9 +25,10 @@ import Combine import Foundation -import PassepartoutUtils +import PassepartoutCore +import PassepartoutServices -public class DefaultWebServices: WebServices { +public final class APIWebServices: WebServices { private enum Group: String { case providers } @@ -58,7 +59,7 @@ public class DefaultWebServices: WebServices { } } - private let ws: GenericWebServices + private let ws: GenericWebServices public init(_ version: String, _ root: URL, timeout: TimeInterval?, queue: DispatchQueue = .main) { ws = GenericWebServices(version, root, timeout: timeout) @@ -69,7 +70,7 @@ public class DefaultWebServices: WebServices { return ws.parse(WSProvidersIndex.self, request: request) .tryMap { guard let value = $0.value else { - throw WebError.emptyResponse + throw APIError.emptyResponse } return value }.eraseToAnyPublisher() @@ -84,12 +85,37 @@ public class DefaultWebServices: WebServices { } } -extension DefaultWebServices { - public static func bundledServices(withVersion version: String) -> DefaultWebServices { +enum APIError: GenericWebServicesError, LocalizedError { + case http(Int) + + case emptyResponse + + case unknown + + static func httpStatus(_ status: Int) -> APIError { + .http(status) + } + + var errorDescription: String? { + switch self { + case .http(let status): + return "HTTP \(status)" + + case .emptyResponse: + return "Empty response" + + default: + return nil + } + } +} + +extension APIWebServices { + public static func bundledServices(withVersion version: String) -> WebServices { guard let apiURL = Bundle.module.url(forResource: "API", withExtension: nil) else { fatalError("Could not find API in bundle") } - return DefaultWebServices(version, apiURL, timeout: nil) + return APIWebServices(version, apiURL, timeout: nil) } } diff --git a/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Infrastructure.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Infrastructure.swift new file mode 100644 index 00000000..0c11c3f3 --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Infrastructure.swift @@ -0,0 +1,85 @@ +// +// CDLocalProvidersRepository+Infrastructure.swift +// Passepartout +// +// Created by Davide De Rosa on 3/16/22. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import CoreData +import Foundation +import PassepartoutCore +import PassepartoutProviders + +extension CDLocalProvidersRepository: InfrastructureRepository { + func defaultUsername(forProviderWithName name: ProviderName, vpnProtocol: VPNProtocolType) -> String? { + let request = fetchRequest(name, vpnProtocol) + request.sortDescriptors = [ + .init(keyPath: \CDInfrastructure.lastUpdate, ascending: false) + ] + request.relationshipKeyPathsForPrefetching = [ + "defaults" + ] + do { + guard let infrastructureDTO = try context.fetch(request).first else { + Utils.logFetchNotFound(#file, #function, #line) + return nil + } + return infrastructureDTO.defaults?.usernamePlaceholder + } catch { + Utils.logFetchError(#file, #function, #line, error) + return nil + } + } + + func lastInfrastructureUpdate(withName name: ProviderName, vpnProtocol: VPNProtocolType) -> Date? { + let request = fetchRequest(name, vpnProtocol) + request.sortDescriptors = [ + .init(keyPath: \CDInfrastructure.lastUpdate, ascending: false) + ] + request.relationshipKeyPathsForPrefetching = [ + "provider", + "provider.infrastructures" + ] + do { + let infrastructures = try context.fetch(request) + guard !infrastructures.isEmpty else { + Utils.logFetchNotFound(#file, #function, #line) + return nil + } + let recent = infrastructures.first! + return recent.lastUpdate + } catch { + context.rollback() + Utils.logFetchError(#file, #function, #line, error) + return nil + } + } + + private func fetchRequest(_ name: ProviderName, _ vpnProtocol: VPNProtocolType) -> NSFetchRequest { + let request = CDInfrastructure.fetchRequest() + request.predicate = NSPredicate( + format: "provider.name == %@ AND vpnProtocol == %@", + name, + vpnProtocol.rawValue + ) + return request + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ProviderRepository.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Provider.swift similarity index 51% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ProviderRepository.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Provider.swift index a5ad6afc..e97e1350 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ProviderRepository.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Provider.swift @@ -1,5 +1,5 @@ // -// ProviderRepository.swift +// CDLocalProvidersRepository+Provider.swift // Passepartout // // Created by Davide De Rosa on 3/15/22. @@ -26,16 +26,9 @@ import CoreData import Foundation import PassepartoutCore -import PassepartoutServices -import PassepartoutUtils - -class ProviderRepository: Repository { - private let context: NSManagedObjectContext - - required init(_ context: NSManagedObjectContext) { - self.context = context - } +import PassepartoutProviders +extension CDLocalProvidersRepository: ProviderRepository { func allProviders() -> [ProviderMetadata] { let request = CDProvider.fetchRequest() request.sortDescriptors = [ @@ -79,65 +72,4 @@ class ProviderRepository: Repository { return nil } } - - func mergeIndex(_ index: WSProvidersIndex) throws { - let request = CDProvider.fetchRequest() - request.propertiesToFetch = [ - "name", - "fullName" - ] - do { - let providers = try context.fetch(request) - - let indexNames = index.metadata.map(\.name) - let existingNames = providers.compactMap(\.name) - pp_log.debug("Fetched providers: \(indexNames)") - pp_log.debug("Existing providers: \(existingNames)") - - let newNames = Set(indexNames).subtracting(existingNames) - pp_log.info("New providers: \(newNames)") - - // add new - index.metadata.filter { - newNames.contains($0.name) - }.forEach { - _ = ProviderMapper(context).toDTO($0) - pp_log.info("Creating new provider metadata: \($0)") - } - - // update existing - providers.forEach { dto in - guard let name = dto.name else { - return - } - guard let ws = index.metadata.first(where: { - $0.name == name - }) else { - // delete if not in new index - pp_log.info("Deleting provider: \(name)") - context.delete(dto) - return - } - pp_log.info("Updating provider: \(name)") - dto.fullName = ws.fullName - dto.lastUpdate = Date() - } - - try context.save() - } catch { - context.rollback() - throw error - } - } - - private func reassignInfrastructures(from oldProvider: CDProvider, to newProvider: CDProvider) { - oldProvider.infrastructures?.forEach { - guard let infra = $0 as? CDInfrastructure else { - return - } - pp_log.debug("Reassigning provider infrastructure: \(infra)") - oldProvider.removeFromInfrastructures(infra) - newProvider.addToInfrastructures(infra) - } - } } diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ServerRepository.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Server.swift similarity index 96% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ServerRepository.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Server.swift index 95a7d8b2..80830380 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ServerRepository.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository+Server.swift @@ -1,5 +1,5 @@ // -// ServerRepository.swift +// CDLocalProvidersRepository+Server.swift // Passepartout // // Created by Davide De Rosa on 3/15/22. @@ -26,16 +26,9 @@ import CoreData import Foundation import PassepartoutCore -import PassepartoutServices -import PassepartoutUtils - -class ServerRepository: Repository { - private let context: NSManagedObjectContext - - required init(_ context: NSManagedObjectContext) { - self.context = context - } +import PassepartoutProviders +extension CDLocalProvidersRepository: ServerRepository { func categories(forProviderWithName name: ProviderName, vpnProtocol: VPNProtocolType) -> [ProviderCategory] { let request = CDInfrastructureCategory.fetchRequest() request.predicate = NSPredicate( diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/PassepartoutDataModels.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository.swift similarity index 76% rename from PassepartoutLibrary/Sources/PassepartoutCore/PassepartoutDataModels.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository.swift index 5c480dbe..8e20aafc 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/PassepartoutDataModels.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDLocalProvidersRepository.swift @@ -1,8 +1,8 @@ // -// PassepartoutDataModels.swift +// CDLocalProvidersRepository.swift // Passepartout // -// Created by Davide De Rosa on 3/18/22. +// Created by Davide De Rosa on 5/24/23. // Copyright (c) 2023 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -23,7 +23,13 @@ // along with Passepartout. If not, see . // +import CoreData import Foundation -public enum PassepartoutDataModels { +final class CDLocalProvidersRepository { + let context: NSManagedObjectContext + + init(_ context: NSManagedObjectContext) { + self.context = context + } } diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/InfrastructureRepository.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDWebServicesRepository.swift similarity index 63% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/InfrastructureRepository.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDWebServicesRepository.swift index 068128f7..5044919e 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/InfrastructureRepository.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CDWebServicesRepository.swift @@ -1,8 +1,8 @@ // -// InfrastructureRepository.swift +// CDWebServicesRepository.swift // Passepartout // -// Created by Davide De Rosa on 3/16/22. +// Created by Davide De Rosa on 5/22/23. // Copyright (c) 2023 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -26,16 +26,66 @@ import CoreData import Foundation import PassepartoutCore +import PassepartoutProviders import PassepartoutServices -import PassepartoutUtils -class InfrastructureRepository: Repository { +final class CDWebServicesRepository: WebServicesRepository { private let context: NSManagedObjectContext - required init(_ context: NSManagedObjectContext) { + init(_ context: NSManagedObjectContext) { self.context = context } + func mergeIndex(_ index: WSProvidersIndex) throws { + let request = CDProvider.fetchRequest() + request.propertiesToFetch = [ + "name", + "fullName" + ] + do { + let providers = try context.fetch(request) + + let indexNames = index.metadata.map(\.name) + let existingNames = providers.compactMap(\.name) + pp_log.debug("Fetched providers: \(indexNames)") + pp_log.debug("Existing providers: \(existingNames)") + + let newNames = Set(indexNames).subtracting(existingNames) + pp_log.info("New providers: \(newNames)") + + // add new + index.metadata.filter { + newNames.contains($0.name) + }.forEach { + _ = ProviderMapper(context).toDTO($0) + pp_log.info("Creating new provider metadata: \($0)") + } + + // update existing + providers.forEach { dto in + guard let name = dto.name else { + return + } + guard let ws = index.metadata.first(where: { + $0.name == name + }) else { + // delete if not in new index + pp_log.info("Deleting provider: \(name)") + context.delete(dto) + return + } + pp_log.info("Updating provider: \(name)") + dto.fullName = ws.fullName + dto.lastUpdate = Date() + } + + try context.save() + } catch { + context.rollback() + throw error + } + } + func saveInfrastructure( _ infrastructure: WSProviderInfrastructure, vpnProtocol: VPNProtocolType, @@ -70,50 +120,6 @@ class InfrastructureRepository: Repository { } } - func defaultUsername(forProviderWithName name: ProviderName, vpnProtocol: VPNProtocolType) -> String? { - let request = fetchRequest(name, vpnProtocol) - request.sortDescriptors = [ - .init(keyPath: \CDInfrastructure.lastUpdate, ascending: false) - ] - request.relationshipKeyPathsForPrefetching = [ - "defaults" - ] - do { - guard let infrastructureDTO = try context.fetch(request).first else { - Utils.logFetchNotFound(#file, #function, #line) - return nil - } - return infrastructureDTO.defaults?.usernamePlaceholder - } catch { - Utils.logFetchError(#file, #function, #line, error) - return nil - } - } - - func lastInfrastructureUpdate(withName name: ProviderName, vpnProtocol: VPNProtocolType) -> Date? { - let request = fetchRequest(name, vpnProtocol) - request.sortDescriptors = [ - .init(keyPath: \CDInfrastructure.lastUpdate, ascending: false) - ] - request.relationshipKeyPathsForPrefetching = [ - "provider", - "provider.infrastructures" - ] - do { - let infrastructures = try context.fetch(request) - guard !infrastructures.isEmpty else { - Utils.logFetchNotFound(#file, #function, #line) - return nil - } - let recent = infrastructures.first! - return recent.lastUpdate - } catch { - context.rollback() - Utils.logFetchError(#file, #function, #line, error) - return nil - } - } - private func fetchRequest(_ name: ProviderName, _ vpnProtocol: VPNProtocolType) -> NSFetchRequest { let request = CDInfrastructure.fetchRequest() request.predicate = NSPredicate( diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/CategoryMapper.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CategoryMapper.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/CategoryMapper.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CategoryMapper.swift index 616f202e..a5d4037e 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/CategoryMapper.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/CategoryMapper.swift @@ -26,8 +26,8 @@ import CoreData import Foundation import PassepartoutCore +import PassepartoutProviders import PassepartoutServices -import PassepartoutUtils struct CategoryMapper: DTOMapper, ModelMapper { private let context: NSManagedObjectContext diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/DefaultSettingsMapper.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/DefaultSettingsMapper.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/DefaultSettingsMapper.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/DefaultSettingsMapper.swift index 01041131..a271ec47 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/DefaultSettingsMapper.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/DefaultSettingsMapper.swift @@ -26,8 +26,8 @@ import CoreData import Foundation import PassepartoutCore +import PassepartoutProviders import PassepartoutServices -import PassepartoutUtils struct DefaultSettingsMapper: DTOMapper { private let context: NSManagedObjectContext diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/InfrastructureMapper.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/InfrastructureMapper.swift similarity index 99% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/InfrastructureMapper.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/InfrastructureMapper.swift index dda66971..5de4df7a 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/InfrastructureMapper.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/InfrastructureMapper.swift @@ -26,8 +26,8 @@ import CoreData import Foundation import PassepartoutCore +import PassepartoutProviders import PassepartoutServices -import PassepartoutUtils struct InfrastructureMapper: DTOMapper { private let context: NSManagedObjectContext diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/LocationMapper.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/LocationMapper.swift similarity index 99% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/LocationMapper.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/LocationMapper.swift index d35cb719..95dc6106 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/LocationMapper.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/LocationMapper.swift @@ -26,8 +26,8 @@ import CoreData import Foundation import PassepartoutCore +import PassepartoutProviders import PassepartoutServices -import PassepartoutUtils struct LocationMapper: DTOMapper, ModelMapper { private let context: NSManagedObjectContext diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/PresetMapper.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/PresetMapper.swift similarity index 99% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/PresetMapper.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/PresetMapper.swift index 84cb1ca3..57152da0 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/PresetMapper.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/PresetMapper.swift @@ -27,8 +27,8 @@ import CoreData import Foundation import GenericJSON import PassepartoutCore +import PassepartoutProviders import PassepartoutServices -import PassepartoutUtils struct PresetMapper: DTOMapper, ModelMapper { private let context: NSManagedObjectContext diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ProviderMapper.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/ProviderMapper.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ProviderMapper.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/ProviderMapper.swift index c8c47238..ee9b469b 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ProviderMapper.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/ProviderMapper.swift @@ -26,8 +26,8 @@ import CoreData import Foundation import PassepartoutCore +import PassepartoutProviders import PassepartoutServices -import PassepartoutUtils struct ProviderMapper: DTOMapper, ModelMapper { private let context: NSManagedObjectContext diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ServerMapper.swift b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/ServerMapper.swift similarity index 99% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ServerMapper.swift rename to PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/ServerMapper.swift index 1064655c..7792a2b1 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Repositories/ServerMapper.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProvidersImpl/Strategies/ServerMapper.swift @@ -26,8 +26,8 @@ import CoreData import Foundation import PassepartoutCore +import PassepartoutProviders import PassepartoutServices -import PassepartoutUtils struct ServerMapper: DTOMapper, ModelMapper { private let context: NSManagedObjectContext diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderCategory.swift b/PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderCategory.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderCategory.swift rename to PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderCategory.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderInfrastructure.swift b/PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderInfrastructure.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderInfrastructure.swift rename to PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderInfrastructure.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderLocation.swift b/PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderLocation.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderLocation.swift rename to PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderLocation.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderName.swift b/PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderName.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderName.swift rename to PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderName.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderPreset.swift b/PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderPreset.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderPreset.swift rename to PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderPreset.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderServer.swift b/PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderServer.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProviderServer.swift rename to PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProviderServer.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProvidersIndex.swift b/PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProvidersIndex.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSProvidersIndex.swift rename to PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSProvidersIndex.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSVPNProtocol.swift b/PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSVPNProtocol.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutServices/DataModels/WSVPNProtocol.swift rename to PassepartoutLibrary/Sources/PassepartoutServices/Domain/WSVPNProtocol.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/WebServices.swift b/PassepartoutLibrary/Sources/PassepartoutServices/Strategies/WebServices.swift similarity index 71% rename from PassepartoutLibrary/Sources/PassepartoutServices/WebServices.swift rename to PassepartoutLibrary/Sources/PassepartoutServices/Strategies/WebServices.swift index abdf5312..1f139965 100644 --- a/PassepartoutLibrary/Sources/PassepartoutServices/WebServices.swift +++ b/PassepartoutLibrary/Sources/PassepartoutServices/Strategies/WebServices.swift @@ -25,32 +25,7 @@ import Combine import Foundation -import PassepartoutUtils - -public enum WebError: GenericWebServicesError, LocalizedError { - case http(Int) - - case emptyResponse - - case unknown - - public static func httpStatus(_ status: Int) -> WebError { - .http(status) - } - - public var errorDescription: String? { - switch self { - case .http(let status): - return "HTTP \(status)" - - case .emptyResponse: - return "Empty response" - - default: - return nil - } - } -} +import PassepartoutCore public protocol WebServices { func providersIndex() -> AnyPublisher diff --git a/PassepartoutLibrary/Sources/PassepartoutServices/Strategies/WebServicesRepository.swift b/PassepartoutLibrary/Sources/PassepartoutServices/Strategies/WebServicesRepository.swift new file mode 100644 index 00000000..5a02e69f --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutServices/Strategies/WebServicesRepository.swift @@ -0,0 +1,38 @@ +// +// WebServicesRepository.swift +// Passepartout +// +// Created by Davide De Rosa on 5/22/23. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Combine +import Foundation +import PassepartoutCore + +public protocol WebServicesRepository { + func mergeIndex(_ index: WSProvidersIndex) throws + + func saveInfrastructure( + _ infrastructure: WSProviderInfrastructure, + vpnProtocol: VPNProtocolType, + lastUpdate: Date + ) throws +} diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Exports.swift b/PassepartoutLibrary/Sources/PassepartoutUtils/Exports.swift deleted file mode 100644 index 0e5be140..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Exports.swift +++ /dev/null @@ -1,3 +0,0 @@ -import SwiftyBeaver - -public let pp_log = SwiftyBeaver.self diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/LogManager.swift b/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/LogManager.swift deleted file mode 100644 index 5debb9c7..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/LogManager.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// LogManager.swift -// Passepartout -// -// Created by Davide De Rosa on 6/15/22. -// Copyright (c) 2023 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import SwiftyBeaver - -@MainActor -public class LogManager { - public let logFile: URL? - - public var logLevel: SwiftyBeaver.Level = .info - - public var logFormat: String? - - public init(logFile: URL?) { - self.logFile = logFile - } - - public func configureLogging() { - let console = ConsoleDestination() - console.minLevel = logLevel -// console.useNSLog = true - if let logFormat = logFormat { - console.format = logFormat - } - SwiftyBeaver.addDestination(console) - - if let fileURL = logFile { - let file = FileDestination() - file.minLevel = logLevel - file.logFileURL = fileURL - if let logFormat = logFormat { - file.format = logFormat - } - _ = file.deleteLogFile() - SwiftyBeaver.addDestination(file) - } - } -} diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/DebugLog.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/DebugLog.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/DebugLog.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/DebugLog.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutError+Providers.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Errors.swift similarity index 83% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutError+Providers.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Errors.swift index 41be7701..d721ee1c 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Extensions/PassepartoutError+Providers.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Errors.swift @@ -1,5 +1,5 @@ // -// PassepartoutError+Providers.swift +// Errors.swift // Passepartout // // Created by Davide De Rosa on 6/21/22. @@ -27,7 +27,13 @@ import Foundation import PassepartoutCore extension PassepartoutError { + public static let missingProfile = Self("missingProfile") + + public static let missingAccount = Self("missingAccount") + public static let missingProviderServer = Self("missingProviderServer") public static let missingProviderPreset = Self("missingProviderPreset") } + +public typealias VPNConfigurationError = (profile: Profile, error: Error) diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Network.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Network.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/Network.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Network.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+Account.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+Account.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+Account.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+Account.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+Header.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+Header.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+Header.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+Header.swift index 3987a965..9bccf0ae 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+Header.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+Header.swift @@ -24,6 +24,7 @@ // import Foundation +import PassepartoutProviders extension Profile { public struct Header: Codable, Identifiable, Hashable { diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+Host.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+Host.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+Host.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+Host.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+NetworkSettings.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+NetworkSettings.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+NetworkSettings.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+NetworkSettings.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+OnDemand.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+OnDemand.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+OnDemand.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+OnDemand.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+OpenVPNSettings.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+OpenVPNSettings.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+OpenVPNSettings.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+OpenVPNSettings.swift index e2619109..a117dfd3 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+OpenVPNSettings.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+OpenVPNSettings.swift @@ -24,6 +24,7 @@ // import Foundation +import PassepartoutCore import TunnelKitOpenVPN extension Profile { diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+Provider.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+Provider.swift similarity index 96% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+Provider.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+Provider.swift index 2634f302..5e037bfd 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+Provider.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+Provider.swift @@ -24,6 +24,8 @@ // import Foundation +import PassepartoutCore +import PassepartoutProviders import TunnelKitCore extension Profile { diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+WireGuardSettings.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+WireGuardSettings.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+WireGuardSettings.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+WireGuardSettings.swift index 5731b001..ddd8cb90 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile+WireGuardSettings.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile+WireGuardSettings.swift @@ -24,6 +24,7 @@ // import Foundation +import PassepartoutCore import TunnelKitWireGuard extension Profile { diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile.swift similarity index 99% rename from PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile.swift index 3b3cd389..c32d73a2 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/Profile.swift @@ -24,6 +24,7 @@ // import Foundation +import PassepartoutCore import TunnelKitOpenVPN import TunnelKitWireGuard diff --git a/PassepartoutLibrary/Sources/PassepartoutProviders/Managers/ProviderManagerFetchPriority.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfiguration.swift similarity index 84% rename from PassepartoutLibrary/Sources/PassepartoutProviders/Managers/ProviderManagerFetchPriority.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfiguration.swift index ea442a62..6f7215bb 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProviders/Managers/ProviderManagerFetchPriority.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfiguration.swift @@ -1,5 +1,5 @@ // -// ProviderManagerFetchPriority.swift +// VPNConfiguration.swift // Passepartout // // Created by Davide De Rosa on 6/22/22. @@ -24,11 +24,6 @@ // import Foundation +import TunnelKitManager -public enum ProviderManagerFetchPriority { - case bundle - - case remote - - case remoteThenBundle -} +public typealias VPNConfiguration = (neConfiguration: NetworkExtensionConfiguration, neExtra: NetworkExtensionExtra) diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Models/VPNConfiguration.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfigurationParameters.swift similarity index 59% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Models/VPNConfiguration.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfigurationParameters.swift index 49fbeee1..d7b94af1 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Models/VPNConfiguration.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNConfigurationParameters.swift @@ -24,48 +24,43 @@ // import Foundation -import NetworkExtension import PassepartoutCore import TunnelKitManager -public typealias VPNConfiguration = (neConfiguration: NetworkExtensionConfiguration, neExtra: NetworkExtensionExtra) +public struct VPNConfigurationParameters { + public let profile: Profile -protocol VPNConfigurationProviding { - func vpnConfiguration(_ parameters: VPNConfigurationParameters) throws -> VPNConfiguration -} + public var title: String { + profile.header.name + } -struct VPNConfigurationParameters { - let title: String + public let preferences: VPNPreferences - let appGroup: String + public var networkSettings: Profile.NetworkSettings { + profile.networkSettings + } - let preferences: VPNPreferences + public var username: String? { + !profile.account.username.isEmpty ? profile.account.username : nil + } - let networkSettings: Profile.NetworkSettings + public let passwordReference: Data? - let username: String? + public let withNetworkSettings: Bool - let passwordReference: Data? - - let withNetworkSettings: Bool - - let onDemandRules: [NEOnDemandRule] + public let withCustomRules: Bool init( _ profile: Profile, - appGroup: String, preferences: VPNPreferences, passwordReference: Data?, withNetworkSettings: Bool, withCustomRules: Bool ) { - title = profile.header.name - self.appGroup = appGroup + self.profile = profile self.preferences = preferences - networkSettings = profile.networkSettings - username = !profile.account.username.isEmpty ? profile.account.username : nil self.passwordReference = passwordReference self.withNetworkSettings = withNetworkSettings - onDemandRules = profile.onDemandRules(withCustomRules: withCustomRules) + self.withCustomRules = withCustomRules } } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Models/VPNPreferences.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNPreferences.swift similarity index 75% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Models/VPNPreferences.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNPreferences.swift index e570f06d..1b981f29 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Models/VPNPreferences.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNPreferences.swift @@ -25,18 +25,10 @@ import Foundation -public protocol VPNPreferences { - var tunnelLogPath: String? { get } +public struct VPNPreferences { + public let tunnelLogPath: String? - var tunnelLogFormat: String? { get } + public let tunnelLogFormat: String? - var masksPrivateData: Bool { get } -} - -struct DefaultVPNPreferences: VPNPreferences { - let tunnelLogPath: String? - - let tunnelLogFormat: String? - - let masksPrivateData: Bool + public let masksPrivateData: Bool } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNState.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNState.swift new file mode 100644 index 00000000..8ac010a8 --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Domain/VPNState.swift @@ -0,0 +1,38 @@ +// +// VPNState.swift +// Passepartout +// +// Created by Davide De Rosa on 5/23/23. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import TunnelKitCore +import TunnelKitManager + +public protocol VPNState { + var isEnabled: Bool { get } + + var vpnStatus: VPNStatus { get } + + var lastError: Error? { get } + + var dataCount: DataCount? { get } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutLibrary/Extensions/DebugLog+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/DebugLog+Extensions.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutLibrary/Extensions/DebugLog+Extensions.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/DebugLog+Extensions.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+Logging.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Domain+Logging.swift similarity index 94% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+Logging.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Domain+Logging.swift index 0345c412..308d3fb3 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+Logging.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Domain+Logging.swift @@ -1,5 +1,5 @@ // -// PassepartoutProfiles+Logging.swift +// Domain+Logging.swift // Passepartout // // Created by Davide De Rosa on 4/7/22. @@ -24,7 +24,6 @@ // import Foundation -import PassepartoutCore extension Profile.Header { public var logDescription: String { diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Extensions/Host+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Host+Extensions.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutCore/Extensions/Host+Extensions.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Host+Extensions.swift index a4c2a0e2..dda940f1 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Extensions/Host+Extensions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Host+Extensions.swift @@ -24,6 +24,7 @@ // import Foundation +import PassepartoutCore import TunnelKitCore extension Profile { diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Extensions/NetworkSettings+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/NetworkSettings+Extensions.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutCore/Extensions/NetworkSettings+Extensions.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/NetworkSettings+Extensions.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Extensions/OnDemand+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/OnDemand+Extensions.swift similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutCore/Extensions/OnDemand+Extensions.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/OnDemand+Extensions.swift diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/PassepartoutError+VPN.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/PassepartoutError+VPN.swift deleted file mode 100644 index 0d997e33..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/PassepartoutError+VPN.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// PassepartoutError+VPN.swift -// Passepartout -// -// Created by Davide De Rosa on 4/7/22. -// Copyright (c) 2023 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import PassepartoutCore - -extension PassepartoutError { - public static let missingAccount = Self("missingAccount") -} diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/Profile+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Profile+Extensions.swift similarity index 69% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/Profile+Extensions.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Profile+Extensions.swift index 8822216d..f0831b84 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/Profile+Extensions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Profile+Extensions.swift @@ -25,7 +25,37 @@ import Foundation import PassepartoutCore -import PassepartoutUtils + +extension Profile { + public var isProvider: Bool { + provider != nil + } + + public var vpnProtocols: [VPNProtocolType] { + if isProvider { + return provider?.vpnProtocols ?? [] + } else { + return host?.vpnProtocols ?? [] + } + } + + public var account: Profile.Account { + get { + if isProvider { + return providerAccount ?? .init() + } else { + return hostAccount ?? .init() + } + } + set { + if isProvider { + providerAccount = newValue + } else { + hostAccount = newValue + } + } + } +} extension Profile.Header { public func withNewId() -> Self { @@ -78,3 +108,13 @@ extension Profile { return profile } } + +extension Profile { + public var requiresCredentials: Bool { + if let providerName = providerName { + return providerName.requiresCredentials(forProtocol: currentVPNProtocol) + } else { + return currentVPNProtocol == .openVPN && (hostOpenVPNSettings?.configuration.authUserPass ?? false) + } + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Extensions/Provider+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Provider+Extensions.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutCore/Extensions/Provider+Extensions.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Provider+Extensions.swift index ff7f5768..9b427c26 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Extensions/Provider+Extensions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/Provider+Extensions.swift @@ -24,6 +24,8 @@ // import Foundation +import PassepartoutCore +import PassepartoutProviders import TunnelKitCore extension Profile { diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Models/ObservableProfile.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ObservableProfile.swift similarity index 93% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Models/ObservableProfile.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ObservableProfile.swift index bf3c36f3..8ce9ec89 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Models/ObservableProfile.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ObservableProfile.swift @@ -25,9 +25,8 @@ import Foundation import PassepartoutCore -import PassepartoutUtils -public class ObservableProfile: ValueHolder, ObservableObject { +public final class ObservableProfile: ValueHolder, ObservableObject { @Published public internal(set) var isLoading = false @Published public var value: Profile diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Models/ObservableVPNState.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ObservableVPNState.swift similarity index 64% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Models/ObservableVPNState.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ObservableVPNState.swift index d5be8b59..2b24ba8c 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Models/ObservableVPNState.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ObservableVPNState.swift @@ -24,11 +24,11 @@ // import Foundation -import PassepartoutUtils +import PassepartoutCore import TunnelKitCore import TunnelKitManager -public class ObservableVPNState: ObservableObject { +public final class ObservableVPNState: ObservableObject, VPNState { @Published public internal(set) var isEnabled = false { didSet { pp_log.debug("VPN enabled -> \(isEnabled)") @@ -62,3 +62,47 @@ public class ObservableVPNState: ObservableObject { public init() { } } + +public final class MutableObservableVPNState: VPNState { + private let observable: ObservableVPNState + + public var isEnabled: Bool { + get { + observable.isEnabled + } + set { + observable.isEnabled = newValue + } + } + + public var vpnStatus: VPNStatus { + get { + observable.vpnStatus + } + set { + observable.vpnStatus = newValue + } + } + + public var lastError: Error? { + get { + observable.lastError + } + set { + observable.lastError = newValue + } + } + + public var dataCount: DataCount? { + get { + observable.dataCount + } + set { + observable.dataCount = newValue + } + } + + init(_ observable: ObservableVPNState) { + self.observable = observable + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/ProfileManager+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ProfileManager+Extensions.swift similarity index 87% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/ProfileManager+Extensions.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ProfileManager+Extensions.swift index d0c9076c..bd8f48e0 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/ProfileManager+Extensions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ProfileManager+Extensions.swift @@ -53,11 +53,6 @@ extension ProfileManager { public func saveProfile(_ profile: Profile, isActive: Bool?) { saveProfile(profile, isActive: isActive, updateIfCurrent: true) } - - public func profile(withHeader header: Profile.Header, fromURL url: URL, passphrase: String?) throws -> Profile { - let contents = try String(contentsOf: url) - return try profile(withHeader: header, fromContents: contents, originalURL: url, passphrase: passphrase) - } } extension ProfileManager { diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ProfileManager.swift similarity index 82% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ProfileManager.swift index 6f619a63..95a44825 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/ProfileManager.swift @@ -27,26 +27,28 @@ import Combine import Foundation import PassepartoutCore import PassepartoutProviders -import PassepartoutUtils -import TunnelKitManager @MainActor public final class ProfileManager: ObservableObject { public typealias ProfileEx = (profile: Profile, isReady: Bool) + public typealias KeychainEntry = (Profile) -> String + + public typealias KeychainLabel = (Profile) -> String + // MARK: Initialization private let store: KeyValueStore private let providerManager: ProviderManager - let appGroup: String + private let profileRepository: ProfileRepository - let keychainLabel: (String, VPNProtocolType) -> String + private let keychain: SecretRepository - let keychain: Keychain + private let keychainEntry: (Profile) -> String - private let strategy: ProfileManagerStrategy + private let keychainLabel: (Profile) -> String // MARK: State @@ -92,19 +94,17 @@ public final class ProfileManager: ObservableObject { public init( store: KeyValueStore, providerManager: ProviderManager, - appGroup: String, - keychainLabel: @escaping (String, VPNProtocolType) -> String, - strategy: ProfileManagerStrategy + profileRepository: ProfileRepository, + keychain: SecretRepository, + keychainEntry: @escaping KeychainEntry, + keychainLabel: @escaping KeychainLabel ) { - guard UserDefaults(suiteName: appGroup) != nil else { - fatalError("No entitlements for group '\(appGroup)'") - } self.store = store self.providerManager = providerManager - self.appGroup = appGroup + self.profileRepository = profileRepository + self.keychain = keychain + self.keychainEntry = keychainEntry self.keychainLabel = keychainLabel - keychain = Keychain(group: appGroup) - self.strategy = strategy currentProfile = ObservableProfile() } @@ -114,11 +114,11 @@ public final class ProfileManager: ObservableObject { extension ProfileManager { private var allProfiles: [UUID: Profile] { - strategy.allProfiles + profileRepository.allProfiles() } public var profiles: [Profile] { - strategy.profiles() + profileRepository.profiles() } public var headers: [Profile.Header] { @@ -157,7 +157,7 @@ extension ProfileManager { return currentProfile.value } - guard let profile = strategy.profile(withId: id) else { + guard let profile = profileRepository.profile(withId: id) else { assertionFailure("Profile in headers yet not found in persistent store") return nil } @@ -167,7 +167,7 @@ extension ProfileManager { } pp_log.debug("Profile \(profile.logDescription) found") - keychain.debugAllPasswords(matching: id, context: appGroup) + keychain.debugAllPasswords(matching: id) return profile } @@ -179,7 +179,7 @@ extension ProfileManager { } pp_log.info("Writing profile \(profile.logDescription) to persistent store") - strategy.saveProfile(profile) + profileRepository.saveProfilesAndLog([profile]) if let isActive = isActive { if isActive { @@ -206,11 +206,11 @@ extension ProfileManager { pp_log.info("\tDeleting passwords from keychain...") for id in ids { - keychain.removeAllPasswords(matching: id, context: appGroup) + keychain.removeAllPasswords(matching: id) } pp_log.info("\tDeleting from persistent store...") - strategy.removeProfiles(withIds: ids) + profileRepository.removeProfiles(withIds: ids) } @available(*, deprecated, message: "only use for testing") @@ -235,7 +235,7 @@ extension ProfileManager { // autosaves copy if non-existing in persistent store setCurrentProfile(copy) } else { - strategy.saveProfile(copy) + profileRepository.saveProfilesAndLog([copy]) } } @@ -247,6 +247,53 @@ extension ProfileManager { } } +// MARK: Keychain + +extension ProfileManager { + public func savePassword(forProfile profile: Profile, newPassword: String? = nil) { + guard !profile.isPlaceholder else { + assertionFailure("Placeholder") + return + } + let entry = keychainEntry(profile) + let password = newPassword ?? profile.account.password + guard !password.isEmpty else { + keychain.removePassword( + for: entry, + userDefined: profile.id.uuidString + ) + return + } + do { + try keychain.set( + password: password, + for: entry, + userDefined: profile.id.uuidString, + label: keychainLabel(profile) + ) + } catch { + pp_log.error("Unable to save password to keychain: \(error)") + } + } + + public func passwordReference(forProfile profile: Profile) -> Data? { + guard !profile.isPlaceholder else { + assertionFailure("Placeholder") + return nil + } + let entry = keychainEntry(profile) + do { + return try keychain.passwordReference( + for: entry, + userDefined: profile.id.uuidString + ) + } catch { + pp_log.debug("Unable to load password reference from keychain: \(error)") + return nil + } + } +} + // MARK: Observation extension ProfileManager { @@ -281,7 +328,7 @@ extension ProfileManager { } defer { if !profilesToSave.isEmpty { - strategy.saveProfiles(profilesToSave) + profileRepository.saveProfilesAndLog(profilesToSave) } } @@ -305,7 +352,7 @@ extension ProfileManager { self.didUpdateActiveProfile.send($0) }.store(in: &cancellables) - strategy.willUpdateProfiles() + profileRepository.willUpdateProfiles() .dropFirst() .sink { self.willUpdateProfiles($0) @@ -324,7 +371,7 @@ extension ProfileManager { currentProfile.value = .placeholder } - let newProfile = strategy.profile(withId: currentProfile.value.id) + let newProfile = profileRepository.profile(withId: currentProfile.value.id) if let newProfile = newProfile, newProfile != currentProfile.value { pp_log.info("Current profile remotely updated") currentProfile.value = newProfile @@ -385,12 +432,22 @@ extension ProfileManager { } } if !renamedProfiles.isEmpty { - strategy.saveProfiles(renamedProfiles) + profileRepository.saveProfilesAndLog(renamedProfiles) pp_log.debug("Duplicates successfully renamed!") } } } +private extension ProfileRepository { + func saveProfilesAndLog(_ profiles: [Profile]) { + do { + try saveProfiles(profiles) + } catch { + pp_log.error("Unable to save profile(s): \(error)") + } + } +} + // MARK: Readiness extension ProfileManager { diff --git a/PassepartoutLibrary/Sources/PassepartoutLibrary/Managers/UpgradeManager.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/UpgradeManager.swift similarity index 87% rename from PassepartoutLibrary/Sources/PassepartoutLibrary/Managers/UpgradeManager.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Managers/UpgradeManager.swift index 0428060d..da2cf643 100644 --- a/PassepartoutLibrary/Sources/PassepartoutLibrary/Managers/UpgradeManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/UpgradeManager.swift @@ -23,11 +23,8 @@ // along with Passepartout. If not, see . // -import CoreData import Foundation import PassepartoutCore -import PassepartoutUtils -import SwiftyBeaver @MainActor public final class UpgradeManager: ObservableObject { @@ -36,21 +33,27 @@ public final class UpgradeManager: ObservableObject { private let store: KeyValueStore + private let strategy: UpgradeStrategy + // MARK: State @Published public private(set) var isDoingMigrations = true - public init(store: KeyValueStore) { + public init( + store: KeyValueStore, + strategy: UpgradeStrategy + ) { self.store = store + self.strategy = strategy } public func doMigrations(_ profileManager: ProfileManager) { - doMigrateStore(store) + strategy.doMigrateStore(store, didMigrate: &didMigrateToV2) -// profileManager.removeAllProfiles() +// profileManager.removeAllProfiles() // testing only guard didMigrateToV2 else { isDoingMigrations = true - let migrated = doMigrateToV2() + let migrated = strategy.migratedProfilesToV2() if !migrated.isEmpty { pp_log.info("Migrating \(migrated.count) profiles") migrated.forEach { diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Configuration.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Configuration.swift deleted file mode 100644 index 71f51c72..00000000 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Configuration.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// VPNManager+Configuration.swift -// Passepartout -// -// Created by Davide De Rosa on 3/12/22. -// Copyright (c) 2023 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -import Foundation -import PassepartoutCore -import PassepartoutUtils - -extension VPNManager { - var vpnPreferences: VPNPreferences { - DefaultVPNPreferences( - tunnelLogPath: tunnelLogPath, - tunnelLogFormat: tunnelLogFormat, - masksPrivateData: masksPrivateData - ) - } - - func vpnConfigurationWithCurrentProfile() -> VPNConfiguration? { - do { - guard profileManager.isCurrentProfileActive() else { - pp_log.info("Skipping VPN configuration, current profile is not active") - return nil - } - return try vpnConfiguration(withProfile: profileManager.currentProfile.value) - } catch { - return nil - } - } - - func vpnConfiguration(withProfile profile: Profile) throws -> VPNConfiguration { - do { - if profile.requiresCredentials { - guard !profile.account.isEmpty else { - throw PassepartoutError.missingAccount - } - } - - // specific provider customizations - var newPassword: String? - if let providerName = profile.providerName { - switch providerName { - case .mullvad: - newPassword = "m" - - default: - break - } - } - - // IMPORTANT: must commit password to keychain (tunnel needs a password reference) - profileManager.savePassword(forProfile: profile, newPassword: newPassword) - - let parameters = VPNConfigurationParameters( - profile, - appGroup: appGroup, - preferences: vpnPreferences, - passwordReference: profileManager.passwordReference(forProfile: profile), - withNetworkSettings: isNetworkSettingsSupported(), - withCustomRules: isOnDemandRulesSupported() - ) - - switch profile.currentVPNProtocol { - case .openVPN: - let settings: Profile.OpenVPNSettings - if profile.isProvider { - settings = try profile.providerOpenVPNSettings(withManager: providerManager) - } else { - guard let hostSettings = profile.hostOpenVPNSettings else { - fatalError("Profile currentVPNProtocol is OpenVPN, but host has no OpenVPN settings") - } - settings = hostSettings - } - return try settings.vpnConfiguration(parameters) - - case .wireGuard: - let settings: Profile.WireGuardSettings - if profile.isProvider { - settings = try profile.providerWireGuardSettings(withManager: providerManager) - } else { - guard let hostSettings = profile.hostWireGuardSettings else { - fatalError("Profile currentVPNProtocol is WireGuard, but host has no WireGuard settings") - } - settings = hostSettings - } - return try settings.vpnConfiguration(parameters) - } - } catch { - pp_log.error("Unable to build VPNConfiguration: \(error)") - - // UI is certainly interested in configuration errors - configurationError.send((profile, error)) - - throw error - } - } -} diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Actions.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Extensions.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Actions.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Extensions.swift index b961e3d1..4d454b1b 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Actions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager+Extensions.swift @@ -1,5 +1,5 @@ // -// VPNManager+Actions.swift +// VPNManager+Extensions.swift // Passepartout // // Created by Davide De Rosa on 3/30/22. @@ -25,7 +25,6 @@ import Foundation import PassepartoutCore -import PassepartoutUtils // IMPORTANT: if active profile is set/modified and it happens to also be // current profile, this must be updated too. this is done in diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift index cfc808ba..6559e21d 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManager.swift @@ -26,17 +26,13 @@ import Combine import Foundation import PassepartoutCore -import PassepartoutProfiles import PassepartoutProviders -import PassepartoutUtils @MainActor public final class VPNManager: ObservableObject { // MARK: Initialization - let appGroup: String - private let store: KeyValueStore let profileManager: ProfileManager @@ -73,13 +69,11 @@ public final class VPNManager: ObservableObject { private var cancellables: Set = [] public init( - appGroup: String, store: KeyValueStore, profileManager: ProfileManager, providerManager: ProviderManager, strategy: VPNManagerStrategy ) { - self.appGroup = appGroup self.store = store self.profileManager = profileManager self.providerManager = providerManager @@ -145,7 +139,7 @@ extension VPNManager { } private func observeStrategy() { - strategy.observe(into: currentState) + strategy.observe(into: MutableObservableVPNState(currentState)) } private func observeProfileManager() { @@ -250,6 +244,64 @@ extension VPNManager { } } +// MARK: Configuration + +extension VPNManager { + func vpnConfigurationWithCurrentProfile() -> VPNConfiguration? { + do { + guard profileManager.isCurrentProfileActive() else { + pp_log.info("Skipping VPN configuration, current profile is not active") + return nil + } + return try vpnConfiguration(withProfile: profileManager.currentProfile.value) + } catch { + return nil + } + } + + func vpnConfiguration(withProfile profile: Profile) throws -> VPNConfiguration { + do { + if profile.requiresCredentials { + guard !profile.account.isEmpty else { + throw PassepartoutError.missingAccount + } + } + + // specific provider customizations + var newPassword: String? + if let providerName = profile.providerName { + switch providerName { + case .mullvad: + newPassword = "m" + + default: + break + } + } + + // IMPORTANT: must commit password to keychain (tunnel needs a password reference) + profileManager.savePassword(forProfile: profile, newPassword: newPassword) + + let parameters = VPNConfigurationParameters( + profile, + preferences: vpnPreferences, + passwordReference: profileManager.passwordReference(forProfile: profile), + withNetworkSettings: isNetworkSettingsSupported(), + withCustomRules: isOnDemandRulesSupported() + ) + + return try strategy.vpnConfiguration(parameters, providerManager: providerManager) + } catch { + pp_log.error("Unable to build VPNConfiguration: \(error)") + + // UI is certainly interested in configuration errors + configurationError.send((profile, error)) + + throw error + } + } +} + // MARK: KeyValueStore extension VPNManager { @@ -282,6 +334,14 @@ extension VPNManager { didUpdatePreferences.send(vpnPreferences) } } + + private var vpnPreferences: VPNPreferences { + .init( + tunnelLogPath: tunnelLogPath, + tunnelLogFormat: tunnelLogFormat, + masksPrivateData: masksPrivateData + ) + } } private extension VPNManager { diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/ProfileRepository.swift similarity index 73% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/ProfileRepository.swift index d7cff1c2..f1da941f 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/ProfileRepository.swift @@ -1,8 +1,8 @@ // -// ProfileManagerStrategy.swift +// ProfileRepository.swift // Passepartout // -// Created by Davide De Rosa on 4/9/22. +// Created by Davide De Rosa on 3/19/22. // Copyright (c) 2023 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -27,22 +27,16 @@ import Combine import Foundation import PassepartoutCore -public protocol ProfileManagerStrategy { - var allProfiles: [UUID: Profile] { get } +public protocol ProfileRepository { + func allProfiles() -> [UUID: Profile] func profiles() -> [Profile] - func profile(withId: UUID) -> Profile? + func profile(withId id: UUID) -> Profile? - func saveProfiles(_ profiles: [Profile]) + func saveProfiles(_ profiles: [Profile]) throws func removeProfiles(withIds ids: [UUID]) func willUpdateProfiles() -> AnyPublisher<[UUID: Profile], Never> } - -extension ProfileManagerStrategy { - public func saveProfile(_ profile: Profile) { - saveProfiles([profile]) - } -} diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/SecretRepository.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/SecretRepository.swift new file mode 100644 index 00000000..9d38a002 --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/SecretRepository.swift @@ -0,0 +1,43 @@ +// +// SecretRepository.swift +// Passepartout +// +// Created by Davide De Rosa on 5/23/23. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation + +public protocol SecretRepository { + func set( + password: String, + for entry: String, + userDefined: String, + label: String + ) throws + + func removePassword(for entry: String, userDefined: String) + + func passwordReference(for entry: String, userDefined: String) throws -> Data + + func removeAllPasswords(matching id: UUID) + + func debugAllPasswords(matching id: UUID) +} diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutError+Profiles.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/UpgradeStrategy.swift similarity index 79% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutError+Profiles.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/UpgradeStrategy.swift index 37f4a882..4e523469 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutError+Profiles.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/UpgradeStrategy.swift @@ -1,8 +1,8 @@ // -// PassepartoutError+Profiles.swift +// UpgradeStrategy.swift // Passepartout // -// Created by Davide De Rosa on 6/21/22. +// Created by Davide De Rosa on 3/20/22. // Copyright (c) 2023 Davide De Rosa. All rights reserved. // // https://github.com/passepartoutvpn @@ -26,6 +26,8 @@ import Foundation import PassepartoutCore -extension PassepartoutError { - public static let missingProfile = Self("missingProfile") +public protocol UpgradeStrategy { + func doMigrateStore(_ store: KeyValueStore, didMigrate: inout Bool) + + func migratedProfilesToV2() -> [Profile] } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManagerStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/VPNManagerStrategy.swift similarity index 85% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManagerStrategy.swift rename to PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/VPNManagerStrategy.swift index 75d1b906..150c8898 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/VPNManagerStrategy.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPN/Strategies/VPNManagerStrategy.swift @@ -26,9 +26,10 @@ import Combine import Foundation import PassepartoutCore +import PassepartoutProviders public protocol VPNManagerStrategy { - func observe(into state: ObservableVPNState) + func observe(into state: MutableObservableVPNState) func reinstate(configuration: VPNConfiguration) async @@ -43,4 +44,9 @@ public protocol VPNManagerStrategy { func serverConfiguration(forProtocol vpnProtocol: VPNProtocolType) -> Any? func debugLogURL(forProtocol vpnProtocol: VPNProtocolType) -> URL? + + func vpnConfiguration( + _ parameters: VPNConfigurationParameters, + providerManager: ProviderManager + ) throws -> VPNConfiguration } diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataClass.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Data/CDProfile+CoreDataClass.swift similarity index 83% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataClass.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Data/CDProfile+CoreDataClass.swift index 04a69871..b6ad3e91 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataClass.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Data/CDProfile+CoreDataClass.swift @@ -11,6 +11,6 @@ import CoreData import Foundation @objc(CDProfile) -public class CDProfile: NSManagedObject { +class CDProfile: NSManagedObject { } diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataProperties.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Data/CDProfile+CoreDataProperties.swift similarity index 54% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataProperties.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Data/CDProfile+CoreDataProperties.swift index b4f2950c..c6f82f5a 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/DataModels/CDProfile+CoreDataProperties.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Data/CDProfile+CoreDataProperties.swift @@ -12,15 +12,15 @@ import Foundation extension CDProfile { - @nonobjc public class func fetchRequest() -> NSFetchRequest { + @nonobjc class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "CDProfile") } - @NSManaged public var json: Data? - @NSManaged public var name: String? - @NSManaged public var providerName: String? - @NSManaged public var uuid: UUID? - @NSManaged public var lastUpdate: Date? + @NSManaged var json: Data? + @NSManaged var name: String? + @NSManaged var providerName: String? + @NSManaged var uuid: UUID? + @NSManaged var lastUpdate: Date? } diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutDataModels+Profiles.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Data/Persistence.swift similarity index 64% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutDataModels+Profiles.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Data/Persistence.swift index b0f684ab..132876fa 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutDataModels+Profiles.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Data/Persistence.swift @@ -1,5 +1,5 @@ // -// PassepartoutDataModels+Profiles.swift +// Persistence.swift // Passepartout // // Created by Davide De Rosa on 4/7/22. @@ -26,12 +26,27 @@ import CoreData import Foundation import PassepartoutCore +import PassepartoutVPN -extension PassepartoutDataModels { - public static let profiles: NSManagedObjectModel = { +extension PassepartoutPersistence { + private static let profilesDataModel: NSManagedObjectModel = { guard let model = NSManagedObjectModel.mergedModel(from: [.module]) else { fatalError("Could not load PassepartoutProfiles model") } return model }() + + public static func profilesStore(withName containerName: String, cloudKit: Bool, author: String?) -> CoreDataPersistentStore { + .init( + withName: containerName, + model: profilesDataModel, + cloudKit: cloudKit, + author: author + ) + } +} +extension PassepartoutPersistence { + public static func profileRepository(_ store: CoreDataPersistentStore) -> ProfileRepository { + CDProfileRepository(store.context) + } } diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/DataModels/Profiles.xcdatamodeld/Model.xcdatamodel/contents b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Data/Profiles.xcdatamodeld/Model.xcdatamodel/contents similarity index 100% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/DataModels/Profiles.xcdatamodeld/Model.xcdatamodel/contents rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Data/Profiles.xcdatamodeld/Model.xcdatamodel/contents diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/OnDemand+Rules.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OnDemand+Rules.swift similarity index 91% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/OnDemand+Rules.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OnDemand+Rules.swift index 64a228a6..da4ecfbc 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/OnDemand+Rules.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OnDemand+Rules.swift @@ -26,7 +26,7 @@ import Foundation import NetworkExtension import PassepartoutCore -import PassepartoutUtils +import PassepartoutVPN extension NEOnDemandRuleInterfaceType { static var compatibleEthernet: NEOnDemandRuleInterfaceType? { @@ -42,9 +42,9 @@ extension NEOnDemandRuleInterfaceType { } } -extension Profile { - func onDemandRules(withCustomRules: Bool) -> [NEOnDemandRule] { - onDemand.rules(isInteractive: account.authenticationMethod == .interactive, withCustomRules: withCustomRules) +extension VPNConfigurationParameters { + var onDemandRules: [NEOnDemandRule] { + profile.onDemand.rules(isInteractive: profile.account.authenticationMethod == .interactive, withCustomRules: withCustomRules) } } @@ -91,7 +91,7 @@ private extension Profile.OnDemand { return rules } - private var policyRule: NEOnDemandRule { + var policyRule: NEOnDemandRule { disconnectsIfNotMatching ? NEOnDemandRuleDisconnect() : NEOnDemandRuleIgnore() } } diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/OpenVPNSettings+Network.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+Network.swift similarity index 99% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/OpenVPNSettings+Network.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+Network.swift index f6ca96de..16c01876 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/OpenVPNSettings+Network.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+Network.swift @@ -25,6 +25,7 @@ import Foundation import PassepartoutCore +import PassepartoutVPN import TunnelKitCore import TunnelKitOpenVPN diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/OpenVPNSettings+VPNConfiguration.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift similarity index 94% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/OpenVPNSettings+VPNConfiguration.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift index 6cfa7f6b..59bc4923 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/OpenVPNSettings+VPNConfiguration.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/OpenVPNSettings+TunnelKit.swift @@ -1,5 +1,5 @@ // -// OpenVPNSettings+VPNConfiguration.swift +// OpenVPNSettings+TunnelKit.swift // Passepartout // // Created by Davide De Rosa on 4/7/22. @@ -25,12 +25,12 @@ import Foundation import PassepartoutCore -import PassepartoutUtils +import PassepartoutVPN import TunnelKitManager import TunnelKitOpenVPN -extension Profile.OpenVPNSettings: VPNConfigurationProviding { - func vpnConfiguration(_ parameters: VPNConfigurationParameters) throws -> VPNConfiguration { +extension Profile.OpenVPNSettings: TunnelKitConfigurationProviding { + func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> VPNConfiguration { var customBuilder = configuration.builder() // tolerate widest range of certificates @@ -53,7 +53,7 @@ extension Profile.OpenVPNSettings: VPNConfigurationProviding { var cfg = OpenVPN.ProviderConfiguration( parameters.title, - appGroup: parameters.appGroup, + appGroup: appGroup, configuration: customConfiguration ) cfg.username = parameters.username diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProviders+TunnelKit.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/PassepartoutProviders+TunnelKit.swift similarity index 99% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProviders+TunnelKit.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/PassepartoutProviders+TunnelKit.swift index 9012fe27..3a81fcc6 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProviders+TunnelKit.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/PassepartoutProviders+TunnelKit.swift @@ -26,7 +26,7 @@ import Combine import Foundation import PassepartoutCore -import PassepartoutUtils +import PassepartoutProviders import TunnelKitOpenVPN import TunnelKitWireGuard diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+StrippableContent.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/PassepartoutVPN+StrippableContent.swift similarity index 96% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+StrippableContent.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/PassepartoutVPN+StrippableContent.swift index 9f9e2b3d..da3cce9c 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+StrippableContent.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/PassepartoutVPN+StrippableContent.swift @@ -1,5 +1,5 @@ // -// PassepartoutProfiles+StrippableContent.swift +// PassepartoutVPN+StrippableContent.swift // Passepartout // // Created by Davide De Rosa on 4/7/22. @@ -25,7 +25,7 @@ import Foundation import PassepartoutCore -import PassepartoutUtils +import PassepartoutVPN extension Profile.Account: StrippableContent { public var stripped: Self { diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+Subtype.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/Profile+ProviderManager.swift similarity index 89% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+Subtype.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/Profile+ProviderManager.swift index 620e3063..9e349ba2 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/PassepartoutProfiles+Subtype.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/Profile+ProviderManager.swift @@ -1,5 +1,5 @@ // -// PassepartoutProfiles+Subtype.swift +// Profile+ProviderManager.swift // Passepartout // // Created by Davide De Rosa on 6/20/22. @@ -26,16 +26,7 @@ import Foundation import PassepartoutCore import PassepartoutProviders - -extension Profile { - public var requiresCredentials: Bool { - if let providerName = providerName { - return providerName.requiresCredentials(forProtocol: currentVPNProtocol) - } else { - return currentVPNProtocol == .openVPN && (hostOpenVPNSettings?.configuration.authUserPass ?? false) - } - } -} +import PassepartoutVPN extension Profile { public func providerServer(_ providerManager: ProviderManager) -> ProviderServer? { diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager+Processing.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProfileManager+TunnelKit.swift similarity index 82% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager+Processing.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProfileManager+TunnelKit.swift index f651db0b..cfe30b1c 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager+Processing.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProfileManager+TunnelKit.swift @@ -1,5 +1,5 @@ // -// ProfileManager+Processing.swift +// ProfileManager+TunnelKit.swift // Passepartout // // Created by Davide De Rosa on 4/7/22. @@ -24,11 +24,16 @@ // import Foundation -import PassepartoutCore +import PassepartoutVPN import TunnelKitOpenVPN import TunnelKitWireGuard extension ProfileManager { + public func profile(withHeader header: Profile.Header, fromURL url: URL, passphrase: String?) throws -> Profile { + let contents = try String(contentsOf: url) + return try profile(withHeader: header, fromContents: contents, originalURL: url, passphrase: passphrase) + } + public func profile(withHeader header: Profile.Header, fromContents contents: String, originalURL: URL?, passphrase: String?) throws -> Profile { do { let ovpn = try OpenVPN.ConfigurationParser.parsed(fromContents: contents, passphrase: passphrase, originalURL: originalURL) diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/ProviderManager+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProviderManager+Extensions.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/ProviderManager+Extensions.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProviderManager+Extensions.swift index a41fe728..01cfd986 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/ProviderManager+Extensions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/ProviderManager+Extensions.swift @@ -27,6 +27,7 @@ import Combine import Foundation import PassepartoutCore import PassepartoutProviders +import PassepartoutVPN extension ProviderManager { public func fetchRemoteProviderPublisher(forProfile profile: Profile) -> AnyPublisher { diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/VPNProtocolType+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/VPNProtocolType+Extensions.swift similarity index 82% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/VPNProtocolType+Extensions.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/VPNProtocolType+Extensions.swift index d25bfd97..5b1568d4 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/VPNProtocolType+Extensions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/VPNProtocolType+Extensions.swift @@ -28,6 +28,18 @@ import PassepartoutCore import TunnelKitOpenVPN import TunnelKitWireGuard +extension VPNProtocolType: CustomStringConvertible { + public var description: String { + switch self { + case .openVPN: + return "OpenVPN" + + case .wireGuard: + return "WireGuard" + } + } +} + extension VPNProtocolType: Comparable { public static func < (lhs: Self, rhs: Self) -> Bool { lhs.description < rhs.description @@ -67,7 +79,13 @@ extension VPNProtocolType { extension VPNProtocolProviding { func vpnPath(with path: String) -> String { var components = path.split(separator: "/").map(String.init) - components.insert(vpnProtocol.description, at: components.count - 1) + components.insert(vpnProtocol.pathComponent, at: components.count - 1) return components.joined(separator: "/") } } + +private extension VPNProtocolType { + var pathComponent: String { + description + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/WireGuardSettings+Network.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+Network.swift similarity index 98% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/WireGuardSettings+Network.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+Network.swift index af5abf1b..831cab56 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/WireGuardSettings+Network.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+Network.swift @@ -25,6 +25,7 @@ import Foundation import PassepartoutCore +import PassepartoutVPN import TunnelKitCore import TunnelKitWireGuard diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/WireGuardSettings+VPNConfiguration.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift similarity index 93% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/WireGuardSettings+VPNConfiguration.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift index 1928e4cb..4cfe3115 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Extensions/WireGuardSettings+VPNConfiguration.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Extensions/WireGuardSettings+TunnelKit.swift @@ -1,5 +1,5 @@ // -// WireGuardSettings+VPNConfiguration.swift +// WireGuardSettings+TunnelKit.swift // Passepartout // // Created by Davide De Rosa on 4/7/22. @@ -25,12 +25,12 @@ import Foundation import PassepartoutCore -import PassepartoutUtils +import PassepartoutVPN import TunnelKitManager import TunnelKitWireGuard -extension Profile.WireGuardSettings: VPNConfigurationProviding { - func vpnConfiguration(_ parameters: VPNConfigurationParameters) throws -> VPNConfiguration { +extension Profile.WireGuardSettings: TunnelKitConfigurationProviding { + func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> VPNConfiguration { var customBuilder = configuration.builder() // network settings @@ -44,7 +44,7 @@ extension Profile.WireGuardSettings: VPNConfigurationProviding { var cfg = WireGuard.ProviderConfiguration( parameters.title, - appGroup: parameters.appGroup, + appGroup: appGroup, configuration: customConfiguration ) cfg.shouldDebug = true diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Repositories/ProfileRepository.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/CDProfileRepository.swift similarity index 85% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Repositories/ProfileRepository.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/CDProfileRepository.swift index 4a722090..ce457aec 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Repositories/ProfileRepository.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/CDProfileRepository.swift @@ -1,5 +1,5 @@ // -// ProfileRepository.swift +// CDProfileRepository.swift // Passepartout // // Created by Davide De Rosa on 3/19/22. @@ -23,42 +23,25 @@ // along with Passepartout. If not, see . // +import Combine import CoreData import Foundation import PassepartoutCore -import PassepartoutUtils +import PassepartoutProviders +import PassepartoutVPN -class ProfileRepository: Repository { +final class CDProfileRepository: ProfileRepository { private let context: NSManagedObjectContext - required init(_ context: NSManagedObjectContext) { + private let observableProfiles: FetchedValueHolder<[UUID: Profile]> + + init(_ context: NSManagedObjectContext) { self.context = context + observableProfiles = Self.fetchedProfiles(context: context) } - func fetchedProfiles() -> FetchedValueHolder<[UUID: Profile]> { - let request: NSFetchRequest = CDProfile.fetchRequest() - request.sortDescriptors = [ - .init(keyPath: \CDProfile.lastUpdate, ascending: true) - ] - request.propertiesToFetch = [ - "json" - ] - return .init( - context: context, - request: request, - mapping: { - $0.reduce(into: [UUID: Profile]()) { - guard let dto = $1 as? CDProfile else { - return - } - guard let profile = try? ProfileMapper.toModel(dto) else { - return - } - $0[profile.id] = profile - } - }, - initial: [:] - ) + func allProfiles() -> [UUID: Profile] { + observableProfiles.value } func profiles() -> [Profile] { @@ -140,4 +123,37 @@ class ProfileRepository: Repository { context.rollback() } } + + func willUpdateProfiles() -> AnyPublisher<[UUID: Profile], Never> { + observableProfiles.$value + .eraseToAnyPublisher() + } +} + +private extension CDProfileRepository { + static func fetchedProfiles(context: NSManagedObjectContext) -> FetchedValueHolder<[UUID: Profile]> { + let request: NSFetchRequest = CDProfile.fetchRequest() + request.sortDescriptors = [ + .init(keyPath: \CDProfile.lastUpdate, ascending: true) + ] + request.propertiesToFetch = [ + "json" + ] + return .init( + context: context, + request: request, + mapping: { + $0.reduce(into: [UUID: Profile]()) { + guard let dto = $1 as? CDProfile else { + return + } + guard let profile = try? ProfileMapper.toModel(dto) else { + return + } + $0[profile.id] = profile + } + }, + initial: [:] + ) + } } diff --git a/PassepartoutLibrary/Sources/PassepartoutLibrary/Managers/UpgradeManager+Migrations.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/DefaultUpgradeStrategy.swift similarity index 95% rename from PassepartoutLibrary/Sources/PassepartoutLibrary/Managers/UpgradeManager+Migrations.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/DefaultUpgradeStrategy.swift index d9ca38e2..f5281fc2 100644 --- a/PassepartoutLibrary/Sources/PassepartoutLibrary/Managers/UpgradeManager+Migrations.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/DefaultUpgradeStrategy.swift @@ -1,5 +1,5 @@ // -// UpgradeManager+Migrations.swift +// DefaultUpgradeStrategy.swift // Passepartout // // Created by Davide De Rosa on 3/20/22. @@ -26,17 +26,23 @@ import Foundation import GenericJSON import PassepartoutCore -import PassepartoutUtils +import PassepartoutProviders +import PassepartoutVPN import TunnelKitCore import TunnelKitManager import TunnelKitOpenVPNCore private typealias Map = [String: Any] +public final class DefaultUpgradeStrategy: UpgradeStrategy { + public init() { + } +} + // MARK: Migrate old store -extension UpgradeManager { - fileprivate enum LegacyStoreKey: String, KeyStoreLocation, CaseIterable { +extension DefaultUpgradeStrategy { + private enum LegacyStoreKey: String, KeyStoreLocation, CaseIterable { case activeProfileId case launchesOnLogin @@ -72,12 +78,12 @@ extension UpgradeManager { } } - func doMigrateStore(_ store: KeyValueStore) { - if !didMigrateToV2 { + public func doMigrateStore(_ store: KeyValueStore, didMigrate: inout Bool) { + if !didMigrate { guard let legacyDidMigrateToV2: Bool = store.value(forLocation: LegacyStoreKey.didMigrateToV2) else { return } - didMigrateToV2 = legacyDidMigrateToV2 + didMigrate = legacyDidMigrateToV2 } LegacyStoreKey.allCases.forEach { @@ -88,7 +94,7 @@ extension UpgradeManager { // MARK: Migrate to version 2 -extension UpgradeManager { +extension DefaultUpgradeStrategy { fileprivate enum MigrationError: Error { case json @@ -107,7 +113,7 @@ extension UpgradeManager { "group.com.algoritmico.Passepartout" } - func doMigrateToV2() -> [Profile] { + public func migratedProfilesToV2() -> [Profile] { var migrated: [Profile] = [] pp_log.info("Migrating data to v2") @@ -376,7 +382,7 @@ private extension URL { func asJSON() throws -> Map { let data = try Data(contentsOf: self) guard let json = try JSONSerialization.jsonObject(with: data) as? Map else { - throw UpgradeManager.MigrationError.json + throw DefaultUpgradeStrategy.MigrationError.json } return json } diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/KeychainSecretRepository.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/KeychainSecretRepository.swift new file mode 100644 index 00000000..36c60670 --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/KeychainSecretRepository.swift @@ -0,0 +1,87 @@ +// +// KeychainSecretRepository.swift +// Passepartout +// +// Created by Davide De Rosa on 5/23/23. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore +import PassepartoutVPN +import TunnelKitManager + +public final class KeychainSecretRepository: SecretRepository { + private let appGroup: String + + private let keychain: Keychain + + public init(appGroup: String) { + guard UserDefaults(suiteName: appGroup) != nil else { + fatalError("No entitlements for group '\(appGroup)'") + } + self.appGroup = appGroup + keychain = Keychain(group: appGroup) + } + + public func set(password: String, for entry: String, userDefined: String, label: String) throws { + try keychain.set(password: password, for: entry, context: appGroup, userDefined: userDefined, label: label) + } + + public func removePassword(for entry: String, userDefined: String) { + keychain.removePassword(for: entry, context: appGroup, userDefined: userDefined) + } + + public func passwordReference(for entry: String, userDefined: String) throws -> Data { + try keychain.passwordReference(for: entry, context: appGroup, userDefined: userDefined) + } +} + +extension KeychainSecretRepository { + public func debugAllPasswords(matching id: UUID) { + var query = allPasswordsQuery(id, appGroup) + query[kSecReturnAttributes as String] = true + + var list: CFTypeRef? + switch SecItemCopyMatching(query as CFDictionary, &list) { + case errSecSuccess: + break + + default: + return + } + guard let list = list else { + pp_log.debug("Keychain items: none") + return + } + pp_log.debug("Keychain items: \(list)") + } + + public func removeAllPasswords(matching id: UUID) { + _ = SecItemDelete(allPasswordsQuery(id, appGroup) as CFDictionary) + } + + private func allPasswordsQuery(_ id: UUID, _ context: String) -> [String: Any] { + var query = [String: Any]() + keychain.setScope(query: &query, context: context, userDefined: id.uuidString) + query[kSecClass as String] = kSecClassGenericPassword + return query + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Repositories/ProfileMapper.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/ProfileMapper.swift similarity index 99% rename from PassepartoutLibrary/Sources/PassepartoutProfiles/Repositories/ProfileMapper.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/ProfileMapper.swift index 31c85136..53321603 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Repositories/ProfileMapper.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/ProfileMapper.swift @@ -26,7 +26,7 @@ import CoreData import Foundation import PassepartoutCore -import PassepartoutUtils +import PassepartoutVPN struct ProfileMapper: DTOMapper, ModelMapper { private let context: NSManagedObjectContext diff --git a/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/SwiftyBeaverLogger.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/SwiftyBeaverLogger.swift new file mode 100644 index 00000000..e254b6af --- /dev/null +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/SwiftyBeaverLogger.swift @@ -0,0 +1,96 @@ +// +// SwiftyBeaverLogger.swift +// Passepartout +// +// Created by Davide De Rosa on 5/21/23. +// Copyright (c) 2023 Davide De Rosa. All rights reserved. +// +// https://github.com/passepartoutvpn +// +// This file is part of Passepartout. +// +// Passepartout is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Passepartout is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Passepartout. If not, see . +// + +import Foundation +import PassepartoutCore +import SwiftyBeaver + +public final class SwiftyBeaverLogger: Logger { + public let logFile: URL? + + public var logLevel: LoggerLevel { + didSet { + SwiftyBeaver.destinations.forEach { + $0.minLevel = logLevel.toSwiftyBeaver + } + } + } + + public init(logFile: URL?, logLevel: LoggerLevel = .info, logFormat: String? = nil) { + self.logFile = logFile + self.logLevel = logLevel + + let console = ConsoleDestination() + console.minLevel = logLevel.toSwiftyBeaver +// console.useNSLog = true + if let logFormat { + console.format = logFormat + } + SwiftyBeaver.addDestination(console) + + if let logFile { + let file = FileDestination() + file.minLevel = logLevel.toSwiftyBeaver + file.logFileURL = logFile + if let logFormat { + file.format = logFormat + } + _ = file.deleteLogFile() + SwiftyBeaver.addDestination(file) + } + } + + public func verbose(_ message: Any) { + SwiftyBeaver.verbose(message) + } + + public func debug(_ message: Any) { + SwiftyBeaver.debug(message) + } + + public func info(_ message: Any) { + SwiftyBeaver.info(message) + } + + public func warning(_ message: Any) { + SwiftyBeaver.warning(message) + } + + public func error(_ message: Any) { + SwiftyBeaver.error(message) + } +} + +private extension LoggerLevel { + var toSwiftyBeaver: SwiftyBeaver.Level { + switch self { + case .verbose: return .verbose + case .debug: return .debug + case .info: return .info + case .warning: return .warning + case .error: return .error + } + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/TunnelKitVPNManagerStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/TunnelKitVPNManagerStrategy.swift similarity index 81% rename from PassepartoutLibrary/Sources/PassepartoutVPN/Managers/TunnelKitVPNManagerStrategy.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/TunnelKitVPNManagerStrategy.swift index 7c4c932c..5d7cd82c 100644 --- a/PassepartoutLibrary/Sources/PassepartoutVPN/Managers/TunnelKitVPNManagerStrategy.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/TunnelKitVPNManagerStrategy.swift @@ -27,12 +27,17 @@ import Combine import Foundation import NetworkExtension import PassepartoutCore -import PassepartoutUtils +import PassepartoutProviders +import PassepartoutVPN import TunnelKitCore import TunnelKitManager import TunnelKitOpenVPNCore -public class TunnelKitVPNManagerStrategy: VPNManagerStrategy where VPNType.Configuration == NetworkExtensionConfiguration, VPNType.Extra == NetworkExtensionExtra { +protocol TunnelKitConfigurationProviding { + func tunnelKitConfiguration(_ appGroup: String, parameters: VPNConfigurationParameters) throws -> VPNConfiguration +} + +public final class TunnelKitVPNManagerStrategy: VPNManagerStrategy where VPNType.Configuration == NetworkExtensionConfiguration, VPNType.Extra == NetworkExtensionExtra { private struct AtomicState: Equatable { let isEnabled: Bool @@ -58,7 +63,7 @@ public class TunnelKitVPNManagerStrategy: VPNManagerStrategy where // MARK: State - private var currentState: ObservableVPNState? + private var currentState: MutableObservableVPNState? private let vpnState = CurrentValueSubject(.init()) @@ -105,10 +110,12 @@ public class TunnelKitVPNManagerStrategy: VPNManagerStrategy where .sink(receiveValue: perform) .store(in: &cancellables) } +} - // MARK: Strategy +// MARK: Actions - public func observe(into state: ObservableVPNState) { +extension TunnelKitVPNManagerStrategy { + public func observe(into state: MutableObservableVPNState) { currentState = state // use this to drop redundant NE notifications @@ -181,9 +188,11 @@ public class TunnelKitVPNManagerStrategy: VPNManagerStrategy where vpnStatus: vpnState.value.vpnStatus )) } +} - // MARK: Notifications +// MARK: Notifications +extension TunnelKitVPNManagerStrategy { private func onVPNReinstall(_ notification: Notification) { guard isRelevantNotification(notification) else { return @@ -281,9 +290,11 @@ public class TunnelKitVPNManagerStrategy: VPNManagerStrategy where currentState?.dataCount = nil } +} - // MARK: Pulled +// MARK: Pulled +extension TunnelKitVPNManagerStrategy { public func serverConfiguration(forProtocol vpnProtocol: VPNProtocolType) -> Any? { switch vpnProtocol { case .openVPN: @@ -329,3 +340,36 @@ public class TunnelKitVPNManagerStrategy: VPNManagerStrategy where } } } + +// MARK: Configuration + +extension TunnelKitVPNManagerStrategy { + public func vpnConfiguration(_ parameters: VPNConfigurationParameters, providerManager: ProviderManager) throws -> VPNConfiguration { + let profile = parameters.profile + switch profile.currentVPNProtocol { + case .openVPN: + let settings: Profile.OpenVPNSettings + if profile.isProvider { + settings = try profile.providerOpenVPNSettings(withManager: providerManager) + } else { + guard let hostSettings = profile.hostOpenVPNSettings else { + fatalError("Profile currentVPNProtocol is OpenVPN, but host has no OpenVPN settings") + } + settings = hostSettings + } + return try settings.tunnelKitConfiguration(appGroup, parameters: parameters) + + case .wireGuard: + let settings: Profile.WireGuardSettings + if profile.isProvider { + settings = try profile.providerWireGuardSettings(withManager: providerManager) + } else { + guard let hostSettings = profile.hostWireGuardSettings else { + fatalError("Profile currentVPNProtocol is WireGuard, but host has no WireGuard settings") + } + settings = hostSettings + } + return try settings.tunnelKitConfiguration(appGroup, parameters: parameters) + } + } +} diff --git a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/UserDefaultsStore.swift b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/UserDefaultsStore.swift similarity index 62% rename from PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/UserDefaultsStore.swift rename to PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/UserDefaultsStore.swift index 6f6fbfc8..838a09ee 100644 --- a/PassepartoutLibrary/Sources/PassepartoutUtils/Reusable/UserDefaultsStore.swift +++ b/PassepartoutLibrary/Sources/PassepartoutVPNImpl/Strategies/UserDefaultsStore.swift @@ -24,27 +24,31 @@ // import Foundation +import PassepartoutCore public struct UserDefaultsStore: KeyValueStore { private let defaults: UserDefaults - public init(defaults: UserDefaults) { + private let key: (any KeyStoreLocation) -> String + + public init(defaults: UserDefaults, key: @escaping (any KeyStoreLocation) -> String) { self.defaults = defaults + self.key = key } - public func setValue(_ value: V?, forLocation location: L) { + public func setValue(_ value: V?, forLocation location: L) where L: KeyStoreLocation { guard let value = value else { - defaults.removeObject(forKey: location.key) + defaults.removeObject(forKey: key(location)) return } - defaults.setValue(value, forKey: location.key) + defaults.setValue(value, forKey: key(location)) } - public func value(forLocation location: L) -> V? { - defaults.value(forKey: location.key) as? V + public func value(forLocation location: L) -> V? where L: KeyStoreLocation { + defaults.value(forKey: key(location)) as? V } - public func removeValue(forLocation location: L) { - defaults.removeObject(forKey: location.key) + public func removeValue(forLocation location: L) where L: KeyStoreLocation { + defaults.removeObject(forKey: key(location)) } } diff --git a/PassepartoutLibrary/Tests/PassepartoutCoreTests/CoreTests.swift b/PassepartoutLibrary/Tests/PassepartoutCoreTests/CoreTests.swift index 731bf7e2..7c3b7320 100644 --- a/PassepartoutLibrary/Tests/PassepartoutCoreTests/CoreTests.swift +++ b/PassepartoutLibrary/Tests/PassepartoutCoreTests/CoreTests.swift @@ -26,11 +26,44 @@ @testable import PassepartoutCore import XCTest -class CoreTests: XCTestCase { +final class CoreTests: XCTestCase { override func setUp() { } override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testLanguageLocalization() { + let languages = ["en", "it", "de", "pt-BR", "ru"] + let english = Locale(identifier: "en") + let italian = Locale(identifier: "it") + + let languagesEN = privateSortedLanguages(languages, with: english) + let languagesIT = privateSortedLanguages(languages, with: italian) + + // English, German, Italian, Portuguese, Russian + XCTAssertEqual(languagesEN, ["en", "de", "it", "pt-BR", "ru"]) + + // Inglese, Italiano, Portoghese, Russo, Tedesco + XCTAssertEqual(languagesIT, ["en", "it", "pt-BR", "ru", "de"]) + } + + func testTrailing() { + let file = Bundle.module.url(forResource: "Debug", withExtension: "log")! + + for len in [10, 100, 1000] { + let last = file.trailingContent(bytes: UInt64(len)) + XCTAssertEqual(last.count, len) + pp_log.debug(last) + } + XCTAssertNotEqual(file.trailingContent(bytes: 100000).count, 100000) + + pp_log.debug(file.trailingLines(bytes: 1000)) + } + + private func privateSortedLanguages(_ languages: [String], with locale: Locale) -> [String] { + languages.sorted { + locale.localizedString(forLanguageCode: $0)! < locale.localizedString(forLanguageCode: $1)! + } } } diff --git a/PassepartoutLibrary/Tests/PassepartoutUtilsTests/Resources/Debug.log b/PassepartoutLibrary/Tests/PassepartoutCoreTests/Resources/Debug.log similarity index 100% rename from PassepartoutLibrary/Tests/PassepartoutUtilsTests/Resources/Debug.log rename to PassepartoutLibrary/Tests/PassepartoutCoreTests/Resources/Debug.log diff --git a/PassepartoutLibrary/Tests/PassepartoutLibraryTests/LibraryTests.swift b/PassepartoutLibrary/Tests/PassepartoutLibraryTests/LibraryTests.swift deleted file mode 100644 index c09cffb0..00000000 --- a/PassepartoutLibrary/Tests/PassepartoutLibraryTests/LibraryTests.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// LibraryTests.swift -// Passepartout -// -// Created by Davide De Rosa on 10/29/22. -// Copyright (c) 2023 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -@testable import PassepartoutLibrary -import XCTest - -class LibraryTests: XCTestCase { - override func setUp() { - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - } diff --git a/PassepartoutLibrary/Tests/PassepartoutProvidersTests/ProvidersTests.swift b/PassepartoutLibrary/Tests/PassepartoutProvidersTests/ProvidersTests.swift index 2b8e0042..00e949c0 100644 --- a/PassepartoutLibrary/Tests/PassepartoutProvidersTests/ProvidersTests.swift +++ b/PassepartoutLibrary/Tests/PassepartoutProvidersTests/ProvidersTests.swift @@ -26,36 +26,36 @@ import Combine import CoreData import PassepartoutCore -@testable import PassepartoutProviders -import PassepartoutServices -import PassepartoutUtils -import SwiftyBeaver +import PassepartoutProviders +@testable import PassepartoutProvidersImpl import XCTest -class ProvidersTests: XCTestCase { - private static let persistence: Persistence = { - let model = NSManagedObjectModel.mergedModel(from: [.module])! - return Persistence(withLocalName: "ProvidersTests", model: model, author: nil) - }() +final class ProvidersTests: XCTestCase { + private var persistence: CoreDataPersistentStore! private var manager: ProviderManager! private var cancellables: Set = [] override func setUp() { - pp_log.addDestination(ConsoleDestination()) + let model = NSManagedObjectModel.mergedModel(from: [.module])! + persistence = CoreDataPersistentStore(withName: "ProvidersTests", model: model, cloudKit: false, author: nil) - manager = ProviderManager( + let remoteStrategy = APIRemoteProvidersStrategy( appBuild: 10000, - bundleServices: DefaultWebServices.bundledServices(withVersion: "v5"), - webServices: DefaultWebServices("v5", URL(string: "https://passepartoutvpn.app/api/")!, timeout: nil), - persistence: ProvidersTests.persistence + bundleServices: APIWebServices.bundledServices(withVersion: "v5"), + remoteServices: APIWebServices("v5", URL(string: "https://passepartoutvpn.app/api/")!, timeout: nil), + webServicesRepository: PassepartoutPersistence.webServicesRepository(persistence) ) -// manager.reset() + manager = ProviderManager( + localProvidersRepository: PassepartoutPersistence.localProvidersRepository(persistence), + remoteProvidersStrategy: remoteStrategy + ) +// persistence.truncate() } override func tearDown() { -// manager.reset() +// persistence.truncate() } func testFetchLocalIndex() throws { diff --git a/PassepartoutLibrary/Tests/PassepartoutServicesTests/ServicesTests.swift b/PassepartoutLibrary/Tests/PassepartoutProvidersTests/ServicesTests.swift similarity index 87% rename from PassepartoutLibrary/Tests/PassepartoutServicesTests/ServicesTests.swift rename to PassepartoutLibrary/Tests/PassepartoutProvidersTests/ServicesTests.swift index 141e87e6..6ae08dc6 100644 --- a/PassepartoutLibrary/Tests/PassepartoutServicesTests/ServicesTests.swift +++ b/PassepartoutLibrary/Tests/PassepartoutProvidersTests/ServicesTests.swift @@ -24,20 +24,18 @@ // import Combine -@testable import PassepartoutServices -import PassepartoutUtils -import SwiftyBeaver +import PassepartoutCore +@testable import PassepartoutProvidersImpl import XCTest -class ServicesTests: XCTestCase { - let wsLocal = DefaultWebServices.bundledServices(withVersion: "v5") +final class ServicesTests: XCTestCase { + let wsLocal = APIWebServices.bundledServices(withVersion: "v5") - let wsRemote = DefaultWebServices("v5", URL(string: "https://passepartoutvpn.app/api/")!, timeout: nil) + let wsRemote = APIWebServices("v5", URL(string: "https://passepartoutvpn.app/api/")!, timeout: nil) private var cancellables: Set = [] override func setUp() { - SwiftyBeaver.addDestination(ConsoleDestination()) } override func tearDown() { diff --git a/PassepartoutLibrary/Tests/PassepartoutUtilsTests/UtilsTests.swift b/PassepartoutLibrary/Tests/PassepartoutUtilsTests/UtilsTests.swift deleted file mode 100644 index 0f7fa420..00000000 --- a/PassepartoutLibrary/Tests/PassepartoutUtilsTests/UtilsTests.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// UtilsTests.swift -// Passepartout -// -// Created by Davide De Rosa on 3/30/19. -// Copyright (c) 2023 Davide De Rosa. All rights reserved. -// -// https://github.com/passepartoutvpn -// -// This file is part of Passepartout. -// -// Passepartout is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Passepartout is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Passepartout. If not, see . -// - -@testable import PassepartoutUtils -import SwiftyBeaver -import XCTest - -class UtilsTests: XCTestCase { - override func setUp() { - pp_log.addDestination(ConsoleDestination()) - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testLanguageLocalization() { - let languages = ["en", "it", "de", "pt-BR", "ru"] - let english = Locale(identifier: "en") - let italian = Locale(identifier: "it") - - let languagesEN = privateSortedLanguages(languages, with: english) - let languagesIT = privateSortedLanguages(languages, with: italian) - - // English, German, Italian, Portuguese, Russian - XCTAssertEqual(languagesEN, ["en", "de", "it", "pt-BR", "ru"]) - - // Inglese, Italiano, Portoghese, Russo, Tedesco - XCTAssertEqual(languagesIT, ["en", "it", "pt-BR", "ru", "de"]) - } - - func testTrailing() { - let file = Bundle.module.url(forResource: "Debug", withExtension: "log")! - - for len in [10, 100, 1000] { - let last = file.trailingContent(bytes: UInt64(len)) - XCTAssertEqual(last.count, len) - pp_log.debug(last) - } - XCTAssertNotEqual(file.trailingContent(bytes: 100000).count, 100000) - - pp_log.debug(file.trailingLines(bytes: 1000)) - } - - private func privateSortedLanguages(_ languages: [String], with locale: Locale) -> [String] { - languages.sorted { - locale.localizedString(forLanguageCode: $0)! < locale.localizedString(forLanguageCode: $1)! - } - } -} diff --git a/PassepartoutLibrary/Tests/PassepartoutProfilesTests/ProfilesTests.swift b/PassepartoutLibrary/Tests/PassepartoutVPNTests/ProfilesTests.swift similarity index 89% rename from PassepartoutLibrary/Tests/PassepartoutProfilesTests/ProfilesTests.swift rename to PassepartoutLibrary/Tests/PassepartoutVPNTests/ProfilesTests.swift index 4d40b8ae..51dda764 100644 --- a/PassepartoutLibrary/Tests/PassepartoutProfilesTests/ProfilesTests.swift +++ b/PassepartoutLibrary/Tests/PassepartoutVPNTests/ProfilesTests.swift @@ -23,12 +23,10 @@ // along with Passepartout. If not, see . // -import PassepartoutCore -@testable import PassepartoutProfiles -import SwiftyBeaver +@testable import PassepartoutVPN import XCTest -class ProfilesTests: XCTestCase { +final class ProfilesTests: XCTestCase { override func setUp() { } diff --git a/PassepartoutLibrary/Tests/PassepartoutVPNTests/VPNTests.swift b/PassepartoutLibrary/Tests/PassepartoutVPNTests/VPNTests.swift index a13b6094..a4b9b801 100644 --- a/PassepartoutLibrary/Tests/PassepartoutVPNTests/VPNTests.swift +++ b/PassepartoutLibrary/Tests/PassepartoutVPNTests/VPNTests.swift @@ -26,7 +26,7 @@ @testable import PassepartoutVPN import XCTest -class VPNTests: XCTestCase { +final class VPNTests: XCTestCase { override func setUp() { }