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:
parent
5d2e24792c
commit
2155fe1892
|
@ -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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue