From 360ffec9992d86d32784f799739a16e5b3b228c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Apr 2026 07:56:28 +0000 Subject: [PATCH] Sync @CloudStorage instances to app group for share extension @CloudStorage wraps NSUbiquitousKeyValueStore and cannot be pointed at an app group container. Instead, mirror `instances` into the shared UserDefaults (group.com.ruddarr) so the Share Extension can read them: - Add com.apple.security.application-groups entitlement to the main app - Add ShareExtension/ShareExtension.entitlements with the same entitlement and wire it into the Xcode project (CODE_SIGN_ENTITLEMENTS for both configs) - Add AppSettings.syncInstancesToAppGroup() and call it from saveInstance(), deleteInstance(), and on app launch (WithAppStateModifier) - Observe NSUbiquitousKeyValueStore.didChangeExternallyNotification to also propagate iCloud syncs from other devices into the shared container Closes the TODO in ShareExtension/README.md. https://claude.ai/code/session_01VsmqhzkysTDdfwKPpdt7Fb --- Ruddarr.xcodeproj/project.pbxproj | 4 ++++ Ruddarr/Ruddarr.entitlements | 4 ++++ Ruddarr/Services/AppSettings.swift | 22 ++++++++++++++++++++++ Ruddarr/Utilities/View.swift | 1 + ShareExtension/README.md | 2 +- ShareExtension/ShareExtension.entitlements | 10 ++++++++++ 6 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 ShareExtension/ShareExtension.entitlements diff --git a/Ruddarr.xcodeproj/project.pbxproj b/Ruddarr.xcodeproj/project.pbxproj index ea08ce54..de98a6b4 100644 --- a/Ruddarr.xcodeproj/project.pbxproj +++ b/Ruddarr.xcodeproj/project.pbxproj @@ -402,6 +402,7 @@ BB8100092CD5976B00499666 /* radarr-history.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "radarr-history.json"; sourceTree = ""; }; BB83AFA42CB2080C00349997 /* InstanceCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceCommand.swift; sourceTree = ""; }; BB846F2E2E7DE1BC00E0A367 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = ""; }; + CC5E00012F5A000100000020 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = ""; }; BB865E602B83C11E00ADCEFE /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; BB89058A2E41359E0057C62B /* SeasonCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeasonCard.swift; sourceTree = ""; }; BB89ABC52B756B91009FB62D /* MovieReleaseRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieReleaseRow.swift; sourceTree = ""; }; @@ -645,6 +646,7 @@ CC5E00012F5A000100000004 /* ShareExtension */ = { isa = PBXGroup; children = ( + CC5E00012F5A000100000020 /* ShareExtension.entitlements */, CC5E00012F5A000100000003 /* Info.plist */, CC5E00012F5A000100000002 /* ShareViewController.swift */, CC5E00012F5A000100000012 /* ShareURLHandler.swift */, @@ -1616,6 +1618,7 @@ CC5E00012F5A00010000000D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -1652,6 +1655,7 @@ CC5E00012F5A00010000000E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; diff --git a/Ruddarr/Ruddarr.entitlements b/Ruddarr/Ruddarr.entitlements index 954d70ef..d0abde39 100644 --- a/Ruddarr/Ruddarr.entitlements +++ b/Ruddarr/Ruddarr.entitlements @@ -16,5 +16,9 @@ com.apple.developer.ubiquity-kvstore-identifier $(TeamIdentifierPrefix)$(CFBundleIdentifier) + com.apple.security.application-groups + + group.com.ruddarr + diff --git a/Ruddarr/Services/AppSettings.swift b/Ruddarr/Services/AppSettings.swift index 7cbc37e1..0cab53ac 100644 --- a/Ruddarr/Services/AppSettings.swift +++ b/Ruddarr/Services/AppSettings.swift @@ -12,6 +12,20 @@ class AppSettings: ObservableObject { @CloudStorage("instances") var instances: [Instance] = [] #endif + init() { + NotificationCenter.default.addObserver( + forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification, + object: NSUbiquitousKeyValueStore.default, + queue: .main + ) { [weak self] notification in + guard let keys = notification.userInfo?[NSUbiquitousKeyValueStoreChangedKeysKey] as? [String], + keys.contains("instances") else { return } + Task { @MainActor [weak self] in + self?.syncInstancesToAppGroup() + } + } + } + @AppStorage("icon", store: dependencies.store) var icon: AppIcon = .factory @AppStorage("theme", store: dependencies.store) var theme: Theme = .factory @AppStorage("appearance", store: dependencies.store) var appearance: Appearance = .automatic @@ -77,6 +91,7 @@ extension AppSettings { } Queue.shared.instances = instances + syncInstancesToAppGroup() } func deleteInstance(_ instance: Instance) { @@ -95,6 +110,13 @@ extension AppSettings { } Queue.shared.instances = instances + syncInstancesToAppGroup() + } + + func syncInstancesToAppGroup() { + guard let data = try? JSONEncoder().encode(instances), + let defaults = UserDefaults(suiteName: "group.com.ruddarr") else { return } + defaults.set(data, forKey: "instances") } } diff --git a/Ruddarr/Utilities/View.swift b/Ruddarr/Utilities/View.swift index bb828ffa..1e143a11 100644 --- a/Ruddarr/Utilities/View.swift +++ b/Ruddarr/Utilities/View.swift @@ -85,6 +85,7 @@ private struct WithAppStateModifier: ViewModifier { .environment(SonarrInstance(sonarrInstance)) .task { Queue.shared.instances = settings.instances + settings.syncInstancesToAppGroup() setSentryContext(for: "Configuration", settings.context()) await setSentryCloudKitContext() } diff --git a/ShareExtension/README.md b/ShareExtension/README.md index ba9bc59d..4999f51d 100644 --- a/ShareExtension/README.md +++ b/ShareExtension/README.md @@ -34,7 +34,7 @@ in the main app (`RadarrInstance`/`SonarrInstance`, `AppSettings`, ## TODOs -- [ ] Sync instances to App Groups shared container from the main app +- [x] Sync instances to App Groups shared container from the main app - [x] Replace `AsyncImage` with NukeUI `LazyImage` (add Nuke dependency to extension target) - [ ] Deduplicate models/views by extracting shared code into a framework or Swift package - [x] Handle case when no instances are configured (show setup instructions) diff --git a/ShareExtension/ShareExtension.entitlements b/ShareExtension/ShareExtension.entitlements new file mode 100644 index 00000000..5f0bc9f5 --- /dev/null +++ b/ShareExtension/ShareExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.ruddarr + + +