From 44ccd2153693c742d7b6dc1307ec8abacc1b87f1 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Thu, 16 Mar 2023 16:49:09 +0100 Subject: [PATCH] Fetch full profiles from Core Data (#258) * Fetch full profiles * Manage full profiles in organizer --- .../App/Views/OrganizerView+Profiles.swift | 22 +++++----- Passepartout/App/Views/ProfileRow.swift | 6 +-- Passepartout/AppShared/L10n/Core+L10n.swift | 6 +++ .../PassepartoutCore/Models/Profile.swift | 6 --- .../ProfileManager+Extensions.swift | 2 +- .../CoreDataProfileManagerStrategy.swift | 12 ++--- .../Managers/ProfileManager.swift | 44 ++++++++++--------- .../Managers/ProfileManagerStrategy.swift | 4 +- .../Repositories/ProfileRepository.swift | 13 +++--- 9 files changed, 57 insertions(+), 58 deletions(-) diff --git a/Passepartout/App/Views/OrganizerView+Profiles.swift b/Passepartout/App/Views/OrganizerView+Profiles.swift index 587f7ed6..c26aacd2 100644 --- a/Passepartout/App/Views/OrganizerView+Profiles.swift +++ b/Passepartout/App/Views/OrganizerView+Profiles.swift @@ -68,7 +68,7 @@ extension OrganizerView { } private var profilesView: some View { - ForEach(sortedHeaders, content: profileRow(forHeader:)) + ForEach(sortedProfiles, content: profileRow(forProfile:)) .onDelete(perform: removeProfiles) } @@ -79,25 +79,25 @@ extension OrganizerView { } } - private func profileRow(forHeader header: Profile.Header) -> some View { - NavigationLink(tag: header.id, selection: $profileManager.currentProfileId) { + private func profileRow(forProfile profile: Profile) -> some View { + NavigationLink(tag: profile.id, selection: $profileManager.currentProfileId) { ProfileView() } label: { - profileLabel(forHeader: header) + profileLabel(forProfile: profile) }.contextMenu { - ProfileContextMenu(header: header) + ProfileContextMenu(header: profile.header) } } - private func profileLabel(forHeader header: Profile.Header) -> some View { + private func profileLabel(forProfile profile: Profile) -> some View { ProfileRow( - header: header, - isActiveProfile: profileManager.isActiveProfile(header.id) + profile: profile, + isActiveProfile: profileManager.isActiveProfile(profile.id) ) } - private var sortedHeaders: [Profile.Header] { - profileManager.headers + private var sortedProfiles: [Profile] { + profileManager.profiles .sorted() // .sorted { // if profileManager.isActiveProfile($0.id) { @@ -111,7 +111,7 @@ extension OrganizerView { } private func removeProfiles(at offsets: IndexSet) { - let currentHeaders = sortedHeaders + let currentHeaders = sortedProfiles var toDelete: [UUID] = [] offsets.forEach { toDelete.append(currentHeaders[$0].id) diff --git a/Passepartout/App/Views/ProfileRow.swift b/Passepartout/App/Views/ProfileRow.swift index 2ee187d2..f34569b4 100644 --- a/Passepartout/App/Views/ProfileRow.swift +++ b/Passepartout/App/Views/ProfileRow.swift @@ -27,7 +27,7 @@ import SwiftUI import PassepartoutLibrary struct ProfileRow: View { - let header: Profile.Header + let profile: Profile let isActiveProfile: Bool @@ -35,7 +35,7 @@ struct ProfileRow: View { debugChanges() return HStack { VStack(alignment: .leading, spacing: 5) { - Text(header.name) + Text(profile.header.name) .font(.headline) .themeLongTextStyle() @@ -44,7 +44,7 @@ struct ProfileRow: View { .themeSecondaryTextStyle() } Spacer() - VPNToggle(profileId: header.id, rateLimit: Constants.RateLimit.vpnToggle) + VPNToggle(profileId: profile.id, rateLimit: Constants.RateLimit.vpnToggle) .labelsHidden() }.padding([.top, .bottom], 10) } diff --git a/Passepartout/AppShared/L10n/Core+L10n.swift b/Passepartout/AppShared/L10n/Core+L10n.swift index 8ce5e5b4..c1eebe0a 100644 --- a/Passepartout/AppShared/L10n/Core+L10n.swift +++ b/Passepartout/AppShared/L10n/Core+L10n.swift @@ -74,6 +74,12 @@ extension ObservableVPNState { } } +extension Profile: Comparable { + public static func <(lhs: Self, rhs: Self) -> Bool { + lhs.header < rhs.header + } +} + extension Profile.Header: Comparable { public static func <(lhs: Self, rhs: Self) -> Bool { lhs.name.lowercased() < rhs.name.lowercased() diff --git a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile.swift b/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile.swift index 4e328f7a..ba23ec3d 100644 --- a/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile.swift +++ b/PassepartoutLibrary/Sources/PassepartoutCore/Models/Profile.swift @@ -129,9 +129,3 @@ extension Profile { header.id == Self.placeholder.id } } - -extension Profile.Header { - public var isPlaceholder: Bool { - id == Profile.placeholder.id - } -} diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/ProfileManager+Extensions.swift b/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/ProfileManager+Extensions.swift index abcba144..cc356b65 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/ProfileManager+Extensions.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProfiles/Extensions/ProfileManager+Extensions.swift @@ -28,7 +28,7 @@ import PassepartoutCore extension ProfileManager { public var hasProfiles: Bool { - !headers.isEmpty + !profiles.isEmpty } public var activeProfile: Profile? { diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/CoreDataProfileManagerStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/CoreDataProfileManagerStrategy.swift index d8998127..c2d0813f 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/CoreDataProfileManagerStrategy.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/CoreDataProfileManagerStrategy.swift @@ -31,15 +31,15 @@ import PassepartoutUtils public class CoreDataProfileManagerStrategy: ProfileManagerStrategy { private let profileRepository: ProfileRepository - private let fetchedHeaders: FetchedValueHolder<[UUID: Profile.Header]> + private let fetchedProfiles: FetchedValueHolder<[UUID: Profile]> public init(persistence: Persistence) { profileRepository = ProfileRepository(persistence.context) - fetchedHeaders = profileRepository.fetchedHeaders() + fetchedProfiles = profileRepository.fetchedProfiles() } - public var allHeaders: [UUID: Profile.Header] { - fetchedHeaders.value + public var allProfiles: [UUID: Profile] { + fetchedProfiles.value } public func profiles() -> [Profile] { @@ -62,8 +62,8 @@ public class CoreDataProfileManagerStrategy: ProfileManagerStrategy { profileRepository.removeProfiles(withIds: ids) } - public func willUpdateProfiles() -> AnyPublisher<[UUID : Profile.Header], Never> { - fetchedHeaders.$value + public func willUpdateProfiles() -> AnyPublisher<[UUID : Profile], Never> { + fetchedProfiles.$value .eraseToAnyPublisher() } } diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager.swift b/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager.swift index fd61e9a8..708fcfe1 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManager.swift @@ -113,25 +113,25 @@ public final class ProfileManager: ObservableObject { // MARK: Index extension ProfileManager { - private var allHeaders: [UUID: Profile.Header] { - strategy.allHeaders - } - - public var headers: [Profile.Header] { - Array(allHeaders.values) + private var allProfiles: [UUID: Profile] { + strategy.allProfiles } public var profiles: [Profile] { strategy.profiles() } + + public var headers: [Profile.Header] { + Array(allProfiles.values.map(\.header)) + } public func isExistingProfile(withId id: UUID) -> Bool { - allHeaders[id] != nil + allProfiles[id] != nil } public func isExistingProfile(withName name: String) -> Bool { - allHeaders.contains { - $0.value.name == name + allProfiles.contains { + $0.value.header.name == name } } } @@ -189,7 +189,7 @@ extension ProfileManager { pp_log.info("\tDeactivating profile...") activeProfileId = nil } - } else if allHeaders.isEmpty { + } else if allProfiles.isEmpty { pp_log.info("\tActivating first profile...") activeProfileId = profile.id } @@ -215,7 +215,7 @@ extension ProfileManager { @available(*, deprecated, message: "only use for testing") public func removeAllProfiles() { - let ids = Array(allHeaders.keys) + let ids = Array(allProfiles.keys) removeProfiles(withIds: ids) } @@ -314,14 +314,14 @@ extension ProfileManager { }.store(in: &cancellables) } - private func willUpdateProfiles(_ newHeaders: [UUID: Profile.Header]) { - pp_log.debug("Profiles updated: \(newHeaders)") + private func willUpdateProfiles(_ newProfiles: [UUID: Profile]) { + pp_log.debug("Profiles updated: \(newProfiles.values.map(\.header))") defer { objectWillChange.send() } // IMPORTANT: invalidate current profile if deleted - if !currentProfile.value.isPlaceholder && !newHeaders.keys.contains(currentProfile.value.id) { + if !currentProfile.value.isPlaceholder && !newProfiles.keys.contains(currentProfile.value.id) { pp_log.info("\tCurrent profile deleted, invalidating...") currentProfile.value = .placeholder } @@ -332,7 +332,7 @@ extension ProfileManager { currentProfile.value = newProfile } - if let activeProfileId = activeProfileId, !newHeaders.keys.contains(activeProfileId) { + if let activeProfileId = activeProfileId, !newProfiles.keys.contains(activeProfileId) { pp_log.info("\tActive profile was deleted") self.activeProfileId = nil } @@ -342,12 +342,12 @@ extension ProfileManager { // IMPORTANT: defer task to avoid recursive saves (is non-main thread an issue?) // FIXME: Core Data, not sure about this workaround Task { - fixDuplicateNames(in: newHeaders) + fixDuplicateNames(in: newProfiles) } } - private func fixDuplicateNames(in newHeaders: [UUID: Profile.Header]) { - var allNames = newHeaders.values.map(\.name) + private func fixDuplicateNames(in newProfiles: [UUID: Profile]) { + var allNames = newProfiles.values.map(\.header.name) let distinctNames = Set(allNames) distinctNames.forEach { guard let i = allNames.firstIndex(of: $0) else { @@ -364,9 +364,11 @@ extension ProfileManager { var renamedProfiles: [Profile] = [] duplicates.forEach { name in - let headers = newHeaders.values.filter { - $0.name == name - } + let headers = newProfiles.values + .map(\.header) + .filter { + $0.name == name + } guard headers.count > 1 else { assertionFailure("Name '\(name)' marked as duplicate but headers.count not > 1") return diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy.swift b/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy.swift index ad23d6d3..cd441d0d 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProfiles/Managers/ProfileManagerStrategy.swift @@ -28,7 +28,7 @@ import Combine import PassepartoutCore public protocol ProfileManagerStrategy { - var allHeaders: [UUID: Profile.Header] { get } + var allProfiles: [UUID: Profile] { get } func profiles() -> [Profile] @@ -38,7 +38,7 @@ public protocol ProfileManagerStrategy { func removeProfiles(withIds ids: [UUID]) - func willUpdateProfiles() -> AnyPublisher<[UUID: Profile.Header], Never> + func willUpdateProfiles() -> AnyPublisher<[UUID: Profile], Never> } extension ProfileManagerStrategy { diff --git a/PassepartoutLibrary/Sources/PassepartoutProfiles/Repositories/ProfileRepository.swift b/PassepartoutLibrary/Sources/PassepartoutProfiles/Repositories/ProfileRepository.swift index 430821f9..7de9a951 100644 --- a/PassepartoutLibrary/Sources/PassepartoutProfiles/Repositories/ProfileRepository.swift +++ b/PassepartoutLibrary/Sources/PassepartoutProfiles/Repositories/ProfileRepository.swift @@ -35,29 +35,26 @@ class ProfileRepository: Repository { self.context = context } - func fetchedHeaders() -> FetchedValueHolder<[UUID: Profile.Header]> { + func fetchedProfiles() -> FetchedValueHolder<[UUID: Profile]> { let request: NSFetchRequest = CDProfile.fetchRequest() request.sortDescriptors = [ .init(keyPath: \CDProfile.lastUpdate, ascending: true) ] request.propertiesToFetch = [ - "uuid", - "lastUpdate", - "name", - "providerName" + "json" ] return .init( context: context, request: request, mapping: { - $0.reduce(into: [UUID: Profile.Header]()) { + $0.reduce(into: [UUID: Profile]()) { guard let dto = $1 as? CDProfile else { return } - guard let header = ProfileHeaderMapper.toModel(dto) else { + guard let profile = try? ProfileMapper.toModel(dto) else { return } - $0[header.id] = header + $0[profile.id] = profile } }, initial: [:]