Optimize updates in NEProfileRepository (#742)

Currently, NEProfileRepository decodes profiles from ALL NE managers on
any update. This is undesirable considering that:

- Profiles are only _added_ by the app
- Externally, profiles can only be _removed_

Therefore:

- Observe the initial managers to decode the initial profiles from them
- Publish values manually on save/delete (to ProfileManager eventually)
- Observe the subsequent updates for when a profile is removed
externally, i.e. its ID doesn't appear in managers

Fixes #741
This commit is contained in:
Davide 2024-10-19 18:22:27 +02:00 committed by GitHub
parent 5d2e24792c
commit 2155fe1892
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 65 additions and 14 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" : "9a43e23e9134c3e93926271b2d630be607433fa0" "revision" : "ead8d1652fc6875f8865a1c8610b0cc35828dee6"
} }
}, },
{ {

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: "9a43e23e9134c3e93926271b2d630be607433fa0"), .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "ead8d1652fc6875f8865a1c8610b0cc35828dee6"),
// .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

@ -26,6 +26,7 @@
import Combine import Combine
import CommonLibrary import CommonLibrary
import Foundation import Foundation
import NetworkExtension
import PassepartoutKit import PassepartoutKit
public final class NEProfileRepository: ProfileRepository { public final class NEProfileRepository: ProfileRepository {
@ -35,26 +36,29 @@ public final class NEProfileRepository: ProfileRepository {
private let profilesSubject: CurrentValueSubject<[Profile], Never> private let profilesSubject: CurrentValueSubject<[Profile], Never>
private var subscription: AnyCancellable? private var subscriptions: Set<AnyCancellable>
public init(repository: NETunnelManagerRepository, title: @escaping (Profile) -> String) { public init(repository: NETunnelManagerRepository, title: @escaping (Profile) -> String) {
self.repository = repository self.repository = repository
self.title = title self.title = title
profilesSubject = CurrentValueSubject([]) profilesSubject = CurrentValueSubject([])
subscriptions = []
subscription = repository repository
.managersPublisher .managersPublisher
.sink { [weak self] allManagers in .first()
let profiles = allManagers.values.compactMap { .sink { [weak self] in
do { self?.onLoadedManagers($0)
return try repository.profile(from: $0)
} catch {
pp_log(.app, .error, "Unable to decode profile from NE manager '\($0.localizedDescription ?? "")': \(error)")
return nil
}
}
self?.profilesSubject.send(profiles)
} }
.store(in: &subscriptions)
repository
.managersPublisher
.dropFirst()
.sink { [weak self] in
self?.onUpdatedManagers($0)
}
.store(in: &subscriptions)
} }
public var profilesPublisher: AnyPublisher<[Profile], Never> { public var profilesPublisher: AnyPublisher<[Profile], Never> {
@ -68,11 +72,58 @@ public final class NEProfileRepository: ProfileRepository {
public func saveProfile(_ profile: Profile) async throws { public func saveProfile(_ profile: Profile) async throws {
try await repository.save(profile, connect: false, title: title) try await repository.save(profile, connect: false, title: title)
if let index = profilesSubject.value.firstIndex(where: { $0.id == profile.id }) {
profilesSubject.value[index] = profile
} else {
profilesSubject.value.append(profile)
}
} }
public func removeProfiles(withIds profileIds: [Profile.ID]) async throws { public func removeProfiles(withIds profileIds: [Profile.ID]) async throws {
var removedIds: Set<Profile.ID> = []
defer {
profilesSubject.value.removeAll {
removedIds.contains($0.id)
}
}
for id in profileIds { for id in profileIds {
try await repository.remove(profileId: id) try await repository.remove(profileId: id)
removedIds.insert(id)
} }
} }
} }
private extension NEProfileRepository {
func onLoadedManagers(_ managers: [Profile.ID: NETunnelProviderManager]) {
let profiles = managers.values.compactMap {
do {
return try repository.profile(from: $0)
} catch {
pp_log(.app, .error, "Unable to decode profile from NE manager '\($0.localizedDescription ?? "")': \(error)")
return nil
}
}
profilesSubject.send(profiles)
}
func onUpdatedManagers(_ managers: [Profile.ID: NETunnelProviderManager]) {
let profiles = profilesSubject
.value
.filter {
managers.keys.contains($0.id)
}
let removedProfilesDesc = profilesSubject
.value
.filter {
!managers.keys.contains($0.id)
}
.map {
"\($0.name)(\($0.id)"
}
pp_log(.app, .info, "Sync profiles removed externally: \(removedProfilesDesc)")
profilesSubject.send(profiles)
}
}