Allow erasing remote iCloud store (#351)
Convenient for those with privacy concerns.
This commit is contained in:
parent
a4ca8cc996
commit
791b6be7d5
|
@ -45,6 +45,12 @@ extension Constants {
|
|||
}()
|
||||
}
|
||||
|
||||
enum CloudKit {
|
||||
static let containerId: String = bundleConfig("cloudkit_id")
|
||||
|
||||
static let coreDataZone = "com.apple.coredata.cloudkit.zone"
|
||||
}
|
||||
|
||||
enum Plugins {
|
||||
static let macBridgeName = "PassepartoutMac.bundle"
|
||||
}
|
||||
|
|
|
@ -137,4 +137,8 @@ extension AppContext {
|
|||
lastIsCloudSyncingEnabled = isCloudSyncingEnabled
|
||||
}
|
||||
}
|
||||
|
||||
func eraseCloudKitStore() async {
|
||||
await coreContext.eraseCloudKitStore()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import CloudKit
|
||||
import Combine
|
||||
import Foundation
|
||||
import PassepartoutLibrary
|
||||
|
@ -35,6 +36,8 @@ final class CoreContext {
|
|||
|
||||
private let persistenceManager: PersistenceManager
|
||||
|
||||
private(set) var vpnPersistence: VPNPersistence
|
||||
|
||||
let upgradeManager: UpgradeManager
|
||||
|
||||
let providerManager: ProviderManager
|
||||
|
@ -63,7 +66,7 @@ final class CoreContext {
|
|||
upgradeManager.migrate(toVersion: Constants.Global.appVersionNumber)
|
||||
|
||||
persistenceManager = PersistenceManager(store: store)
|
||||
let vpnPersistence = persistenceManager.vpnPersistence(
|
||||
vpnPersistence = persistenceManager.vpnPersistence(
|
||||
withName: Constants.Persistence.profilesContainerName,
|
||||
cloudKit: store.isCloudSyncingEnabled
|
||||
)
|
||||
|
@ -140,14 +143,17 @@ private extension CoreContext {
|
|||
|
||||
extension CoreContext {
|
||||
func reloadCloudKitObjects(isEnabled: Bool) {
|
||||
let vpnPersistence = persistenceManager.vpnPersistence(
|
||||
vpnPersistence = persistenceManager.vpnPersistence(
|
||||
withName: Constants.Persistence.profilesContainerName,
|
||||
cloudKit: isEnabled
|
||||
)
|
||||
profileManager.swapProfileRepository(vpnPersistence.profileRepository())
|
||||
}
|
||||
|
||||
func eraseCloudKitStore() {
|
||||
// TODO: iCloud, erase remote records
|
||||
func eraseCloudKitStore() async {
|
||||
await vpnPersistence.eraseCloudKitStore(
|
||||
fromContainerWithId: Constants.CloudKit.containerId,
|
||||
zoneId: .init(zoneName: Constants.CloudKit.coreDataZone)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,6 +101,8 @@
|
|||
<string>$(CFG_APPSTORE_ID)</string>
|
||||
<key>group_id</key>
|
||||
<string>group.$(CFG_GROUP_ID)</string>
|
||||
<key>cloudkit_id</key>
|
||||
<string>iCloud.$(CFG_GROUP_ID)</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -257,6 +257,8 @@ enum Unlocalized {
|
|||
enum Other {
|
||||
static let siri = "Siri"
|
||||
|
||||
static let iCloud = "iCloud"
|
||||
|
||||
static let totp = "TOTP"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ struct SettingsView: View {
|
|||
|
||||
@Binding private var shouldEnableCloudSyncing: Bool
|
||||
|
||||
@State private var isErasingCloudStore = false
|
||||
|
||||
private let versionString = Constants.Global.appVersionString
|
||||
|
||||
init() {
|
||||
|
@ -45,14 +47,20 @@ struct SettingsView: View {
|
|||
|
||||
_shouldEnableCloudSyncing = .init {
|
||||
AppContext.shared.shouldEnableCloudSyncing
|
||||
} set: {
|
||||
AppContext.shared.shouldEnableCloudSyncing = $0
|
||||
} set: { isEnabled in
|
||||
withAnimation {
|
||||
AppContext.shared.shouldEnableCloudSyncing = isEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
preferencesSection
|
||||
#endif
|
||||
iCloudSection
|
||||
diagnosticsSection
|
||||
aboutSection
|
||||
}.toolbar {
|
||||
themeCloseItem(presentationMode: presentationMode)
|
||||
|
@ -66,29 +74,48 @@ struct SettingsView: View {
|
|||
private extension SettingsView {
|
||||
var preferencesSection: some View {
|
||||
Section {
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
Toggle(L10n.Settings.Items.LocksInBackground.caption, isOn: $locksInBackground)
|
||||
#endif
|
||||
Toggle(L10n.Settings.Items.ShouldEnableCloudSyncing.caption, isOn: $shouldEnableCloudSyncing)
|
||||
} header: {
|
||||
Text(L10n.Preferences.title)
|
||||
}
|
||||
}
|
||||
|
||||
var iCloudSection: some View {
|
||||
Section {
|
||||
Toggle(L10n.Settings.Items.ShouldEnableCloudSyncing.caption, isOn: $shouldEnableCloudSyncing)
|
||||
Button(L10n.Settings.Items.EraseCloudStore.caption) {
|
||||
isErasingCloudStore = true
|
||||
Task {
|
||||
await AppContext.shared.eraseCloudKitStore()
|
||||
isErasingCloudStore = false
|
||||
}
|
||||
}.withTrailingProgress(when: isErasingCloudStore)
|
||||
.disabled(shouldEnableCloudSyncing || isErasingCloudStore)
|
||||
} header: {
|
||||
Text(Unlocalized.Other.iCloud)
|
||||
} footer: {
|
||||
Text(L10n.Settings.Sections.Icloud.footer)
|
||||
}
|
||||
}
|
||||
|
||||
var diagnosticsSection: some View {
|
||||
Section {
|
||||
DiagnosticsRow(currentProfile: profileManager.currentProfile)
|
||||
}
|
||||
}
|
||||
|
||||
var aboutSection: some View {
|
||||
Section {
|
||||
NavigationLink {
|
||||
AboutView()
|
||||
} label: {
|
||||
Text(L10n.About.title)
|
||||
}
|
||||
NavigationLink {
|
||||
DonateView()
|
||||
} label: {
|
||||
Text(L10n.Settings.Items.Donate.caption)
|
||||
}.disabled(!productManager.canMakePayments())
|
||||
|
||||
DiagnosticsRow(currentProfile: profileManager.currentProfile)
|
||||
NavigationLink {
|
||||
AboutView()
|
||||
} label: {
|
||||
Text(L10n.About.title)
|
||||
}
|
||||
} footer: {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
|
|
@ -331,8 +331,10 @@
|
|||
/* MARK: SettingsView */
|
||||
|
||||
"settings.title" = "Settings";
|
||||
"settings.sections.icloud.footer" = "Disable sync to allow erase. To erase the iCloud store securely, do so on all your synced devices. This will not affect local profiles.";
|
||||
"settings.items.locks_in_background.caption" = "Lock app access";
|
||||
"settings.items.should_enable_cloud_syncing.caption" = "Sync with iCloud";
|
||||
"settings.items.erase_cloud_store.caption" = "Erase iCloud store";
|
||||
"settings.items.donate.caption" = "Make a donation";
|
||||
|
||||
/* MARK: AboutView */
|
||||
|
|
|
@ -921,6 +921,10 @@ internal enum L10n {
|
|||
/// Make a donation
|
||||
internal static let caption = L10n.tr("Localizable", "settings.items.donate.caption", fallback: "Make a donation")
|
||||
}
|
||||
internal enum EraseCloudStore {
|
||||
/// Erase iCloud store
|
||||
internal static let caption = L10n.tr("Localizable", "settings.items.erase_cloud_store.caption", fallback: "Erase iCloud store")
|
||||
}
|
||||
internal enum LocksInBackground {
|
||||
/// Lock app access
|
||||
internal static let caption = L10n.tr("Localizable", "settings.items.locks_in_background.caption", fallback: "Lock app access")
|
||||
|
@ -930,6 +934,12 @@ internal enum L10n {
|
|||
internal static let caption = L10n.tr("Localizable", "settings.items.should_enable_cloud_syncing.caption", fallback: "Sync with iCloud")
|
||||
}
|
||||
}
|
||||
internal enum Sections {
|
||||
internal enum Icloud {
|
||||
/// Disable sync to allow erase. To erase the iCloud store securely, do so on all your synced devices. This will not affect local profiles.
|
||||
internal static let footer = L10n.tr("Localizable", "settings.sections.icloud.footer", fallback: "Disable sync to allow erase. To erase the iCloud store securely, do so on all your synced devices. This will not affect local profiles.")
|
||||
}
|
||||
}
|
||||
}
|
||||
internal enum Shortcuts {
|
||||
internal enum Add {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import CloudKit
|
||||
import Combine
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
@ -69,7 +70,25 @@ public final class CoreDataPersistentStore {
|
|||
container.viewContext.transactionAuthor = author
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CoreDataPersistentStore {
|
||||
public var context: NSManagedObjectContext {
|
||||
container.viewContext
|
||||
}
|
||||
|
||||
public var backgroundContext: NSManagedObjectContext {
|
||||
container.newBackgroundContext()
|
||||
}
|
||||
|
||||
public var coordinator: NSPersistentStoreCoordinator {
|
||||
container.persistentStoreCoordinator
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Development
|
||||
|
||||
extension CoreDataPersistentStore {
|
||||
public var containerURLs: [URL]? {
|
||||
guard let url = container.persistentStoreDescriptions.first?.url else {
|
||||
return nil
|
||||
|
@ -104,13 +123,3 @@ public final class CoreDataPersistentStore {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CoreDataPersistentStore {
|
||||
public var context: NSManagedObjectContext {
|
||||
container.viewContext
|
||||
}
|
||||
|
||||
public var coordinator: NSPersistentStoreCoordinator {
|
||||
container.persistentStoreCoordinator
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import CloudKit
|
||||
import CoreData
|
||||
import Foundation
|
||||
import PassepartoutCore
|
||||
|
@ -54,4 +55,15 @@ public final class VPNPersistence {
|
|||
public func profileRepository() -> ProfileRepository {
|
||||
CDProfileRepository(store.context)
|
||||
}
|
||||
|
||||
// WARNING: this is not running on main actor
|
||||
public func eraseCloudKitStore(fromContainerWithId containerId: String, zoneId: CKRecordZone.ID) async {
|
||||
do {
|
||||
let container = CKContainer(identifier: containerId)
|
||||
let db = container.privateCloudDatabase
|
||||
try await db.deleteRecordZone(withID: zoneId)
|
||||
} catch {
|
||||
pp_log.error("Unable to erase CloudKit store: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue