From 364ccc2d522bf783f368bb3afa2d69aaf2d1848c Mon Sep 17 00:00:00 2001 From: Shawn Schantz Date: Fri, 17 Apr 2026 14:13:23 -0400 Subject: [PATCH] feat(mac): migrate keyman packages individually to group containers also rename Keyman-Keyboards directory to Keyman-Packages --- mac/Config/Config/ConfigView.swift | 6 + .../Keyman4MacIM/KMDataRepository.h | 3 + .../Keyman4MacIM/KMDataRepository.m | 135 ++++++++++++---- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 18 +-- .../Keyman4MacIM/KMSettingsRepository.h | 2 + .../Keyman4MacIM/KMSettingsRepository.m | 145 ++++++++++++------ .../KeymanSettings/SettingsContainer.swift | 10 +- .../Sources/Persistence/KeymanPaths.swift | 4 +- .../Persistence/SettingsRepository.swift | 25 ++- .../KeymanSettingsTests.swift | 2 +- 10 files changed, 257 insertions(+), 93 deletions(-) diff --git a/mac/Config/Config/ConfigView.swift b/mac/Config/Config/ConfigView.swift index 64ee8f460f..a963a2784c 100644 --- a/mac/Config/Config/ConfigView.swift +++ b/mac/Config/Config/ConfigView.swift @@ -24,6 +24,12 @@ struct ConfigView: View { Button("debug") { settings.debug() } + Button("log defaults") { + settings.logSettings() + } + Button("clear defaults") { + settings.clearSettings() + } Spacer() } .padding() diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index 0dfc229e31..8b4b3a1f4e 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -9,7 +9,10 @@ NS_ASSUME_NONNULL_BEGIN +extern NSString *const kKeymanGroupId; + @interface KMDataRepository : NSObject + // keyman18DataDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman' @property (readonly) NSURL *keyman18DataDirectory; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 894a3b4fc4..fae02818f5 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -11,6 +11,11 @@ #import "KMDataRepository.h" #import "KMLogs.h" +// the name of the app group shared by the Keyman input method and the Keyman configuration app +// an iOS-style app group of this name is defined for Keyman on developer.apple.com and linked +// to the each app's provisioning profile +NSString *const kKeymanGroupId = @"group.com.keyman"; + @interface KMDataRepository () @property (readonly) NSURL *applicationSupportSubDirectory; @property (readonly) NSURL *documentsSubDirectory; @@ -22,7 +27,7 @@ * Three directory trees are represented by the following properties, one in active use * and two that are obsolete. * The actively used directories, introduced in Keyman 19, are shared via the app group `group.com.keyman`: - * 'Group Containers/group.com.keyman/Library/Application Support/Keyman-Keyboards/ + * 'Group Containers/group.com.keyman/Library/Application Support/Keyman-Packages/ * The obsolete directories from Keyman 18 are: * applicationSupportSubDirectory: '~/Library/Application Support' * keyman18DataDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman' @@ -49,10 +54,7 @@ NSString *const kKeyboardsDirectoryName = @"Keyman-Keyboards"; */ NSString *const kKeymanSubdirectoryName = @"keyman.inputmethod.Keyman"; -//NSString *const kKeymanGroupId = @"3YE4W86L3G.com.keyman"; -NSString *const kKeymanGroupId = @"group.com.keyman"; - -NSString *const kContainerKeyboardsPartialPath = @"Library/Application Support/Keyman-Keyboards"; +NSString *const kContainerKeyboardsPartialPath = @"Library/Application Support/Keyman-Packages"; + (KMDataRepository *)shared { static KMDataRepository *shared = nil; @@ -63,6 +65,52 @@ NSString *const kContainerKeyboardsPartialPath = @"Library/Application Support/K return shared; } +/** + * Returns true if URL is an existing directory that is not empty + */ ++ (BOOL)isExistingNonEmptyDirectory:(NSURL *)directoryUrl { + return [KMDataRepository isExistingDirectory: directoryUrl] && ![KMDataRepository isEmptyDirectory: directoryUrl]; +} + +/** + * Returns true if URL is an existing directory that is empty + */ ++ (BOOL)isExistingEmptyDirectory:(NSURL *)directoryUrl { + return [KMDataRepository isExistingDirectory: directoryUrl] && [KMDataRepository isEmptyDirectory: directoryUrl]; +} + +/** + * Returns true if the directory is empty + */ ++ (BOOL)isExistingDirectory:(NSURL *)directoryUrl { + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL nonEmptyDirectoryExists = false; + BOOL isDirectory; + BOOL urlExists = ([fileManager fileExistsAtPath:directoryUrl.path isDirectory:&isDirectory]); + return urlExists && isDirectory; +} + +/** + * Returns true if the directory is empty + */ ++ (BOOL)isEmptyDirectory:(NSURL *)directoryUrl { + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isEmpty = true; + + NSError *error = nil; + NSArray *contents = [fileManager contentsOfDirectoryAtPath:directoryUrl.path error:&error]; + + if (error) { + os_log_debug([KMLogs dataLog], "cannot read directory: %@", directoryUrl.path); + } else if (contents.count > 0) { + isEmpty = false; + } else { + isEmpty = true; + } + + return isEmpty; +} + - (NSURL *)documentsSubDirectory { if (_documentsSubDirectory == nil) { NSError *directoryError = nil; @@ -225,35 +273,24 @@ NSString *const kContainerKeyboardsPartialPath = @"Library/Application Support/K * otherwise we would not be attempting to migrate. */ - (BOOL)keyboardsExistInDocumentsDirectory { - NSFileManager *fileManager = [NSFileManager defaultManager]; - BOOL isDir; - BOOL exists = ([fileManager fileExistsAtPath:self.keyman17KeyboardsDirectory.path isDirectory:&isDir]); - return exists; + return [KMDataRepository isExistingNonEmptyDirectory: self.keyman17KeyboardsDirectory]; } /** * Only called from migrateDataForKeyman19. - * Checks to see if the keyboards directory exists in '~/Library/Application Support/keyman.inputmethod.Keyman' + * Checks to see if the keyboards directory exists in '~/Library/Application Support/keyman.inputmethod.Keyman' and is not empty */ - (BOOL)keyboardsExistInInputMethodDataDirectory { - NSFileManager *fileManager = [NSFileManager defaultManager]; - BOOL isDir; - BOOL exists = ([fileManager fileExistsAtPath:self.keyman18KeyboardsDirectory.path isDirectory:&isDir]); - return exists; + return [KMDataRepository isExistingNonEmptyDirectory: self.keyman18KeyboardsDirectory]; } -// TODO: unused? /** - * Checks to see if the keyboards directory exists in '~/Library/Group Containers/group.com.keyman/Library/Application Support' + * Checks to see if the keyboards directory exists in '~/Library/Group Containers/group.com.keyman/Library/Application Support' and is not empty */ - (BOOL)keyboardsExistInGroupContainerDirectory { - NSFileManager *fileManager = [NSFileManager defaultManager]; - BOOL isDir; - BOOL exists = ([fileManager fileExistsAtPath:self.keyman19KeyboardsDirectory.path isDirectory:&isDir]); - return exists; + return [KMDataRepository isExistingNonEmptyDirectory: self.keyman19KeyboardsDirectory]; } - // TODO: delete - no reason to move data more than once /** * Migrate the keyboards data from the old location in '~/Documents' to the location '~/Library/Application Support/keyman.inputmethod.Keyman' @@ -283,6 +320,49 @@ NSString *const kContainerKeyboardsPartialPath = @"Library/Application Support/K return didMoveData; } +/** + * Move all the packages contained in the directory + */ ++ (void)movePackages: (NSURL*)sourceDirectory to: (NSURL*)destinationDirectory { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSArray *keys = @[NSURLIsDirectoryKey]; + + // fetch the individual keyboard package directories within the keyboards directory + NSArray *packageDirectories = [fileManager contentsOfDirectoryAtURL:sourceDirectory includingPropertiesForKeys:keys options:NSDirectoryEnumerationSkipsHiddenFiles error:nil]; + + // Iterate and filter for directories + for (NSURL *packageUrl in packageDirectories) { + NSNumber *isDirectory; + [packageUrl getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil]; + + if ([isDirectory boolValue]) { + NSString *packageName = packageUrl.lastPathComponent; + NSURL *packageSourceUrl = [sourceDirectory URLByAppendingPathComponent:packageName isDirectory: true]; + NSURL *packageDestinationUrl = [destinationDirectory URLByAppendingPathComponent:packageName isDirectory: true]; + + os_log_info([KMLogs dataLog], "moving package directory: '%{public}@'", packageName); + BOOL movedDirectory = [KMDataRepository moveDirectory: packageSourceUrl to: packageDestinationUrl]; + } + } +} + +/** + * Move a single directory + */ ++ (BOOL)moveDirectory: (NSURL*)sourceDirectory to: (NSURL*)destinationDirectory { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *moveError = nil; + BOOL didMoveData = [fileManager moveItemAtURL:sourceDirectory + toURL:destinationDirectory + error:&moveError]; + if (moveError) { + os_log_error([KMLogs dataLog], "data migration failed: '%{public}@'", moveError.localizedDescription); + } else { + os_log_info([KMLogs dataLog], "data migrated successfully to: '%{public}@'", destinationDirectory.path); + } + return didMoveData; +} + /** * Migrate the keyboards data from the input method specific location in '~/Application Support/keyman.inputmethod.Keyman/' * to the shared location in '~/Library/Group Containers/group.com.keyman/Library/Application Support' @@ -296,20 +376,15 @@ NSString *const kContainerKeyboardsPartialPath = @"Library/Application Support/K // only move data if there is something to move if (dataExistsInOldLocation) { - NSError *moveError = nil; - didMoveData = [fileManager moveItemAtURL:self.keyman18KeyboardsDirectory - toURL:self.keyman19KeyboardsDirectory - error:&moveError]; - if (moveError) { - os_log_error([KMLogs dataLog], "data migration failed: '%{public}@'", moveError.localizedDescription); - } else { - os_log_info([KMLogs dataLog], "data migrated successfully to: '%{public}@'", self.keyman19KeyboardsDirectory.path); - } + + [KMDataRepository.shared createKeyman19SharedDirectoriesIfNecessary]; + [KMDataRepository movePackages:[self keyman18KeyboardsDirectory] to:[self keyman19KeyboardsDirectory]]; } return didMoveData; } + - (NSString*)buildFullPath:(NSString *)fromPartialPath { NSString *fullPath = [self.keyman18KeyboardsDirectory.path stringByAppendingString:fromPartialPath]; os_log_debug([KMLogs dataLog], "buildFullPath: '%{public}@' fromPartialPath '%{public}@'", diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 01716589bd..7cf5e178c9 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -756,29 +756,23 @@ CGEventRef eventTapFunction(CGEventTapProxy proxy, CGEventType type, CGEventRef - (void)prepareStorage { os_log_debug([KMLogs dataLog], "*** prepareStorage ***"); -// TODO: create directory before copy during migration? -// [KMDataRepository.shared createKeyman19SharedDirectoriesIfNecessary]; -// [KMDataRepository.shared createKeyman18DataDirectoryIfNecessary]; - -// Keyman 18 data migration: TODO: uncomment and modify for double migration + // Keyman 18 data migration: TODO: uncomment and modify for double migration /* if ([KMSettingsRepository.shared keyman18DataMigrationNeeded]) { [KMDataRepository.shared migrateDataForKeyman18]; [KMSettingsRepository.shared migrateSettingsForKeyman18]; } */ - -// Keyman 19 data migration + + // if necessary, migrate settings and keyboard data for compatibility with Keyman 19 if ([KMSettingsRepository.shared keyman19SettingsMigrationNeeded]) { [KMDataRepository.shared migrateDataForKeyman19]; - //[KMSettingsRepository.shared migrateSettingsForKeyman18]; + [KMSettingsRepository.shared migrateSettingsForKeyman19]; } - - [KMDataRepository.shared createKeyman19SharedDirectoriesIfNecessary]; - // TODO: delete + // TODO: delete, instead manage shared settings //[KMDataRepository.shared createKeyboardsDirectoryIfNecessary]; - [KMSettingsRepository.shared setDataModelVersionIfNecessary]; + //[KMSettingsRepository.shared setDataModelVersionIfNecessary]; } - (void)setDefaultKeymanMenuItems { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h index 68dd54db62..862a9c47f6 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h @@ -18,6 +18,8 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)keyman19SettingsMigrationNeeded; - (void)migrateSettingsForKeyman18; - (void)setDataModelVersionIfNecessary; +- (void)migrateSettingsForKeyman19; +- (BOOL)migrateInputMethodSettingsToAppGroup; - (NSString *)readSelectedKeyboard; - (void)writeSelectedKeyboard:(NSString *)selectedKeyboard; - (NSArray *)readActiveKeyboards; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 6b235a3679..1d71a7effc 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -11,6 +11,9 @@ #import "KMLogs.h" #import "KMDataRepository.h" +// same as app group ID, kKeymanGroupId +//NSString *const kKeymanSuiteName = @"group.com.keyman"; + NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; NSString *const kPersistedOptionsKey = @"KMPersistedOptionsKey"; @@ -45,7 +48,8 @@ NSString *const kNewPathComponent = @"/Library/Application Support/keyman.inputm * directory instead of in the Documents directory. */ NSString *const kDataModelVersion = @"KMDataModelVersion"; -NSInteger const kVersionStoreDataInLibraryDirectory = 1; +NSInteger const kVersionStoreDataInLibraryDirectory = 1; // introduced with Keyman 18 +NSInteger const kVersionStoreDataInGroupContainer = 2; // introduced with Keyman 19 NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInLibraryDirectory; @implementation KMSettingsRepository @@ -60,6 +64,100 @@ NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInLibraryDirec return shared; } +/** + * Determines whether the keyboard data needs to be moved from pre-Keyman-18 location to the Keyman 18 location + * This is true if + * 1) the UserDefaults exist (indicating that this is not a new installation of Keyman) and + * 2) the value for kVersionStoreDataInLibraryDirectory is < 1, + */ +- (BOOL)keyman18DataMigrationNeeded { + BOOL keymanSettingsExist = [self settingsExist]; + os_log([KMLogs dataLog], "keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); + + BOOL keyboardsStoredInLibrary = [self dataModelWithKeyboardsInLibrary]; + os_log([KMLogs dataLog], "settings indicate that keyboards are stored in ~/Library: %{public}@", keyboardsStoredInLibrary ? @"YES" : @"NO" ); + + BOOL migrationNeeded = keymanSettingsExist && !keyboardsStoredInLibrary; + os_log([KMLogs dataLog], "dataMigrationNeeded: %{public}@", migrationNeeded ? @"YES" : @"NO" ); + + return migrationNeeded; +} + +/** + * Determines whether the keyboard data needs to be moved from the Keyman 18 location to the Keyman 19 location + * This is true if the UserDefaults exist in the old location for the input method + */ +- (BOOL)keyman19SettingsMigrationNeeded { + BOOL keymanSettingsExistForInputMethod = [self inputMethodUserDefaultsExist]; + os_log([KMLogs dataLog], "keyman input method settings exist (for 18 and earlier): %{public}@", keymanSettingsExistForInputMethod ? @"YES" : @"NO" ); + + BOOL keyboardsStoredInLibrary = [self dataModelWithKeyboardsInLibrary]; + os_log([KMLogs dataLog], "settings indicate that keyboards are stored in ~/Library: %{public}@", keyboardsStoredInLibrary ? @"YES" : @"NO" ); + + BOOL migrationNeeded = keymanSettingsExistForInputMethod; + os_log([KMLogs dataLog], "dataMigrationNeeded: %{public}@", migrationNeeded ? @"YES" : @"NO" ); + + return migrationNeeded; +} + +- (void)migrateSettingsForKeyman19 { + [self migrateInputMethodSettingsToAppGroup]; + + NSUserDefaults *groupUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:kKeymanGroupId]; + + // set kDataModelVersion to indicate that we are using the group container + [groupUserDefaults setInteger:kVersionStoreDataInGroupContainer forKey:kDataModelVersion]; +} + +/** + * Move userdefaults from app to app group + * Read the settings in the input method's user defaults + * Write them to the shared app group user defaults + * Delete them from the input method's user defaults + */ +- (BOOL)migrateInputMethodSettingsToAppGroup { + NSUserDefaults *appUserDefaults = [NSUserDefaults standardUserDefaults]; + NSUserDefaults *groupUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:kKeymanGroupId]; + + NSString *selectedKeyboard = [appUserDefaults stringForKey:kSelectedKeyboardKey]; + if (selectedKeyboard != nil) { + [groupUserDefaults setObject:selectedKeyboard forKey:kSelectedKeyboardKey]; + } + + NSArray * activeKeyboards = [appUserDefaults arrayForKey:kActiveKeyboardsKey]; + if (activeKeyboards != nil) { + [groupUserDefaults setObject:activeKeyboards forKey:kActiveKeyboardsKey]; + } + + if ([appUserDefaults objectForKey:kShowOskOnActivate] != nil) { + BOOL showOsk = [appUserDefaults boolForKey:kShowOskOnActivate]; + [groupUserDefaults setBool:showOsk forKey:kShowOskOnActivate]; + } + + if ([appUserDefaults objectForKey:kForceSentryError] != nil) { + BOOL forceSentryError = [appUserDefaults boolForKey:kForceSentryError]; + [groupUserDefaults setBool:forceSentryError forKey:kForceSentryError]; + } + + return true; +} + +/* + NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; + NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; + NSString *const kPersistedOptionsKey = @"KMPersistedOptionsKey"; + NSString *const kShowOskOnActivate = @"KMShowOskOnActivate"; + NSString *const kForceSentryError = @"KMForceSentryError"; + + */ + +- (void)migrateSettingsForKeyman18 { + os_log_debug([KMLogs dataLog], "converting settings in UserDefaults for migration"); + [self convertSelectedKeyboardPathForKeyman18Migration]; + [self convertActiveKeyboardArrayForKeyman18Migration]; + [self convertOptionsPathsForKeyman18Migration]; +} + - (void)setDataModelVersionIfNecessary { if (![self dataModelWithKeyboardsInLibrary]) { [[NSUserDefaults standardUserDefaults] setInteger:kVersionStoreDataInLibraryDirectory forKey:kDataModelVersion]; @@ -85,7 +183,6 @@ NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInLibraryDirec return ([[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey] != nil); } - - (void)writeOptionForSelectedKeyboard:(NSString *)key withValue:(NSString*)value { NSDictionary *optionsMap = [self readOptionsForSelectedKeyboard]; NSDictionary *newOptionsMap = nil; @@ -134,43 +231,6 @@ NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInLibraryDirec return dataModelVersion >= kVersionStoreDataInLibraryDirectory; } -/** - * Determines whether the keyboard data needs to be moved from pre-Keyman-18 location to the Keyman 18 location - * This is true if - * 1) the UserDefaults exist (indicating that this is not a new installation of Keyman) and - * 2) the value for kVersionStoreDataInLibraryDirectory is < 1, - */ -- (BOOL)keyman18DataMigrationNeeded { - BOOL keymanSettingsExist = [self settingsExist]; - os_log([KMLogs dataLog], "keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); - - BOOL keyboardsStoredInLibrary = [self dataModelWithKeyboardsInLibrary]; - os_log([KMLogs dataLog], "settings indicate that keyboards are stored in ~/Library: %{public}@", keyboardsStoredInLibrary ? @"YES" : @"NO" ); - - BOOL migrationNeeded = keymanSettingsExist && !keyboardsStoredInLibrary; - os_log([KMLogs dataLog], "dataMigrationNeeded: %{public}@", migrationNeeded ? @"YES" : @"NO" ); - - return migrationNeeded; -} - -/** - * Determines whether the keyboard data needs to be moved from the Keyman 18 location to the Keyman 19 location - * This is true if the UserDefaults exist in the old location for the input method - */ -- (BOOL)keyman19SettingsMigrationNeeded { - BOOL keymanSettingsExistForInputMethod = [self inputMethodUserDefaultsExist]; - os_log([KMLogs dataLog], "keyman input method settings exist (for 18 and earlier): %{public}@", keymanSettingsExistForInputMethod ? @"YES" : @"NO" ); - - BOOL keyboardsStoredInLibrary = [self dataModelWithKeyboardsInLibrary]; - os_log([KMLogs dataLog], "settings indicate that keyboards are stored in ~/Library: %{public}@", keyboardsStoredInLibrary ? @"YES" : @"NO" ); - - BOOL migrationNeeded = keymanSettingsExistForInputMethod; - os_log([KMLogs dataLog], "dataMigrationNeeded: %{public}@", migrationNeeded ? @"YES" : @"NO" ); - - return migrationNeeded; -} - - - (NSString *)readSelectedKeyboard { return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey]; } @@ -254,13 +314,6 @@ NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInLibraryDirec return [userData removeObjectForKey:kPersistedOptionsKey]; } -- (void)migrateSettingsForKeyman18 { - os_log_debug([KMLogs dataLog], "converting settings in UserDefaults for migration"); - [self convertSelectedKeyboardPathForKeyman18Migration]; - [self convertActiveKeyboardArrayForKeyman18Migration]; - [self convertOptionsPathsForKeyman18Migration]; -} - - (void)convertSelectedKeyboardPathForKeyman18Migration { NSString *selectedKeyboardPath = [self readSelectedKeyboard]; if (selectedKeyboardPath != nil) { diff --git a/mac/KeymanSettings/Sources/KeymanSettings/SettingsContainer.swift b/mac/KeymanSettings/Sources/KeymanSettings/SettingsContainer.swift index a273153ac8..4415a64c63 100644 --- a/mac/KeymanSettings/Sources/KeymanSettings/SettingsContainer.swift +++ b/mac/KeymanSettings/Sources/KeymanSettings/SettingsContainer.swift @@ -52,7 +52,15 @@ public class SettingsContainer : ObservableObject { } } } - + + public func logSettings() { + self.settingsRepository.logSettings() + } + + public func clearSettings() { + self.settingsRepository.clearSettings() + } + // TODO: extract method for keyboard search // TODO: throw error for keyboard not found or log and fail silently (should never happen) public func isKeyboardEnabled(packageId: UUID, keyboardId: String) -> Bool { diff --git a/mac/KeymanSettings/Sources/Persistence/KeymanPaths.swift b/mac/KeymanSettings/Sources/Persistence/KeymanPaths.swift index 342e365048..b6c617ea6c 100644 --- a/mac/KeymanSettings/Sources/Persistence/KeymanPaths.swift +++ b/mac/KeymanSettings/Sources/Persistence/KeymanPaths.swift @@ -30,7 +30,7 @@ import Foundation * containerDirectory: '~/Library/Group Containers' * containerKeymanDirectory: '~/Library/Group Containers/group.com.keyman' * groupKeymanSupportDirectory: '~/Library/Group Containers/group.com.keyman/Library/Application Support' - * keyman19KeyboardsDirectory: '~/Library/Group Containers/group.com.keyman/Library/Application Support/Keyman-Keyboards' + * keyman19KeyboardsDirectory: '~/Library/Group Containers/group.com.keyman/Library/Application Support/Keyman-Packages' */ public struct KeymanPaths { @@ -43,7 +43,7 @@ public struct KeymanPaths { static private let keymanSubdirectoryName = "keyman.inputmethod.Keyman" static private let containerPreferencesPartialPath = "Library/Preferences" - static private let containerKeyboardsPartialPath = "Library/Application Support/Keyman-Keyboards" + static private let containerKeyboardsPartialPath = "Library/Application Support/Keyman-Packages" // keyman 17 and earlier let keyman17DocumentsDirectory: URL? diff --git a/mac/KeymanSettings/Sources/Persistence/SettingsRepository.swift b/mac/KeymanSettings/Sources/Persistence/SettingsRepository.swift index 58e6a7c64a..cf8d466ec6 100644 --- a/mac/KeymanSettings/Sources/Persistence/SettingsRepository.swift +++ b/mac/KeymanSettings/Sources/Persistence/SettingsRepository.swift @@ -22,7 +22,7 @@ public struct SettingsRepository { let kActiveKeyboardsKey = "KMActiveKeyboardsKey" let kSelectedKeyboardKey = "KMSelectedKeyboardKey" let kPersistedOptionsKey = "KMPersistedOptionsKey" - let kDataModelVersionKey = "KMDataModelVersionKey" + let kDataModelVersionKey = "KMDataModelVersion" let kShowOskOnActivateKey = "KMShowOskOnActivate" let kForceSentryError = "KMForceSentryError" @@ -68,4 +68,27 @@ public struct SettingsRepository { sharedDefaults.set(keyboardName, forKey: kSelectedKeyboardKey) } } + + public func logSettings() { + print("UserDefaults:") + if let sharedDefaults = self.defaults { + print("kSelectedKeyboardKey: \(sharedDefaults.value(forKey: kSelectedKeyboardKey) ?? "nil")") + print("kDataModelVersionKey: \(sharedDefaults.value(forKey: kDataModelVersionKey) ?? "nil")") + print("kForceSentryError: \(sharedDefaults.value(forKey: kForceSentryError) ?? "nil")") + print("kShowOskOnActivateKey: \(sharedDefaults.value(forKey: kShowOskOnActivateKey) ?? "nil")") + print("kActiveKeyboardsKey: \(sharedDefaults.value(forKey: kActiveKeyboardsKey) ?? "nil")") + print("kPersistedOptionsKey: \(sharedDefaults.value(forKey: kPersistedOptionsKey) ?? "nil")") + } + } + + public func clearSettings() { + guard let sharedDefaults = self.defaults else { + print("Group container UserDefaults not found.") + return + } + + sharedDefaults.dictionaryRepresentation().keys.forEach { key in + sharedDefaults.removeObject(forKey: key) + } + } } diff --git a/mac/KeymanSettings/Tests/KeymanSettingsTests/KeymanSettingsTests.swift b/mac/KeymanSettings/Tests/KeymanSettingsTests/KeymanSettingsTests.swift index 768635a9ae..0b5a026d1a 100644 --- a/mac/KeymanSettings/Tests/KeymanSettingsTests/KeymanSettingsTests.swift +++ b/mac/KeymanSettings/Tests/KeymanSettingsTests/KeymanSettingsTests.swift @@ -46,7 +46,7 @@ import Foundation @Test("Check Keyman 19 keyboards directory") func testKeyman19KeyboardsDirectory() async throws { #expect(true) let keyboardsDirectory = try #require(KeymanPaths().keyman19KeyboardsDirectory) - #expect(keyboardsDirectory.absoluteString.hasSuffix("Group%20Containers/group.com.keyman/Library/Application%20Support/Keyman-Keyboards/")) + #expect(keyboardsDirectory.absoluteString.hasSuffix("Group%20Containers/group.com.keyman/Library/Application%20Support/Keyman-Packages/")) } }