From c717487b0ec575745f97d7e4d7de85b02a45efe7 Mon Sep 17 00:00:00 2001 From: relikd Date: Fri, 9 Aug 2019 21:07:54 +0200 Subject: [PATCH] Database options for version migration --- baRSS/AppHook.m | 9 +++ baRSS/Core Data/NSFetchRequest+Ext.h | 3 +- baRSS/Core Data/NSFetchRequest+Ext.m | 7 ++- baRSS/Core Data/StoreCoordinator.h | 6 ++ baRSS/Core Data/StoreCoordinator.m | 26 +++++++- .../DBv1.xcdatamodel/contents | 7 ++- baRSS/Info.plist | 4 +- .../Preferences/About Tab/SettingsAboutView.m | 11 ++-- baRSS/Preferences/Helper/UserPrefs.h | 14 +++++ baRSS/Preferences/Helper/UserPrefs.m | 63 +++++++++++++++++++ 10 files changed, 139 insertions(+), 11 deletions(-) diff --git a/baRSS/AppHook.m b/baRSS/AppHook.m index 1ef0371..a7afe7f 100644 --- a/baRSS/AppHook.m +++ b/baRSS/AppHook.m @@ -26,6 +26,7 @@ #import "Preferences.h" #import "DrawImage.h" #import "SettingsFeeds+DragDrop.h" +#import "UserPrefs.h" @interface AppHook() @property (strong) NSWindowController *prefWindow; @@ -40,6 +41,7 @@ } - (void)applicationWillFinishLaunching:(NSNotification *)notification { + [self migrateVersionUpdate]; _statusItem = [BarStatusItem new]; NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; [appleEventManager setEventHandler:self andSelector:@selector(handleGetURLEvent:withReplyEvent:) @@ -177,6 +179,13 @@ return NSTerminateNow; } +/// Called during application start. Perform any version dependent updates here +- (void)migrateVersionUpdate { + // Currently unused, but you'll be thankful to know the previous version number in the future + [UserPrefs dbUpdateFileVersion]; + [UserPrefs dbUpdateAppVersion]; +} + #pragma mark - Event Handling, Forward Send Key Down Events diff --git a/baRSS/Core Data/NSFetchRequest+Ext.h b/baRSS/Core Data/NSFetchRequest+Ext.h index 51ce640..6d30825 100644 --- a/baRSS/Core Data/NSFetchRequest+Ext.h +++ b/baRSS/Core Data/NSFetchRequest+Ext.h @@ -26,8 +26,9 @@ // Perform core data request and fetch data - (NSArray*)fetchAllRows:(NSManagedObjectContext*)moc; - (NSArray*)fetchIDs:(NSManagedObjectContext*)moc; +- (NSDictionary*)fetchFirstDict:(NSManagedObjectContext*)moc; // limit 1 +- (ResultType)fetchFirst:(NSManagedObjectContext*)moc; // limit 1 - (NSUInteger)fetchCount:(NSManagedObjectContext*)moc; -- (id)fetchFirst:(NSManagedObjectContext*)moc; // limit 1 // Selecting, filtering, sorting results - (instancetype)select:(NSArray*)cols; // sets .propertiesToFetch diff --git a/baRSS/Core Data/NSFetchRequest+Ext.m b/baRSS/Core Data/NSFetchRequest+Ext.m index 232747b..747cc62 100644 --- a/baRSS/Core Data/NSFetchRequest+Ext.m +++ b/baRSS/Core Data/NSFetchRequest+Ext.m @@ -40,7 +40,12 @@ return [self fetchAllRows:moc]; } -/// Set @c limit to @c 1 and fetch first objcect. May return object type or @c NSDictionary if @c resultType @c = @c NSManagedObjectIDResultType. +/// Same as @c fetchFirst: but with dictionary return type +- (NSDictionary*)fetchFirstDict:(NSManagedObjectContext*)moc { + return [self fetchFirst:moc]; +} + +/// Set @c limit to @c 1 and fetch first object. May return object type or @c NSDictionary if @c resultType @c = @c NSManagedObjectIDResultType. - (id)fetchFirst:(NSManagedObjectContext*)moc { self.fetchLimit = 1; return [[self fetchAllRows:moc] firstObject]; diff --git a/baRSS/Core Data/StoreCoordinator.h b/baRSS/Core Data/StoreCoordinator.h index 3ae4cd2..b173911 100644 --- a/baRSS/Core Data/StoreCoordinator.h +++ b/baRSS/Core Data/StoreCoordinator.h @@ -23,11 +23,17 @@ #import #import "DBv1+CoreDataModel.h" +static const int dbFileVersion = 1; // update in case database structure changes + @interface StoreCoordinator : NSObject // Managing contexts + (NSManagedObjectContext*)createChildContext; + (void)saveContext:(NSManagedObjectContext*)context andParent:(BOOL)flag; +// Options ++ (nullable NSString*)optionForKey:(NSString*)key; ++ (void)setOption:(NSString*)key value:(NSString*)value; + // Feed update + (NSDate*)nextScheduledUpdate; + (NSArray*)getListOfFeedsThatNeedUpdate:(BOOL)forceAll inContext:(NSManagedObjectContext*)moc; diff --git a/baRSS/Core Data/StoreCoordinator.m b/baRSS/Core Data/StoreCoordinator.m index 1afd672..efa7ab6 100644 --- a/baRSS/Core Data/StoreCoordinator.m +++ b/baRSS/Core Data/StoreCoordinator.m @@ -64,13 +64,35 @@ } +#pragma mark - Options + + +/// @return Value for option with @c key or @c nil. ++ (nullable NSString*)optionForKey:(NSString*)key { + return [[[Options fetchRequest] where:@"key = %@", key] fetchFirst:[self getMainContext]].value; +} + +/// Init new option with given @c key ++ (void)setOption:(NSString*)key value:(NSString*)value { + NSManagedObjectContext *moc = [self getMainContext]; + Options *opt = [[[Options fetchRequest] where:@"key = %@", key] fetchFirst:moc]; + if (!opt) { + opt = [[Options alloc] initWithEntity:Options.entity insertIntoManagedObjectContext:moc]; + opt.key = key; + } + opt.value = value; + [self saveContext:moc andParent:YES]; + [moc reset]; +} + + #pragma mark - Feed Update /// @return @c NSDate of next (earliest) feed update. May be @c nil. + (NSDate*)nextScheduledUpdate { NSFetchRequest *fr = [FeedMeta fetchRequest]; [fr addFunctionExpression:@"min:" onKeyPath:@"scheduled" name:@"minDate" type:NSDateAttributeType]; - return [fr fetchAllRows: [self getMainContext]].firstObject[@"minDate"]; + return [fr fetchFirstDict: [self getMainContext]][@"minDate"]; } /** @@ -140,7 +162,7 @@ /// @return URL of @c Feed item where @c Feed.indexPath @c = @c path. + (NSString*)urlForFeedWithIndexPath:(nonnull NSString*)path { - return [[[[Feed fetchRequest] where:@"indexPath = %@", path] select:@[@"link"]] fetchFirst: [self getMainContext]][@"link"]; + return [[[[Feed fetchRequest] where:@"indexPath = %@", path] select:@[@"link"]] fetchFirstDict: [self getMainContext]][@"link"]; } /// @return Unsorted list of object IDs where @c Feed.indexPath begins with @c path @c + @c "." diff --git a/baRSS/DBv1.xcdatamodeld/DBv1.xcdatamodel/contents b/baRSS/DBv1.xcdatamodeld/DBv1.xcdatamodel/contents index b777361..578f79a 100644 --- a/baRSS/DBv1.xcdatamodeld/DBv1.xcdatamodel/contents +++ b/baRSS/DBv1.xcdatamodeld/DBv1.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -43,11 +43,16 @@ + + + + + \ No newline at end of file diff --git a/baRSS/Info.plist b/baRSS/Info.plist index 726f23a..3bb63b4 100644 --- a/baRSS/Info.plist +++ b/baRSS/Info.plist @@ -45,7 +45,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.9.4 + 1.0.0-alpha CFBundleURLTypes @@ -60,7 +60,7 @@ CFBundleVersion - 9859 + 9923 LSApplicationCategoryType public.app-category.news LSMinimumSystemVersion diff --git a/baRSS/Preferences/About Tab/SettingsAboutView.m b/baRSS/Preferences/About Tab/SettingsAboutView.m index 597492e..0c65aaa 100644 --- a/baRSS/Preferences/About Tab/SettingsAboutView.m +++ b/baRSS/Preferences/About Tab/SettingsAboutView.m @@ -22,17 +22,20 @@ #import "SettingsAboutView.h" #import "NSView+Ext.h" +#import "UserPrefs.h" @implementation SettingsAboutView - (instancetype)init { self = [super initWithFrame: NSZeroRect]; - NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; - NSString *name = infoDict[@"CFBundleName"]; - NSString *version = [NSString stringWithFormat:NSLocalizedString(@"Version %@", nil), infoDict[@"CFBundleShortVersionString"]]; + NSString *name = [UserPrefs appName]; + NSString *version = [NSString stringWithFormat:NSLocalizedString(@"Version %@", nil), #if DEBUG - version = [version stringByAppendingFormat:@" (%@)", infoDict[@"CFBundleVersion"]]; + [UserPrefs appVersionWithBuildNo] +#else + [UserPrefs appVersion] #endif + ]; // Application icon image (top-centered) NSImageView *logo = [[NSView imageView:NSImageNameApplicationIcon size:64] placeIn:self x:CENTER yTop:PAD_M]; diff --git a/baRSS/Preferences/Helper/UserPrefs.h b/baRSS/Preferences/Helper/UserPrefs.h index df58ae0..a885a11 100644 --- a/baRSS/Preferences/Helper/UserPrefs.h +++ b/baRSS/Preferences/Helper/UserPrefs.h @@ -23,6 +23,7 @@ #import @interface UserPrefs : NSObject +// User Preferences Plist + (BOOL)defaultYES:(NSString*)key; + (BOOL)defaultNO:(NSString*)key; @@ -30,7 +31,20 @@ + (void)setHttpApplication:(NSString*)bundleID; + (BOOL)openURLsWithPreferredBrowser:(NSArray*)urls; +// Hidden Plist Properties + (NSUInteger)openFewLinksLimit; // Change with: 'defaults write de.relikd.baRSS openFewLinksLimit -int 10' + (NSUInteger)shortArticleNamesLimit; // Change with: 'defaults write de.relikd.baRSS shortArticleNamesLimit -int 50' + (NSUInteger)articlesInMenuLimit; // Change with: 'defaults write de.relikd.baRSS articlesInMenuLimit -int 40' + +// Application Info Plist ++ (NSString*)appName; ++ (NSString*)appVersion; ++ (NSString*)appVersionWithBuildNo; + +// Core Data Properties ++ (BOOL)dbIsUnusedInitalState; ++ (BOOL)dbIsCurrentFileVersion; ++ (BOOL)dbIsCurrentAppVersion; ++ (void)dbUpdateFileVersion; ++ (void)dbUpdateAppVersion; @end diff --git a/baRSS/Preferences/Helper/UserPrefs.m b/baRSS/Preferences/Helper/UserPrefs.m index 0dc5a6d..1750ce8 100644 --- a/baRSS/Preferences/Helper/UserPrefs.m +++ b/baRSS/Preferences/Helper/UserPrefs.m @@ -21,10 +21,14 @@ // SOFTWARE. #import "UserPrefs.h" +#import "StoreCoordinator.h" + #import @implementation UserPrefs +#pragma mark - User Preferences Plist + /// @return @c YES if key is not set. Otherwise, return user defaults property from plist. + (BOOL)defaultYES:(NSString*)key { if ([[NSUserDefaults standardUserDefaults] objectForKey:key] == NULL) { @@ -66,8 +70,10 @@ return [[NSWorkspace sharedWorkspace] openURLs:urls withAppBundleIdentifier:[self getHttpApplication] options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:nil launchIdentifiers:nil]; } + #pragma mark - Hidden Plist Properties - + /// @return The limit on how many links should be opened at the same time, if user holds the option key. /// Default: @c 10 + (NSUInteger)openFewLinksLimit { @@ -86,4 +92,61 @@ return (NSUInteger)[self defaultInt:40 forKey:@"articlesInMenuLimit"]; } + +#pragma mark - Application Info Plist + + +/// @return The application name, e.g., 'baRSS' or 'baRSS Beta' ++ (NSString*)appName { + return [[NSBundle mainBundle] infoDictionary][(NSString*)kCFBundleNameKey]; +} + +/// @return The application version number, e.g., '0.9.4' ++ (NSString*)appVersion { + return [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; +} + +/// @return The application version number including build number, e.g., '0.9.4 (9906)' ++ (NSString*)appVersionWithBuildNo { + NSString *buildNo = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"]; + return [[self appVersion] stringByAppendingFormat:@" (%@)", buildNo]; +} + + +#pragma mark - Core Data Properties - + + +/// Helper method that retrieves and transforms option value to int ++ (int)dbIntForKey:(NSString*)key defaultsTo:(int)otherwise { + NSString *str = [StoreCoordinator optionForKey:key]; + if (!str) return otherwise; + int num = [NSDecimalNumber decimalNumberWithString:str].intValue; + return isnan(num) ? otherwise : num; +} + +/// Check whether the database was just initialized (first install) ++ (BOOL)dbIsUnusedInitalState { + return [StoreCoordinator optionForKey:@"db-version"] == nil; +} + +/// Check whether the stored database version is up to date ++ (BOOL)dbIsCurrentFileVersion { + return [self dbIntForKey:@"db-version" defaultsTo:-1] == dbFileVersion; +} + +/// Write current database version to core data ++ (void)dbUpdateFileVersion { + [StoreCoordinator setOption:@"db-version" value:[NSString stringWithFormat:@"%d", dbFileVersion]]; +} + +/// Check whether the stored application version is up to date ++ (BOOL)dbIsCurrentAppVersion { + return [[StoreCoordinator optionForKey:@"app-version"] isEqualToString:[self appVersion]]; +} + +/// Write current application version to core data ++ (void)dbUpdateAppVersion { + [StoreCoordinator setOption:@"app-version" value:[self appVersion]]; +} + @end