From 1cb46e066c3a52a6e2f34015d01a84289bfc2029 Mon Sep 17 00:00:00 2001 From: Davide Date: Tue, 5 Nov 2024 10:41:02 +0100 Subject: [PATCH] Encapsulate behavior on app active (#812) Implement inside AppContext. --- .../xcshareddata/swiftpm/Package.resolved | 2 +- Passepartout/App/PassepartoutApp.swift | 9 +- Passepartout/Library/Package.swift | 2 +- .../Library/Sources/AppUIMain/AppUIMain.swift | 4 +- .../Views/Provider/VPNFiltersView+Model.swift | 32 ++++--- .../Business/ExtendedTunnel.swift | 96 ++++++++++--------- .../UILibrary/Business/AppContext.swift | 12 ++- .../Library/Sources/UILibrary/UILibrary.swift | 1 + .../ExtendedTunnelTests.swift | 2 - 9 files changed, 90 insertions(+), 70 deletions(-) diff --git a/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8c37e601..0711e1d9 100644 --- a/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Passepartout.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,7 +41,7 @@ "kind" : "remoteSourceControl", "location" : "git@github.com:passepartoutvpn/passepartoutkit-source", "state" : { - "revision" : "cd54765853982204f83d721a6c67a26742dc99e3" + "revision" : "2b639620b371181fa56594296918b09acf528058" } }, { diff --git a/Passepartout/App/PassepartoutApp.swift b/Passepartout/App/PassepartoutApp.swift index 0f97d564..0956ad95 100644 --- a/Passepartout/App/PassepartoutApp.swift +++ b/Passepartout/App/PassepartoutApp.swift @@ -73,14 +73,7 @@ extension PassepartoutApp { .onChange(of: scenePhase) { switch $0 { case .active: - Task { - do { - pp_log(.app, .notice, "Prepare tunnel and purge stale data") - try await context.tunnel.prepare(purge: true) - } catch { - pp_log(.app, .fault, "Unable to prepare tunnel: \(error)") - } - } + context.onApplicationActive() default: break diff --git a/Passepartout/Library/Package.swift b/Passepartout/Library/Package.swift index c4ef0ec4..f39bc32b 100644 --- a/Passepartout/Library/Package.swift +++ b/Passepartout/Library/Package.swift @@ -40,7 +40,7 @@ let package = Package( ], dependencies: [ // .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.9.0"), - .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "cd54765853982204f83d721a6c67a26742dc99e3"), + .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "2b639620b371181fa56594296918b09acf528058"), // .package(path: "../../../passepartoutkit-source"), .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.9.1"), // .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"), diff --git a/Passepartout/Library/Sources/AppUIMain/AppUIMain.swift b/Passepartout/Library/Sources/AppUIMain/AppUIMain.swift index f1748e2d..f44498b6 100644 --- a/Passepartout/Library/Sources/AppUIMain/AppUIMain.swift +++ b/Passepartout/Library/Sources/AppUIMain/AppUIMain.swift @@ -38,9 +38,7 @@ public final class AppUIMain: UILibraryConfiguring { // keep this for login item because scenePhase is not triggered if isStartedFromLoginItem { - Task { - try await context.tunnel.prepare(purge: true) - } + context.onApplicationActive() } } } diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Provider/VPNFiltersView+Model.swift b/Passepartout/Library/Sources/AppUIMain/Views/Provider/VPNFiltersView+Model.swift index f718ea3d..eba0f924 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Provider/VPNFiltersView+Model.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Provider/VPNFiltersView+Model.swift @@ -63,17 +63,7 @@ extension VPNFiltersView { presets = [] subscriptions = [] - $filters - .sink { [weak self] in - self?.filtersDidChange.send($0) - } - .store(in: &subscriptions) - - $onlyShowsFavorites - .sink { [weak self] in - self?.onlyShowsFavoritesDidChange.send($0) - } - .store(in: &subscriptions) + observeObjects() } func load(options: VPNFilterOptions, initialFilters: VPNFilters?) { @@ -135,6 +125,26 @@ private extension VPNFiltersView.Model { } } +// MARK: - Observation + +private extension VPNFiltersView.Model { + func observeObjects() { + $filters + .sink { [weak self] in + self?.filtersDidChange.send($0) + } + .store(in: &subscriptions) + + $onlyShowsFavorites + .sink { [weak self] in + self?.onlyShowsFavoritesDidChange.send($0) + } + .store(in: &subscriptions) + } +} + +// MARK: - + private extension String { var asCountryCodeWithDescription: VPNFiltersView.Model.CodeWithDescription { (self, localizedAsRegionCode ?? self) diff --git a/Passepartout/Library/Sources/CommonLibrary/Business/ExtendedTunnel.swift b/Passepartout/Library/Sources/CommonLibrary/Business/ExtendedTunnel.swift index 3528f170..531438b2 100644 --- a/Passepartout/Library/Sources/CommonLibrary/Business/ExtendedTunnel.swift +++ b/Passepartout/Library/Sources/CommonLibrary/Business/ExtendedTunnel.swift @@ -64,53 +64,13 @@ public final class ExtendedTunnel: ObservableObject { self.processor = processor self.interval = interval subscriptions = [] - } - public func observeObjects() { - tunnel - .$status - .receive(on: DispatchQueue.main) - .sink { [weak self] in - guard let self else { - return - } - switch $0 { - case .activating: - lastErrorCode = nil - - default: - lastErrorCode = value(forKey: TunnelEnvironmentKeys.lastErrorCode) - } - if $0 != .active { - dataCount = nil - } - } - .store(in: &subscriptions) - - tunnel - .$currentProfile - .receive(on: DispatchQueue.main) - .sink { [weak self] _ in - self?.objectWillChange.send() - } - .store(in: &subscriptions) - - Timer - .publish(every: interval, on: .main, in: .common) - .autoconnect() - .sink { [weak self] _ in - guard let self else { - return - } - guard tunnel.status == .active else { - return - } - dataCount = value(forKey: TunnelEnvironmentKeys.dataCount) - } - .store(in: &subscriptions) + observeObjects() } } +// MARK: - Public interface + extension ExtendedTunnel { public var status: TunnelStatus { tunnel.status @@ -171,6 +131,56 @@ extension ExtendedTunnel { } } +// MARK: - Observation + +private extension ExtendedTunnel { + func observeObjects() { + tunnel + .$status + .receive(on: DispatchQueue.main) + .sink { [weak self] in + guard let self else { + return + } + switch $0 { + case .activating: + lastErrorCode = nil + + default: + lastErrorCode = value(forKey: TunnelEnvironmentKeys.lastErrorCode) + } + if $0 != .active { + dataCount = nil + } + } + .store(in: &subscriptions) + + tunnel + .$currentProfile + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.objectWillChange.send() + } + .store(in: &subscriptions) + + Timer + .publish(every: interval, on: .main, in: .common) + .autoconnect() + .sink { [weak self] _ in + guard let self else { + return + } + guard tunnel.status == .active else { + return + } + dataCount = value(forKey: TunnelEnvironmentKeys.dataCount) + } + .store(in: &subscriptions) + } +} + +// MARK: - Processing + private extension ExtendedTunnel { var processedTitle: (Profile) -> String { if let processor { diff --git a/Passepartout/Library/Sources/UILibrary/Business/AppContext.swift b/Passepartout/Library/Sources/UILibrary/Business/AppContext.swift index ef7f10a6..ab944c77 100644 --- a/Passepartout/Library/Sources/UILibrary/Business/AppContext.swift +++ b/Passepartout/Library/Sources/UILibrary/Business/AppContext.swift @@ -59,11 +59,21 @@ public final class AppContext: ObservableObject { Task { await iapManager.reloadReceipt() - self.tunnel.observeObjects() profileManager.observeObjects() observeObjects() } } + + public func onApplicationActive() { + Task { + do { + pp_log(.app, .notice, "Prepare tunnel and purge stale data") + try await tunnel.prepare(purge: true) + } catch { + pp_log(.app, .fault, "Unable to prepare tunnel: \(error)") + } + } + } } // MARK: - Observation diff --git a/Passepartout/Library/Sources/UILibrary/UILibrary.swift b/Passepartout/Library/Sources/UILibrary/UILibrary.swift index 2f73df01..d0fa9a73 100644 --- a/Passepartout/Library/Sources/UILibrary/UILibrary.swift +++ b/Passepartout/Library/Sources/UILibrary/UILibrary.swift @@ -28,6 +28,7 @@ import CommonLibrary import Foundation import PassepartoutKit +@MainActor public protocol UILibraryConfiguring { func configure(with context: AppContext) } diff --git a/Passepartout/Library/Tests/CommonLibraryTests/ExtendedTunnelTests.swift b/Passepartout/Library/Tests/CommonLibraryTests/ExtendedTunnelTests.swift index 3971498b..68e9714d 100644 --- a/Passepartout/Library/Tests/CommonLibraryTests/ExtendedTunnelTests.swift +++ b/Passepartout/Library/Tests/CommonLibraryTests/ExtendedTunnelTests.swift @@ -37,7 +37,6 @@ extension ExtendedTunnelTests { let env = InMemoryEnvironment() let tunnel = Tunnel(strategy: FakeTunnelStrategy(environment: env)) let sut = ExtendedTunnel(tunnel: tunnel, environment: env, interval: 0.1) - sut.observeObjects() let module = try DNSModule.Builder().tryBuild() let profile = try Profile.Builder(modules: [module], activatingModules: true).tryBuild() @@ -53,7 +52,6 @@ extension ExtendedTunnelTests { let env = InMemoryEnvironment() let tunnel = Tunnel(strategy: FakeTunnelStrategy(environment: env)) let sut = ExtendedTunnel(tunnel: tunnel, environment: env, interval: 0.1) - sut.observeObjects() let module = try DNSModule.Builder().tryBuild() let profile = try Profile.Builder(modules: [module], activatingModules: true).tryBuild()