Bugfix: initial empty list + errorCount + manual update
This commit is contained in:
@@ -86,7 +86,7 @@ ToDo
|
|||||||
- [x] Code Documentation (mostly methods)
|
- [x] Code Documentation (mostly methods)
|
||||||
- [ ] Add Sandboxing
|
- [ ] Add Sandboxing
|
||||||
- [ ] Disable Startup checkbox (or other workaround)
|
- [ ] Disable Startup checkbox (or other workaround)
|
||||||
- [ ] Fix nasty bug: empty feed list (initial state)
|
- [x] Fix nasty bug: empty feed list (initial state)
|
||||||
|
|
||||||
|
|
||||||
- [ ] Additional features
|
- [ ] Additional features
|
||||||
|
|||||||
@@ -22,13 +22,14 @@
|
|||||||
|
|
||||||
#import "FeedGroup+CoreDataClass.h"
|
#import "FeedGroup+CoreDataClass.h"
|
||||||
|
|
||||||
@interface FeedGroup (Ext)
|
|
||||||
/// Enum type to distinguish different @c FeedGroup types: @c GROUP, @c FEED, @c SEPARATOR
|
/// Enum type to distinguish different @c FeedGroup types: @c GROUP, @c FEED, @c SEPARATOR
|
||||||
typedef NS_ENUM(int16_t, FeedGroupType) {
|
typedef NS_ENUM(int16_t, FeedGroupType) {
|
||||||
/// Other types: @c GROUP, @c FEED, @c SEPARATOR
|
/// Other types: @c GROUP, @c FEED, @c SEPARATOR
|
||||||
GROUP = 0, FEED = 1, SEPARATOR = 2
|
GROUP = 0, FEED = 1, SEPARATOR = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@interface FeedGroup (Ext)
|
||||||
/// Overwrites @c type attribute with enum. Use one of: @c GROUP, @c FEED, @c SEPARATOR.
|
/// Overwrites @c type attribute with enum. Use one of: @c GROUP, @c FEED, @c SEPARATOR.
|
||||||
@property (nonatomic) FeedGroupType type;
|
@property (nonatomic) FeedGroupType type;
|
||||||
|
|
||||||
|
|||||||
@@ -22,20 +22,22 @@
|
|||||||
|
|
||||||
#import "FeedMeta+CoreDataClass.h"
|
#import "FeedMeta+CoreDataClass.h"
|
||||||
|
|
||||||
@interface FeedMeta (Ext)
|
|
||||||
/// Easy memorable @c int16_t enum for refresh unit index
|
/// Easy memorable @c int16_t enum for refresh unit index
|
||||||
typedef NS_ENUM(int16_t, RefreshUnitType) {
|
typedef NS_ENUM(int16_t, RefreshUnitType) {
|
||||||
RefreshUnitSeconds = 0, RefreshUnitMinutes = 1, RefreshUnitHours = 2, RefreshUnitDays = 3, RefreshUnitWeeks = 4
|
RefreshUnitSeconds = 0, RefreshUnitMinutes = 1, RefreshUnitHours = 2, RefreshUnitDays = 3, RefreshUnitWeeks = 4
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@interface FeedMeta (Ext)
|
||||||
|
@property (readonly) BOOL refreshIntervalDisabled; // self.refreshNum <= 0
|
||||||
|
@property (readonly) int32_t refreshInterval; // self.refreshNum * RefreshUnitValue
|
||||||
|
|
||||||
|
// HTTP response
|
||||||
- (void)setErrorAndPostponeSchedule;
|
- (void)setErrorAndPostponeSchedule;
|
||||||
- (void)setSucessfulWithResponse:(NSHTTPURLResponse*)response;
|
- (void)setSucessfulWithResponse:(NSHTTPURLResponse*)response;
|
||||||
|
// Setter
|
||||||
- (void)setUrlIfChanged:(NSString*)url;
|
- (void)setUrlIfChanged:(NSString*)url;
|
||||||
- (void)setEtag:(NSString*)etag modified:(NSString*)modified;
|
- (void)setEtag:(NSString*)etag modified:(NSString*)modified;
|
||||||
- (BOOL)setRefresh:(int32_t)refresh unit:(RefreshUnitType)unit;
|
- (BOOL)setRefresh:(int32_t)refresh unit:(RefreshUnitType)unit;
|
||||||
- (BOOL)setRefreshAndUnitFromInterval:(int32_t)interval;
|
- (BOOL)setRefreshAndUnitFromInterval:(int32_t)interval;
|
||||||
|
|
||||||
- (int32_t)refreshInterval;
|
|
||||||
- (NSString*)readableRefreshString;
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -29,6 +29,27 @@ static const int32_t RefreshUnitValues[] = {1, 60, 3600, 86400, 604800}; // smhd
|
|||||||
|
|
||||||
@implementation FeedMeta (Ext)
|
@implementation FeedMeta (Ext)
|
||||||
|
|
||||||
|
#pragma mark - Getter
|
||||||
|
|
||||||
|
/// Check whether update interval is disabled by user (refresh interval is 0).
|
||||||
|
- (BOOL)refreshIntervalDisabled {
|
||||||
|
return (self.refreshNum <= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return Time interval respecting the selected unit. E.g., returns @c 180 for @c '3m'
|
||||||
|
- (int32_t)refreshInterval {
|
||||||
|
return self.refreshNum * RefreshUnitValues[self.refreshUnit % 5];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return Formatted string for update interval ( e.g., @c 30m or @c 12h )
|
||||||
|
- (NSString*)readableRefreshString {
|
||||||
|
if (self.refreshIntervalDisabled)
|
||||||
|
return @"∞"; // ∞ ƒ Ø
|
||||||
|
return [NSString stringWithFormat:@"%d%c", self.refreshNum, [@"smhdw" characterAtIndex:self.refreshUnit % 5]];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - HTTP response
|
||||||
|
|
||||||
/// Increment @c errorCount and set new @c scheduled date (2^N minutes, max. 5.7 days).
|
/// Increment @c errorCount and set new @c scheduled date (2^N minutes, max. 5.7 days).
|
||||||
- (void)setErrorAndPostponeSchedule {
|
- (void)setErrorAndPostponeSchedule {
|
||||||
if (self.errorCount < 0)
|
if (self.errorCount < 0)
|
||||||
@@ -36,7 +57,7 @@ static const int32_t RefreshUnitValues[] = {1, 60, 3600, 86400, 604800}; // smhd
|
|||||||
int16_t n = self.errorCount + 1; // always increment errorCount (can be used to indicate bad feeds)
|
int16_t n = self.errorCount + 1; // always increment errorCount (can be used to indicate bad feeds)
|
||||||
NSTimeInterval retryWaitTime = pow(2, (n > 13 ? 13 : n)) * 60; // 2^N (between: 2 minutes and 5.7 days)
|
NSTimeInterval retryWaitTime = pow(2, (n > 13 ? 13 : n)) * 60; // 2^N (between: 2 minutes and 5.7 days)
|
||||||
self.errorCount = n;
|
self.errorCount = n;
|
||||||
self.scheduled = [NSDate dateWithTimeIntervalSinceNow:retryWaitTime];
|
[self scheduleNow:retryWaitTime];
|
||||||
// TODO: remove logging
|
// TODO: remove logging
|
||||||
NSLog(@"ERROR: Feed download failed: %@ (errorCount: %d)", self.url, n);
|
NSLog(@"ERROR: Feed download failed: %@ (errorCount: %d)", self.url, n);
|
||||||
}
|
}
|
||||||
@@ -45,14 +66,10 @@ static const int32_t RefreshUnitValues[] = {1, 60, 3600, 86400, 604800}; // smhd
|
|||||||
self.errorCount = 0; // reset counter
|
self.errorCount = 0; // reset counter
|
||||||
NSDictionary *header = [response allHeaderFields];
|
NSDictionary *header = [response allHeaderFields];
|
||||||
[self setEtag:header[@"Etag"] modified:header[@"Date"]]; // @"Expires", @"Last-Modified"
|
[self setEtag:header[@"Etag"] modified:header[@"Date"]]; // @"Expires", @"Last-Modified"
|
||||||
[self calculateAndSetScheduled];
|
[self scheduleNow:[self refreshInterval]];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate date from @c refreshNum and @c refreshUnit and set as next scheduled feed update.
|
#pragma mark - Setter
|
||||||
- (void)calculateAndSetScheduled {
|
|
||||||
NSTimeInterval interval = [self refreshInterval]; // 0 if refresh = 0 (update deactivated)
|
|
||||||
self.scheduled = (interval <= 0 ? nil : [[NSDate date] dateByAddingTimeInterval:interval]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set @c url attribute but only if value differs.
|
/// Set @c url attribute but only if value differs.
|
||||||
- (void)setUrlIfChanged:(NSString*)url {
|
- (void)setUrlIfChanged:(NSString*)url {
|
||||||
@@ -65,7 +82,6 @@ static const int32_t RefreshUnitValues[] = {1, 60, 3600, 86400, 604800}; // smhd
|
|||||||
if (![self.modified isEqualToString:modified]) self.modified = modified;
|
if (![self.modified isEqualToString:modified]) self.modified = modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Set @c refresh and @c unit from popup button selection. Only values that differ will be updated.
|
Set @c refresh and @c unit from popup button selection. Only values that differ will be updated.
|
||||||
Also, calculate and set new @c scheduled date and update FeedGroup @c refreshStr (if changed).
|
Also, calculate and set new @c scheduled date and update FeedGroup @c refreshStr (if changed).
|
||||||
@@ -78,7 +94,7 @@ static const int32_t RefreshUnitValues[] = {1, 60, 3600, 86400, 604800}; // smhd
|
|||||||
if (self.refreshUnit != unit) self.refreshUnit = unit;
|
if (self.refreshUnit != unit) self.refreshUnit = unit;
|
||||||
|
|
||||||
if (intervalChanged) {
|
if (intervalChanged) {
|
||||||
[self calculateAndSetScheduled];
|
[self scheduleNow:[self refreshInterval]];
|
||||||
NSString *str = [self readableRefreshString];
|
NSString *str = [self readableRefreshString];
|
||||||
if (![self.feed.group.refreshStr isEqualToString:str])
|
if (![self.feed.group.refreshStr isEqualToString:str])
|
||||||
self.feed.group.refreshStr = str;
|
self.feed.group.refreshStr = str;
|
||||||
@@ -102,14 +118,14 @@ static const int32_t RefreshUnitValues[] = {1, 60, 3600, 86400, 604800}; // smhd
|
|||||||
return NO; // since loop didn't return, no value was changed
|
return NO; // since loop didn't return, no value was changed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @return Time interval respecting the selected unit. E.g., returns @c 180 for @c '3m'
|
/// Calculate date from @c refreshNum and @c refreshUnit and set as next scheduled feed update.
|
||||||
- (int32_t)refreshInterval {
|
- (void)scheduleNow:(NSTimeInterval)future {
|
||||||
return self.refreshNum * RefreshUnitValues[self.refreshUnit % 5];
|
if (self.refreshIntervalDisabled) { // update deactivated; manually update with force update all
|
||||||
|
if (self.scheduled != nil) // already nil? Avoid unnecessary core data edits
|
||||||
|
self.scheduled = nil;
|
||||||
|
} else {
|
||||||
|
self.scheduled = [NSDate dateWithTimeIntervalSinceNow:future];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @return Formatted string for update interval ( e.g., @c 30m or @c 12h )
|
|
||||||
- (NSString*)readableRefreshString {
|
|
||||||
return [NSString stringWithFormat:@"%d%c", self.refreshNum, [@"smhdw" characterAtIndex:self.refreshUnit % 5]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -128,10 +128,10 @@ static BOOL _nextUpdateIsForced = NO;
|
|||||||
|
|
||||||
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
||||||
NSArray<Feed*> *list = [StoreCoordinator getListOfFeedsThatNeedUpdate:updateAll inContext:moc];
|
NSArray<Feed*> *list = [StoreCoordinator getListOfFeedsThatNeedUpdate:updateAll inContext:moc];
|
||||||
NSAssert(list.count > 0, @"ERROR: Something went wrong, timer fired too early.");
|
//NSAssert(list.count > 0, @"ERROR: Something went wrong, timer fired too early.");
|
||||||
|
|
||||||
[FeedDownload batchUpdateFeeds:list showErrorAlert:NO finally:^(NSArray<Feed*> *successful, NSArray<Feed*> *failed) {
|
[FeedDownload batchUpdateFeeds:list showErrorAlert:NO finally:^(NSArray<Feed*> *successful, NSArray<Feed*> *failed) {
|
||||||
[self postChanges:successful andSaveContext:moc];
|
[self saveContext:moc andPostChanges:successful];
|
||||||
[moc reset];
|
[moc reset];
|
||||||
[self resumeUpdates]; // always reset the timer
|
[self resumeUpdates]; // always reset the timer
|
||||||
}];
|
}];
|
||||||
@@ -139,19 +139,13 @@ static BOOL _nextUpdateIsForced = NO;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Perform save on context and all parents. Then post @c FeedUpdated notification.
|
Perform save on context and all parents. Then post @c FeedUpdated notification.
|
||||||
Use return value to download additional data.
|
|
||||||
|
|
||||||
@return @c YES if @c (list.count @c > @c 0).
|
|
||||||
Return @c NO if context wasn't saved, and no notification was sent.
|
|
||||||
*/
|
*/
|
||||||
+ (BOOL)postChanges:(NSArray<Feed*>*)changedFeeds andSaveContext:(NSManagedObjectContext*)moc {
|
+ (void)saveContext:(NSManagedObjectContext*)moc andPostChanges:(NSArray<Feed*>*)changedFeeds {
|
||||||
if (changedFeeds && changedFeeds.count > 0) {
|
|
||||||
[StoreCoordinator saveContext:moc andParent:YES];
|
[StoreCoordinator saveContext:moc andParent:YES];
|
||||||
|
if (changedFeeds && changedFeeds.count > 0) {
|
||||||
NSArray<NSManagedObjectID*> *list = [changedFeeds valueForKeyPath:@"objectID"];
|
NSArray<NSManagedObjectID*> *list = [changedFeeds valueForKeyPath:@"objectID"];
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFeedUpdated object:list];
|
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFeedUpdated object:list];
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
return NO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -279,7 +273,11 @@ static BOOL _nextUpdateIsForced = NO;
|
|||||||
Feed *f = [Feed appendToRootWithDefaultIntervalInContext:moc];
|
Feed *f = [Feed appendToRootWithDefaultIntervalInContext:moc];
|
||||||
f.meta.url = url;
|
f.meta.url = url;
|
||||||
[self batchDownloadRSSAndFavicons:@[f] showErrorAlert:YES rssFinished:^(NSArray<Feed *> *successful, BOOL *cancelFavicons) {
|
[self batchDownloadRSSAndFavicons:@[f] showErrorAlert:YES rssFinished:^(NSArray<Feed *> *successful, BOOL *cancelFavicons) {
|
||||||
*cancelFavicons = ![self postChanges:successful andSaveContext:moc];
|
if (successful.count == 0) {
|
||||||
|
*cancelFavicons = YES;
|
||||||
|
} else {
|
||||||
|
[self saveContext:moc andPostChanges:successful];
|
||||||
|
}
|
||||||
} finally:^(BOOL successful) {
|
} finally:^(BOOL successful) {
|
||||||
if (successful) {
|
if (successful) {
|
||||||
[StoreCoordinator saveContext:moc andParent:YES];
|
[StoreCoordinator saveContext:moc andParent:YES];
|
||||||
|
|||||||
@@ -233,7 +233,7 @@
|
|||||||
[child setAttribute:@"rss" forKey:OPMLTypeKey];
|
[child setAttribute:@"rss" forKey:OPMLTypeKey];
|
||||||
[child setAttribute:item.feed.link forKey:OPMLHMTLURLKey];
|
[child setAttribute:item.feed.link forKey:OPMLHMTLURLKey];
|
||||||
[child setAttribute:item.feed.meta.url forKey:OPMLXMLURLKey];
|
[child setAttribute:item.feed.meta.url forKey:OPMLXMLURLKey];
|
||||||
NSNumber *refreshNum = [NSNumber numberWithInteger:[item.feed.meta refreshInterval]];
|
NSNumber *refreshNum = [NSNumber numberWithInteger:item.feed.meta.refreshInterval];
|
||||||
[child setAttribute:refreshNum forKey:@"refreshInterval"]; // baRSS specific
|
[child setAttribute:refreshNum forKey:@"refreshInterval"]; // baRSS specific
|
||||||
} else {
|
} else {
|
||||||
for (FeedGroup *subItem in [item sortedChildren]) {
|
for (FeedGroup *subItem in [item sortedChildren]) {
|
||||||
|
|||||||
@@ -275,25 +275,22 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
|||||||
/// Populate @c NSOutlineView data cells with core data object values.
|
/// Populate @c NSOutlineView data cells with core data object values.
|
||||||
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
|
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
|
||||||
FeedGroup *fg = [(NSTreeNode*)item representedObject];
|
FeedGroup *fg = [(NSTreeNode*)item representedObject];
|
||||||
BOOL isFeed = (fg.type == FEED);
|
|
||||||
BOOL isSeperator = (fg.type == SEPARATOR);
|
BOOL isSeperator = (fg.type == SEPARATOR);
|
||||||
BOOL isRefreshColumn = [tableColumn.identifier isEqualToString:@"RefreshColumn"];
|
BOOL isRefreshColumn = [tableColumn.identifier isEqualToString:@"RefreshColumn"];
|
||||||
BOOL refreshDisabled = (!isFeed || fg.refreshStr.length == 0 || [fg.refreshStr characterAtIndex:0] == '0');
|
|
||||||
|
|
||||||
NSString *cellIdent = (isRefreshColumn ? @"cellRefresh" : (isSeperator ? @"cellSeparator" : @"cellFeed"));
|
NSString *cellIdent = (isRefreshColumn ? @"cellRefresh" : (isSeperator ? @"cellSeparator" : @"cellFeed"));
|
||||||
// owner is nil to prohibit repeated awakeFromNib calls
|
// owner is nil to prohibit repeated awakeFromNib calls
|
||||||
NSTableCellView *cellView = [self.outlineView makeViewWithIdentifier:cellIdent owner:nil];
|
NSTableCellView *cellView = [self.outlineView makeViewWithIdentifier:cellIdent owner:nil];
|
||||||
|
|
||||||
if (isRefreshColumn) {
|
if (isRefreshColumn) {
|
||||||
cellView.textField.stringValue = (refreshDisabled ? (isFeed ? @"--" : @"") : fg.refreshStr);
|
cellView.textField.objectValue = fg.refreshStr;
|
||||||
|
cellView.textField.textColor = (fg.refreshStr.length > 1 ? [NSColor controlTextColor] : [NSColor disabledControlTextColor]);
|
||||||
} else if (isSeperator) {
|
} else if (isSeperator) {
|
||||||
return cellView; // the refresh cell is already skipped with the above if condition
|
return cellView; // refresh cell already skipped with the above if condition
|
||||||
} else {
|
} else {
|
||||||
cellView.textField.objectValue = fg.name;
|
cellView.textField.objectValue = fg.name;
|
||||||
cellView.imageView.image = (fg.type == GROUP ? [NSImage imageNamed:NSImageNameFolder] : [fg.feed iconImage16]);
|
cellView.imageView.image = (fg.type == GROUP ? [NSImage imageNamed:NSImageNameFolder] : [fg.feed iconImage16]);
|
||||||
}
|
}
|
||||||
// also for refresh column
|
|
||||||
cellView.textField.textColor = (isFeed && refreshDisabled ? [NSColor disabledControlTextColor] : [NSColor controlTextColor]);
|
|
||||||
return cellView;
|
return cellView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
@property (strong) NSStatusItem *barItem;
|
@property (strong) NSStatusItem *barItem;
|
||||||
@property (strong) Preferences *prefWindow;
|
@property (strong) Preferences *prefWindow;
|
||||||
@property (assign, atomic) NSInteger unreadCountTotal;
|
@property (assign, atomic) NSInteger unreadCountTotal;
|
||||||
|
@property (assign) BOOL coreDataEmpty;
|
||||||
@property (weak) NSMenu *currentOpenMenu;
|
@property (weak) NSMenu *currentOpenMenu;
|
||||||
@property (strong) NSArray<NSManagedObjectID*> *objectIDsForMenu;
|
@property (strong) NSArray<NSManagedObjectID*> *objectIDsForMenu;
|
||||||
@property (strong) NSManagedObjectContext *readContext;
|
@property (strong) NSManagedObjectContext *readContext;
|
||||||
@@ -67,7 +68,7 @@
|
|||||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Update Menu Bar Icon -
|
#pragma mark - Update Menu Bar Icon
|
||||||
|
|
||||||
/// Regardless of current unread count, perform new core data fetch on total unread count and update icon.
|
/// Regardless of current unread count, perform new core data fetch on total unread count and update icon.
|
||||||
- (void)asyncReloadUnreadCountAndUpdateBarIcon {
|
- (void)asyncReloadUnreadCountAndUpdateBarIcon {
|
||||||
@@ -96,7 +97,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Notification callback methods -
|
#pragma mark - Notification callback methods
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,7 +181,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Menu Delegate & Menu Generation -
|
#pragma mark - Menu Delegate & Menu Generation
|
||||||
|
|
||||||
|
|
||||||
/// @c currentOpenMenu is needed when a background update occurs. In case a feed items menu is open.
|
/// @c currentOpenMenu is needed when a background update occurs. In case a feed items menu is open.
|
||||||
@@ -206,14 +207,19 @@
|
|||||||
|
|
||||||
/// Perform a core data fatch request, store sorted object ids array and return object count.
|
/// Perform a core data fatch request, store sorted object ids array and return object count.
|
||||||
- (NSInteger)numberOfItemsInMenu:(NSMenu*)menu {
|
- (NSInteger)numberOfItemsInMenu:(NSMenu*)menu {
|
||||||
NSMenuItem *parent = [menu.supermenu itemAtIndex:[menu.supermenu indexOfItemWithSubmenu:menu]];
|
[self prepareContextAndTemporaryObjectIDs:menu];
|
||||||
self.readContext = [StoreCoordinator createChildContext]; // will be deleted after menu:updateItem:
|
if (_coreDataEmpty) return 1; // only if main menu empty
|
||||||
self.objectIDsForMenu = [StoreCoordinator sortedObjectIDsForParent:parent.representedObject isFeed:[menu isFeedMenu] inContext:self.readContext];
|
return (NSInteger)self.objectIDsForMenu.count;
|
||||||
return (NSInteger)[self.objectIDsForMenu count];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lazy populate system bar menus when needed.
|
/// Lazy populate system bar menus when needed.
|
||||||
- (BOOL)menu:(NSMenu*)menu updateItem:(NSMenuItem*)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel {
|
- (BOOL)menu:(NSMenu*)menu updateItem:(NSMenuItem*)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel {
|
||||||
|
if (_coreDataEmpty) {
|
||||||
|
item.title = NSLocalizedString(@"~~~ list empty ~~~", nil);
|
||||||
|
item.enabled = NO;
|
||||||
|
[self finalizeMenu:menu object:nil];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
id obj = [self.readContext objectWithID:[self.objectIDsForMenu objectAtIndex:(NSUInteger)index]];
|
id obj = [self.readContext objectWithID:[self.objectIDsForMenu objectAtIndex:(NSUInteger)index]];
|
||||||
if ([obj isKindOfClass:[FeedGroup class]]) {
|
if ([obj isKindOfClass:[FeedGroup class]]) {
|
||||||
[item setFeedGroup:obj];
|
[item setFeedGroup:obj];
|
||||||
@@ -226,12 +232,27 @@
|
|||||||
|
|
||||||
if (index + 1 == menu.numberOfItems) { // last item of the menu
|
if (index + 1 == menu.numberOfItems) { // last item of the menu
|
||||||
[self finalizeMenu:menu object:obj];
|
[self finalizeMenu:menu object:obj];
|
||||||
|
[self resetContextAndTemporaryObjectIDs];
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - Helper
|
||||||
|
|
||||||
|
|
||||||
|
- (void)prepareContextAndTemporaryObjectIDs:(NSMenu*)menu {
|
||||||
|
NSMenuItem *parent = [menu.supermenu itemAtIndex:[menu.supermenu indexOfItemWithSubmenu:menu]];
|
||||||
|
self.readContext = [StoreCoordinator createChildContext]; // will be deleted after menu:updateItem:
|
||||||
|
self.objectIDsForMenu = [StoreCoordinator sortedObjectIDsForParent:parent.representedObject isFeed:[menu isFeedMenu] inContext:self.readContext];
|
||||||
|
_coreDataEmpty = ([menu isMainMenu] && self.objectIDsForMenu.count == 0); // initial state or no feeds in date store
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resetContextAndTemporaryObjectIDs {
|
||||||
self.objectIDsForMenu = nil;
|
self.objectIDsForMenu = nil;
|
||||||
[self.readContext reset];
|
[self.readContext reset];
|
||||||
self.readContext = nil;
|
self.readContext = nil;
|
||||||
}
|
}
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Add default menu items that are present in each menu as header and disable menu items if necessary
|
Add default menu items that are present in each menu as header and disable menu items if necessary
|
||||||
@@ -301,7 +322,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Menu Actions -
|
#pragma mark - Menu Actions
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -92,11 +92,8 @@ typedef NS_ENUM(char, DisplaySetting) {
|
|||||||
} else if (fg.type == GROUP && [UserPrefs defaultYES:@"groupUnreadCount"]) {
|
} else if (fg.type == GROUP && [UserPrefs defaultYES:@"groupUnreadCount"]) {
|
||||||
uCount = [self.submenu coreDataUnreadCount];
|
uCount = [self.submenu coreDataUnreadCount];
|
||||||
}
|
}
|
||||||
if (uCount > 0) {
|
NSString *name = (fg.name ? fg.name : NSLocalizedString(@"(error)", nil));
|
||||||
self.title = [NSString stringWithFormat:@"%@ (%ld)", fg.name, uCount];
|
self.title = (uCount == 0 ? name : [NSString stringWithFormat:@"%@ (%ld)", name, uCount]);
|
||||||
} else {
|
|
||||||
self.title = (fg.name ? fg.name : @"(error)");
|
|
||||||
}
|
|
||||||
return uCount;
|
return uCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user