Database options for version migration
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -26,8 +26,9 @@
|
||||
// Perform core data request and fetch data
|
||||
- (NSArray<ResultType>*)fetchAllRows:(NSManagedObjectContext*)moc;
|
||||
- (NSArray<NSManagedObjectID*>*)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<NSString*>*)cols; // sets .propertiesToFetch
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -23,11 +23,17 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#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<Feed*>*)getListOfFeedsThatNeedUpdate:(BOOL)forceAll inContext:(NSManagedObjectContext*)moc;
|
||||
|
||||
@@ -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 "."
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14315.18" systemVersion="17G5019" minimumToolsVersion="Automatic" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="v1">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="14460.32" systemVersion="17G8030" minimumToolsVersion="Automatic" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="v1">
|
||||
<entity name="Feed" representedClassName="Feed" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="indexPath" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="link" optional="YES" attributeType="String" syncable="YES"/>
|
||||
@@ -43,11 +43,16 @@
|
||||
<attribute name="url" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<relationship name="feed" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Feed" inverseName="meta" inverseEntity="Feed" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="Options" representedClassName="Options" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="key" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="value" optional="YES" attributeType="String" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Feed" positionX="-278.84765625" positionY="-112.953125" width="128" height="165"/>
|
||||
<element name="FeedArticle" positionX="-96.77734375" positionY="-113.83984375" width="128" height="195"/>
|
||||
<element name="FeedGroup" positionX="-460.37890625" positionY="-111.62890625" width="130.52734375" height="135"/>
|
||||
<element name="FeedIcon" positionX="-202.79296875" positionY="137.71875" width="128" height="75"/>
|
||||
<element name="FeedMeta" positionX="-348.02734375" positionY="136.89453125" width="128" height="150"/>
|
||||
<element name="Options" positionX="-279" positionY="36" width="128" height="75"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -45,7 +45,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.9.4</string>
|
||||
<string>1.0.0-alpha</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -60,7 +60,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>9859</string>
|
||||
<string>9923</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.news</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@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<NSURL*>*)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
|
||||
|
||||
@@ -21,10 +21,14 @@
|
||||
// SOFTWARE.
|
||||
|
||||
#import "UserPrefs.h"
|
||||
#import "StoreCoordinator.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@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
|
||||
|
||||
Reference in New Issue
Block a user