Improve configuration on app launch/active (#821)
- Centralize context initialization/refresh in platform-specific app delegates - Prevent multiple calls to .onApplicationActive() - Simplify local/remote profile fingerprint comparison - Revert to always replacing Core Data entities - The remote store somehow ended up having duplicates, which caused repeated imports of remote profiles due to randomly different fingerprints - Optimize reload of in-app receipt
This commit is contained in:
parent
d8c4e87239
commit
68df6066ba
|
@ -41,7 +41,7 @@
|
|||
"kind" : "remoteSourceControl",
|
||||
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
|
||||
"state" : {
|
||||
"revision" : "caf31aff2e2641356de0d01f3c2c2d0d635d6a2b"
|
||||
"revision" : "e7bd9636ac31d6111b0bc7c171398e68eeb384b5"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -35,6 +35,11 @@ final class AppDelegate: NSObject {
|
|||
|
||||
func configure(with uiConfiguring: UILibraryConfiguring) {
|
||||
UILibrary(uiConfiguring)
|
||||
.configure(with: context)
|
||||
.configure()
|
||||
|
||||
Task {
|
||||
pp_log(.app, .notice, "Fetch providers index...")
|
||||
try await context.providerManager.fetchIndex(from: API.shared)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import SwiftUI
|
|||
|
||||
extension AppDelegate: UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
configure(with: AppUIMain(isStartedFromLoginItem: false))
|
||||
configure(with: AppUIMain())
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,8 +33,11 @@ import SwiftUI
|
|||
|
||||
extension AppDelegate: NSApplicationDelegate {
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
configure(with: AppUIMain(isStartedFromLoginItem: isStartedFromLoginItem))
|
||||
hideIfLoginItem()
|
||||
configure(with: AppUIMain())
|
||||
context.onApplicationActive()
|
||||
if isStartedFromLoginItem {
|
||||
AppWindow.shared.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
|
@ -60,12 +63,6 @@ private extension AppDelegate {
|
|||
var isStartedFromLoginItem: Bool {
|
||||
NSApp.isHidden
|
||||
}
|
||||
|
||||
func hideIfLoginItem() {
|
||||
if isStartedFromLoginItem {
|
||||
AppWindow.shared.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PassepartoutApp {
|
||||
|
|
|
@ -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: "caf31aff2e2641356de0d01f3c2c2d0d635d6a2b"),
|
||||
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "e7bd9636ac31d6111b0bc7c171398e68eeb384b5"),
|
||||
// .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"),
|
||||
|
|
|
@ -50,7 +50,7 @@ extension AppData {
|
|||
} fromMapper: {
|
||||
try fromMapper($0, registry: registry, coder: coder)
|
||||
} toMapper: {
|
||||
try toMapper($0, $1, $2, registry: registry, coder: coder)
|
||||
try toMapper($0, $1, registry: registry, coder: coder)
|
||||
} onResultError: {
|
||||
onResultError?($0) ?? .ignore
|
||||
}
|
||||
|
@ -73,14 +73,13 @@ private extension AppData {
|
|||
|
||||
static func toMapper(
|
||||
_ profile: Profile,
|
||||
_ oldCdEntity: CDProfileV3?,
|
||||
_ context: NSManagedObjectContext,
|
||||
registry: Registry,
|
||||
coder: ProfileCoder
|
||||
) throws -> CDProfileV3 {
|
||||
let encoded = try registry.encodedProfile(profile, with: coder)
|
||||
|
||||
let cdProfile = oldCdEntity ?? CDProfileV3(context: context)
|
||||
let cdProfile = CDProfileV3(context: context)
|
||||
cdProfile.uuid = profile.id
|
||||
cdProfile.name = profile.name
|
||||
cdProfile.encoded = encoded
|
||||
|
|
|
@ -27,15 +27,11 @@ import Foundation
|
|||
@_exported import UILibrary
|
||||
|
||||
public final class AppUIMain: UILibraryConfiguring {
|
||||
private let isStartedFromLoginItem: Bool
|
||||
|
||||
public init(isStartedFromLoginItem: Bool) {
|
||||
self.isStartedFromLoginItem = isStartedFromLoginItem
|
||||
public init() {
|
||||
}
|
||||
|
||||
public func configure(with context: AppContext) {
|
||||
public func configure() {
|
||||
assertMissingImplementations()
|
||||
context.onApplicationActive()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,6 @@ public final class AppUITV: UILibraryConfiguring {
|
|||
public init() {
|
||||
}
|
||||
|
||||
public func configure(with context: AppContext) {
|
||||
public func configure() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -353,18 +353,28 @@ private extension ProfileManager {
|
|||
}
|
||||
objectWillChange.send()
|
||||
|
||||
let profilesToImport = result
|
||||
let allFingerprints = allProfiles.values.reduce(into: [:]) {
|
||||
$0[$1.id] = $1.attributes.fingerprint
|
||||
}
|
||||
let remotelyDeletedIds = Set(allProfiles.keys).subtracting(Set(allRemoteProfiles.keys))
|
||||
let deletingRemotely = deletingRemotely
|
||||
|
||||
Task.detached { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
pp_log(.app, .info, "Start importing remote profiles...")
|
||||
|
||||
pp_log(.app, .debug, "Local fingerprints:")
|
||||
let localFingerprints: [Profile.ID: UUID] = await allProfiles.values.reduce(into: [:]) {
|
||||
$0[$1.id] = $1.attributes.fingerprint
|
||||
pp_log(.app, .debug, "\t\($1.id) = \($1.attributes.fingerprint?.description ?? "nil")")
|
||||
}
|
||||
pp_log(.app, .debug, "Remote fingerprints:")
|
||||
let remoteFingerprints: [Profile.ID: UUID] = result.reduce(into: [:]) {
|
||||
$0[$1.id] = $1.attributes.fingerprint
|
||||
pp_log(.app, .debug, "\t\($1.id) = \($1.attributes.fingerprint?.description ?? "nil")")
|
||||
}
|
||||
|
||||
let profilesToImport = result
|
||||
let remotelyDeletedIds = await Set(allProfiles.keys).subtracting(Set(allRemoteProfiles.keys))
|
||||
let deletingRemotely = deletingRemotely
|
||||
|
||||
var idsToRemove: [Profile.ID] = []
|
||||
if !remotelyDeletedIds.isEmpty {
|
||||
pp_log(.app, .info, "Will \(deletingRemotely ? "delete" : "retain") local profiles not present in remote repository: \(remotelyDeletedIds)")
|
||||
|
@ -380,11 +390,9 @@ private extension ProfileManager {
|
|||
idsToRemove.append(remoteProfile.id)
|
||||
continue
|
||||
}
|
||||
if let localFingerprint = allFingerprints[remoteProfile.id] {
|
||||
guard remoteProfile.attributes.fingerprint != localFingerprint else {
|
||||
pp_log(.app, .info, "Skip re-importing local profile \(remoteProfile.id)")
|
||||
continue
|
||||
}
|
||||
guard remoteFingerprints[remoteProfile.id] != localFingerprints[remoteProfile.id] else {
|
||||
pp_log(.app, .info, "Skip re-importing local profile \(remoteProfile.id)")
|
||||
continue
|
||||
}
|
||||
pp_log(.app, .notice, "Import remote profile \(remoteProfile.id)...")
|
||||
try await save(remoteProfile)
|
||||
|
|
|
@ -104,6 +104,8 @@ extension IAPManager {
|
|||
purchasedProducts.removeAll()
|
||||
eligibleFeatures.removeAll()
|
||||
|
||||
pp_log(.app, .notice, "Reload IAP receipt...")
|
||||
|
||||
if let receipt = await receiptReader.receipt(at: userLevel) {
|
||||
if let originalBuildNumber = receipt.originalBuildNumber {
|
||||
purchasedAppBuild = originalBuildNumber
|
||||
|
|
|
@ -52,7 +52,7 @@ public actor CoreDataRepository<CD, T>: NSObject,
|
|||
|
||||
private let fromMapper: (CD) throws -> T?
|
||||
|
||||
private let toMapper: (T, CD?, NSManagedObjectContext) throws -> CD
|
||||
private let toMapper: (T, NSManagedObjectContext) throws -> CD
|
||||
|
||||
private let onResultError: ((Error) -> CoreDataResultAction)?
|
||||
|
||||
|
@ -66,7 +66,7 @@ public actor CoreDataRepository<CD, T>: NSObject,
|
|||
observingResults: Bool,
|
||||
beforeFetch: ((NSFetchRequest<CD>) -> Void)? = nil,
|
||||
fromMapper: @escaping (CD) throws -> T?,
|
||||
toMapper: @escaping (T, CD?, NSManagedObjectContext) throws -> CD,
|
||||
toMapper: @escaping (T, NSManagedObjectContext) throws -> CD,
|
||||
onResultError: ((Error) -> CoreDataResultAction)? = nil
|
||||
) {
|
||||
guard let entityName = CD.entity().name else {
|
||||
|
@ -127,11 +127,9 @@ public actor CoreDataRepository<CD, T>: NSObject,
|
|||
existingIds
|
||||
)
|
||||
let existing = try context.fetch(request)
|
||||
existing.forEach(context.delete)
|
||||
for entity in entities {
|
||||
let oldCdEntity = existing.first {
|
||||
$0.uuid == entity.uuid
|
||||
}
|
||||
_ = try self.toMapper(entity, oldCdEntity, context)
|
||||
_ = try self.toMapper(entity, context)
|
||||
}
|
||||
try context.save()
|
||||
} catch {
|
||||
|
|
|
@ -41,6 +41,8 @@ public final class AppContext: ObservableObject {
|
|||
|
||||
public let providerManager: ProviderManager
|
||||
|
||||
private var isActivating = false
|
||||
|
||||
private var subscriptions: Set<AnyCancellable>
|
||||
|
||||
public init(
|
||||
|
@ -61,16 +63,20 @@ public final class AppContext: ObservableObject {
|
|||
}
|
||||
|
||||
public func onApplicationActive() {
|
||||
guard !isActivating else {
|
||||
return
|
||||
}
|
||||
isActivating = true
|
||||
Task {
|
||||
do {
|
||||
pp_log(.app, .notice, "Application became active")
|
||||
pp_log(.app, .notice, "Reload IAP receipt...")
|
||||
await iapManager.reloadReceipt()
|
||||
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)")
|
||||
}
|
||||
isActivating = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import PassepartoutKit
|
|||
|
||||
@MainActor
|
||||
public protocol UILibraryConfiguring {
|
||||
func configure(with context: AppContext)
|
||||
func configure()
|
||||
}
|
||||
|
||||
public final class UILibrary: UILibraryConfiguring {
|
||||
|
@ -40,15 +40,12 @@ public final class UILibrary: UILibraryConfiguring {
|
|||
self.uiConfiguring = uiConfiguring
|
||||
}
|
||||
|
||||
public func configure(with context: AppContext) {
|
||||
public func configure() {
|
||||
PassepartoutConfiguration.shared.configureLogging(
|
||||
to: BundleConfiguration.urlForAppLog,
|
||||
parameters: Constants.shared.log,
|
||||
logsPrivateData: UserDefaults.appGroup.bool(forKey: AppPreference.logsPrivateData.key)
|
||||
)
|
||||
Task {
|
||||
try await context.providerManager.fetchIndex(from: API.shared)
|
||||
}
|
||||
uiConfiguring?.configure(with: context)
|
||||
uiConfiguring?.configure()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue