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",
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
"state" : {
"revision" : "9a43e23e9134c3e93926271b2d630be607433fa0"
"revision" : "ead8d1652fc6875f8865a1c8610b0cc35828dee6"
}
},
{

View File

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

View File

@ -26,6 +26,7 @@
import Combine
import CommonLibrary
import Foundation
import NetworkExtension
import PassepartoutKit
public final class NEProfileRepository: ProfileRepository {
@ -35,26 +36,29 @@ public final class NEProfileRepository: ProfileRepository {
private let profilesSubject: CurrentValueSubject<[Profile], Never>
private var subscription: AnyCancellable?
private var subscriptions: Set<AnyCancellable>
public init(repository: NETunnelManagerRepository, title: @escaping (Profile) -> String) {
self.repository = repository
self.title = title
profilesSubject = CurrentValueSubject([])
subscriptions = []
subscription = repository
repository
.managersPublisher
.sink { [weak self] allManagers in
let profiles = allManagers.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
}
}
self?.profilesSubject.send(profiles)
.first()
.sink { [weak self] in
self?.onLoadedManagers($0)
}
.store(in: &subscriptions)
repository
.managersPublisher
.dropFirst()
.sink { [weak self] in
self?.onUpdatedManagers($0)
}
.store(in: &subscriptions)
}
public var profilesPublisher: AnyPublisher<[Profile], Never> {
@ -68,11 +72,58 @@ public final class NEProfileRepository: ProfileRepository {
public func saveProfile(_ profile: Profile) async throws {
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 {
var removedIds: Set<Profile.ID> = []
defer {
profilesSubject.value.removeAll {
removedIds.contains($0.id)
}
}
for id in profileIds {
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)
}
}