mirror of
https://github.com/passepartoutvpn/passepartout-apple.git
synced 2025-01-22 08:32:11 +00:00
Fix regressions with CloudKit synchronization (#1029)
The remote container is shared by ProfileManager and PreferencesManager, but it must be the same for CloudKit sync to work properly. Externalize the logic of onEligibleFeatures() so that the AppContext singleton can update the managers (and their repositories) with the new remote store. Now that the remote profile repository is reloaded every time that eligible features change, the .removeDuplicates() may also be restored. Just add a .dropFirst() to skip the initially empty value of eligible features. Even when features are eventually empty, a value is always emitted after IAPManager.reloadReceipt() Lastly, enable Core Data lightweight migration. Regressions from #1017
This commit is contained in:
parent
26e97625fa
commit
f8e623e1fe
@ -212,7 +212,7 @@ private extension ProfileRowView {
|
|||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
do {
|
do {
|
||||||
try await profileManager.observeRemote(true)
|
try await profileManager.observeRemote(repository: InMemoryProfileRepository())
|
||||||
try await profileManager.save(profile, isLocal: true, remotelyShared: true)
|
try await profileManager.save(profile, isLocal: true, remotelyShared: true)
|
||||||
} catch {
|
} catch {
|
||||||
fatalError(error.localizedDescription)
|
fatalError(error.localizedDescription)
|
||||||
|
@ -29,18 +29,15 @@ import PassepartoutKit
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public final class PreferencesManager: ObservableObject {
|
public final class PreferencesManager: ObservableObject {
|
||||||
private let modulesFactory: (UUID) throws -> ModulePreferencesRepository
|
public var modulesRepositoryFactory: (UUID) throws -> ModulePreferencesRepository
|
||||||
|
|
||||||
private let providersFactory: (ProviderID) throws -> ProviderPreferencesRepository
|
public var providersRepositoryFactory: (ProviderID) throws -> ProviderPreferencesRepository
|
||||||
|
|
||||||
public init(
|
public init() {
|
||||||
modulesFactory: ((UUID) throws -> ModulePreferencesRepository)? = nil,
|
modulesRepositoryFactory = { _ in
|
||||||
providersFactory: ((ProviderID) throws -> ProviderPreferencesRepository)? = nil
|
|
||||||
) {
|
|
||||||
self.modulesFactory = modulesFactory ?? { _ in
|
|
||||||
DummyModulePreferencesRepository()
|
DummyModulePreferencesRepository()
|
||||||
}
|
}
|
||||||
self.providersFactory = providersFactory ?? { _ in
|
providersRepositoryFactory = { _ in
|
||||||
DummyProviderPreferencesRepository()
|
DummyProviderPreferencesRepository()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,11 +45,11 @@ public final class PreferencesManager: ObservableObject {
|
|||||||
|
|
||||||
extension PreferencesManager {
|
extension PreferencesManager {
|
||||||
public func preferencesRepository(forModuleWithId moduleId: UUID) throws -> ModulePreferencesRepository {
|
public func preferencesRepository(forModuleWithId moduleId: UUID) throws -> ModulePreferencesRepository {
|
||||||
try modulesFactory(moduleId)
|
try modulesRepositoryFactory(moduleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func preferencesRepository(forProviderWithId providerId: ProviderID) throws -> ProviderPreferencesRepository {
|
public func preferencesRepository(forProviderWithId providerId: ProviderID) throws -> ProviderPreferencesRepository {
|
||||||
try providersFactory(providerId)
|
try providersRepositoryFactory(providerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +59,6 @@ public final class ProfileManager: ObservableObject {
|
|||||||
|
|
||||||
private let backupRepository: ProfileRepository?
|
private let backupRepository: ProfileRepository?
|
||||||
|
|
||||||
private let remoteRepositoryBlock: ((Bool) -> ProfileRepository)?
|
|
||||||
|
|
||||||
private var remoteRepository: ProfileRepository?
|
private var remoteRepository: ProfileRepository?
|
||||||
|
|
||||||
private let mirrorsRemoteRepository: Bool
|
private let mirrorsRemoteRepository: Bool
|
||||||
@ -94,7 +92,7 @@ public final class ProfileManager: ObservableObject {
|
|||||||
private var requiredFeatures: [Profile.ID: Set<AppFeature>]
|
private var requiredFeatures: [Profile.ID: Set<AppFeature>]
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
public private(set) var isRemoteImportingEnabled: Bool
|
public var isRemoteImportingEnabled = false
|
||||||
|
|
||||||
private var waitingObservers: Set<Observer> {
|
private var waitingObservers: Set<Observer> {
|
||||||
didSet {
|
didSet {
|
||||||
@ -120,34 +118,25 @@ public final class ProfileManager: ObservableObject {
|
|||||||
|
|
||||||
// for testing/previews
|
// for testing/previews
|
||||||
public convenience init(profiles: [Profile]) {
|
public convenience init(profiles: [Profile]) {
|
||||||
self.init(
|
self.init(repository: InMemoryProfileRepository(profiles: profiles))
|
||||||
repository: InMemoryProfileRepository(profiles: profiles),
|
|
||||||
remoteRepositoryBlock: { _ in
|
|
||||||
InMemoryProfileRepository()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
processor: ProfileProcessor? = nil,
|
||||||
repository: ProfileRepository,
|
repository: ProfileRepository,
|
||||||
backupRepository: ProfileRepository? = nil,
|
backupRepository: ProfileRepository? = nil,
|
||||||
remoteRepositoryBlock: ((Bool) -> ProfileRepository)?,
|
mirrorsRemoteRepository: Bool = false
|
||||||
mirrorsRemoteRepository: Bool = false,
|
|
||||||
processor: ProfileProcessor? = nil
|
|
||||||
) {
|
) {
|
||||||
precondition(!mirrorsRemoteRepository || remoteRepositoryBlock != nil, "mirrorsRemoteRepository requires a non-nil remoteRepositoryBlock")
|
self.processor = processor
|
||||||
self.repository = repository
|
self.repository = repository
|
||||||
self.backupRepository = backupRepository
|
self.backupRepository = backupRepository
|
||||||
self.remoteRepositoryBlock = remoteRepositoryBlock
|
|
||||||
self.mirrorsRemoteRepository = mirrorsRemoteRepository
|
self.mirrorsRemoteRepository = mirrorsRemoteRepository
|
||||||
self.processor = processor
|
|
||||||
|
|
||||||
allProfiles = [:]
|
allProfiles = [:]
|
||||||
allRemoteProfiles = [:]
|
allRemoteProfiles = [:]
|
||||||
filteredProfiles = []
|
filteredProfiles = []
|
||||||
requiredFeatures = [:]
|
requiredFeatures = [:]
|
||||||
isRemoteImportingEnabled = false
|
if mirrorsRemoteRepository {
|
||||||
if remoteRepositoryBlock != nil {
|
|
||||||
waitingObservers = [.local, .remote]
|
waitingObservers = [.local, .remote]
|
||||||
} else {
|
} else {
|
||||||
waitingObservers = [.local]
|
waitingObservers = [.local]
|
||||||
@ -341,24 +330,13 @@ extension ProfileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func observeRemote(_ isRemoteImportingEnabled: Bool) async throws {
|
public func observeRemote(repository: ProfileRepository) async throws {
|
||||||
guard let remoteRepositoryBlock else {
|
|
||||||
// preconditionFailure("Missing remoteRepositoryBlock")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard remoteRepository == nil || isRemoteImportingEnabled != self.isRemoteImportingEnabled else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.isRemoteImportingEnabled = isRemoteImportingEnabled
|
|
||||||
|
|
||||||
remoteSubscription = nil
|
remoteSubscription = nil
|
||||||
let newRepository = remoteRepositoryBlock(isRemoteImportingEnabled)
|
remoteRepository = repository
|
||||||
let initialProfiles = try await newRepository.fetchProfiles()
|
let initialProfiles = try await repository.fetchProfiles()
|
||||||
reloadRemoteProfiles(initialProfiles)
|
reloadRemoteProfiles(initialProfiles)
|
||||||
remoteRepository = newRepository
|
|
||||||
|
|
||||||
remoteSubscription = remoteRepository?
|
remoteSubscription = repository
|
||||||
.profilesPublisher
|
.profilesPublisher
|
||||||
.dropFirst()
|
.dropFirst()
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
@ -422,6 +400,7 @@ private extension ProfileManager {
|
|||||||
if waitingObservers.contains(.remote) {
|
if waitingObservers.contains(.remote) {
|
||||||
waitingObservers.remove(.remote)
|
waitingObservers.remove(.remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
Task { [weak self] in
|
Task { [weak self] in
|
||||||
self?.didChange.send(.startRemoteImport)
|
self?.didChange.send(.startRemoteImport)
|
||||||
await self?.importRemoteProfiles(result)
|
await self?.importRemoteProfiles(result)
|
||||||
|
@ -90,6 +90,10 @@ public final class CoreDataPersistentStore: Sendable {
|
|||||||
// container was formerly created with CloudKit option
|
// container was formerly created with CloudKit option
|
||||||
desc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
|
desc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
|
||||||
|
|
||||||
|
// migrate automatically
|
||||||
|
desc.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
|
||||||
|
desc.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
|
||||||
|
|
||||||
// report remote notifications (do this BEFORE loadPersistentStores)
|
// report remote notifications (do this BEFORE loadPersistentStores)
|
||||||
//
|
//
|
||||||
// https://stackoverflow.com/a/69507329/784615
|
// https://stackoverflow.com/a/69507329/784615
|
||||||
|
@ -180,20 +180,19 @@ private extension CoreDataRepository {
|
|||||||
request.predicate = predicate
|
request.predicate = predicate
|
||||||
beforeFetch?(request)
|
beforeFetch?(request)
|
||||||
|
|
||||||
let newController = try await context.perform {
|
let newController = NSFetchedResultsController(
|
||||||
let newController = NSFetchedResultsController(
|
fetchRequest: request,
|
||||||
fetchRequest: request,
|
managedObjectContext: self.context,
|
||||||
managedObjectContext: self.context,
|
sectionNameKeyPath: nil,
|
||||||
sectionNameKeyPath: nil,
|
cacheName: nil
|
||||||
cacheName: nil
|
)
|
||||||
)
|
newController.delegate = self
|
||||||
newController.delegate = self
|
|
||||||
try newController.performFetch()
|
|
||||||
return newController
|
|
||||||
}
|
|
||||||
|
|
||||||
resultsController = newController
|
resultsController = newController
|
||||||
return await sendResults(from: newController)
|
|
||||||
|
return try await context.perform {
|
||||||
|
try newController.performFetch()
|
||||||
|
return self.unsafeSendResults(from: newController)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
@ -48,6 +48,8 @@ public final class AppContext: ObservableObject, Sendable {
|
|||||||
|
|
||||||
private let tunnelReceiptURL: URL?
|
private let tunnelReceiptURL: URL?
|
||||||
|
|
||||||
|
private let onEligibleFeaturesBlock: ((Set<AppFeature>) async -> Void)?
|
||||||
|
|
||||||
private var launchTask: Task<Void, Error>?
|
private var launchTask: Task<Void, Error>?
|
||||||
|
|
||||||
private var pendingTask: Task<Void, Never>?
|
private var pendingTask: Task<Void, Never>?
|
||||||
@ -62,7 +64,8 @@ public final class AppContext: ObservableObject, Sendable {
|
|||||||
preferencesManager: PreferencesManager,
|
preferencesManager: PreferencesManager,
|
||||||
registry: Registry,
|
registry: Registry,
|
||||||
tunnel: ExtendedTunnel,
|
tunnel: ExtendedTunnel,
|
||||||
tunnelReceiptURL: URL?
|
tunnelReceiptURL: URL?,
|
||||||
|
onEligibleFeaturesBlock: ((Set<AppFeature>) async -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
self.iapManager = iapManager
|
self.iapManager = iapManager
|
||||||
self.migrationManager = migrationManager
|
self.migrationManager = migrationManager
|
||||||
@ -72,6 +75,7 @@ public final class AppContext: ObservableObject, Sendable {
|
|||||||
self.registry = registry
|
self.registry = registry
|
||||||
self.tunnel = tunnel
|
self.tunnel = tunnel
|
||||||
self.tunnelReceiptURL = tunnelReceiptURL
|
self.tunnelReceiptURL = tunnelReceiptURL
|
||||||
|
self.onEligibleFeaturesBlock = onEligibleFeaturesBlock
|
||||||
subscriptions = []
|
subscriptions = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,22 +103,16 @@ private extension AppContext {
|
|||||||
pp_log(.App.profiles, .info, "\tObserve in-app events...")
|
pp_log(.App.profiles, .info, "\tObserve in-app events...")
|
||||||
iapManager.observeObjects()
|
iapManager.observeObjects()
|
||||||
|
|
||||||
// load in background, see comment right below
|
// defer load receipt
|
||||||
Task {
|
Task {
|
||||||
await iapManager.reloadReceipt()
|
await iapManager.reloadReceipt()
|
||||||
}
|
}
|
||||||
|
|
||||||
// using Task above (#1019) causes the receipt to be loaded asynchronously.
|
|
||||||
// the initial call to onEligibleFeatures() may execute before the receipt is
|
|
||||||
// loaded and therefore do nothing. with .removeDuplicates(), there would
|
|
||||||
// not be a second chance to call onEligibleFeatures() if the eligible
|
|
||||||
// features haven't changed after reloading the receipt (this is the case
|
|
||||||
// for TestFlight where some features are set statically). that's why it's
|
|
||||||
// commented now
|
|
||||||
pp_log(.App.profiles, .info, "\tObserve eligible features...")
|
pp_log(.App.profiles, .info, "\tObserve eligible features...")
|
||||||
iapManager
|
iapManager
|
||||||
.$eligibleFeatures
|
.$eligibleFeatures
|
||||||
// .removeDuplicates()
|
.dropFirst()
|
||||||
|
.removeDuplicates()
|
||||||
.sink { [weak self] eligible in
|
.sink { [weak self] eligible in
|
||||||
Task {
|
Task {
|
||||||
try await self?.onEligibleFeatures(eligible)
|
try await self?.onEligibleFeatures(eligible)
|
||||||
@ -184,19 +182,7 @@ private extension AppContext {
|
|||||||
|
|
||||||
pp_log(.app, .notice, "Application did update eligible features")
|
pp_log(.app, .notice, "Application did update eligible features")
|
||||||
pendingTask = Task {
|
pendingTask = Task {
|
||||||
|
await onEligibleFeaturesBlock?(features)
|
||||||
// toggle sync based on .sharing eligibility
|
|
||||||
let isEligibleForSharing = features.contains(.sharing)
|
|
||||||
do {
|
|
||||||
pp_log(.App.profiles, .info, "\tRefresh remote profiles observers (eligible=\(isEligibleForSharing), CloudKit=\(isCloudKitEnabled))...")
|
|
||||||
try await profileManager.observeRemote(isEligibleForSharing && isCloudKitEnabled)
|
|
||||||
} catch {
|
|
||||||
pp_log(.App.profiles, .error, "\tUnable to re-observe remote profiles: \(error)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// refresh required profile features
|
|
||||||
pp_log(.App.profiles, .info, "\tReload profiles required features...")
|
|
||||||
profileManager.reloadRequiredFeatures()
|
|
||||||
}
|
}
|
||||||
await pendingTask?.value
|
await pendingTask?.value
|
||||||
pendingTask = nil
|
pendingTask = nil
|
||||||
@ -262,18 +248,3 @@ private extension AppContext {
|
|||||||
return didLaunch
|
return didLaunch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
|
||||||
|
|
||||||
private extension AppContext {
|
|
||||||
var isCloudKitEnabled: Bool {
|
|
||||||
#if os(tvOS)
|
|
||||||
true
|
|
||||||
#else
|
|
||||||
if AppCommandLine.contains(.uiTesting) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return FileManager.default.ubiquityIdentityToken != nil
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -50,7 +50,7 @@ extension ProfileManagerTests {
|
|||||||
|
|
||||||
func test_givenRepository_whenNotReady_thenHasNoProfiles() {
|
func test_givenRepository_whenNotReady_thenHasNoProfiles() {
|
||||||
let repository = InMemoryProfileRepository(profiles: [])
|
let repository = InMemoryProfileRepository(profiles: [])
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil)
|
let sut = ProfileManager(repository: repository)
|
||||||
XCTAssertFalse(sut.isReady)
|
XCTAssertFalse(sut.isReady)
|
||||||
XCTAssertFalse(sut.hasProfiles)
|
XCTAssertFalse(sut.hasProfiles)
|
||||||
XCTAssertTrue(sut.previews.isEmpty)
|
XCTAssertTrue(sut.previews.isEmpty)
|
||||||
@ -59,7 +59,7 @@ extension ProfileManagerTests {
|
|||||||
func test_givenRepository_whenReady_thenHasProfiles() async throws {
|
func test_givenRepository_whenReady_thenHasProfiles() async throws {
|
||||||
let profile = newProfile()
|
let profile = newProfile()
|
||||||
let repository = InMemoryProfileRepository(profiles: [profile])
|
let repository = InMemoryProfileRepository(profiles: [profile])
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil)
|
let sut = ProfileManager(repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
@ -72,7 +72,7 @@ extension ProfileManagerTests {
|
|||||||
let profile1 = newProfile("foo")
|
let profile1 = newProfile("foo")
|
||||||
let profile2 = newProfile("bar")
|
let profile2 = newProfile("bar")
|
||||||
let repository = InMemoryProfileRepository(profiles: [profile1, profile2])
|
let repository = InMemoryProfileRepository(profiles: [profile1, profile2])
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil)
|
let sut = ProfileManager(repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
@ -93,7 +93,7 @@ extension ProfileManagerTests {
|
|||||||
let repository = InMemoryProfileRepository(profiles: [profile])
|
let repository = InMemoryProfileRepository(profiles: [profile])
|
||||||
let processor = MockProfileProcessor()
|
let processor = MockProfileProcessor()
|
||||||
processor.requiredFeatures = [.appleTV, .onDemand]
|
processor.requiredFeatures = [.appleTV, .onDemand]
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil, processor: processor)
|
let sut = ProfileManager(processor: processor, repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
@ -115,7 +115,7 @@ extension ProfileManagerTests {
|
|||||||
processor.isIncludedBlock = {
|
processor.isIncludedBlock = {
|
||||||
$0.name == "local2"
|
$0.name == "local2"
|
||||||
}
|
}
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil, processor: processor)
|
let sut = ProfileManager(processor: processor, repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
@ -129,7 +129,7 @@ extension ProfileManagerTests {
|
|||||||
let repository = InMemoryProfileRepository(profiles: [profile])
|
let repository = InMemoryProfileRepository(profiles: [profile])
|
||||||
let processor = MockProfileProcessor()
|
let processor = MockProfileProcessor()
|
||||||
processor.requiredFeatures = [.appleTV, .onDemand]
|
processor.requiredFeatures = [.appleTV, .onDemand]
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil, processor: processor)
|
let sut = ProfileManager(processor: processor, repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
@ -155,7 +155,7 @@ extension ProfileManagerTests {
|
|||||||
extension ProfileManagerTests {
|
extension ProfileManagerTests {
|
||||||
func test_givenRepository_whenSave_thenIsSaved() async throws {
|
func test_givenRepository_whenSave_thenIsSaved() async throws {
|
||||||
let repository = InMemoryProfileRepository(profiles: [])
|
let repository = InMemoryProfileRepository(profiles: [])
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil)
|
let sut = ProfileManager(repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
@ -172,7 +172,7 @@ extension ProfileManagerTests {
|
|||||||
func test_givenRepository_whenSaveExisting_thenIsReplaced() async throws {
|
func test_givenRepository_whenSaveExisting_thenIsReplaced() async throws {
|
||||||
let profile = newProfile("oldName")
|
let profile = newProfile("oldName")
|
||||||
let repository = InMemoryProfileRepository(profiles: [profile])
|
let repository = InMemoryProfileRepository(profiles: [profile])
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil)
|
let sut = ProfileManager(repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
@ -191,7 +191,7 @@ extension ProfileManagerTests {
|
|||||||
func test_givenRepositoryAndProcessor_whenSave_thenProcessorIsNotInvoked() async throws {
|
func test_givenRepositoryAndProcessor_whenSave_thenProcessorIsNotInvoked() async throws {
|
||||||
let repository = InMemoryProfileRepository(profiles: [])
|
let repository = InMemoryProfileRepository(profiles: [])
|
||||||
let processor = MockProfileProcessor()
|
let processor = MockProfileProcessor()
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil, processor: processor)
|
let sut = ProfileManager(processor: processor, repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
@ -207,7 +207,7 @@ extension ProfileManagerTests {
|
|||||||
func test_givenRepositoryAndProcessor_whenSaveLocal_thenProcessorIsInvoked() async throws {
|
func test_givenRepositoryAndProcessor_whenSaveLocal_thenProcessorIsInvoked() async throws {
|
||||||
let repository = InMemoryProfileRepository(profiles: [])
|
let repository = InMemoryProfileRepository(profiles: [])
|
||||||
let processor = MockProfileProcessor()
|
let processor = MockProfileProcessor()
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil, processor: processor)
|
let sut = ProfileManager(processor: processor, repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
@ -221,7 +221,7 @@ extension ProfileManagerTests {
|
|||||||
func test_givenRepository_whenSave_thenIsStoredToBackUpRepository() async throws {
|
func test_givenRepository_whenSave_thenIsStoredToBackUpRepository() async throws {
|
||||||
let repository = InMemoryProfileRepository(profiles: [])
|
let repository = InMemoryProfileRepository(profiles: [])
|
||||||
let backupRepository = InMemoryProfileRepository(profiles: [])
|
let backupRepository = InMemoryProfileRepository(profiles: [])
|
||||||
let sut = ProfileManager(repository: repository, backupRepository: backupRepository, remoteRepositoryBlock: nil)
|
let sut = ProfileManager(repository: repository, backupRepository: backupRepository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
@ -247,7 +247,7 @@ extension ProfileManagerTests {
|
|||||||
func test_givenRepository_whenRemove_thenIsRemoved() async throws {
|
func test_givenRepository_whenRemove_thenIsRemoved() async throws {
|
||||||
let profile = newProfile()
|
let profile = newProfile()
|
||||||
let repository = InMemoryProfileRepository(profiles: [profile])
|
let repository = InMemoryProfileRepository(profiles: [profile])
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil)
|
let sut = ProfileManager(repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertTrue(sut.isReady)
|
XCTAssertTrue(sut.isReady)
|
||||||
@ -267,11 +267,9 @@ extension ProfileManagerTests {
|
|||||||
let profile = newProfile()
|
let profile = newProfile()
|
||||||
let repository = InMemoryProfileRepository()
|
let repository = InMemoryProfileRepository()
|
||||||
let remoteRepository = InMemoryProfileRepository()
|
let remoteRepository = InMemoryProfileRepository()
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
let sut = ProfileManager(repository: repository)
|
||||||
remoteRepository
|
|
||||||
})
|
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut, remoteRepository: remoteRepository)
|
||||||
|
|
||||||
let exp = expectation(description: "Remote")
|
let exp = expectation(description: "Remote")
|
||||||
remoteRepository
|
remoteRepository
|
||||||
@ -291,15 +289,13 @@ extension ProfileManagerTests {
|
|||||||
XCTAssertTrue(sut.isRemotelyShared(profileWithId: profile.id))
|
XCTAssertTrue(sut.isRemotelyShared(profileWithId: profile.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_givenRemoteRepository_whenSaveNotRemotelyShared_thenIsRemoveFromRemoteRepository() async throws {
|
func test_givenRemoteRepository_whenSaveNotRemotelyShared_thenIsRemovedFromRemoteRepository() async throws {
|
||||||
let profile = newProfile()
|
let profile = newProfile()
|
||||||
let repository = InMemoryProfileRepository(profiles: [profile])
|
let repository = InMemoryProfileRepository(profiles: [profile])
|
||||||
let remoteRepository = InMemoryProfileRepository(profiles: [profile])
|
let remoteRepository = InMemoryProfileRepository(profiles: [profile])
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
let sut = ProfileManager(repository: repository)
|
||||||
remoteRepository
|
|
||||||
})
|
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut, remoteRepository: remoteRepository)
|
||||||
|
|
||||||
let exp = expectation(description: "Remote")
|
let exp = expectation(description: "Remote")
|
||||||
remoteRepository
|
remoteRepository
|
||||||
@ -325,7 +321,7 @@ extension ProfileManagerTests {
|
|||||||
func test_givenRepository_whenNew_thenReturnsProfileWithNewName() async throws {
|
func test_givenRepository_whenNew_thenReturnsProfileWithNewName() async throws {
|
||||||
let profile = newProfile("example")
|
let profile = newProfile("example")
|
||||||
let repository = InMemoryProfileRepository(profiles: [profile])
|
let repository = InMemoryProfileRepository(profiles: [profile])
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil)
|
let sut = ProfileManager(repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
XCTAssertEqual(sut.previews.count, 1)
|
XCTAssertEqual(sut.previews.count, 1)
|
||||||
@ -337,7 +333,7 @@ extension ProfileManagerTests {
|
|||||||
func test_givenRepository_whenDuplicate_thenSavesProfileWithNewName() async throws {
|
func test_givenRepository_whenDuplicate_thenSavesProfileWithNewName() async throws {
|
||||||
let profile = newProfile("example")
|
let profile = newProfile("example")
|
||||||
let repository = InMemoryProfileRepository(profiles: [profile])
|
let repository = InMemoryProfileRepository(profiles: [profile])
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: nil)
|
let sut = ProfileManager(repository: repository)
|
||||||
|
|
||||||
try await waitForReady(sut)
|
try await waitForReady(sut)
|
||||||
|
|
||||||
@ -381,13 +377,11 @@ extension ProfileManagerTests {
|
|||||||
let allProfiles = localProfiles + remoteProfiles
|
let allProfiles = localProfiles + remoteProfiles
|
||||||
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
||||||
let remoteRepository = InMemoryProfileRepository(profiles: remoteProfiles)
|
let remoteRepository = InMemoryProfileRepository(profiles: remoteProfiles)
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
let sut = ProfileManager(repository: repository)
|
||||||
remoteRepository
|
|
||||||
})
|
|
||||||
|
|
||||||
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
||||||
try await $0.observeLocal()
|
try await $0.observeLocal()
|
||||||
try await $0.observeRemote(true)
|
try await $0.observeRemote(repository: remoteRepository)
|
||||||
}
|
}
|
||||||
XCTAssertEqual(sut.previews.count, allProfiles.count)
|
XCTAssertEqual(sut.previews.count, allProfiles.count)
|
||||||
|
|
||||||
@ -417,13 +411,11 @@ extension ProfileManagerTests {
|
|||||||
]
|
]
|
||||||
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
||||||
let remoteRepository = InMemoryProfileRepository(profiles: remoteProfiles)
|
let remoteRepository = InMemoryProfileRepository(profiles: remoteProfiles)
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
let sut = ProfileManager(repository: repository)
|
||||||
remoteRepository
|
|
||||||
})
|
|
||||||
|
|
||||||
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
||||||
try await $0.observeLocal()
|
try await $0.observeLocal()
|
||||||
try await $0.observeRemote(true)
|
try await $0.observeRemote(repository: remoteRepository)
|
||||||
}
|
}
|
||||||
XCTAssertEqual(sut.previews.count, 4) // unique IDs
|
XCTAssertEqual(sut.previews.count, 4) // unique IDs
|
||||||
|
|
||||||
@ -464,13 +456,11 @@ extension ProfileManagerTests {
|
|||||||
processor.isIncludedBlock = {
|
processor.isIncludedBlock = {
|
||||||
!$0.name.hasPrefix("remote")
|
!$0.name.hasPrefix("remote")
|
||||||
}
|
}
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
let sut = ProfileManager(processor: processor, repository: repository)
|
||||||
remoteRepository
|
|
||||||
}, processor: processor)
|
|
||||||
|
|
||||||
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
||||||
try await $0.observeLocal()
|
try await $0.observeLocal()
|
||||||
try await $0.observeRemote(true)
|
try await $0.observeRemote(repository: remoteRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
XCTAssertEqual(processor.isIncludedCount, allProfiles.count)
|
XCTAssertEqual(processor.isIncludedCount, allProfiles.count)
|
||||||
@ -499,13 +489,11 @@ extension ProfileManagerTests {
|
|||||||
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
||||||
let remoteRepository = InMemoryProfileRepository(profiles: remoteProfiles)
|
let remoteRepository = InMemoryProfileRepository(profiles: remoteProfiles)
|
||||||
let processor = MockProfileProcessor()
|
let processor = MockProfileProcessor()
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
let sut = ProfileManager(processor: processor, repository: repository)
|
||||||
remoteRepository
|
|
||||||
}, processor: processor)
|
|
||||||
|
|
||||||
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
||||||
try await $0.observeLocal()
|
try await $0.observeLocal()
|
||||||
try await $0.observeRemote(true)
|
try await $0.observeRemote(repository: remoteRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
try sut.previews.forEach {
|
try sut.previews.forEach {
|
||||||
@ -531,13 +519,11 @@ extension ProfileManagerTests {
|
|||||||
]
|
]
|
||||||
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
||||||
let remoteRepository = InMemoryProfileRepository()
|
let remoteRepository = InMemoryProfileRepository()
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
let sut = ProfileManager(repository: repository)
|
||||||
remoteRepository
|
|
||||||
})
|
|
||||||
|
|
||||||
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
||||||
try await $0.observeLocal()
|
try await $0.observeLocal()
|
||||||
try await $0.observeRemote(true)
|
try await $0.observeRemote(repository: remoteRepository)
|
||||||
}
|
}
|
||||||
XCTAssertEqual(sut.previews.count, localProfiles.count)
|
XCTAssertEqual(sut.previews.count, localProfiles.count)
|
||||||
|
|
||||||
@ -589,13 +575,11 @@ extension ProfileManagerTests {
|
|||||||
let remoteProfiles = [profile]
|
let remoteProfiles = [profile]
|
||||||
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
||||||
let remoteRepository = InMemoryProfileRepository(profiles: remoteProfiles)
|
let remoteRepository = InMemoryProfileRepository(profiles: remoteProfiles)
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
let sut = ProfileManager(repository: repository)
|
||||||
remoteRepository
|
|
||||||
})
|
|
||||||
|
|
||||||
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
||||||
try await $0.observeLocal()
|
try await $0.observeLocal()
|
||||||
try await $0.observeRemote(true)
|
try await $0.observeRemote(repository: remoteRepository)
|
||||||
}
|
}
|
||||||
XCTAssertEqual(sut.previews.count, 1)
|
XCTAssertEqual(sut.previews.count, 1)
|
||||||
|
|
||||||
@ -611,13 +595,11 @@ extension ProfileManagerTests {
|
|||||||
let localProfiles = [profile]
|
let localProfiles = [profile]
|
||||||
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
let repository = InMemoryProfileRepository(profiles: localProfiles)
|
||||||
let remoteRepository = InMemoryProfileRepository(profiles: localProfiles)
|
let remoteRepository = InMemoryProfileRepository(profiles: localProfiles)
|
||||||
let sut = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
let sut = ProfileManager(repository: repository, mirrorsRemoteRepository: true)
|
||||||
remoteRepository
|
|
||||||
}, mirrorsRemoteRepository: true)
|
|
||||||
|
|
||||||
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
try await wait(sut, "Remote import", until: .stopRemoteImport) {
|
||||||
try await $0.observeLocal()
|
try await $0.observeLocal()
|
||||||
try await $0.observeRemote(true)
|
try await $0.observeRemote(repository: remoteRepository)
|
||||||
}
|
}
|
||||||
XCTAssertEqual(sut.previews.count, 1)
|
XCTAssertEqual(sut.previews.count, 1)
|
||||||
|
|
||||||
@ -644,10 +626,12 @@ private extension ProfileManagerTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForReady(_ sut: ProfileManager, importingRemote: Bool = true) async throws {
|
func waitForReady(_ sut: ProfileManager, remoteRepository: ProfileRepository? = nil) async throws {
|
||||||
try await wait(sut, "Ready", until: .ready) {
|
try await wait(sut, "Ready", until: .ready) {
|
||||||
try await $0.observeLocal()
|
try await $0.observeLocal()
|
||||||
try await $0.observeRemote(importingRemote)
|
if let remoteRepository {
|
||||||
|
try await $0.observeRemote(repository: remoteRepository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,18 +40,39 @@ extension AppContext {
|
|||||||
static let shared: AppContext = {
|
static let shared: AppContext = {
|
||||||
let dependencies: Dependencies = .shared
|
let dependencies: Dependencies = .shared
|
||||||
|
|
||||||
|
// MARK: Core Data
|
||||||
|
|
||||||
|
guard let cdLocalModel = NSManagedObjectModel.mergedModel(from: [
|
||||||
|
AppData.providersBundle
|
||||||
|
]) else {
|
||||||
|
fatalError("Unable to load local model")
|
||||||
|
}
|
||||||
guard let cdRemoteModel = NSManagedObjectModel.mergedModel(from: [
|
guard let cdRemoteModel = NSManagedObjectModel.mergedModel(from: [
|
||||||
AppData.profilesBundle,
|
AppData.profilesBundle,
|
||||||
AppData.preferencesBundle
|
AppData.preferencesBundle
|
||||||
]) else {
|
]) else {
|
||||||
fatalError("Unable to load remote model")
|
fatalError("Unable to load remote model")
|
||||||
}
|
}
|
||||||
guard let cdLocalModel = NSManagedObjectModel.mergedModel(from: [
|
|
||||||
AppData.providersBundle
|
let localStore = CoreDataPersistentStore(
|
||||||
]) else {
|
logger: dependencies.coreDataLogger(),
|
||||||
fatalError("Unable to load local model")
|
containerName: Constants.shared.containers.local,
|
||||||
|
model: cdLocalModel,
|
||||||
|
cloudKitIdentifier: nil,
|
||||||
|
author: nil
|
||||||
|
)
|
||||||
|
let newRemoteStore: (_ cloudKit: Bool) -> CoreDataPersistentStore = {
|
||||||
|
CoreDataPersistentStore(
|
||||||
|
logger: dependencies.coreDataLogger(),
|
||||||
|
containerName: Constants.shared.containers.remote,
|
||||||
|
model: cdRemoteModel,
|
||||||
|
cloudKitIdentifier: $0 ? BundleConfiguration.mainString(for: .cloudKitId) : nil,
|
||||||
|
author: nil
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Managers
|
||||||
|
|
||||||
let iapManager = IAPManager(
|
let iapManager = IAPManager(
|
||||||
customUserLevel: dependencies.customUserLevel,
|
customUserLevel: dependencies.customUserLevel,
|
||||||
inAppHelper: dependencies.simulatedAppProductHelper(),
|
inAppHelper: dependencies.simulatedAppProductHelper(),
|
||||||
@ -59,13 +80,13 @@ extension AppContext {
|
|||||||
betaChecker: dependencies.betaChecker(),
|
betaChecker: dependencies.betaChecker(),
|
||||||
productsAtBuild: dependencies.productsAtBuild()
|
productsAtBuild: dependencies.productsAtBuild()
|
||||||
)
|
)
|
||||||
|
|
||||||
let processor = dependencies.appProcessor(with: iapManager)
|
let processor = dependencies.appProcessor(with: iapManager)
|
||||||
let tunnelEnvironment = dependencies.tunnelEnvironment()
|
let tunnelReceiptURL = BundleConfiguration.urlForBetaReceipt
|
||||||
|
|
||||||
|
let tunnelEnvironment = dependencies.tunnelEnvironment()
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
let tunnelStrategy = FakeTunnelStrategy(environment: tunnelEnvironment, dataCountInterval: 1000)
|
let tunnelStrategy = FakeTunnelStrategy(environment: tunnelEnvironment, dataCountInterval: 1000)
|
||||||
let mainProfileRepository = dependencies.coreDataProfileRepository(
|
let mainProfileRepository = dependencies.backupProfileRepository(
|
||||||
model: cdRemoteModel,
|
model: cdRemoteModel,
|
||||||
observingResults: true
|
observingResults: true
|
||||||
)
|
)
|
||||||
@ -80,36 +101,15 @@ extension AppContext {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
let profileManager: ProfileManager = {
|
let profileManager = ProfileManager(
|
||||||
let remoteRepositoryBlock: (Bool) -> ProfileRepository = {
|
processor: processor,
|
||||||
let remoteStore = CoreDataPersistentStore(
|
repository: mainProfileRepository,
|
||||||
logger: dependencies.coreDataLogger(),
|
backupRepository: dependencies.backupProfileRepository(
|
||||||
containerName: Constants.shared.containers.remote,
|
model: cdRemoteModel,
|
||||||
model: cdRemoteModel,
|
observingResults: false
|
||||||
cloudKitIdentifier: $0 ? BundleConfiguration.mainString(for: .cloudKitId) : nil,
|
),
|
||||||
author: nil
|
mirrorsRemoteRepository: dependencies.mirrorsRemoteRepository
|
||||||
)
|
)
|
||||||
return AppData.cdProfileRepositoryV3(
|
|
||||||
registry: dependencies.registry,
|
|
||||||
coder: CodableProfileCoder(),
|
|
||||||
context: remoteStore.context,
|
|
||||||
observingResults: true,
|
|
||||||
onResultError: {
|
|
||||||
pp_log(.App.profiles, .error, "Unable to decode remote profile: \($0)")
|
|
||||||
return .ignore
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return ProfileManager(
|
|
||||||
repository: mainProfileRepository,
|
|
||||||
backupRepository: dependencies.backupProfileRepository(
|
|
||||||
model: cdRemoteModel
|
|
||||||
),
|
|
||||||
remoteRepositoryBlock: remoteRepositoryBlock,
|
|
||||||
mirrorsRemoteRepository: dependencies.mirrorsRemoteRepository,
|
|
||||||
processor: processor
|
|
||||||
)
|
|
||||||
}()
|
|
||||||
|
|
||||||
let tunnel = ExtendedTunnel(
|
let tunnel = ExtendedTunnel(
|
||||||
tunnel: Tunnel(strategy: tunnelStrategy),
|
tunnel: Tunnel(strategy: tunnelStrategy),
|
||||||
@ -119,14 +119,7 @@ extension AppContext {
|
|||||||
)
|
)
|
||||||
|
|
||||||
let providerManager: ProviderManager = {
|
let providerManager: ProviderManager = {
|
||||||
let store = CoreDataPersistentStore(
|
let repository = AppData.cdProviderRepositoryV3(context: localStore.backgroundContext())
|
||||||
logger: dependencies.coreDataLogger(),
|
|
||||||
containerName: Constants.shared.containers.local,
|
|
||||||
model: cdLocalModel,
|
|
||||||
cloudKitIdentifier: nil,
|
|
||||||
author: nil
|
|
||||||
)
|
|
||||||
let repository = AppData.cdProviderRepositoryV3(context: store.backgroundContext())
|
|
||||||
return ProviderManager(repository: repository)
|
return ProviderManager(repository: repository)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -155,29 +148,61 @@ extension AppContext {
|
|||||||
return MigrationManager(profileStrategy: profileStrategy, simulation: migrationSimulation)
|
return MigrationManager(profileStrategy: profileStrategy, simulation: migrationSimulation)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let preferencesManager: PreferencesManager = {
|
let preferencesManager = PreferencesManager()
|
||||||
let preferencesStore = CoreDataPersistentStore(
|
|
||||||
logger: dependencies.coreDataLogger(),
|
// MARK: Eligibility
|
||||||
containerName: Constants.shared.containers.remote,
|
|
||||||
model: cdRemoteModel,
|
let onEligibleFeaturesBlock: (Set<AppFeature>) async -> Void = { @MainActor features in
|
||||||
cloudKitIdentifier: BundleConfiguration.mainString(for: .cloudKitId),
|
let isEligibleForSharing = features.contains(.sharing)
|
||||||
author: nil
|
let isRemoteImportingEnabled = isEligibleForSharing && isCloudKitEnabled
|
||||||
)
|
|
||||||
return PreferencesManager(
|
// toggle CloudKit sync based on .sharing eligibility
|
||||||
modulesFactory: {
|
let remoteStore = newRemoteStore(isRemoteImportingEnabled)
|
||||||
|
|
||||||
|
// @Published
|
||||||
|
profileManager.isRemoteImportingEnabled = isRemoteImportingEnabled
|
||||||
|
|
||||||
|
do {
|
||||||
|
pp_log(.app, .info, "\tRefresh remote sync (eligible=\(isEligibleForSharing), CloudKit=\(isCloudKitEnabled))...")
|
||||||
|
|
||||||
|
pp_log(.App.profiles, .info, "\tRefresh remote profiles repository (sync=\(isRemoteImportingEnabled))...")
|
||||||
|
try await profileManager.observeRemote(repository: {
|
||||||
|
AppData.cdProfileRepositoryV3(
|
||||||
|
registry: dependencies.registry,
|
||||||
|
coder: dependencies.profileCoder(),
|
||||||
|
context: remoteStore.context,
|
||||||
|
observingResults: true,
|
||||||
|
onResultError: {
|
||||||
|
pp_log(.App.profiles, .error, "Unable to decode remote profile: \($0)")
|
||||||
|
return .ignore
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}())
|
||||||
|
|
||||||
|
pp_log(.app, .info, "\tRefresh modules preferences repository...")
|
||||||
|
preferencesManager.modulesRepositoryFactory = {
|
||||||
try AppData.cdModulePreferencesRepositoryV3(
|
try AppData.cdModulePreferencesRepositoryV3(
|
||||||
context: preferencesStore.context,
|
context: remoteStore.context,
|
||||||
moduleId: $0
|
moduleId: $0
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
providersFactory: {
|
|
||||||
|
pp_log(.app, .info, "\tRefresh providers preferences repository...")
|
||||||
|
preferencesManager.providersRepositoryFactory = {
|
||||||
try AppData.cdProviderPreferencesRepositoryV3(
|
try AppData.cdProviderPreferencesRepositoryV3(
|
||||||
context: preferencesStore.context,
|
context: remoteStore.context,
|
||||||
providerId: $0
|
providerId: $0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
} catch {
|
||||||
}()
|
pp_log(.App.profiles, .error, "\tUnable to re-observe remote profiles: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
pp_log(.App.profiles, .info, "\tReload profiles required features...")
|
||||||
|
profileManager.reloadRequiredFeatures()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Build
|
||||||
|
|
||||||
return AppContext(
|
return AppContext(
|
||||||
iapManager: iapManager,
|
iapManager: iapManager,
|
||||||
@ -187,11 +212,25 @@ extension AppContext {
|
|||||||
preferencesManager: preferencesManager,
|
preferencesManager: preferencesManager,
|
||||||
registry: dependencies.registry,
|
registry: dependencies.registry,
|
||||||
tunnel: tunnel,
|
tunnel: tunnel,
|
||||||
tunnelReceiptURL: BundleConfiguration.urlForBetaReceipt
|
tunnelReceiptURL: tunnelReceiptURL,
|
||||||
|
onEligibleFeaturesBlock: onEligibleFeaturesBlock
|
||||||
)
|
)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension AppContext {
|
||||||
|
static var isCloudKitEnabled: Bool {
|
||||||
|
#if os(tvOS)
|
||||||
|
true
|
||||||
|
#else
|
||||||
|
if AppCommandLine.contains(.uiTesting) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return FileManager.default.ubiquityIdentityToken != nil
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Dependencies
|
// MARK: - Dependencies
|
||||||
|
|
||||||
private extension Dependencies {
|
private extension Dependencies {
|
||||||
@ -237,15 +276,7 @@ private extension Dependencies {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func backupProfileRepository(model: NSManagedObjectModel) -> ProfileRepository? {
|
func backupProfileRepository(model: NSManagedObjectModel, observingResults: Bool) -> ProfileRepository {
|
||||||
#if targetEnvironment(simulator)
|
|
||||||
nil
|
|
||||||
#else
|
|
||||||
coreDataProfileRepository(model: model, observingResults: false)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
func coreDataProfileRepository(model: NSManagedObjectModel, observingResults: Bool) -> ProfileRepository {
|
|
||||||
let store = CoreDataPersistentStore(
|
let store = CoreDataPersistentStore(
|
||||||
logger: coreDataLogger(),
|
logger: coreDataLogger(),
|
||||||
containerName: Constants.shared.containers.backup,
|
containerName: Constants.shared.containers.backup,
|
||||||
@ -255,7 +286,7 @@ private extension Dependencies {
|
|||||||
)
|
)
|
||||||
return AppData.cdProfileRepositoryV3(
|
return AppData.cdProfileRepositoryV3(
|
||||||
registry: registry,
|
registry: registry,
|
||||||
coder: CodableProfileCoder(),
|
coder: profileCoder(),
|
||||||
context: store.context,
|
context: store.context,
|
||||||
observingResults: observingResults,
|
observingResults: observingResults,
|
||||||
onResultError: {
|
onResultError: {
|
||||||
|
@ -31,14 +31,12 @@ extension ProfileManager {
|
|||||||
public static func forUITesting(withRegistry registry: Registry, processor: ProfileProcessor) -> ProfileManager {
|
public static func forUITesting(withRegistry registry: Registry, processor: ProfileProcessor) -> ProfileManager {
|
||||||
let repository = InMemoryProfileRepository()
|
let repository = InMemoryProfileRepository()
|
||||||
let remoteRepository = InMemoryProfileRepository()
|
let remoteRepository = InMemoryProfileRepository()
|
||||||
let manager = ProfileManager(repository: repository, remoteRepositoryBlock: { _ in
|
let manager = ProfileManager(processor: processor, repository: repository)
|
||||||
remoteRepository
|
|
||||||
}, processor: processor)
|
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
try await manager.observeLocal()
|
try await manager.observeLocal()
|
||||||
try await manager.observeRemote(true)
|
try await manager.observeRemote(repository: remoteRepository)
|
||||||
|
|
||||||
for parameters in mockParameters {
|
for parameters in mockParameters {
|
||||||
var builder = Profile.Builder()
|
var builder = Profile.Builder()
|
||||||
|
@ -33,11 +33,15 @@ extension Dependencies {
|
|||||||
Self.sharedRegistry
|
Self.sharedRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func profileCoder() -> ProfileCoder {
|
||||||
|
CodableProfileCoder()
|
||||||
|
}
|
||||||
|
|
||||||
func neProtocolCoder() -> NEProtocolCoder {
|
func neProtocolCoder() -> NEProtocolCoder {
|
||||||
KeychainNEProtocolCoder(
|
KeychainNEProtocolCoder(
|
||||||
tunnelBundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
|
tunnelBundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
|
||||||
registry: registry,
|
registry: registry,
|
||||||
coder: CodableProfileCoder(),
|
coder: profileCoder(),
|
||||||
keychain: AppleKeychain(group: BundleConfiguration.mainString(for: .keychainGroupId))
|
keychain: AppleKeychain(group: BundleConfiguration.mainString(for: .keychainGroupId))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user