feat: "Update feeds" for all menu levels
This commit is contained in:
@@ -15,7 +15,8 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
// Feed update
|
||||
+ (NSDate*)nextScheduledUpdate;
|
||||
+ (NSArray<Feed*>*)listOfFeedsThatNeedUpdate:(BOOL)forceAll inContext:(nullable NSManagedObjectContext*)moc;
|
||||
+ (NSArray<Feed*>*)feedsThatNeedUpdate:(nullable NSManagedObjectContext*)moc;
|
||||
+ (NSArray<Feed*>*)feedsWithIndexPath:(nullable NSString*)path inContext:(nullable NSManagedObjectContext*)moc;
|
||||
|
||||
// Count elements
|
||||
+ (BOOL)isEmpty;
|
||||
|
||||
@@ -79,14 +79,24 @@
|
||||
/**
|
||||
List of @c Feed items that need to be updated. Scheduled time is now (or in past).
|
||||
|
||||
@param forceAll If @c YES get a list of all @c Feed regardless of schedules time.
|
||||
@param moc If @c nil perform requests on main context (ok for reading).
|
||||
*/
|
||||
+ (NSArray<Feed*>*)listOfFeedsThatNeedUpdate:(BOOL)forceAll inContext:(nullable NSManagedObjectContext*)moc {
|
||||
+ (NSArray<Feed*>*)feedsThatNeedUpdate:(nullable NSManagedObjectContext*)moc {
|
||||
NSFetchRequest *fr = [Feed fetchRequest];
|
||||
if (!forceAll) {
|
||||
// when fetching also get those feeds that would need update soon (now + 2s)
|
||||
[fr where:@"meta.scheduled <= %@", [NSDate dateWithTimeIntervalSinceNow:+2]];
|
||||
// when fetching also get those feeds that would need update soon (now + 2s)
|
||||
[fr where:@"meta.scheduled <= %@", [NSDate dateWithTimeIntervalSinceNow:+2]];
|
||||
return [fr fetchAllRows:moc ? moc : [self getMainContext]];
|
||||
}
|
||||
|
||||
/** List of @c Feed items that match @c Feed.indexPath either by direct match or some child thereof.
|
||||
|
||||
@param path If @c nil return all @c Feed items. May match either full string OR startswith string + "."
|
||||
@param moc If @c nil perform requests on main context (ok for reading).
|
||||
*/
|
||||
+ (NSArray<Feed*>*)feedsWithIndexPath:(nullable NSString*)path inContext:(nullable NSManagedObjectContext*)moc {
|
||||
NSFetchRequest *fr = [Feed fetchRequest];
|
||||
if (path && path.length > 0) {
|
||||
[fr where:@"indexPath = %@ OR indexPath BEGINSWITH %@", path, [path stringByAppendingString:@"."]];
|
||||
}
|
||||
return [fr fetchAllRows:moc ? moc : [self getMainContext]];
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
+ (NSString*)updatingXFeeds;
|
||||
// Scheduling
|
||||
+ (void)scheduleNextFeed;
|
||||
+ (void)forceUpdateAllFeeds;
|
||||
+ (void)forceUpdate:(NSString*)indexPath;
|
||||
+ (void)downloadList:(NSArray<Feed*>*)list userInitiated:(BOOL)flag notifications:(BOOL)notify finally:(nullable os_block_t)block;
|
||||
+ (void)updateAllFavicons;
|
||||
// Auto Download & Parse Feed URL
|
||||
|
||||
@@ -18,7 +18,6 @@ static NSTimer *_timer;
|
||||
static SCNetworkReachabilityRef _reachability = NULL;
|
||||
static BOOL _isReachable = YES;
|
||||
static BOOL _updatePaused = NO;
|
||||
static BOOL _nextUpdateIsForced = NO;
|
||||
static _Atomic(NSUInteger) _queueSize = 0;
|
||||
|
||||
@implementation UpdateScheduler
|
||||
@@ -90,14 +89,9 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
||||
nextTime = [NSDate dateWithTimeIntervalSinceNow:1];
|
||||
}
|
||||
[self scheduleTimer:nextTime];
|
||||
}
|
||||
|
||||
/// Start download of all feeds (immediatelly) regardless of @c .scheduled property.
|
||||
+ (void)forceUpdateAllFeeds {
|
||||
if (![self allowNetworkConnection]) // timer will restart once connection exists
|
||||
return;
|
||||
_nextUpdateIsForced = YES;
|
||||
[self scheduleTimer:[NSDate dateWithTimeIntervalSinceNow:0.05]];
|
||||
#ifdef DEBUG
|
||||
NSLog(@"schedule next update: %@", nextTime);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,13 +110,9 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
||||
if (!nextTime)
|
||||
nextTime = [NSDate distantFuture];
|
||||
int tolerance = (int)([nextTime timeIntervalSinceNow] * 0.15);
|
||||
tolerance = (tolerance < 1 ? 1 : tolerance > 600 ? 600 : tolerance); // at least 1 sec, upto 10 min
|
||||
_timer.tolerance = tolerance;
|
||||
_timer.tolerance = (tolerance < 1 ? 1 : tolerance > 600 ? 600 : tolerance); // at least 1 sec, upto 10 min
|
||||
_timer.fireDate = nextTime;
|
||||
PostNotification(kNotificationScheduleTimerChanged, nil);
|
||||
#ifdef DEBUG
|
||||
NSLog(@"schedule timer: %@ (+/- %d sec)", nextTime, tolerance);
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (void)didWakeAfterSleep {
|
||||
@@ -134,17 +124,26 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
||||
|
||||
/// Called when schedule timer runs out (earliest @c .schedule date). Or if forced by user.
|
||||
+ (void)updateTimerCallback {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"fired");
|
||||
#endif
|
||||
BOOL updateAll = _nextUpdateIsForced;
|
||||
_nextUpdateIsForced = NO;
|
||||
|
||||
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
||||
NSArray<Feed*> *list = [StoreCoordinator listOfFeedsThatNeedUpdate:updateAll inContext:moc];
|
||||
//NSAssert(list.count > 0, @"ERROR: Something went wrong, timer fired too early.");
|
||||
|
||||
[self downloadList:list userInitiated:updateAll notifications:YES finally:^{
|
||||
NSArray<Feed*> *list = [StoreCoordinator feedsThatNeedUpdate:moc];
|
||||
[self update:list userInitiated:NO context:moc];
|
||||
}
|
||||
|
||||
/// Start download of feeds immediatelly, regardless of @c .scheduled property.
|
||||
+ (void)forceUpdate:(NSString*)indexPath {
|
||||
if (![self allowNetworkConnection]) // menu item should be disabled anyway
|
||||
return;
|
||||
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
||||
NSArray<Feed*> *list = [StoreCoordinator feedsWithIndexPath:indexPath inContext:moc];
|
||||
[self update:list userInitiated:YES context:moc];
|
||||
}
|
||||
|
||||
/// Helper method for actual download
|
||||
+ (void)update:(NSArray<Feed*>*)list userInitiated:(BOOL)flag context:(NSManagedObjectContext*)moc {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"updating feeds: %ld (%@)", list.count, flag ? @"forced" : @"scheduled");
|
||||
#endif
|
||||
[self downloadList:list userInitiated:flag notifications:YES finally:^{
|
||||
[StoreCoordinator saveContext:moc andParent:YES]; // save parents too ...
|
||||
[moc reset];
|
||||
[self scheduleNextFeed]; // always reset the timer
|
||||
@@ -157,7 +156,7 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
||||
|
||||
/// Perform @c FaviconDownload on all core data @c Feed entries.
|
||||
+ (void)updateAllFavicons {
|
||||
for (Feed *f in [StoreCoordinator listOfFeedsThatNeedUpdate:YES inContext:nil])
|
||||
for (Feed *f in [StoreCoordinator feedsWithIndexPath:nil inContext:nil])
|
||||
[FaviconDownload updateFeed:f finally:nil];
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
// menu buttons
|
||||
/** default: @c NO */ static NSString* const Pref_globalToggleHidden = @"globalToggleHidden";
|
||||
/** default: @c YES */ static NSString* const Pref_globalUpdateAll = @"globalUpdateAll";
|
||||
/** default: @c YES */ static NSString* const Pref_groupUpdateAll = @"groupUpdateAll";
|
||||
/** default: @c YES */ static NSString* const Pref_feedUpdateAll = @"feedUpdateAll";
|
||||
/** default: @c YES */ static NSString* const Pref_globalOpenUnread = @"globalOpenUnread";
|
||||
/** default: @c YES */ static NSString* const Pref_groupOpenUnread = @"groupOpenUnread";
|
||||
/** default: @c YES */ static NSString* const Pref_feedOpenUnread = @"feedOpenUnread";
|
||||
|
||||
@@ -12,7 +12,7 @@ void UserPrefsInit(void) {
|
||||
NSMutableDictionary *defs = [NSMutableDictionary dictionary];
|
||||
defaultsAppend(defs, @YES, @[
|
||||
Pref_globalTintMenuIcon,
|
||||
Pref_globalUpdateAll,
|
||||
Pref_globalUpdateAll, Pref_groupUpdateAll, Pref_feedUpdateAll,
|
||||
Pref_globalOpenUnread, Pref_groupOpenUnread, Pref_feedOpenUnread,
|
||||
Pref_globalMarkRead, Pref_groupMarkRead, Pref_feedMarkRead,
|
||||
Pref_globalMarkUnread, Pref_groupMarkUnread, Pref_feedMarkUnread,
|
||||
|
||||
@@ -54,11 +54,6 @@
|
||||
tip:NSLocalizedString(@"You can hold down option-key before opening the main menu to temporarily show all hidden entries.", nil)
|
||||
c1:Pref_globalToggleHidden c2:nil c3:nil c4:nil];
|
||||
|
||||
[self entry:NSLocalizedString(@"“Update all feeds”", nil)
|
||||
help:NSLocalizedString(@"Show button to reload all feeds. This will force fetch new online content regardless of next-update timer.", nil)
|
||||
tip:nil
|
||||
c1:Pref_globalUpdateAll c2:nil c3:nil c4:nil];
|
||||
|
||||
[self entry:NSLocalizedString(@"“Open all unread”", nil)
|
||||
help:NSLocalizedString(@"Show button to open unread articles.", nil)
|
||||
tip:nil
|
||||
@@ -74,6 +69,11 @@
|
||||
tip:NSLocalizedString(@"Alternatively, you can hold down option-key and click on an article to toggle that item (un-)read.", nil)
|
||||
c1:Pref_globalMarkUnread c2:Pref_groupMarkUnread c3:Pref_feedMarkUnread c4:nil];
|
||||
|
||||
[self entry:NSLocalizedString(@"“Update feeds”", nil)
|
||||
help:NSLocalizedString(@"Show button to reload all feeds. This will force fetch new online content regardless of next-update timer.", nil)
|
||||
tip:nil
|
||||
c1:Pref_globalUpdateAll c2:Pref_groupUpdateAll c3:Pref_feedUpdateAll c4:nil];
|
||||
|
||||
// self.y += PAD_M;
|
||||
[self intInput:Pref_openFewLinksLimit
|
||||
unit:NSLocalizedString(@"%ld unread", nil)
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
#import "NotifyEndpoint.h"
|
||||
#import "NSView+Ext.h"
|
||||
#import "NSColor+Ext.h"
|
||||
#import "NSMenu+Ext.h"
|
||||
|
||||
@interface BarStatusItem()
|
||||
@property (strong) BarMenu *barMenu;
|
||||
@property (strong) NSStatusItem *statusItem;
|
||||
@property (assign) NSInteger unreadCountTotal;
|
||||
@property (weak) NSMenuItem *updateAllItem;
|
||||
/// Set to `true` if user toggled the `"Show hidden feeds"` menu option.
|
||||
@property (assign) BOOL optShowHidden;
|
||||
/// Set to `true` if menu bar was opened while holding down option-key.
|
||||
@@ -49,8 +49,8 @@
|
||||
/// Fired when network conditions change.
|
||||
- (void)networkChanged:(NSNotification*)notify {
|
||||
BOOL available = [[notify object] boolValue];
|
||||
self.updateAllItem.enabled = available;
|
||||
[self updateBarIcon];
|
||||
[self.statusItem.menu recursiveSetNetworkAvailable:available];
|
||||
}
|
||||
|
||||
/// Fired when a single feed has been updated. Object contains relative unread count change.
|
||||
@@ -197,13 +197,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 'Update all feeds' item
|
||||
if (UserPrefsBool(Pref_globalUpdateAll)) {
|
||||
NSMenuItem *updateAll = [menu addItemWithTitle:NSLocalizedString(@"Update all feeds", nil) action:@selector(updateAllFeeds) keyEquivalent:@""];
|
||||
updateAll.target = self;
|
||||
updateAll.enabled = [UpdateScheduler allowNetworkConnection];
|
||||
self.updateAllItem = updateAll;
|
||||
}
|
||||
// Separator between main header and default header
|
||||
[menu addItem:[NSMenuItem separatorItem]];
|
||||
}
|
||||
@@ -220,10 +213,4 @@
|
||||
self.barMenu.showHidden = self.optShowHidden;
|
||||
}
|
||||
|
||||
/// Called when user clicks on `Update all feeds` (main menu only).
|
||||
- (void)updateAllFeeds {
|
||||
// [self asyncReloadUnreadCount]; // should not be necessary
|
||||
[UpdateScheduler forceUpdateAllFeeds];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
- (void)insertDefaultHeader;
|
||||
// Update menu
|
||||
- (void)setHeaderHasUnread:(UnreadTotal*)count;
|
||||
- (void)recursiveSetNetworkAvailable:(BOOL)flag;
|
||||
- (nullable NSMenuItem*)deepestItemWithPath:(nonnull NSString*)path;
|
||||
@end
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
#import "Constants.h"
|
||||
#import "MapUnreadTotal.h"
|
||||
#import "NotifyEndpoint.h"
|
||||
#import "UpdateScheduler.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, MenuItemTag) {
|
||||
/// Used in @c allowDisplayOfHeaderItem: to identify and enable items
|
||||
TagMarkAllRead = 1,
|
||||
TagMarkAllUnread = 2,
|
||||
TagOpenAllUnread = 3,
|
||||
TagUpdateFeeds = 4,
|
||||
/// Delimiter item between default header and core data items
|
||||
TagHeaderDelimiter = 8,
|
||||
/// Indicator whether unread count is currently shown in menu item title or not
|
||||
@@ -83,6 +85,9 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
|
||||
}
|
||||
[self addItemIfAllowed:TagMarkAllRead title:NSLocalizedString(@"Mark all read", nil)];
|
||||
[self addItemIfAllowed:TagMarkAllUnread title:NSLocalizedString(@"Mark all unread", nil)];
|
||||
[self addItemIfAllowed:TagUpdateFeeds title:self.isFeedMenu ? NSLocalizedString(@"Update feed", nil) : NSLocalizedString(@"Update feeds", nil)]
|
||||
.enabled = [UpdateScheduler allowNetworkConnection];
|
||||
|
||||
if (self.numberOfItems > 0) {
|
||||
// in case someone has disabled all header items. Else, during articles menu rebuild it will stay on top.
|
||||
NSMenuItem *sep = [NSMenuItem separatorItem];
|
||||
@@ -112,6 +117,16 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Call this method whenever network availability changes to mark "Update feeds" button en-/disabled.
|
||||
- (void)recursiveSetNetworkAvailable:(BOOL)flag {
|
||||
[self itemWithTag:TagUpdateFeeds].enabled = flag;
|
||||
for (NSMenuItem *item in self.itemArray) {
|
||||
if (item.hasSubmenu) {
|
||||
[item.submenu recursiveSetNetworkAvailable:flag];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Iterate over all menu items in @c self.itemArray and find the item where @c submenu.title matches
|
||||
the first @c sortIndex in @c path. Recursively repeat the process for the items of this submenu and so on.
|
||||
@@ -141,11 +156,13 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
|
||||
static NSString* const mr[] = {Pref_globalMarkRead, Pref_groupMarkRead, Pref_feedMarkRead};
|
||||
static NSString* const mu[] = {Pref_globalMarkUnread, Pref_groupMarkUnread, Pref_feedMarkUnread};
|
||||
static NSString* const ou[] = {Pref_globalOpenUnread, Pref_groupOpenUnread, Pref_feedOpenUnread};
|
||||
static NSString* const ua[] = {Pref_globalUpdateAll, Pref_groupUpdateAll, Pref_feedUpdateAll};
|
||||
int i = (self.supermenu == nil ? 0 : (self.isFeedMenu ? 2 : 1));
|
||||
switch (tag) {
|
||||
case TagMarkAllRead: return UserPrefsBool(mr[i]);
|
||||
case TagMarkAllUnread: return UserPrefsBool(mu[i]);
|
||||
case TagOpenAllUnread: return UserPrefsBool(ou[i]);
|
||||
case TagUpdateFeeds: return UserPrefsBool(ua[i]);
|
||||
default: return NO;
|
||||
}
|
||||
}
|
||||
@@ -165,6 +182,10 @@ typedef NS_ENUM(NSInteger, MenuItemTag) {
|
||||
|
||||
/// Prepare @c userInfo dictionary and send @c NSNotification. Callback for every default header menu item.
|
||||
+ (void)headerMenuItemCallback:(NSMenuItem*)sender {
|
||||
if (sender.tag == TagUpdateFeeds) {
|
||||
[UpdateScheduler forceUpdate:sender.menu.titleIndexPath];
|
||||
return;
|
||||
}
|
||||
BOOL openLinks = NO;
|
||||
NSUInteger limit = 0;
|
||||
if (sender.tag == TagOpenAllUnread) {
|
||||
|
||||
Reference in New Issue
Block a user