From 5488e32be28ab06b83db45ec3f6ea7c960ff4b98 Mon Sep 17 00:00:00 2001 From: Shawn Schantz Date: Mon, 29 Jul 2024 16:01:58 +0700 Subject: [PATCH 01/10] change(mac): add class to encapsulate settings --- .../Keyman4MacIM.xcodeproj/project.pbxproj | 6 ++ .../Keyman4MacIM/KMInputMethodAppDelegate.m | 18 +++++- .../Keyman4MacIM/KMInputMethodEventHandler.m | 16 +++++- .../Keyman4MacIM/KMSettingsRepository.h | 20 +++++++ .../Keyman4MacIM/KMSettingsRepository.m | 57 +++++++++++++++++++ 5 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h create mode 100644 mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 8e0b973442e..46b7f70e24b 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -72,6 +72,7 @@ 98FE10631B4DEE5600525F54 /* KMInfoWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 98FE10611B4DEE5600525F54 /* KMInfoWindowController.m */; }; 9A3D6C5D221531B0008785A3 /* KMOSVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 9A3D6C5C221531B0008785A3 /* KMOSVersion.m */; }; B90818AF7ED302187DE0E026 /* Pods_Keyman.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B644E63217FFE54B91C71C94 /* Pods_Keyman.framework */; }; + D861B03F2C5747F70003675E /* KMSettingsRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = D861B03E2C5747F70003675E /* KMSettingsRepository.m */; }; E211769D20E182DD00F8065D /* NoContextTestClient.m in Sources */ = {isa = PBXBuildFile; fileRef = E211769C20E182DD00F8065D /* NoContextTestClient.m */; }; E21176A020E18C5200F8065D /* AppleCompliantTestClient.m in Sources */ = {isa = PBXBuildFile; fileRef = E211769F20E18C5200F8065D /* AppleCompliantTestClient.m */; }; E211CCF620B600A500505C36 /* KeymanEngine4Mac.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = E211CCF520B600A500505C36 /* KeymanEngine4Mac.framework.dSYM */; }; @@ -350,6 +351,8 @@ CEFFECDB2A417FEC00D58C36 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/KMKeyboardHelpWindowController.strings; sourceTree = ""; }; CEFFECDC2A417FEC00D58C36 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/MainMenu.strings; sourceTree = ""; }; CEFFECDD2A4180FD00D58C36 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; + D861B03D2C5747F70003675E /* KMSettingsRepository.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMSettingsRepository.h; sourceTree = ""; }; + D861B03E2C5747F70003675E /* KMSettingsRepository.m */ = {isa = PBXFileReference; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = KMSettingsRepository.m; sourceTree = ""; tabWidth = 2; }; E211769B20E1826800F8065D /* NoContextTestClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NoContextTestClient.h; sourceTree = ""; }; E211769C20E182DD00F8065D /* NoContextTestClient.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NoContextTestClient.m; sourceTree = ""; }; E211769E20E18C0B00F8065D /* AppleCompliantTestClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppleCompliantTestClient.h; sourceTree = ""; }; @@ -548,6 +551,8 @@ 29B4A0D32BF7675A00682049 /* KMLogs.m */, 299ABD6F29ECE75B00AA5948 /* KeySender.m */, 299ABD7029ECE75B00AA5948 /* KeySender.h */, + D861B03D2C5747F70003675E /* KMSettingsRepository.h */, + D861B03E2C5747F70003675E /* KMSettingsRepository.m */, 297A501128DF4D360074EB1B /* Privacy */, 98FE105B1B4DE86300525F54 /* Categories */, 98D6DA791A799EE700B09822 /* Frameworks */, @@ -974,6 +979,7 @@ 29B4A0D52BF7675A00682049 /* KMLogs.m in Sources */, 98BF924F1BF02DC20002126A /* KMBarView.m in Sources */, E240F599202DED740000067D /* KMPackage.m in Sources */, + D861B03F2C5747F70003675E /* KMSettingsRepository.m in Sources */, 984B8F441AF1C3D900E096A8 /* OSKWindowController.m in Sources */, 9836B3711AE5F11D00780482 /* mztools.c in Sources */, 9836B3701AE5F11D00780482 /* ioapi.c in Sources */, diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 0675a78adec..b7acec58e83 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -16,6 +16,7 @@ // Keyman4MacIM[6245]: IMK Stall detected, *please Report* your user scenario in - (sessionFinished) block performed very slowly (0.00 secs) #import "KMInputMethodAppDelegate.h" +#import "KMSettingsRepository.h" #import "KMConfigurationWindowController.h" #import "KMDownloadKBWindowController.h" #import "ZipArchive.h" @@ -509,7 +510,9 @@ - (NSString *)keymanDataPath { if(_keymanDataPath == nil) { NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; _keymanDataPath = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"]; - + + os_log_debug([KMLogs dataLog], "creating keymanDataPath, %{public}@", _keymanDataPath); + NSFileManager *fm = [NSFileManager defaultManager]; if (![fm fileExistsAtPath:_keymanDataPath]) { [fm createDirectoryAtPath:_keymanDataPath withIntermediateDirectories:YES attributes:nil error:nil]; @@ -531,6 +534,7 @@ - (NSString *)keyboardsPath { } - (NSArray *)kmxFileList { + os_log_debug([KMLogs dataLog], "kmxFileList"); if (_kmxFileList == nil) { NSArray *kmxFiles = [self KMXFiles]; _kmxFileList = [[NSMutableArray alloc] initWithCapacity:0]; @@ -650,6 +654,7 @@ - (NSString *)packageNameFromPackageInfo:(NSString *)packageFolder { } - (NSArray *)keyboardNamesFromFolder:(NSString *)packageFolder { + os_log_debug([KMLogs dataLog], "keyboardNamesFromFolder, folder = %{public}@", packageFolder); NSMutableArray *kbNames = [[NSMutableArray alloc] initWithCapacity:0];; for (NSString *kmxFile in [self KMXFilesAtPath:packageFolder]) { NSDictionary * infoDict = [KMXFile keyboardInfoFromKmxFile:kmxFile]; @@ -756,6 +761,12 @@ - (void)setContextBuffer:(NSMutableString *)contextBuffer { } - (void)awakeFromNib { + if ([KMSettingsRepository.shared keyboardsMigrationNeeded]) { + os_log_info([KMLogs startupLog], "keyboards migration needed"); + } else { + os_log_info([KMLogs startupLog], "keyboards migration not needed"); + } + [self setDefaultKeymanMenuItems]; [self updateKeyboardMenuItems]; } @@ -928,13 +939,16 @@ - (NSArray *)KMXFiles { } - (NSArray *)KMXFilesAtPath:(NSString *)path { + os_log_debug([KMLogs dataLog], "Reading KMXFiles at path %{public}@", path); NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path]; NSMutableArray *kmxFiles = [[NSMutableArray alloc] initWithCapacity:0]; NSString *filePath; while (filePath = (NSString *)[dirEnum nextObject]) { NSString *extension = [[filePath pathExtension] lowercaseString]; - if ([extension isEqualToString:@"kmx"]) + if ([extension isEqualToString:@"kmx"]) { [kmxFiles addObject:[path stringByAppendingPathComponent:filePath]]; + os_log_debug([KMLogs dataLog], "file = %{public}@", filePath); + } } return kmxFiles; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index fa912ca67d5..cf9eea07024 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -10,6 +10,7 @@ #import /* For kVK_ constants. */ #import "KeySender.h" #import "TextApiCompliance.h" +#import "KMSettingsRepository.h" #import "KMLogs.h" @import Sentry; @@ -238,6 +239,17 @@ - (BOOL)handleEvent:(NSEvent *)event client:(id)sender { // return NO to pass through to client app return NO; } + + // TODO: remove test code + /* + if (event.keyCode == kVK_ANSI_Slash) { + if ([KMSettingsRepository.shared keyboardsMigrationNeeded]) { + os_log_info([KMLogs startupLog], "keyboards migration needed"); + } else { + os_log_info([KMLogs startupLog], "keyboards migration not needed"); + } + } + */ } if (event.type == NSEventTypeFlagsChanged) { @@ -412,11 +424,11 @@ -(void) persistOptions:(NSDictionary*)options{ for(NSString *key in options) { NSString *value = [options objectForKey:key]; if(key && value) { - os_log_debug([KMLogs keyLog], "applyNonTextualOutput calling writePersistedOptions, key: %{public}@, value: %{public}@", key, value); + os_log_debug([KMLogs keyLog], "persistOptions, key: %{public}@, value: %{public}@", key, value); [self.appDelegate writePersistedOptions:key withValue:value]; } else { - os_log_debug([KMLogs keyLog], "applyNonTextualOutput, invalid values in optionsToPersist, not writing to UserDefaults, key: %{public}@, value: %{public}@", key, value); + os_log_debug([KMLogs keyLog], "invalid values in persistOptions, not writing to UserDefaults, key: %{public}@, value: %{public}@", key, value); } } } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h new file mode 100644 index 00000000000..e4b9f1c158a --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h @@ -0,0 +1,20 @@ +/** + * Keyman is copyright (C) SIL International. MIT License. + * + * KMSettingsRepository.h + * Keyman + * + * Created by Shawn Schantz on 2024-07-29. + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KMSettingsRepository : NSObject ++ (KMSettingsRepository *)shared; +- (BOOL)keyboardsMigrationNeeded; +@end + +NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m new file mode 100644 index 00000000000..d5a26cccac7 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -0,0 +1,57 @@ +/** + * Keyman is copyright (C) SIL International. MIT License. + * + * KMSettingsRepository.h + * Keyman + * + * Created by Shawn Schantz on 2024-07-29. + * + * Singleton object for reading and writing Keyman application settings. + * Serves as an abstraction to StandardUserDefaults which is currently used to persist application settings. + */ + +#import "KMSettingsRepository.h" +#import "KMLogs.h" + +NSString *const kStoreKeyboardsInLibraryKey = @"KMStoreKeyboardsInLibraryKey"; +NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; + +@implementation KMSettingsRepository + ++ (KMSettingsRepository *)shared +{ + static KMSettingsRepository *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[KMSettingsRepository alloc] init]; + }); + return shared; +} + +- (BOOL)settingsExist +{ + return [[NSUserDefaults standardUserDefaults] objectForKey:kActiveKeyboardsKey] != nil; +} + +- (BOOL)keyboardsStoredInLibraryFolder +{ + return [[NSUserDefaults standardUserDefaults] boolForKey:kStoreKeyboardsInLibraryKey]; +} + +/** + * Determines whether the keyboards data needs to be moved from the old location in the Documents folder to the new location under /username/Library... + * This is true if + * 1) the UserDefaults exist (indicating that this is not a new installation of Keyman) and + * 2) the value for KMStoreKeyboardsInLibraryKey is not set to true + */ +- (BOOL)keyboardsMigrationNeeded { + BOOL keyboardSettingsExist = [self settingsExist]; + os_log([KMLogs startupLog], " keyboard settings exist: %@", keyboardSettingsExist ? @"YES" : @"NO" ); + + BOOL keyboardsInLibrary = [self keyboardsStoredInLibraryFolder]; + os_log([KMLogs startupLog], " keyboards stored in Library: %@", keyboardsInLibrary ? @"YES" : @"NO" ); + + return !(keyboardSettingsExist && keyboardsInLibrary); +} + +@end From 37dcacc0cb092201edea1e00905ec40961fb9dd0 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Tue, 30 Jul 2024 16:08:34 +0700 Subject: [PATCH 02/10] change(mac): add KMDataRepository class to handle data (keyboards) migration --- .../Keyman4MacIM.xcodeproj/project.pbxproj | 6 ++ .../Keyman4MacIM/KMDataRepository.h | 21 +++++ .../Keyman4MacIM/KMDataRepository.m | 85 +++++++++++++++++++ .../Keyman4MacIM/KMInputMethodAppDelegate.m | 3 + 4 files changed, 115 insertions(+) create mode 100644 mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h create mode 100644 mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 46b7f70e24b..9d63aac4685 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 29015ABD2C58D86F00CCBB94 /* KMDataRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */; }; 290BC680274B9DB1005CD1C3 /* KMPackageInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 290BC67F274B9DB1005CD1C3 /* KMPackageInfo.m */; }; 290BC75E274F3FD7005CD1C3 /* KMKeyboardInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 290BC75D274F3FD7005CD1C3 /* KMKeyboardInfo.m */; }; 2915DC512BFE35DB0051FC52 /* KMLogs.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B4A0D32BF7675A00682049 /* KMLogs.m */; }; @@ -136,6 +137,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 29015ABB2C58D86F00CCBB94 /* KMDataRepository.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KMDataRepository.h; sourceTree = ""; }; + 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KMDataRepository.m; sourceTree = ""; }; 2901BA8A292332B3009903EC /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/KMAboutWindowController.strings"; sourceTree = ""; }; 2901BA8B292332B3009903EC /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/preferences.strings"; sourceTree = ""; }; 2901BA8C292332B3009903EC /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/KMInfoWindowController.strings"; sourceTree = ""; }; @@ -553,6 +556,8 @@ 299ABD7029ECE75B00AA5948 /* KeySender.h */, D861B03D2C5747F70003675E /* KMSettingsRepository.h */, D861B03E2C5747F70003675E /* KMSettingsRepository.m */, + 29015ABB2C58D86F00CCBB94 /* KMDataRepository.h */, + 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */, 297A501128DF4D360074EB1B /* Privacy */, 98FE105B1B4DE86300525F54 /* Categories */, 98D6DA791A799EE700B09822 /* Frameworks */, @@ -982,6 +987,7 @@ D861B03F2C5747F70003675E /* KMSettingsRepository.m in Sources */, 984B8F441AF1C3D900E096A8 /* OSKWindowController.m in Sources */, 9836B3711AE5F11D00780482 /* mztools.c in Sources */, + 29015ABD2C58D86F00CCBB94 /* KMDataRepository.m in Sources */, 9836B3701AE5F11D00780482 /* ioapi.c in Sources */, E21799051FC5B7BC00F2D66A /* KMInputMethodEventHandler.m in Sources */, 98E6729F1B532F5E00DBDE2F /* KMDownloadKBWindowController.m in Sources */, diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h new file mode 100644 index 00000000000..ed1b8701fe4 --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -0,0 +1,21 @@ +/** + * Keyman is copyright (C) SIL International. MIT License. + * + * KMDataRepository.h + * Keyman + * + * Created by Shawn Schantz on 2024-07-30. + * + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KMDataRepository : NSObject ++ (KMDataRepository *)shared; +- (void)migrateResources; +- (NSString *)keymanDataDirectory; +@end + +NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m new file mode 100644 index 00000000000..fa422472e5d --- /dev/null +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -0,0 +1,85 @@ +/** + * Keyman is copyright (C) SIL International. MIT License. + * + * KMResourcesRepository.m + * Keyman + * + * Created by Shawn Schantz on 2024-07-30. + * + * Singleton object which serves as an abstraction for the reading and writing of Keyman data. + * The 'data' currently consists of keyman keyboards installed by the user. All data is saved locally on disk using NSFileManager. + * This is in contrast with the lighter weight Settings which is stored using UserDefaults and handled by KMSettingsRepository. + */ + +#import "KMDataRepository.h" +#import "KMLogs.h" + +@implementation KMDataRepository + +NSString* _obsoleteKeymanDataDirectory = nil; +NSString* _keymanDataDirectory = nil; + ++ (KMDataRepository *)shared +{ + static KMDataRepository *shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[KMDataRepository alloc] init]; + }); + return shared; +} + +- (void)migrateResources { + NSError *directoryError = nil; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *applicationSupportUrl = [fileManager URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; + + os_log_info([KMLogs startupLog], "applicationSupportDirectory: '%{public}@'", applicationSupportUrl); + + NSString *appId = [[NSBundle mainBundle] bundleIdentifier]; + os_log_info([KMLogs startupLog], "application bundleIdentifier: '%{public}@'", appId); + + NSURL *keymanUrl = [applicationSupportUrl URLByAppendingPathComponent: appId isDirectory: TRUE]; + + //NSURL *keymanUrl = [NSURL fileURLWithPath:appId isDirectory:YES relativeToURL:applicationSupportUrl]; + + os_log_info([KMLogs startupLog], "keymanUrl: '%{public}@'", keymanUrl); + // returns -> '/Users/sgschantz/Library/Application Support' + + BOOL isDir; + BOOL exists = [fileManager fileExistsAtPath:keymanUrl.path isDirectory:&isDir]; + + if (exists) { + os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' exists", keymanUrl); + if (isDir) { + os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' is a directory", keymanUrl); + } + } else { + os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' does not exist", keymanUrl); + } +} + +/** + * Locate and create the Keyman data path; currently in ~/Documents/Keyman-Keyboards + */ +- (NSString *)_obsoleteKeymanDataDirectory { + if(_keymanDataDirectory == nil) { + NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + _keymanDataDirectory = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"]; + + os_log_debug([KMLogs dataLog], "creating keymanDataPath, %{public}@", _keymanDataDirectory); + + NSFileManager *fm = [NSFileManager defaultManager]; + if (![fm fileExistsAtPath:_keymanDataDirectory]) { + [fm createDirectoryAtPath:_keymanDataDirectory withIntermediateDirectories:YES attributes:nil error:nil]; + } + } + return _keymanDataDirectory; +} + +- (NSString *)keymanDataPath { + return _keymanDataDirectory; +} + +@end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index b7acec58e83..9afba1fa0a8 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -17,6 +17,7 @@ #import "KMInputMethodAppDelegate.h" #import "KMSettingsRepository.h" +#import "KMDataRepository.h" #import "KMConfigurationWindowController.h" #import "KMDownloadKBWindowController.h" #import "ZipArchive.h" @@ -763,10 +764,12 @@ - (void)setContextBuffer:(NSMutableString *)contextBuffer { - (void)awakeFromNib { if ([KMSettingsRepository.shared keyboardsMigrationNeeded]) { os_log_info([KMLogs startupLog], "keyboards migration needed"); + [KMDataRepository.shared migrateResources]; } else { os_log_info([KMLogs startupLog], "keyboards migration not needed"); } + [self setDefaultKeymanMenuItems]; [self updateKeyboardMenuItems]; } From bff17dc1c243b8fa92c1d3b51111110201ec31f5 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Wed, 31 Jul 2024 10:52:34 +0700 Subject: [PATCH 03/10] change(mac): customize menus after all views are created rather than reading from disk and adding keyboards include awakeFromNib, do so in applicationDidFinishLaunching --- mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 9afba1fa0a8..87a53824321 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -182,6 +182,9 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { }]; // [SentrySDK captureMessage:@"Starting Keyman [test message]"]; + + [self setDefaultKeymanMenuItems]; + [self updateKeyboardMenuItems]; } #ifdef USE_ALERT_SHOW_HELP_TO_FORCE_EASTER_EGG_CRASH_FROM_ENGINE @@ -769,9 +772,6 @@ - (void)awakeFromNib { os_log_info([KMLogs startupLog], "keyboards migration not needed"); } - - [self setDefaultKeymanMenuItems]; - [self updateKeyboardMenuItems]; } - (void)setDefaultKeymanMenuItems { From ec2e48c11ff16b12bcfe75ac59179fcf2e11a97c Mon Sep 17 00:00:00 2001 From: sgschantz Date: Wed, 31 Jul 2024 15:58:40 +0700 Subject: [PATCH 04/10] change(mac): moved directory locations into KMDataRepository --- .../Keyman4MacIM/KMDataRepository.h | 2 +- .../Keyman4MacIM/KMDataRepository.m | 105 +++++++++++++++--- 2 files changed, 90 insertions(+), 17 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index ed1b8701fe4..53790a21524 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN @interface KMDataRepository : NSObject + (KMDataRepository *)shared; - (void)migrateResources; -- (NSString *)keymanDataDirectory; +- (NSURL *)keymanDataDirectory; @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index fa422472e5d..40f8858aee6 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -14,10 +14,36 @@ #import "KMDataRepository.h" #import "KMLogs.h" +@interface KMDataRepository () +@property (readonly) NSURL *applicationSupportSubDirectory; // '~/Library/Application Support' +@property (readonly) NSURL *documentsSubDirectory; // '~/Documents' +@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/com.keyman.app' +@property (readonly) NSURL *keymanKeyboardsDirectory; +// keymanKeyboardsDirectory = '~/Library/Application Support/com.keyman.app/Keyman-Keyboards' +@property (readonly) NSURL *obsoleteKeymanDataDirectory; // '~/Library/Documents/Keyman-Keyboards' +@end + @implementation KMDataRepository +@synthesize applicationSupportSubDirectory = _applicationSupportSubDirectory; +@synthesize documentsSubDirectory = _documentsSubDirectory; +@synthesize keymanKeyboardsDirectory = _keymanKeyboardsDirectory; +@synthesize obsoleteKeymanDataDirectory = _obsoleteKeymanDataDirectory; +@synthesize keymanDataDirectory = _keymanDataDirectory; + +NSString *const kKeyboardsDirectoryName = @"Keyman-Keyboards"; +/* + name of the subdirectory within '~/Library/Application Support' + the convention is to use bundle identifier ("keyman.inputmethod.Keyman") + but we'll use this name, which matches our logging subsystem + */ +NSString *const kKeymanSubdirectoryName = @"com.keyman.app"; +/* NSString* _obsoleteKeymanDataDirectory = nil; NSString* _keymanDataDirectory = nil; +NSURL* _ApplicationSupportSubDirectory = nil; +NSURL* _DocumentsSubDirectory = nil; +*/ + (KMDataRepository *)shared { @@ -29,24 +55,69 @@ + (KMDataRepository *)shared return shared; } -- (void)migrateResources { - NSError *directoryError = nil; - - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSURL *applicationSupportUrl = [fileManager URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; - - os_log_info([KMLogs startupLog], "applicationSupportDirectory: '%{public}@'", applicationSupportUrl); +- (NSURL *)documentsSubDirectory { + if (self.documentsSubDirectory == nil) { + NSError *directoryError = nil; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *documentsUrl = [fileManager URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; + + if (directoryError) { + os_log_error([KMLogs startupLog], "error getting Documents subdirectory: '%{public}@'", directoryError.localizedDescription); + } else { + os_log_info([KMLogs startupLog], "Documents subdirectory: '%{public}@'", documentsUrl); + _documentsSubDirectory = documentsUrl; + } + } + return self.documentsSubDirectory; +} - NSString *appId = [[NSBundle mainBundle] bundleIdentifier]; - os_log_info([KMLogs startupLog], "application bundleIdentifier: '%{public}@'", appId); - - NSURL *keymanUrl = [applicationSupportUrl URLByAppendingPathComponent: appId isDirectory: TRUE]; +- (NSURL *)applicationSupportSubDirectory { + if (self.applicationSupportSubDirectory == nil) { + NSError *directoryError = nil; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSURL *applicationSupportUrl = [fileManager URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; - //NSURL *keymanUrl = [NSURL fileURLWithPath:appId isDirectory:YES relativeToURL:applicationSupportUrl]; + if (directoryError) { + os_log_error([KMLogs startupLog], "error getting Application Support subdirectory: '%{public}@'", directoryError.localizedDescription); + } else { + os_log_info([KMLogs startupLog], "Application Support subdirectory: '%{public}@'", applicationSupportUrl); + _applicationSupportSubDirectory = applicationSupportUrl; + } + } + return self.applicationSupportSubDirectory; +} + +- (NSURL *)keymanDataDirectory { + if (self.keymanDataDirectory == nil) { + NSURL *keymanDataUrl = [self.applicationSupportSubDirectory URLByAppendingPathComponent:kKeymanSubdirectoryName isDirectory: TRUE]; + _keymanDataDirectory = keymanDataUrl; + } + return self.keymanDataDirectory; +} + +- (NSURL *)keymanKeyboardsDirectory { + if (self.keymanKeyboardsDirectory == nil) { + NSURL *keyboardsUrl = [self.keymanDataDirectory URLByAppendingPathComponent:kKeyboardsDirectoryName isDirectory: TRUE]; + _keymanKeyboardsDirectory = keyboardsUrl; + } + return self.keymanKeyboardsDirectory; +} + +- (NSURL *)obsoleteKeymanDataDirectory { + if (self.obsoleteKeymanDataDirectory == nil) { + NSURL *keymanUrl = [self.documentsSubDirectory URLByAppendingPathComponent:kKeymanSubdirectoryName isDirectory: TRUE]; + _obsoleteKeymanDataDirectory = keymanUrl; + } + return self.obsoleteKeymanDataDirectory; +} - os_log_info([KMLogs startupLog], "keymanUrl: '%{public}@'", keymanUrl); - // returns -> '/Users/sgschantz/Library/Application Support' +- (void)migrateResources { + os_log_info([KMLogs startupLog], "keymanKeyboardsDirectory: '%{public}@'", self.keymanKeyboardsDirectory); + os_log_info([KMLogs startupLog], "obsoleteKeymanDataDirectory: '%{public}@'", self.obsoleteKeymanDataDirectory); + /* BOOL isDir; BOOL exists = [fileManager fileExistsAtPath:keymanUrl.path isDirectory:&isDir]; @@ -58,11 +129,13 @@ - (void)migrateResources { } else { os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' does not exist", keymanUrl); } + */ } /** * Locate and create the Keyman data path; currently in ~/Documents/Keyman-Keyboards */ +/* - (NSString *)_obsoleteKeymanDataDirectory { if(_keymanDataDirectory == nil) { NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; @@ -78,8 +151,8 @@ - (NSString *)_obsoleteKeymanDataDirectory { return _keymanDataDirectory; } -- (NSString *)keymanDataPath { +- (NSString *)keymanDataDirectory { return _keymanDataDirectory; } - +*/ @end From 4fb5d74f496bfb102eb6e47e8c199db2415ca26d Mon Sep 17 00:00:00 2001 From: sgschantz Date: Thu, 1 Aug 2024 17:00:07 +0700 Subject: [PATCH 05/10] change(mac): implemented data migration and settings path updates --- .../Keyman4MacIM/KMDataRepository.h | 7 +- .../Keyman4MacIM/KMDataRepository.m | 112 +++++++++++------- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 29 +++-- .../Keyman4MacIM/KMSettingsRepository.h | 4 +- .../Keyman4MacIM/KMSettingsRepository.m | 103 ++++++++++++++-- 5 files changed, 194 insertions(+), 61 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index 53790a21524..836b8b742b4 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -13,9 +13,12 @@ NS_ASSUME_NONNULL_BEGIN @interface KMDataRepository : NSObject +@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/com.keyman.app' +@property (readonly) NSURL *keymanKeyboardsDirectory; +// keymanKeyboardsDirectory = '~/Library/Application Support/com.keyman.app/Keyman-Keyboards' + (KMDataRepository *)shared; -- (void)migrateResources; -- (NSURL *)keymanDataDirectory; +- (void)createDataDirectoriesIfNecessary; +- (BOOL)migrateData; @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 40f8858aee6..2143d8b7f50 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -17,10 +17,7 @@ @interface KMDataRepository () @property (readonly) NSURL *applicationSupportSubDirectory; // '~/Library/Application Support' @property (readonly) NSURL *documentsSubDirectory; // '~/Documents' -@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/com.keyman.app' -@property (readonly) NSURL *keymanKeyboardsDirectory; -// keymanKeyboardsDirectory = '~/Library/Application Support/com.keyman.app/Keyman-Keyboards' -@property (readonly) NSURL *obsoleteKeymanDataDirectory; // '~/Library/Documents/Keyman-Keyboards' +@property (readonly) NSURL *obsoleteKeymanKeyboardsDirectory; // '~/Library/Documents/Keyman-Keyboards' @end @implementation KMDataRepository @@ -28,22 +25,17 @@ @implementation KMDataRepository @synthesize applicationSupportSubDirectory = _applicationSupportSubDirectory; @synthesize documentsSubDirectory = _documentsSubDirectory; @synthesize keymanKeyboardsDirectory = _keymanKeyboardsDirectory; -@synthesize obsoleteKeymanDataDirectory = _obsoleteKeymanDataDirectory; +@synthesize obsoleteKeymanKeyboardsDirectory = _obsoleteKeymanKeyboardsDirectory; @synthesize keymanDataDirectory = _keymanDataDirectory; NSString *const kKeyboardsDirectoryName = @"Keyman-Keyboards"; /* - name of the subdirectory within '~/Library/Application Support' - the convention is to use bundle identifier ("keyman.inputmethod.Keyman") - but we'll use this name, which matches our logging subsystem + The name of the subdirectory within '~/Library/Application Support'. + The convention is to use bundle identifier. + (Using the subsystem id, "com.keyman.app", is a poor choice because the API + createDirectoryAtPath sees the .app extension and creates an application file. */ -NSString *const kKeymanSubdirectoryName = @"com.keyman.app"; -/* -NSString* _obsoleteKeymanDataDirectory = nil; -NSString* _keymanDataDirectory = nil; -NSURL* _ApplicationSupportSubDirectory = nil; -NSURL* _DocumentsSubDirectory = nil; -*/ +NSString *const kKeymanSubdirectoryName = @"keyman.inputmethod.Keyman"; + (KMDataRepository *)shared { @@ -56,7 +48,7 @@ + (KMDataRepository *)shared } - (NSURL *)documentsSubDirectory { - if (self.documentsSubDirectory == nil) { + if (_documentsSubDirectory == nil) { NSError *directoryError = nil; NSFileManager *fileManager = [NSFileManager defaultManager]; @@ -69,11 +61,11 @@ - (NSURL *)documentsSubDirectory { _documentsSubDirectory = documentsUrl; } } - return self.documentsSubDirectory; + return _documentsSubDirectory; } - (NSURL *)applicationSupportSubDirectory { - if (self.applicationSupportSubDirectory == nil) { + if (_applicationSupportSubDirectory == nil) { NSError *directoryError = nil; NSFileManager *fileManager = [NSFileManager defaultManager]; @@ -86,50 +78,88 @@ - (NSURL *)applicationSupportSubDirectory { _applicationSupportSubDirectory = applicationSupportUrl; } } - return self.applicationSupportSubDirectory; + return _applicationSupportSubDirectory; } - (NSURL *)keymanDataDirectory { - if (self.keymanDataDirectory == nil) { + if (_keymanDataDirectory == nil) { NSURL *keymanDataUrl = [self.applicationSupportSubDirectory URLByAppendingPathComponent:kKeymanSubdirectoryName isDirectory: TRUE]; _keymanDataDirectory = keymanDataUrl; } - return self.keymanDataDirectory; + return _keymanDataDirectory; } - (NSURL *)keymanKeyboardsDirectory { - if (self.keymanKeyboardsDirectory == nil) { + if (_keymanKeyboardsDirectory == nil) { NSURL *keyboardsUrl = [self.keymanDataDirectory URLByAppendingPathComponent:kKeyboardsDirectoryName isDirectory: TRUE]; _keymanKeyboardsDirectory = keyboardsUrl; } - return self.keymanKeyboardsDirectory; + return _keymanKeyboardsDirectory; } -- (NSURL *)obsoleteKeymanDataDirectory { - if (self.obsoleteKeymanDataDirectory == nil) { - NSURL *keymanUrl = [self.documentsSubDirectory URLByAppendingPathComponent:kKeymanSubdirectoryName isDirectory: TRUE]; - _obsoleteKeymanDataDirectory = keymanUrl; +/** + * creates Keyman data directories if they do not exist yet + * This includes 1) the main data subdirectory: keyman.inputmethod.Keyman + * and 2) its subdirectory, Keyman-Keyboards + * + */ +- (void)createDataDirectoriesIfNecessary { + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDir; + BOOL exists = [fileManager fileExistsAtPath:self.keymanKeyboardsDirectory.path isDirectory:&isDir]; + + if (!exists) { + NSError *createError = nil; + [fileManager createDirectoryAtPath:self.keymanKeyboardsDirectory.path withIntermediateDirectories:YES attributes:nil error:nil]; + if (createError) { + os_log_error([KMLogs startupLog], "error creating Keyman-Keyboards directory: '%{public}@'", createError.localizedDescription); + } else { + os_log_info([KMLogs startupLog], "created Keyman-Keyboards subdirectory: '%{public}@'", self.keymanKeyboardsDirectory.path); + } + } else { + os_log_info([KMLogs startupLog], "Keyman-Keyboards already exists: '%{public}@'", self.keymanKeyboardsDirectory.path); } - return self.obsoleteKeymanDataDirectory; } -- (void)migrateResources { - os_log_info([KMLogs startupLog], "keymanKeyboardsDirectory: '%{public}@'", self.keymanKeyboardsDirectory); - os_log_info([KMLogs startupLog], "obsoleteKeymanDataDirectory: '%{public}@'", self.obsoleteKeymanDataDirectory); +- (NSURL *)obsoleteKeymanKeyboardsDirectory { + if (_obsoleteKeymanKeyboardsDirectory == nil) { + NSURL *keymanUrl = [self.documentsSubDirectory URLByAppendingPathComponent:kKeyboardsDirectoryName isDirectory: TRUE]; + _obsoleteKeymanKeyboardsDirectory = keymanUrl; + } + return _obsoleteKeymanKeyboardsDirectory; +} - /* +- (BOOL)keyboardsExistInDocumentsFolder { + NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL isDir; - BOOL exists = [fileManager fileExistsAtPath:keymanUrl.path isDirectory:&isDir]; + BOOL exists = ([fileManager fileExistsAtPath:self.obsoleteKeymanKeyboardsDirectory.path isDirectory:&isDir]); + return exists; +} + +- (BOOL)migrateData { + BOOL didMoveData = NO; + NSString *obsoleteKeymanKeyboardsDirectory = self.obsoleteKeymanKeyboardsDirectory.path; + NSString *dataDirectory = self.keymanDataDirectory.path; + os_log_info([KMLogs startupLog], "migrateData, move obsoleteKeymanKeyboardsDirectory: '%{public}@' to '%{public}@'", obsoleteKeymanKeyboardsDirectory, dataDirectory); + + // delete, happens whether migration or not + //[self createKeyboardsDirectoriesIfNecessary]; - if (exists) { - os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' exists", keymanUrl); - if (isDir) { - os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' is a directory", keymanUrl); - } - } else { - os_log_info([KMLogs startupLog], "keymanUrl '%{public}@' does not exist", keymanUrl); + BOOL dataExistsInOldLocation = [self keyboardsExistInDocumentsFolder]; + os_log([KMLogs startupLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); + + if (dataExistsInOldLocation) { + NSError *moveError = nil; + NSFileManager *fileManager = [NSFileManager defaultManager]; + didMoveData = [fileManager moveItemAtURL:self.obsoleteKeymanKeyboardsDirectory + toURL:self.keymanDataDirectory + error:&moveError]; + if (moveError) { + os_log_error([KMLogs startupLog], "error migrating data: '%{public}@'", moveError.localizedDescription); + } } - */ + + return didMoveData; } /** diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 87a53824321..ecd3e659e82 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -511,6 +511,7 @@ - (BOOL)useVerboseLogging { * Locate and create the Keyman data path; currently in ~/Documents/Keyman-Keyboards */ - (NSString *)keymanDataPath { + /* if(_keymanDataPath == nil) { NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; _keymanDataPath = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"]; @@ -523,18 +524,22 @@ - (NSString *)keymanDataPath { } } return _keymanDataPath; + */ + return [KMDataRepository shared].keymanDataDirectory.path; } /** * Returns the root folder where keyboards are stored; currently the same * as the keymanDataPath, but may diverge in future versions (possibly a sub-folder) + * + * Actually divering now, get this from KMDataRepository */ - (NSString *)keyboardsPath { if (_keyboardsPath == nil) { _keyboardsPath = [self keymanDataPath]; } - return _keyboardsPath; + return [KMDataRepository shared].keymanKeyboardsDirectory.path; } - (NSArray *)kmxFileList { @@ -765,13 +770,23 @@ - (void)setContextBuffer:(NSMutableString *)contextBuffer { } - (void)awakeFromNib { - if ([KMSettingsRepository.shared keyboardsMigrationNeeded]) { - os_log_info([KMLogs startupLog], "keyboards migration needed"); - [KMDataRepository.shared migrateResources]; - } else { - os_log_info([KMLogs startupLog], "keyboards migration not needed"); - } + [self preparePersistence]; +} +/** + * Prepare the app for all the things that need to be persisted: + * namely, the settings in UserDefaults and keyboard data on disk + */ +- (void)preparePersistence { + [KMDataRepository.shared createDataDirectoriesIfNecessary]; + + if ([KMSettingsRepository.shared dataMigrationNeeded]) { + BOOL movedData = [KMDataRepository.shared migrateData]; + //os_log_info([KMLogs startupLog], "test: call migrateData again"); + //[KMDataRepository.shared migrateData]; + [KMSettingsRepository.shared convertSettingsForMigration]; + } + [KMSettingsRepository.shared createStorageFlagIfNecessary]; } - (void)setDefaultKeymanMenuItems { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h index e4b9f1c158a..9a2fcb4c413 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h @@ -14,7 +14,9 @@ NS_ASSUME_NONNULL_BEGIN @interface KMSettingsRepository : NSObject + (KMSettingsRepository *)shared; -- (BOOL)keyboardsMigrationNeeded; +- (BOOL)dataMigrationNeeded; +- (void)convertSettingsForMigration; +- (void)createStorageFlagIfNecessary; @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index d5a26cccac7..84d6623d2c9 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -13,8 +13,12 @@ #import "KMSettingsRepository.h" #import "KMLogs.h" -NSString *const kStoreKeyboardsInLibraryKey = @"KMStoreKeyboardsInLibraryKey"; +NSString *const kStoreDataInLibraryKey = @"KMStoreDataInLibraryKey"; NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; +NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; + +NSString *const kObsoletePathComponent = @"/Documents/"; +NSString *const kNewPathComponent = @"/Application Support/keyman.inputmethod.Keyman/"; @implementation KMSettingsRepository @@ -28,30 +32,109 @@ + (KMSettingsRepository *)shared return shared; } +- (void)createStorageFlagIfNecessary { + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kStoreDataInLibraryKey]; +} + - (BOOL)settingsExist { return [[NSUserDefaults standardUserDefaults] objectForKey:kActiveKeyboardsKey] != nil; } -- (BOOL)keyboardsStoredInLibraryFolder +- (BOOL)dataStoredInLibraryDirectory { - return [[NSUserDefaults standardUserDefaults] boolForKey:kStoreKeyboardsInLibraryKey]; + return [[NSUserDefaults standardUserDefaults] boolForKey:kStoreDataInLibraryKey]; } /** - * Determines whether the keyboards data needs to be moved from the old location in the Documents folder to the new location under /username/Library... + * Determines whether the keyboard data needs to be moved from the old location in ~/Documents to the new location ~/Library... * This is true if * 1) the UserDefaults exist (indicating that this is not a new installation of Keyman) and * 2) the value for KMStoreKeyboardsInLibraryKey is not set to true */ -- (BOOL)keyboardsMigrationNeeded { - BOOL keyboardSettingsExist = [self settingsExist]; - os_log([KMLogs startupLog], " keyboard settings exist: %@", keyboardSettingsExist ? @"YES" : @"NO" ); +- (BOOL)dataMigrationNeeded { + BOOL keymanSettingsExist = [self settingsExist]; + os_log([KMLogs startupLog], " keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); - BOOL keyboardsInLibrary = [self keyboardsStoredInLibraryFolder]; - os_log([KMLogs startupLog], " keyboards stored in Library: %@", keyboardsInLibrary ? @"YES" : @"NO" ); + BOOL dataInLibrary = [self dataStoredInLibraryDirectory]; + os_log([KMLogs startupLog], " data stored in Library: %{public}@", dataInLibrary ? @"YES" : @"NO" ); - return !(keyboardSettingsExist && keyboardsInLibrary); + return !(keymanSettingsExist && dataInLibrary); +} + +- (void)convertSettingsForMigration { + [self convertSelectedKeyboardPathForMigration]; + [self convertActiveKeyboardArrayForMigration]; +} + +- (void)convertSelectedKeyboardPathForMigration { + NSString *selectedKeyboardPath = [self selectedKeyboard]; + + if (selectedKeyboardPath != nil) { + NSString *newPathString = [self convertOldKeyboardPath:selectedKeyboardPath]; + + if ([selectedKeyboardPath isNotEqualTo:newPathString]) { + [self saveSelectedKeyboard:newPathString]; + os_log([KMLogs startupLog], "converted selected keyboard setting from '%{public}@' to '%{public}@'", selectedKeyboardPath, newPathString); + } + } +} + +/** + * Convert the path of the keyboard designating the Documents folder to its new location + * in the Application Support folder + */ + +- (NSString *)convertOldKeyboardPath:(NSString *)oldPath { + NSString *newPathString = @""; + if(oldPath != nil) { + newPathString = [oldPath stringByReplacingOccurrencesOfString:kObsoletePathComponent withString:kNewPathComponent]; + } + return newPathString; } +- (NSString *)selectedKeyboard { + return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey]; +} + +- (void)saveSelectedKeyboard:(NSString *)selectedKeyboard { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setObject:selectedKeyboard forKey:kSelectedKeyboardKey]; +} + +- (void)convertActiveKeyboardArrayForMigration { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + NSMutableArray *activeKeyboards = [self activeKeyboards]; + NSMutableArray *convertedActiveKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; + BOOL didConvert = NO; + + for (NSString *oldPath in activeKeyboards) { + NSString *newPath = [self convertOldKeyboardPath:oldPath]; + if ([oldPath isNotEqualTo:newPath]) { + [convertedActiveKeyboards addObject:newPath]; + os_log([KMLogs startupLog], "converted active keyboard from old path '%{public}@' to '%{public}@'", oldPath, newPath); + // if we have adjusted at least one path, set flag + didConvert = YES; + } else { + // if, somehow, the path does not need converting then retain it in new array + [convertedActiveKeyboards addObject:oldPath]; + } + } + + // only update array in UserDefaults if we actually converted something + if (didConvert) { + [[NSUserDefaults standardUserDefaults] setObject:convertedActiveKeyboards forKey:kActiveKeyboardsKey]; + } +} + +- (NSMutableArray *)activeKeyboards { + NSMutableArray * activeKeyboards = [[[NSUserDefaults standardUserDefaults] arrayForKey:kActiveKeyboardsKey] mutableCopy]; + + if (!activeKeyboards) { + activeKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; + } + return activeKeyboards; +} + + @end From c2e2cf53c61571d05883a3c6db904bbd89d140bb Mon Sep 17 00:00:00 2001 From: sgschantz Date: Fri, 2 Aug 2024 15:21:56 +0700 Subject: [PATCH 06/10] change(mac): successful migration but not loading from config --- .../Keyman4MacIM.xcodeproj/project.pbxproj | 4 ++ .../Keyman4MacIM/KMDataRepository.h | 3 +- .../Keyman4MacIM/KMDataRepository.m | 49 ++++++++++++++----- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 12 +++-- .../Keyman4MacIM/KMPackageReader.m | 3 ++ .../Keyman4MacIM/KMSettingsRepository.m | 5 +- .../KeymanTests/InputMethodTests.m | 17 +++++++ 7 files changed, 74 insertions(+), 19 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj index 9d63aac4685..8f49e2e0f24 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj +++ b/mac/Keyman4MacIM/Keyman4MacIM.xcodeproj/project.pbxproj @@ -31,6 +31,8 @@ 29B42A602728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 29B42A622728343B00EDD5D3 /* KMKeyboardHelpWindowController.xib */; }; 29B4A0D52BF7675A00682049 /* KMLogs.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B4A0D32BF7675A00682049 /* KMLogs.m */; }; 29B6FB732BC39DD60074BF7F /* TextApiComplianceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B6FB722BC39DD60074BF7F /* TextApiComplianceTests.m */; }; + 29C1CDE22C5B2F8B003C23BB /* KMSettingsRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = D861B03E2C5747F70003675E /* KMSettingsRepository.m */; }; + 29C1CDE32C5B2F8B003C23BB /* KMDataRepository.m in Sources */ = {isa = PBXBuildFile; fileRef = 29015ABC2C58D86F00CCBB94 /* KMDataRepository.m */; }; 37A245C12565DFA6000BBF92 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 37A245C02565DFA6000BBF92 /* Assets.xcassets */; }; 37AE5C9D239A7B770086CC7C /* qrcode.min.js in Resources */ = {isa = PBXBuildFile; fileRef = 37AE5C9C239A7B770086CC7C /* qrcode.min.js */; }; 37C2B0CB25FF2C350092E16A /* Help in Resources */ = {isa = PBXBuildFile; fileRef = 37C2B0CA25FF2C340092E16A /* Help */; }; @@ -1017,6 +1019,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 29C1CDE22C5B2F8B003C23BB /* KMSettingsRepository.m in Sources */, + 29C1CDE32C5B2F8B003C23BB /* KMDataRepository.m in Sources */, 2915DC512BFE35DB0051FC52 /* KMLogs.m in Sources */, 2992F4202A28482800E08929 /* PrivacyWindowController.m in Sources */, 2992F41F2A2847C900E08929 /* TextApiCompliance.m in Sources */, diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index 836b8b742b4..a5af2ccb881 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -17,7 +17,8 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly) NSURL *keymanKeyboardsDirectory; // keymanKeyboardsDirectory = '~/Library/Application Support/com.keyman.app/Keyman-Keyboards' + (KMDataRepository *)shared; -- (void)createDataDirectoriesIfNecessary; +- (void)createDataDirectoryIfNecessary; +- (void)createKeyboardsDirectoryIfNecessary; - (BOOL)migrateData; @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 2143d8b7f50..3d0d0bacd7e 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -74,7 +74,7 @@ - (NSURL *)applicationSupportSubDirectory { if (directoryError) { os_log_error([KMLogs startupLog], "error getting Application Support subdirectory: '%{public}@'", directoryError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "Application Support subdirectory: '%{public}@'", applicationSupportUrl); + os_log_info([KMLogs startupLog], "Application Support subdirectory: '%{public}@'", applicationSupportUrl.path); _applicationSupportSubDirectory = applicationSupportUrl; } } @@ -98,18 +98,39 @@ - (NSURL *)keymanKeyboardsDirectory { } /** - * creates Keyman data directories if they do not exist yet - * This includes 1) the main data subdirectory: keyman.inputmethod.Keyman - * and 2) its subdirectory, Keyman-Keyboards - * + * Creates Keyman data directory if it do not exist yet. This is the main data subdirectory: keyman.inputmethod.Keyman + */ +- (void)createDataDirectoryIfNecessary { + NSFileManager *fileManager = [NSFileManager defaultManager]; + BOOL isDir; + BOOL exists = [fileManager fileExistsAtPath:self.keymanDataDirectory.path isDirectory:&isDir]; + + if (!exists) { + NSError *createError = nil; + os_log_info([KMLogs startupLog], "createDataDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanDataDirectory.path); + [fileManager createDirectoryAtPath:self.keymanDataDirectory.path withIntermediateDirectories:YES attributes:nil error:nil]; + if (createError) { + os_log_error([KMLogs startupLog], "error creating Keyman data directory: '%{public}@'", createError.localizedDescription); + } else { + os_log_info([KMLogs startupLog], "created Keyman data directory: '%{public}@'", self.keymanDataDirectory.path); + } + } else { + os_log_info([KMLogs startupLog], "Keyman data directory already exists: '%{public}@'", self.keymanDataDirectory.path); + } +} + +/** + * Creates Keyman keyboard directory if it does not exist yet. This is the 'Keyman-Keyboards' directory. + * It should not be created until after migrating because its existence would block migrating data from the old location. */ -- (void)createDataDirectoriesIfNecessary { +- (void)createKeyboardsDirectoryIfNecessary { NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL isDir; BOOL exists = [fileManager fileExistsAtPath:self.keymanKeyboardsDirectory.path isDirectory:&isDir]; if (!exists) { NSError *createError = nil; + os_log_info([KMLogs startupLog], "createKeyboardsDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanKeyboardsDirectory.path); [fileManager createDirectoryAtPath:self.keymanKeyboardsDirectory.path withIntermediateDirectories:YES attributes:nil error:nil]; if (createError) { os_log_error([KMLogs startupLog], "error creating Keyman-Keyboards directory: '%{public}@'", createError.localizedDescription); @@ -138,24 +159,30 @@ - (BOOL)keyboardsExistInDocumentsFolder { - (BOOL)migrateData { BOOL didMoveData = NO; + NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *obsoleteKeymanKeyboardsDirectory = self.obsoleteKeymanKeyboardsDirectory.path; NSString *dataDirectory = self.keymanDataDirectory.path; os_log_info([KMLogs startupLog], "migrateData, move obsoleteKeymanKeyboardsDirectory: '%{public}@' to '%{public}@'", obsoleteKeymanKeyboardsDirectory, dataDirectory); - // delete, happens whether migration or not - //[self createKeyboardsDirectoriesIfNecessary]; - + BOOL isDir; + BOOL dataDirectoryExistsInNewLocation = ([fileManager fileExistsAtPath:self.keymanDataDirectory.path isDirectory:&isDir]); + os_log([KMLogs startupLog], "data directory exists in new location, %{public}@: %{public}@", self.keymanDataDirectory.path, dataDirectoryExistsInNewLocation?@"YES":@"NO"); + + BOOL keyboardsDirectoryExistsInNewLocation = ([fileManager fileExistsAtPath:self.keymanKeyboardsDirectory.path isDirectory:&isDir]); + os_log([KMLogs startupLog], "keyboards directory exists in new location, %{public}@: %{public}@", self.keymanKeyboardsDirectory.path, keyboardsDirectoryExistsInNewLocation?@"YES":@"NO"); + BOOL dataExistsInOldLocation = [self keyboardsExistInDocumentsFolder]; os_log([KMLogs startupLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); if (dataExistsInOldLocation) { NSError *moveError = nil; - NSFileManager *fileManager = [NSFileManager defaultManager]; didMoveData = [fileManager moveItemAtURL:self.obsoleteKeymanKeyboardsDirectory - toURL:self.keymanDataDirectory + toURL:self.keymanKeyboardsDirectory error:&moveError]; if (moveError) { os_log_error([KMLogs startupLog], "error migrating data: '%{public}@'", moveError.localizedDescription); + } else { + os_log_error([KMLogs startupLog], "data migrated successfully to: '%{public}@'", self.keymanKeyboardsDirectory.path); } } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index ecd3e659e82..8352bf2a594 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -182,7 +182,7 @@ -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { }]; // [SentrySDK captureMessage:@"Starting Keyman [test message]"]; - + [self setDefaultKeymanMenuItems]; [self updateKeyboardMenuItems]; } @@ -532,7 +532,7 @@ - (NSString *)keymanDataPath { * Returns the root folder where keyboards are stored; currently the same * as the keymanDataPath, but may diverge in future versions (possibly a sub-folder) * - * Actually divering now, get this from KMDataRepository + * Actually diverting now, get this from KMDataRepository */ - (NSString *)keyboardsPath { if (_keyboardsPath == nil) { @@ -774,11 +774,11 @@ - (void)awakeFromNib { } /** - * Prepare the app for all the things that need to be persisted: - * namely, the settings in UserDefaults and keyboard data on disk + * Prepare the app environment for all the things that need to be persisted: + * namely, the keyboard data on disk and the settings in UserDefaults */ - (void)preparePersistence { - [KMDataRepository.shared createDataDirectoriesIfNecessary]; + [KMDataRepository.shared createDataDirectoryIfNecessary]; if ([KMSettingsRepository.shared dataMigrationNeeded]) { BOOL movedData = [KMDataRepository.shared migrateData]; @@ -786,6 +786,8 @@ - (void)preparePersistence { //[KMDataRepository.shared migrateData]; [KMSettingsRepository.shared convertSettingsForMigration]; } + + [KMDataRepository.shared createKeyboardsDirectoryIfNecessary]; [KMSettingsRepository.shared createStorageFlagIfNecessary]; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m index 7406488a8a0..186ec8ab5a0 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m @@ -69,6 +69,8 @@ - (KMPackageInfo *)loadPackageInfo:(NSString *)path { return packageInfo; } +/* + // TODO: not used, delete - (NSString *)packageNameFromPackageInfo:(NSString *)path { NSString *packageName = nil; @@ -77,6 +79,7 @@ - (NSString *)packageNameFromPackageInfo:(NSString *)path { return packageName; } +*/ /** * read JSON file and load it into KMPackageInfo object diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 84d6623d2c9..d0776e01fc2 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -11,14 +11,15 @@ */ #import "KMSettingsRepository.h" +//#import "KMDataRepository.h" #import "KMLogs.h" -NSString *const kStoreDataInLibraryKey = @"KMStoreDataInLibraryKey"; +NSString *const kStoreDataInLibraryKey = @"KMStoreDataInLibrary"; NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; NSString *const kObsoletePathComponent = @"/Documents/"; -NSString *const kNewPathComponent = @"/Application Support/keyman.inputmethod.Keyman/"; +NSString *const kNewPathComponent = @"/Library/Application Support/keyman.inputmethod.Keyman/"; @implementation KMSettingsRepository diff --git a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m index 8eb9d974ceb..38994c351a5 100644 --- a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m +++ b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m @@ -13,6 +13,9 @@ #import "KMInputMethodEventHandler.h" #import "AppleCompliantTestClient.h" #import "TextApiCompliance.h" +#import "KMSettingsRepository.h" +#import "KMDataRepository.h" +#import KMInputMethodEventHandler *testEventHandler = nil; @@ -85,4 +88,18 @@ - (void)testCalculateInsertRange_deleteOneBMPCharacterWithOneSelected_returnsRan XCTAssertTrue(correctResult, @"insert or replacement range expected to be {1,2}"); } +- (void)testMigrateData_oldDataExists_logsLocations { + os_log_t startupLog = os_log_create("com.keyman.app", "data-migration"); + if ([KMSettingsRepository.shared dataMigrationNeeded]) { + os_log_info(startupLog, "data migration needed, calling migrateData"); + [KMDataRepository.shared migrateData]; + os_log_info(startupLog, "test: call migrateData again"); + [KMDataRepository.shared migrateData]; + } else { + os_log_info(startupLog, "data migration not needed"); + } + + XCTAssertTrue(YES, @"test failed"); +} + @end From b435090a6fba6f9c29c15657e5ecef646b537017 Mon Sep 17 00:00:00 2001 From: Shawn Schantz Date: Mon, 5 Aug 2024 16:43:02 +0700 Subject: [PATCH 07/10] change(mac): migrates options and changes keyboards successfully --- .../Keyman4MacIM/KMDataRepository.m | 40 ++------------- .../Keyman4MacIM/KMInputMethodAppDelegate.h | 1 - .../Keyman4MacIM/KMInputMethodAppDelegate.m | 33 ++----------- .../Keyman4MacIM/KMSettingsRepository.m | 49 +++++++++++++++++++ 4 files changed, 58 insertions(+), 65 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 3d0d0bacd7e..ac2d678ce57 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -157,23 +157,16 @@ - (BOOL)keyboardsExistInDocumentsFolder { return exists; } +/** + * Migrate the keyboards data from the old location in '/Documents' to the new location '/Application Support/keyman.inputmethod.Keyman/' + */ - (BOOL)migrateData { BOOL didMoveData = NO; NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString *obsoleteKeymanKeyboardsDirectory = self.obsoleteKeymanKeyboardsDirectory.path; - NSString *dataDirectory = self.keymanDataDirectory.path; - os_log_info([KMLogs startupLog], "migrateData, move obsoleteKeymanKeyboardsDirectory: '%{public}@' to '%{public}@'", obsoleteKeymanKeyboardsDirectory, dataDirectory); - - BOOL isDir; - BOOL dataDirectoryExistsInNewLocation = ([fileManager fileExistsAtPath:self.keymanDataDirectory.path isDirectory:&isDir]); - os_log([KMLogs startupLog], "data directory exists in new location, %{public}@: %{public}@", self.keymanDataDirectory.path, dataDirectoryExistsInNewLocation?@"YES":@"NO"); - - BOOL keyboardsDirectoryExistsInNewLocation = ([fileManager fileExistsAtPath:self.keymanKeyboardsDirectory.path isDirectory:&isDir]); - os_log([KMLogs startupLog], "keyboards directory exists in new location, %{public}@: %{public}@", self.keymanKeyboardsDirectory.path, keyboardsDirectoryExistsInNewLocation?@"YES":@"NO"); - BOOL dataExistsInOldLocation = [self keyboardsExistInDocumentsFolder]; os_log([KMLogs startupLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); + // only move data if there is something there to move if (dataExistsInOldLocation) { NSError *moveError = nil; didMoveData = [fileManager moveItemAtURL:self.obsoleteKeymanKeyboardsDirectory @@ -182,34 +175,11 @@ - (BOOL)migrateData { if (moveError) { os_log_error([KMLogs startupLog], "error migrating data: '%{public}@'", moveError.localizedDescription); } else { - os_log_error([KMLogs startupLog], "data migrated successfully to: '%{public}@'", self.keymanKeyboardsDirectory.path); + os_log_info([KMLogs startupLog], "data migrated successfully to: '%{public}@'", self.keymanKeyboardsDirectory.path); } } return didMoveData; } -/** - * Locate and create the Keyman data path; currently in ~/Documents/Keyman-Keyboards - */ -/* -- (NSString *)_obsoleteKeymanDataDirectory { - if(_keymanDataDirectory == nil) { - NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - _keymanDataDirectory = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"]; - - os_log_debug([KMLogs dataLog], "creating keymanDataPath, %{public}@", _keymanDataDirectory); - - NSFileManager *fm = [NSFileManager defaultManager]; - if (![fm fileExistsAtPath:_keymanDataDirectory]) { - [fm createDirectoryAtPath:_keymanDataDirectory withIntermediateDirectories:YES attributes:nil error:nil]; - } - } - return _keymanDataDirectory; -} - -- (NSString *)keymanDataDirectory { - return _keymanDataDirectory; -} -*/ @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h index 4bf463c177f..7eddf31cb51 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h @@ -126,7 +126,6 @@ static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0; - (NSString *)oskWindowTitle; - (void)postKeyboardEventWithSource: (CGEventSourceRef)source code:(CGKeyCode) virtualKey postCallback:(PostEventCallback)postEvent; - (KeymanVersionInfo)versionInfo; -- (NSString *)keymanDataPath; - (void)registerConfigurationWindow:(NSWindowController *)window; @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 8352bf2a594..fb7edaad012 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -85,7 +85,6 @@ @implementation KMInputMethodAppDelegate @synthesize alwaysShowOSK = _alwaysShowOSK; id _lastServerWithOSKShowing = nil; -NSString* _keymanDataPath = nil; - (id)init { self = [super init]; @@ -508,38 +507,14 @@ - (BOOL)useVerboseLogging { #pragma mark - Keyman Data /** - * Locate and create the Keyman data path; currently in ~/Documents/Keyman-Keyboards - */ -- (NSString *)keymanDataPath { - /* - if(_keymanDataPath == nil) { - NSString *documentDirPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - _keymanDataPath = [documentDirPath stringByAppendingPathComponent:@"Keyman-Keyboards"]; - - os_log_debug([KMLogs dataLog], "creating keymanDataPath, %{public}@", _keymanDataPath); - - NSFileManager *fm = [NSFileManager defaultManager]; - if (![fm fileExistsAtPath:_keymanDataPath]) { - [fm createDirectoryAtPath:_keymanDataPath withIntermediateDirectories:YES attributes:nil error:nil]; - } - } - return _keymanDataPath; - */ - return [KMDataRepository shared].keymanDataDirectory.path; -} - -/** - * Returns the root folder where keyboards are stored; currently the same - * as the keymanDataPath, but may diverge in future versions (possibly a sub-folder) - * - * Actually diverting now, get this from KMDataRepository + * Returns the root folder where keyboards are stored */ - (NSString *)keyboardsPath { if (_keyboardsPath == nil) { - _keyboardsPath = [self keymanDataPath]; + _keyboardsPath = [KMDataRepository shared].keymanKeyboardsDirectory.path; } - return [KMDataRepository shared].keymanKeyboardsDirectory.path; + return _keyboardsPath; } - (NSArray *)kmxFileList { @@ -652,7 +627,7 @@ - (KMPackageInfo *)loadPackageInfo:(NSString *)path { - (NSString *)packageNameFromPackageInfo:(NSString *)packageFolder { NSString *packageName = nil; - NSString *path = [[self keymanDataPath] stringByAppendingPathComponent:packageFolder]; + NSString *path = [[self keyboardsPath] stringByAppendingPathComponent:packageFolder]; KMPackageInfo *packageInfo = [self.packageReader loadPackageInfo:path]; if (packageInfo) { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index d0776e01fc2..5381651e313 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -17,6 +17,7 @@ NSString *const kStoreDataInLibraryKey = @"KMStoreDataInLibrary"; NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; +NSString *const kPersistedOptionsKey = @"KMPersistedOptionsKey"; NSString *const kObsoletePathComponent = @"/Documents/"; NSString *const kNewPathComponent = @"/Library/Application Support/keyman.inputmethod.Keyman/"; @@ -66,6 +67,7 @@ - (BOOL)dataMigrationNeeded { - (void)convertSettingsForMigration { [self convertSelectedKeyboardPathForMigration]; [self convertActiveKeyboardArrayForMigration]; + [self convertPersistedOptionsPathsForMigration]; } - (void)convertSelectedKeyboardPathForMigration { @@ -137,5 +139,52 @@ - (NSMutableArray *)activeKeyboards { return activeKeyboards; } +- (void)convertPersistedOptionsPathsForMigration { + NSDictionary * optionsMap = [self persistedOptions]; + NSMutableDictionary *mutableOptionsMap = nil; + BOOL optionsChanged = NO; + + if (optionsMap != nil) { + os_log_info([KMLogs configLog], "optionsMap != nil"); + mutableOptionsMap = [[NSMutableDictionary alloc] initWithCapacity:0]; + for(id key in optionsMap) { + os_log_info([KMLogs configLog], "persisted options found in UserDefaults with key = %{public}@", key); + } + for (NSString *key in optionsMap) { + NSString *newPathString = [self convertOldKeyboardPath:key]; + NSDictionary *optionsValue = [optionsMap objectForKey:key]; + + if ([key isNotEqualTo:newPathString]) { + optionsChanged = YES; + + // insert options into new map with newly converted path as key + [mutableOptionsMap setObject:optionsValue forKey:newPathString]; + os_log([KMLogs startupLog], "converted option key from '%{public}@' to '%{public}@'", key, newPathString); + } else { + // retain options that did not need converting + [mutableOptionsMap setObject:optionsValue forKey:key]; + } + } + if (optionsChanged) { + [self savePersistedOptions:mutableOptionsMap]; + } + } +} + +- (NSDictionary *)persistedOptions { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData dictionaryForKey:kPersistedOptionsKey]; +} + +- (void)savePersistedOptions:(NSDictionary *) optionsDictionary { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setObject:optionsDictionary forKey:kPersistedOptionsKey]; +} + +- (void)removePersistedOptions { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData removeObjectForKey:kPersistedOptionsKey]; +} + @end From 8b408730b7ba04f502f6f1c9c0c165d8cbec6527 Mon Sep 17 00:00:00 2001 From: Shawn Schantz Date: Tue, 6 Aug 2024 17:14:34 +0700 Subject: [PATCH 08/10] change(mac): uses Library directory instead of Documents Write new key KMDataModelVersion in UserDefaults with integer value that determines how data is expected to be stored. Migrates existing Keyman data to new location if it was written with an older (or no) version. Fixes: #2542 --- .../Keyman4MacIM/KMDataRepository.m | 36 +++--- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 10 +- .../Keyman4MacIM/KMSettingsRepository.h | 2 +- .../Keyman4MacIM/KMSettingsRepository.m | 112 ++++++++++-------- 4 files changed, 87 insertions(+), 73 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index ac2d678ce57..24495007bd4 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -55,9 +55,9 @@ - (NSURL *)documentsSubDirectory { NSURL *documentsUrl = [fileManager URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; if (directoryError) { - os_log_error([KMLogs startupLog], "error getting Documents subdirectory: '%{public}@'", directoryError.localizedDescription); + os_log_error([KMLogs dataLog], "error getting Documents subdirectory: '%{public}@'", directoryError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "Documents subdirectory: '%{public}@'", documentsUrl); + os_log_info([KMLogs dataLog], "Documents subdirectory: '%{public}@'", documentsUrl); _documentsSubDirectory = documentsUrl; } } @@ -72,9 +72,9 @@ - (NSURL *)applicationSupportSubDirectory { NSURL *applicationSupportUrl = [fileManager URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&directoryError]; if (directoryError) { - os_log_error([KMLogs startupLog], "error getting Application Support subdirectory: '%{public}@'", directoryError.localizedDescription); + os_log_error([KMLogs dataLog], "error getting Application Support subdirectory: '%{public}@'", directoryError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "Application Support subdirectory: '%{public}@'", applicationSupportUrl.path); + os_log_info([KMLogs dataLog], "Application Support subdirectory: '%{public}@'", applicationSupportUrl.path); _applicationSupportSubDirectory = applicationSupportUrl; } } @@ -107,15 +107,15 @@ - (void)createDataDirectoryIfNecessary { if (!exists) { NSError *createError = nil; - os_log_info([KMLogs startupLog], "createDataDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanDataDirectory.path); + os_log_info([KMLogs dataLog], "createDataDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanDataDirectory.path); [fileManager createDirectoryAtPath:self.keymanDataDirectory.path withIntermediateDirectories:YES attributes:nil error:nil]; if (createError) { - os_log_error([KMLogs startupLog], "error creating Keyman data directory: '%{public}@'", createError.localizedDescription); + os_log_error([KMLogs dataLog], "error creating Keyman data directory: '%{public}@'", createError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "created Keyman data directory: '%{public}@'", self.keymanDataDirectory.path); + os_log_info([KMLogs dataLog], "created Keyman data directory: '%{public}@'", self.keymanDataDirectory.path); } } else { - os_log_info([KMLogs startupLog], "Keyman data directory already exists: '%{public}@'", self.keymanDataDirectory.path); + os_log_info([KMLogs dataLog], "Keyman data directory already exists: '%{public}@'", self.keymanDataDirectory.path); } } @@ -130,15 +130,15 @@ - (void)createKeyboardsDirectoryIfNecessary { if (!exists) { NSError *createError = nil; - os_log_info([KMLogs startupLog], "createKeyboardsDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanKeyboardsDirectory.path); + os_log_info([KMLogs dataLog], "createKeyboardsDirectoryIfNecessary, about to attempt createDirectoryAtPath for: '%{public}@'", self.keymanKeyboardsDirectory.path); [fileManager createDirectoryAtPath:self.keymanKeyboardsDirectory.path withIntermediateDirectories:YES attributes:nil error:nil]; if (createError) { - os_log_error([KMLogs startupLog], "error creating Keyman-Keyboards directory: '%{public}@'", createError.localizedDescription); + os_log_error([KMLogs dataLog], "error creating Keyman-Keyboards directory: '%{public}@'", createError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "created Keyman-Keyboards subdirectory: '%{public}@'", self.keymanKeyboardsDirectory.path); + os_log_info([KMLogs dataLog], "created Keyman-Keyboards subdirectory: '%{public}@'", self.keymanKeyboardsDirectory.path); } } else { - os_log_info([KMLogs startupLog], "Keyman-Keyboards already exists: '%{public}@'", self.keymanKeyboardsDirectory.path); + os_log_info([KMLogs dataLog], "Keyman-Keyboards already exists: '%{public}@'", self.keymanKeyboardsDirectory.path); } } @@ -150,7 +150,7 @@ - (NSURL *)obsoleteKeymanKeyboardsDirectory { return _obsoleteKeymanKeyboardsDirectory; } -- (BOOL)keyboardsExistInDocumentsFolder { +- (BOOL)keyboardsExistInObsoleteDirectory { NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL isDir; BOOL exists = ([fileManager fileExistsAtPath:self.obsoleteKeymanKeyboardsDirectory.path isDirectory:&isDir]); @@ -163,19 +163,19 @@ - (BOOL)keyboardsExistInDocumentsFolder { - (BOOL)migrateData { BOOL didMoveData = NO; NSFileManager *fileManager = [NSFileManager defaultManager]; - BOOL dataExistsInOldLocation = [self keyboardsExistInDocumentsFolder]; - os_log([KMLogs startupLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); + BOOL dataExistsInOldLocation = [self keyboardsExistInObsoleteDirectory]; + os_log([KMLogs dataLog], "obsolete keyman keyboards directory exists: %@", dataExistsInOldLocation?@"YES":@"NO"); - // only move data if there is something there to move + // only move data if there is something to move if (dataExistsInOldLocation) { NSError *moveError = nil; didMoveData = [fileManager moveItemAtURL:self.obsoleteKeymanKeyboardsDirectory toURL:self.keymanKeyboardsDirectory error:&moveError]; if (moveError) { - os_log_error([KMLogs startupLog], "error migrating data: '%{public}@'", moveError.localizedDescription); + os_log_error([KMLogs dataLog], "data migration failed: '%{public}@'", moveError.localizedDescription); } else { - os_log_info([KMLogs startupLog], "data migrated successfully to: '%{public}@'", self.keymanKeyboardsDirectory.path); + os_log_info([KMLogs dataLog], "data migrated successfully to: '%{public}@'", self.keymanKeyboardsDirectory.path); } } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index fb7edaad012..7d3709ae4f2 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -745,25 +745,23 @@ - (void)setContextBuffer:(NSMutableString *)contextBuffer { } - (void)awakeFromNib { - [self preparePersistence]; + [self prepareStorage]; } /** * Prepare the app environment for all the things that need to be persisted: - * namely, the keyboard data on disk and the settings in UserDefaults + * namely, the keyboard data on disk and the settings in UserDefaults */ -- (void)preparePersistence { +- (void)prepareStorage { [KMDataRepository.shared createDataDirectoryIfNecessary]; if ([KMSettingsRepository.shared dataMigrationNeeded]) { BOOL movedData = [KMDataRepository.shared migrateData]; - //os_log_info([KMLogs startupLog], "test: call migrateData again"); - //[KMDataRepository.shared migrateData]; [KMSettingsRepository.shared convertSettingsForMigration]; } [KMDataRepository.shared createKeyboardsDirectoryIfNecessary]; - [KMSettingsRepository.shared createStorageFlagIfNecessary]; + [KMSettingsRepository.shared setDataModelVersionIfNecessary]; } - (void)setDefaultKeymanMenuItems { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h index 9a2fcb4c413..e6fe4dca38a 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN + (KMSettingsRepository *)shared; - (BOOL)dataMigrationNeeded; - (void)convertSettingsForMigration; -- (void)createStorageFlagIfNecessary; +- (void)setDataModelVersionIfNecessary; @end NS_ASSUME_NONNULL_END diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 5381651e313..489c4e63dc6 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -7,14 +7,12 @@ * Created by Shawn Schantz on 2024-07-29. * * Singleton object for reading and writing Keyman application settings. - * Serves as an abstraction to StandardUserDefaults which is currently used to persist application settings. + * Serves as an abstraction to StandardUserDefaults which is currently used to store application settings. */ #import "KMSettingsRepository.h" -//#import "KMDataRepository.h" #import "KMLogs.h" -NSString *const kStoreDataInLibraryKey = @"KMStoreDataInLibrary"; NSString *const kActiveKeyboardsKey = @"KMActiveKeyboardsKey"; NSString *const kSelectedKeyboardKey = @"KMSelectedKeyboardKey"; NSString *const kPersistedOptionsKey = @"KMPersistedOptionsKey"; @@ -22,6 +20,14 @@ NSString *const kObsoletePathComponent = @"/Documents/"; NSString *const kNewPathComponent = @"/Library/Application Support/keyman.inputmethod.Keyman/"; +/** + * Store the version number of the data model in the UserDefaults with this key. + * The first version, 1, is defined to indicate that we are storing the data/keyboards in the Library + * directory instead of in the Documents directory. + */ +NSString *const kDataModelVersion = @"KMDataModelVersion"; +NSInteger const kVersionStoreDataInLibraryDirectory = 1; + @implementation KMSettingsRepository + (KMSettingsRepository *)shared @@ -34,18 +40,29 @@ + (KMSettingsRepository *)shared return shared; } -- (void)createStorageFlagIfNecessary { - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kStoreDataInLibraryKey]; +- (void)setDataModelVersionIfNecessary { + if (![self dataStoredInLibraryDirectory]) { + [[NSUserDefaults standardUserDefaults] setInteger:kVersionStoreDataInLibraryDirectory forKey:kDataModelVersion]; + } } +/** + * If the selectedKeyboard has not been set, then the settings have not been saved in the UserDefaults. + * If this method is called after applicationDidFinishLaunching, then it will always return true. + * If called from awakeFromNib, then it will return false when running for the first time. + */ - (BOOL)settingsExist { - return [[NSUserDefaults standardUserDefaults] objectForKey:kActiveKeyboardsKey] != nil; + return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey] != nil; } +/** + * For the first numbered version of the data model, the app stores the keyboards under the /Library directory + * For versions before version 1, the keyboards were stored under the /Documents directory. + */ - (BOOL)dataStoredInLibraryDirectory { - return [[NSUserDefaults standardUserDefaults] boolForKey:kStoreDataInLibraryKey]; + return [[NSUserDefaults standardUserDefaults] integerForKey:kDataModelVersion] == kVersionStoreDataInLibraryDirectory; } /** @@ -56,14 +73,47 @@ - (BOOL)dataStoredInLibraryDirectory */ - (BOOL)dataMigrationNeeded { BOOL keymanSettingsExist = [self settingsExist]; - os_log([KMLogs startupLog], " keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); + os_log([KMLogs dataLog], " keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); BOOL dataInLibrary = [self dataStoredInLibraryDirectory]; - os_log([KMLogs startupLog], " data stored in Library: %{public}@", dataInLibrary ? @"YES" : @"NO" ); + os_log([KMLogs dataLog], " data stored in Library: %{public}@", dataInLibrary ? @"YES" : @"NO" ); return !(keymanSettingsExist && dataInLibrary); } +- (NSString *)selectedKeyboard { + return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey]; +} + +- (void)saveSelectedKeyboard:(NSString *)selectedKeyboard { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setObject:selectedKeyboard forKey:kSelectedKeyboardKey]; +} + +- (NSMutableArray *)activeKeyboards { + NSMutableArray * activeKeyboards = [[[NSUserDefaults standardUserDefaults] arrayForKey:kActiveKeyboardsKey] mutableCopy]; + + if (!activeKeyboards) { + activeKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; + } + return activeKeyboards; +} + +- (NSDictionary *)persistedOptions { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData dictionaryForKey:kPersistedOptionsKey]; +} + +- (void)savePersistedOptions:(NSDictionary *) optionsDictionary { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + [userData setObject:optionsDictionary forKey:kPersistedOptionsKey]; +} + +- (void)removePersistedOptions { + NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; + return [userData removeObjectForKey:kPersistedOptionsKey]; +} + - (void)convertSettingsForMigration { [self convertSelectedKeyboardPathForMigration]; [self convertActiveKeyboardArrayForMigration]; @@ -78,7 +128,7 @@ - (void)convertSelectedKeyboardPathForMigration { if ([selectedKeyboardPath isNotEqualTo:newPathString]) { [self saveSelectedKeyboard:newPathString]; - os_log([KMLogs startupLog], "converted selected keyboard setting from '%{public}@' to '%{public}@'", selectedKeyboardPath, newPathString); + os_log([KMLogs dataLog], "converted selected keyboard setting from '%{public}@' to '%{public}@'", selectedKeyboardPath, newPathString); } } } @@ -96,15 +146,6 @@ - (NSString *)convertOldKeyboardPath:(NSString *)oldPath { return newPathString; } -- (NSString *)selectedKeyboard { - return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey]; -} - -- (void)saveSelectedKeyboard:(NSString *)selectedKeyboard { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setObject:selectedKeyboard forKey:kSelectedKeyboardKey]; -} - - (void)convertActiveKeyboardArrayForMigration { NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; NSMutableArray *activeKeyboards = [self activeKeyboards]; @@ -115,7 +156,7 @@ - (void)convertActiveKeyboardArrayForMigration { NSString *newPath = [self convertOldKeyboardPath:oldPath]; if ([oldPath isNotEqualTo:newPath]) { [convertedActiveKeyboards addObject:newPath]; - os_log([KMLogs startupLog], "converted active keyboard from old path '%{public}@' to '%{public}@'", oldPath, newPath); + os_log([KMLogs dataLog], "converted active keyboard from old path '%{public}@' to '%{public}@'", oldPath, newPath); // if we have adjusted at least one path, set flag didConvert = YES; } else { @@ -126,19 +167,10 @@ - (void)convertActiveKeyboardArrayForMigration { // only update array in UserDefaults if we actually converted something if (didConvert) { - [[NSUserDefaults standardUserDefaults] setObject:convertedActiveKeyboards forKey:kActiveKeyboardsKey]; + [[NSUserDefaults standardUserDefaults] setObject:convertedActiveKeyboards forKey:kActiveKeyboardsKey]; } } -- (NSMutableArray *)activeKeyboards { - NSMutableArray * activeKeyboards = [[[NSUserDefaults standardUserDefaults] arrayForKey:kActiveKeyboardsKey] mutableCopy]; - - if (!activeKeyboards) { - activeKeyboards = [[NSMutableArray alloc] initWithCapacity:0]; - } - return activeKeyboards; -} - - (void)convertPersistedOptionsPathsForMigration { NSDictionary * optionsMap = [self persistedOptions]; NSMutableDictionary *mutableOptionsMap = nil; @@ -159,10 +191,10 @@ - (void)convertPersistedOptionsPathsForMigration { // insert options into new map with newly converted path as key [mutableOptionsMap setObject:optionsValue forKey:newPathString]; - os_log([KMLogs startupLog], "converted option key from '%{public}@' to '%{public}@'", key, newPathString); + os_log([KMLogs dataLog], "converted option key from '%{public}@' to '%{public}@'", key, newPathString); } else { // retain options that did not need converting - [mutableOptionsMap setObject:optionsValue forKey:key]; + [mutableOptionsMap setObject:optionsValue forKey:key]; } } if (optionsChanged) { @@ -171,20 +203,4 @@ - (void)convertPersistedOptionsPathsForMigration { } } -- (NSDictionary *)persistedOptions { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - return [userData dictionaryForKey:kPersistedOptionsKey]; -} - -- (void)savePersistedOptions:(NSDictionary *) optionsDictionary { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - [userData setObject:optionsDictionary forKey:kPersistedOptionsKey]; -} - -- (void)removePersistedOptions { - NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; - return [userData removeObjectForKey:kPersistedOptionsKey]; -} - - @end From 24f4a61adacbbfd7e721b48904282bfe9bae86e3 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Wed, 21 Aug 2024 17:05:07 +0700 Subject: [PATCH 09/10] change(mac): responded to review comments --- .../Keyman4MacIM/KMDataRepository.h | 6 +-- .../Keyman4MacIM/KMDataRepository.m | 42 +++++++++++++------ .../Keyman4MacIM/KMInputMethodEventHandler.m | 11 ----- .../Keyman4MacIM/KMPackageReader.m | 12 ------ .../Keyman4MacIM/KMSettingsRepository.m | 35 +++++++++------- .../KeymanTests/InputMethodTests.m | 14 ------- 6 files changed, 52 insertions(+), 68 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index a5af2ccb881..0a1cc8cca37 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -1,4 +1,4 @@ -/** +/* * Keyman is copyright (C) SIL International. MIT License. * * KMDataRepository.h @@ -13,9 +13,9 @@ NS_ASSUME_NONNULL_BEGIN @interface KMDataRepository : NSObject -@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/com.keyman.app' +@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/keyman.inputmethod.Keyman' @property (readonly) NSURL *keymanKeyboardsDirectory; -// keymanKeyboardsDirectory = '~/Library/Application Support/com.keyman.app/Keyman-Keyboards' +// keymanKeyboardsDirectory = '~/Library/Application Support/keyman.inputmethod.Keyman/Keyman-Keyboards' + (KMDataRepository *)shared; - (void)createDataDirectoryIfNecessary; - (void)createKeyboardsDirectoryIfNecessary; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 24495007bd4..20d6e892a46 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -1,4 +1,4 @@ -/** +/* * Keyman is copyright (C) SIL International. MIT License. * * KMResourcesRepository.m @@ -15,30 +15,39 @@ #import "KMLogs.h" @interface KMDataRepository () -@property (readonly) NSURL *applicationSupportSubDirectory; // '~/Library/Application Support' -@property (readonly) NSURL *documentsSubDirectory; // '~/Documents' -@property (readonly) NSURL *obsoleteKeymanKeyboardsDirectory; // '~/Library/Documents/Keyman-Keyboards' +@property (readonly) NSURL *applicationSupportSubDirectory; +@property (readonly) NSURL *documentsSubDirectory; +@property (readonly) NSURL *obsoleteKeymanKeyboardsDirectory; @end @implementation KMDataRepository - +/** + * Two directory trees are represented by the following properties, one in active use + * and one that is obsolete. + * The actively used directories, begin with the parent + * applicationSupportSubDirectory: '~/Library/Application Support' + * keymanDataDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman' + * keymanKeyboardsDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman/Keyman-Keyboards' + * The obsolete directories, begin with the parent + * documentsSubDirectory: '~/Documents' + * obsoleteKeymanKeyboardsDirectory: '~/Documents/Keyman-Keyboards' + */ @synthesize applicationSupportSubDirectory = _applicationSupportSubDirectory; -@synthesize documentsSubDirectory = _documentsSubDirectory; +@synthesize keymanDataDirectory = _keymanDataDirectory; @synthesize keymanKeyboardsDirectory = _keymanKeyboardsDirectory; +@synthesize documentsSubDirectory = _documentsSubDirectory; @synthesize obsoleteKeymanKeyboardsDirectory = _obsoleteKeymanKeyboardsDirectory; -@synthesize keymanDataDirectory = _keymanDataDirectory; NSString *const kKeyboardsDirectoryName = @"Keyman-Keyboards"; /* The name of the subdirectory within '~/Library/Application Support'. - The convention is to use bundle identifier. - (Using the subsystem id, "com.keyman.app", is a poor choice because the API + We follow the convention of using the bundle identifier rather than our subsystem id. + (Also, using the subsystem id, "com.keyman.app", is a poor choice because the API createDirectoryAtPath sees the .app extension and creates an application file. */ NSString *const kKeymanSubdirectoryName = @"keyman.inputmethod.Keyman"; -+ (KMDataRepository *)shared -{ ++ (KMDataRepository *)shared { static KMDataRepository *shared = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -149,7 +158,11 @@ - (NSURL *)obsoleteKeymanKeyboardsDirectory { } return _obsoleteKeymanKeyboardsDirectory; } - +/** + * Only called from migrateData. + * Causes user to be prompted for permission to access ~/Documents, but they should already have it. + * otherwise we would not be attempting to migrate. + */ - (BOOL)keyboardsExistInObsoleteDirectory { NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL isDir; @@ -158,7 +171,10 @@ - (BOOL)keyboardsExistInObsoleteDirectory { } /** - * Migrate the keyboards data from the old location in '/Documents' to the new location '/Application Support/keyman.inputmethod.Keyman/' + * Migrate the keyboards data from the old location in '~/Documents' to the new location '~/Application Support/keyman.inputmethod.Keyman/' + * This should only be called if the Keyman settings written to the UserDefaults indicates that we have data in the old location. + * If this is the case, then we expect the user to have already granted permission for Keyman to access the ~/Documents directory. + * If that permission has been removed for some reason, then calling this code will cause the user to be asked for permission again. */ - (BOOL)migrateData { BOOL didMoveData = NO; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m index efe628c43a3..02521b51cb3 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodEventHandler.m @@ -243,17 +243,6 @@ - (BOOL)handleEvent:(NSEvent *)event client:(id)sender { // return NO to pass through to client app return NO; } - - // TODO: remove test code - /* - if (event.keyCode == kVK_ANSI_Slash) { - if ([KMSettingsRepository.shared keyboardsMigrationNeeded]) { - os_log_info([KMLogs startupLog], "keyboards migration needed"); - } else { - os_log_info([KMLogs startupLog], "keyboards migration not needed"); - } - } - */ } if (event.type == NSEventTypeFlagsChanged) { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m index 186ec8ab5a0..bd9156bdb50 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMPackageReader.m @@ -69,18 +69,6 @@ - (KMPackageInfo *)loadPackageInfo:(NSString *)path { return packageInfo; } -/* - // TODO: not used, delete -- (NSString *)packageNameFromPackageInfo:(NSString *)path { - NSString *packageName = nil; - - KMPackageInfo *packageInfo = [self loadPackageInfo:path]; - packageName = packageInfo.packageName; - - return packageName; -} -*/ - /** * read JSON file and load it into KMPackageInfo object * returns nil if the file does not exist or it cannot be parsed as JSON diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index 489c4e63dc6..a605ea6e6ec 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -1,4 +1,4 @@ -/** +/* * Keyman is copyright (C) SIL International. MIT License. * * KMSettingsRepository.h @@ -27,6 +27,7 @@ */ NSString *const kDataModelVersion = @"KMDataModelVersion"; NSInteger const kVersionStoreDataInLibraryDirectory = 1; +NSInteger const kCurrentDataModelVersionNumber = kVersionStoreDataInLibraryDirectory; @implementation KMSettingsRepository @@ -41,7 +42,7 @@ + (KMSettingsRepository *)shared } - (void)setDataModelVersionIfNecessary { - if (![self dataStoredInLibraryDirectory]) { + if (![self dataModelWithKeyboardsInLibrary]) { [[NSUserDefaults standardUserDefaults] setInteger:kVersionStoreDataInLibraryDirectory forKey:kDataModelVersion]; } } @@ -51,34 +52,38 @@ - (void)setDataModelVersionIfNecessary { * If this method is called after applicationDidFinishLaunching, then it will always return true. * If called from awakeFromNib, then it will return false when running for the first time. */ -- (BOOL)settingsExist -{ +- (BOOL)settingsExist { return [[NSUserDefaults standardUserDefaults] objectForKey:kSelectedKeyboardKey] != nil; } /** - * For the first numbered version of the data model, the app stores the keyboards under the /Library directory - * For versions before version 1, the keyboards were stored under the /Documents directory. + * For the first numbered version of the data model, the app stores the keyboards under the ~/Library directory + * For versions before version 1, the keyboards were stored under the ~/Documents directory. */ -- (BOOL)dataStoredInLibraryDirectory -{ - return [[NSUserDefaults standardUserDefaults] integerForKey:kDataModelVersion] == kVersionStoreDataInLibraryDirectory; +- (BOOL)dataModelWithKeyboardsInLibrary { + // NSUserDefaults returns zero if the key does not exist + NSInteger dataModelVersion = [[NSUserDefaults standardUserDefaults] integerForKey:kDataModelVersion]; + + return dataModelVersion >= kVersionStoreDataInLibraryDirectory; } /** - * Determines whether the keyboard data needs to be moved from the old location in ~/Documents to the new location ~/Library... + * Determines whether the keyboard data needs to be moved from the old location to the new location * This is true if * 1) the UserDefaults exist (indicating that this is not a new installation of Keyman) and - * 2) the value for KMStoreKeyboardsInLibraryKey is not set to true + * 2) the value for kVersionStoreDataInLibraryDirectory is < 1, */ - (BOOL)dataMigrationNeeded { BOOL keymanSettingsExist = [self settingsExist]; - os_log([KMLogs dataLog], " keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); + os_log([KMLogs dataLog], "keyman settings exist: %{public}@", keymanSettingsExist ? @"YES" : @"NO" ); - BOOL dataInLibrary = [self dataStoredInLibraryDirectory]; - os_log([KMLogs dataLog], " data stored in Library: %{public}@", dataInLibrary ? @"YES" : @"NO" ); + BOOL keyboardsStoredInLibrary = [self dataModelWithKeyboardsInLibrary]; + os_log([KMLogs dataLog], "settings indicate that keyboards are stored in ~/Library: %{public}@", keyboardsStoredInLibrary ? @"YES" : @"NO" ); - return !(keymanSettingsExist && dataInLibrary); + BOOL migrationNeeded = keymanSettingsExist && !keyboardsStoredInLibrary; + os_log([KMLogs dataLog], "dataMigrationNeeded: %{public}@", migrationNeeded ? @"YES" : @"NO" ); + + return migrationNeeded; } - (NSString *)selectedKeyboard { diff --git a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m index 38994c351a5..5a339931e70 100644 --- a/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m +++ b/mac/Keyman4MacIM/KeymanTests/InputMethodTests.m @@ -88,18 +88,4 @@ - (void)testCalculateInsertRange_deleteOneBMPCharacterWithOneSelected_returnsRan XCTAssertTrue(correctResult, @"insert or replacement range expected to be {1,2}"); } -- (void)testMigrateData_oldDataExists_logsLocations { - os_log_t startupLog = os_log_create("com.keyman.app", "data-migration"); - if ([KMSettingsRepository.shared dataMigrationNeeded]) { - os_log_info(startupLog, "data migration needed, calling migrateData"); - [KMDataRepository.shared migrateData]; - os_log_info(startupLog, "test: call migrateData again"); - [KMDataRepository.shared migrateData]; - } else { - os_log_info(startupLog, "data migration not needed"); - } - - XCTAssertTrue(YES, @"test failed"); -} - @end From ea6e29581bd930d42d39cef5c774263fc4d56652 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Tue, 27 Aug 2024 13:06:37 +0700 Subject: [PATCH 10/10] change(mac): some minor changes after review comments --- mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h | 10 +++++----- mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m | 14 ++++++-------- .../Keyman4MacIM/KMSettingsRepository.m | 6 +----- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h index 0a1cc8cca37..87d240701d9 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.h @@ -1,9 +1,6 @@ /* * Keyman is copyright (C) SIL International. MIT License. * - * KMDataRepository.h - * Keyman - * * Created by Shawn Schantz on 2024-07-30. * */ @@ -13,9 +10,12 @@ NS_ASSUME_NONNULL_BEGIN @interface KMDataRepository : NSObject -@property (readonly) NSURL *keymanDataDirectory; // '~/Library/Application Support/keyman.inputmethod.Keyman' +// keymanDataDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman' +@property (readonly) NSURL *keymanDataDirectory; + +// keymanKeyboardsDirectory: '~/Library/Application Support/keyman.inputmethod.Keyman/Keyman-Keyboards' @property (readonly) NSURL *keymanKeyboardsDirectory; -// keymanKeyboardsDirectory = '~/Library/Application Support/keyman.inputmethod.Keyman/Keyman-Keyboards' + + (KMDataRepository *)shared; - (void)createDataDirectoryIfNecessary; - (void)createKeyboardsDirectoryIfNecessary; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m index 20d6e892a46..9ecedab2314 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMDataRepository.m @@ -1,9 +1,6 @@ /* * Keyman is copyright (C) SIL International. MIT License. * - * KMResourcesRepository.m - * Keyman - * * Created by Shawn Schantz on 2024-07-30. * * Singleton object which serves as an abstraction for the reading and writing of Keyman data. @@ -39,11 +36,11 @@ @implementation KMDataRepository @synthesize obsoleteKeymanKeyboardsDirectory = _obsoleteKeymanKeyboardsDirectory; NSString *const kKeyboardsDirectoryName = @"Keyman-Keyboards"; -/* - The name of the subdirectory within '~/Library/Application Support'. - We follow the convention of using the bundle identifier rather than our subsystem id. - (Also, using the subsystem id, "com.keyman.app", is a poor choice because the API - createDirectoryAtPath sees the .app extension and creates an application file. +/** + * The name of the subdirectory within '~/Library/Application Support'. + * We follow the convention of using the bundle identifier rather than our subsystem id. + * (Also, using the subsystem id, "com.keyman.app", is a poor choice because the API + * createDirectoryAtPath sees the .app extension and creates an application file.) */ NSString *const kKeymanSubdirectoryName = @"keyman.inputmethod.Keyman"; @@ -158,6 +155,7 @@ - (NSURL *)obsoleteKeymanKeyboardsDirectory { } return _obsoleteKeymanKeyboardsDirectory; } + /** * Only called from migrateData. * Causes user to be prompted for permission to access ~/Documents, but they should already have it. diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m index a605ea6e6ec..630e2c324e5 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMSettingsRepository.m @@ -1,9 +1,6 @@ /* * Keyman is copyright (C) SIL International. MIT License. * - * KMSettingsRepository.h - * Keyman - * * Created by Shawn Schantz on 2024-07-29. * * Singleton object for reading and writing Keyman application settings. @@ -61,7 +58,7 @@ - (BOOL)settingsExist { * For versions before version 1, the keyboards were stored under the ~/Documents directory. */ - (BOOL)dataModelWithKeyboardsInLibrary { - // NSUserDefaults returns zero if the key does not exist + // [NSUserDefaults integerForKey] returns zero if the key does not exist NSInteger dataModelVersion = [[NSUserDefaults standardUserDefaults] integerForKey:kDataModelVersion]; return dataModelVersion >= kVersionStoreDataInLibraryDirectory; @@ -142,7 +139,6 @@ - (void)convertSelectedKeyboardPathForMigration { * Convert the path of the keyboard designating the Documents folder to its new location * in the Application Support folder */ - - (NSString *)convertOldKeyboardPath:(NSString *)oldPath { NSString *newPathString = @""; if(oldPath != nil) {