Resolve issues when toggling/switching profiles (#747)

- Drop logic behind connection button tasks, let the library handle
concurrency
- Drop AppContext observation of saved profiles for reconnection, let
save() actively decide
- NETunnelStrategy and NETunnelManagerRepository are now a single entity
- Avoid flickering when toggling same profile
This commit is contained in:
Davide 2024-10-22 13:03:34 +02:00 committed by GitHub
parent 696f076ac5
commit 39bdf145e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 18 additions and 26 deletions

View File

@ -41,7 +41,7 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source", "location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
"state" : { "state" : {
"revision" : "ead8d1652fc6875f8865a1c8610b0cc35828dee6" "revision" : "288ccbf1e1a984c8dc3a7cd2fd23d3d3fc4464f6"
} }
}, },
{ {

View File

@ -28,7 +28,7 @@ let package = Package(
], ],
dependencies: [ dependencies: [
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.9.0"), // .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.9.0"),
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "ead8d1652fc6875f8865a1c8610b0cc35828dee6"), .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "288ccbf1e1a984c8dc3a7cd2fd23d3d3fc4464f6"),
// .package(path: "../../../passepartoutkit-source"), // .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", from: "0.9.1"),
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"), // .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"),

View File

@ -65,13 +65,10 @@ public final class NEProfileRepository: ProfileRepository {
profilesSubject.eraseToAnyPublisher() profilesSubject.eraseToAnyPublisher()
} }
// unused in app, rely on Tunnel.prepare()
public func loadProfiles(purge: Bool) async throws {
try await repository.load(purge: purge)
}
public func saveProfile(_ profile: Profile) async throws { public func saveProfile(_ profile: Profile) async throws {
try await repository.save(profile, connect: false, title: title)
// FIXME: #379, save + reconnect in some scenarios
try await repository.save(profile, forConnecting: false, title: title)
if let index = profilesSubject.value.firstIndex(where: { $0.id == profile.id }) { if let index = profilesSubject.value.firstIndex(where: { $0.id == profile.id }) {
profilesSubject.value[index] = profile profilesSubject.value[index] = profile
} else { } else {

View File

@ -115,9 +115,6 @@ private extension AppContext {
try await tunnel.disconnect() try await tunnel.disconnect()
return return
} }
if tunnel.status == .active {
try await tunnel.connect(with: profile, processor: profileProcessor)
}
} }
} }
} }

View File

@ -41,9 +41,6 @@ struct TunnelRestartButton<Label>: View where Label: View {
let label: () -> Label let label: () -> Label
@State
private var pendingTask: Task<Void, Error>?
var body: some View { var body: some View {
Button { Button {
guard let profile else { guard let profile else {
@ -52,10 +49,11 @@ struct TunnelRestartButton<Label>: View where Label: View {
guard tunnel.status == .active else { guard tunnel.status == .active else {
return return
} }
pendingTask?.cancel() Task {
pendingTask = Task {
do { do {
try await tunnel.connect(with: profile, processor: profileProcessor) try await tunnel.connect(with: profile, processor: profileProcessor)
} catch is CancellationError {
//
} catch { } catch {
errorHandler.handle( errorHandler.handle(
error, error,

View File

@ -62,9 +62,6 @@ struct TunnelToggleButton<Label>: View, TunnelContextProviding, ThemeProviding w
let label: (Bool) -> Label let label: (Bool) -> Label
@State
private var pendingTask: Task<Void, Error>?
var body: some View { var body: some View {
Button(action: tryPerform) { Button(action: tryPerform) {
label(canConnect) label(canConnect)
@ -100,12 +97,13 @@ private extension TunnelToggleButton {
private extension TunnelToggleButton { private extension TunnelToggleButton {
func tryPerform() { func tryPerform() {
pendingTask?.cancel() Task {
pendingTask = Task {
guard let profile else { guard let profile else {
return return
} }
nextProfileId = profile.id if !isInstalled {
nextProfileId = profile.id
}
defer { defer {
if nextProfileId == profile.id { if nextProfileId == profile.id {
nextProfileId = nil nextProfileId = nil
@ -137,6 +135,8 @@ private extension TunnelToggleButton {
} else { } else {
try await tunnel.connect(with: profile, processor: profileProcessor) try await tunnel.connect(with: profile, processor: profileProcessor)
} }
} catch is CancellationError {
//
} catch { } catch {
errorHandler.handle( errorHandler.handle(
error, error,

View File

@ -182,19 +182,19 @@ private extension ProfileManager {
extension Tunnel { extension Tunnel {
static let shared = Tunnel( static let shared = Tunnel(
strategy: NETunnelStrategy(repository: ProfileManager.neRepository) strategy: ProfileManager.neStrategy
) )
} }
private extension ProfileManager { private extension ProfileManager {
static let localProfileRepository: ProfileRepository = { static let localProfileRepository: ProfileRepository = {
NEProfileRepository(repository: neRepository) { NEProfileRepository(repository: neStrategy) {
ProfileManager.sharedTitle($0) ProfileManager.sharedTitle($0)
} }
}() }()
static let neRepository: NETunnelManagerRepository = { static let neStrategy: NETunnelStrategy = {
NETunnelManagerRepository( NETunnelStrategy(
bundleIdentifier: BundleConfiguration.mainString(for: .tunnelId), bundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
coder: Registry.sharedProtocolCoder, coder: Registry.sharedProtocolCoder,
environment: .shared environment: .shared