Fix status info message in feed settings

This commit is contained in:
relikd
2019-08-15 01:38:57 +02:00
parent 9e7eda692b
commit 5392ac8ab2
9 changed files with 68 additions and 66 deletions

View File

@@ -28,7 +28,9 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
- *Adding feed:* Inserting feeds when offline will postpone download until network is reachable again - *Adding feed:* Inserting feeds when offline will postpone download until network is reachable again
- *Adding feed:* Inserting feeds when paused will postpone download until unpaused - *Adding feed:* Inserting feeds when paused will postpone download until unpaused
- *Settings, Feeds:* Actions `delete` and `edit` use clicked items instead of selected items - *Settings, Feeds:* Actions `delete` and `edit` use clicked items instead of selected items
- *Settings, Feeds:* Accurate download count instead of `Updating feeds …` - *Settings, Feeds:* Status info with accurate download count (instead of `Updating feeds …`)
- *Settings, Feeds:* Status info shows `No network connection` and `Updates paused`
- *Settings, Feeds:* After feed edit, run update scheduler immediately
- *Status Bar Menu*: Feed title is updated properly - *Status Bar Menu*: Feed title is updated properly
- *UI:* If an error occurs, show document URL (path to file or web url) - *UI:* If an error occurs, show document URL (path to file or web url)
- Comparison of existing articles with nonexistent guid and link - Comparison of existing articles with nonexistent guid and link

View File

@@ -66,6 +66,11 @@ NS_INLINE void RegisterNotification(NSNotificationName name, SEL action, id obse
Represents number of feeds that are proccessed in background update. Sends @c 0 when all downloads are finished. Represents number of feeds that are proccessed in background update. Sends @c 0 when all downloads are finished.
*/ */
static NSNotificationName const kNotificationBackgroundUpdateInProgress = @"baRSS-notification-background-update-in-progress"; static NSNotificationName const kNotificationBackgroundUpdateInProgress = @"baRSS-notification-background-update-in-progress";
/**
@c notification.object is @c nil.
Called whenever the update schedule timer is modified.
*/
static NSNotificationName const kNotificationScheduleTimerChanged = @"baRSS-notification-schedule-timer-changed";
/** /**
@c notification.object is @c NSManagedObjectID of type @c FeedGroup. @c notification.object is @c NSManagedObjectID of type @c FeedGroup.
Called whenever a new feed group was created in @c autoDownloadAndParseURL: Called whenever a new feed group was created in @c autoDownloadAndParseURL:

View File

@@ -31,6 +31,10 @@
@property (class, readonly) BOOL isUpdating; @property (class, readonly) BOOL isUpdating;
@property (class, setter=setPaused:) BOOL isPaused; @property (class, setter=setPaused:) BOOL isPaused;
// Getter
+ (NSString*)remainingTimeTillNextUpdate:(nullable double*)remaining;
+ (NSString*)updatingXFeeds;
// Scheduling
+ (void)scheduleNextFeed; + (void)scheduleNextFeed;
+ (void)forceUpdateAllFeeds; + (void)forceUpdateAllFeeds;
+ (void)downloadList:(NSArray<Feed*>*)list background:(BOOL)flag finally:(nullable os_block_t)block; + (void)downloadList:(NSArray<Feed*>*)list background:(BOOL)flag finally:(nullable os_block_t)block;

View File

@@ -25,6 +25,7 @@
#import "WebFeed.h" #import "WebFeed.h"
#import "Constants.h" #import "Constants.h"
#import "StoreCoordinator.h" #import "StoreCoordinator.h"
#import "NSDate+Ext.h"
static NSTimer *_timer; static NSTimer *_timer;
static SCNetworkReachabilityRef _reachability = NULL; static SCNetworkReachabilityRef _reachability = NULL;
@@ -56,21 +57,33 @@ static BOOL _nextUpdateIsForced = NO;
+ (void)setPaused:(BOOL)flag { + (void)setPaused:(BOOL)flag {
// TODO: should pause persist between app launches? // TODO: should pause persist between app launches?
_updatePaused = flag; _updatePaused = flag;
if (flag) [self scheduleTimer:nil];
else [self scheduleNextFeed];
}
/// Update status. 'Paused', 'No conection', or 'Next update in ...'
+ (NSString*)remainingTimeTillNextUpdate:(nullable double*)remaining {
double time = fabs(_timer.fireDate.timeIntervalSinceNow);
if (remaining)
*remaining = time;
if (!_isReachable)
return NSLocalizedString(@"No network connection", nil);
if (_updatePaused) if (_updatePaused)
[self pauseUpdates]; return NSLocalizedString(@"Updates paused", nil);
else if (time > 1e9) // distance future, over 31 years
[self resumeUpdates]; return @""; // aka. no feeds in list
return [NSString stringWithFormat:NSLocalizedString(@"Next update in %@", nil),
[NSDate stringForRemainingTime:_timer.fireDate]];
} }
/// Cancel current timer and stop any updates until enabled again. /// Update status. 'Updating X feeds ' or empty string if not updating.
+ (void)pauseUpdates { + (NSString*)updatingXFeeds {
[self scheduleTimer:nil]; NSUInteger c = [WebFeed feedsInQueue];
switch (c) {
case 0: return @"";
case 1: return NSLocalizedString(@"Updating 1 feed …", nil);
default: return [NSString stringWithFormat:NSLocalizedString(@"Updating %lu feeds …", nil), c];
} }
/// Start normal (non forced) schedule if network is reachable.
+ (void)resumeUpdates {
if (_isReachable)
[self scheduleNextFeed];
} }
@@ -83,6 +96,8 @@ static BOOL _nextUpdateIsForced = NO;
+ (void)scheduleNextFeed { + (void)scheduleNextFeed {
if (![self allowNetworkConnection]) // timer will restart once connection exists if (![self allowNetworkConnection]) // timer will restart once connection exists
return; return;
if ([WebFeed feedsInQueue] > 0) // assume every update ends with scheduleNextFeed
return; // skip until called again
NSDate *nextTime = [StoreCoordinator nextScheduledUpdate]; // if nextTime = nil, then no feeds to update NSDate *nextTime = [StoreCoordinator nextScheduledUpdate]; // if nextTime = nil, then no feeds to update
if (nextTime && [nextTime timeIntervalSinceNow] < 1) { // mostly, if app was closed for a long time if (nextTime && [nextTime timeIntervalSinceNow] < 1) { // mostly, if app was closed for a long time
nextTime = [NSDate dateWithTimeIntervalSinceNow:1]; nextTime = [NSDate dateWithTimeIntervalSinceNow:1];
@@ -116,6 +131,7 @@ static BOOL _nextUpdateIsForced = NO;
NSTimeInterval tolerance = [nextTime timeIntervalSinceNow] * 0.15; NSTimeInterval tolerance = [nextTime timeIntervalSinceNow] * 0.15;
_timer.tolerance = (tolerance < 1 ? 1 : tolerance); // at least 1 sec _timer.tolerance = (tolerance < 1 ? 1 : tolerance); // at least 1 sec
_timer.fireDate = nextTime; _timer.fireDate = nextTime;
PostNotification(kNotificationScheduleTimerChanged, nil);
} }
/** /**
@@ -135,7 +151,7 @@ static BOOL _nextUpdateIsForced = NO;
[self downloadList:list background:!updateAll finally:^{ [self downloadList:list background:!updateAll finally:^{
[StoreCoordinator saveContext:moc andParent:YES]; // save parents too ... [StoreCoordinator saveContext:moc andParent:YES]; // save parents too ...
[moc reset]; [moc reset];
[self resumeUpdates]; // always reset the timer [self scheduleNextFeed]; // always reset the timer
}]; }];
} }
@@ -193,9 +209,9 @@ static void networkReachabilityCallback(SCNetworkReachabilityRef target, SCNetwo
_isReachable = [UpdateScheduler hasConnectivity:flags]; _isReachable = [UpdateScheduler hasConnectivity:flags];
PostNotification(kNotificationNetworkStatusChanged, @(_isReachable)); PostNotification(kNotificationNetworkStatusChanged, @(_isReachable));
if (_isReachable) { if (_isReachable) {
[UpdateScheduler resumeUpdates]; [UpdateScheduler scheduleNextFeed];
} else { } else {
[UpdateScheduler pauseUpdates]; [UpdateScheduler scheduleTimer:nil];
} }
} }

View File

@@ -290,7 +290,6 @@ static _Atomic(NSUInteger) _queueSize = 0;
} }
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if (block) block(); if (block) block();
PostNotification(kNotificationBackgroundUpdateInProgress, @(_queueSize));
}); });
} }

View File

@@ -60,7 +60,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>10782</string> <string>10902</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.news</string> <string>public.app-category.news</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>

View File

@@ -163,7 +163,7 @@ const NSPasteboardType dragReorder = @"de.relikd.baRSS.drag-reorder";
[UpdateScheduler downloadList:feedsList background:NO finally:^{ [UpdateScheduler downloadList:feedsList background:NO finally:^{
[self endCoreDataChangeUndoEmpty:NO forceUndo:NO]; [self endCoreDataChangeUndoEmpty:NO forceUndo:NO];
[self someDatesChangedScheduleUpdateTimer]; [UpdateScheduler scheduleNextFeed];
}]; }];
} }

View File

@@ -38,6 +38,5 @@
- (void)beginCoreDataChange; - (void)beginCoreDataChange;
- (BOOL)endCoreDataChangeUndoEmpty:(BOOL)undoEmpty forceUndo:(BOOL)force; - (BOOL)endCoreDataChangeUndoEmpty:(BOOL)undoEmpty forceUndo:(BOOL)force;
- (void)someDatesChangedScheduleUpdateTimer;
- (void)restoreOrderingAndIndexPathStr:(NSArray<NSTreeNode*>*)parentsList; - (void)restoreOrderingAndIndexPathStr:(NSArray<NSTreeNode*>*)parentsList;
@end @end

View File

@@ -27,7 +27,6 @@
#import "FeedGroup+Ext.h" #import "FeedGroup+Ext.h"
#import "UpdateScheduler.h" #import "UpdateScheduler.h"
#import "SettingsFeedsView.h" #import "SettingsFeedsView.h"
#import "NSDate+Ext.h"
@interface SettingsFeeds () @interface SettingsFeeds ()
@property (strong) SettingsFeedsView *view; // override super @property (strong) SettingsFeedsView *view; // override super
@@ -51,7 +50,10 @@
RegisterNotification(kNotificationFeedUpdated, @selector(feedUpdated:), self); RegisterNotification(kNotificationFeedUpdated, @selector(feedUpdated:), self);
RegisterNotification(kNotificationFeedIconUpdated, @selector(feedUpdated:), self); RegisterNotification(kNotificationFeedIconUpdated, @selector(feedUpdated:), self);
RegisterNotification(kNotificationGroupInserted, @selector(groupInserted:), self); RegisterNotification(kNotificationGroupInserted, @selector(groupInserted:), self);
RegisterNotification(kNotificationBackgroundUpdateInProgress, @selector(updateInProgress:), self); // Status bar
RegisterNotification(kNotificationScheduleTimerChanged, @selector(updateStatusInfo), self);
RegisterNotification(kNotificationNetworkStatusChanged, @selector(updateStatusInfo), self);
RegisterNotification(kNotificationBackgroundUpdateInProgress, @selector(updateStatusInfo), self);
} }
- (void)dealloc { - (void)dealloc {
@@ -62,10 +64,9 @@
- (void)viewWillAppear { - (void)viewWillAppear {
// needed to scroll outline view to top (if prefs open on another tab) // needed to scroll outline view to top (if prefs open on another tab)
[self.dataStore setSelectionIndexPath:[NSIndexPath indexPathWithIndex:0]]; [self.dataStore setSelectionIndexPath:[NSIndexPath indexPathWithIndex:0]];
self.timerStatusInfo = [NSTimer timerWithTimeInterval:NSTimeIntervalSince1970 target:self selector:@selector(keepTimerRunning) userInfo:nil repeats:YES]; self.timerStatusInfo = [NSTimer timerWithTimeInterval:NSTimeIntervalSince1970 target:self selector:@selector(updateStatusInfo) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timerStatusInfo forMode:NSRunLoopCommonModes]; [[NSRunLoop mainRunLoop] addTimer:self.timerStatusInfo forMode:NSRunLoopCommonModes];
// start spinner if update is in progress when preferences open [self updateStatusInfo];
[self activateSpinner:[UpdateScheduler feedsInQueue]];
} }
/// Timer cleanup /// Timer cleanup
@@ -143,12 +144,7 @@
[StoreCoordinator saveContext:self.dataStore.managedObjectContext andParent:YES]; [StoreCoordinator saveContext:self.dataStore.managedObjectContext andParent:YES];
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationTotalUnreadCountReset object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationTotalUnreadCountReset object:nil];
[self.dataStore rearrangeObjects]; // update ordering [self.dataStore rearrangeObjects]; // update ordering
}
/// Query core data for next update date and set bottom status message
- (void)someDatesChangedScheduleUpdateTimer {
[UpdateScheduler scheduleNextFeed]; [UpdateScheduler scheduleNextFeed];
[self.timerStatusInfo fire];
} }
/// Callback method fired when feed (or icon) has been updated in the background. /// Callback method fired when feed (or icon) has been updated in the background.
@@ -172,43 +168,22 @@
#pragma mark - Activity Spinner & Status Info #pragma mark - Activity Spinner & Status Info
/// Callback method to update status info. Will be called more often when interval is getting shorter. /// Callback method to update status info. Called more often as the interval is getting shorter.
- (void)keepTimerRunning { - (void)updateStatusInfo {
NSDate *date = [UpdateScheduler dateScheduled]; if ([UpdateScheduler feedsInQueue] > 0) {
if (date) { [self.timerStatusInfo setFireDate:[NSDate distantFuture]];
double nextFire = fabs(date.timeIntervalSinceNow); self.view.status.stringValue = [UpdateScheduler updatingXFeeds];
if (nextFire > 1e9) { // distance future, over 31 years [self.view.spinner startAnimation:nil];
self.view.status.stringValue = @""; } else {
return; [self.view.spinner stopAnimation:nil];
} double remaining;
self.view.status.stringValue = [NSString stringWithFormat:NSLocalizedString(@"Next update in %@", nil), self.view.status.stringValue = [UpdateScheduler remainingTimeTillNextUpdate:&remaining];
[NSDate stringForRemainingTime:date]]; if (remaining < 1e5) { // keep timer running if < 28 hours
// Next update is aligned with minute (fmod) else update 1/sec // Next update is aligned with minute (fmod) else update 1/sec
NSDate *nextUpdate = [NSDate dateWithTimeIntervalSinceNow: (nextFire > 60 ? fmod(nextFire, 60) : 1)]; NSDate *nextUpdate = [NSDate dateWithTimeIntervalSinceNow: (remaining > 60 ? fmod(remaining, 60) : 1)];
[self.timerStatusInfo setFireDate:nextUpdate]; [self.timerStatusInfo setFireDate:nextUpdate];
} }
} }
/// Start ( @c c @c > @c 0 ) or stop ( @c c @c = @c 0 ) activity spinner. Also, sets status info.
- (void)activateSpinner:(NSUInteger)c {
if (c == 0) {
[self.view.spinner stopAnimation:nil];
self.view.status.stringValue = @"";
[self.timerStatusInfo fire];
} else {
[self.timerStatusInfo setFireDate:[NSDate distantFuture]];
[self.view.spinner startAnimation:nil];
if (c == 1) { // exactly one feed
self.view.status.stringValue = NSLocalizedString(@"Updating 1 feed …", nil);
} else {
self.view.status.stringValue = [NSString stringWithFormat:NSLocalizedString(@"Updating %lu feeds …", nil), c];
}
}
}
/// Callback method fired when background feed update begins and ends.
- (void)updateInProgress:(NSNotification*)notify {
[self activateSpinner:[notify.object unsignedIntegerValue]];
} }
@@ -252,7 +227,7 @@
[self.dataStore removeObjectsAtArrangedObjectIndexPaths:[nodes valueForKeyPath:@"indexPath"]]; [self.dataStore removeObjectsAtArrangedObjectIndexPaths:[nodes valueForKeyPath:@"indexPath"]];
[self restoreOrderingAndIndexPathStr:parentNodes]; [self restoreOrderingAndIndexPathStr:parentNodes];
[self endCoreDataChangeUndoEmpty:NO forceUndo:NO]; [self endCoreDataChangeUndoEmpty:NO forceUndo:NO];
[self someDatesChangedScheduleUpdateTimer]; [UpdateScheduler scheduleNextFeed];
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationTotalUnreadCountReset object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationTotalUnreadCountReset object:nil];
} }
@@ -325,16 +300,18 @@
[self beginCoreDataChange]; [self beginCoreDataChange];
if (!fg || ![fg isKindOfClass:[FeedGroup class]]) { if (!fg || ![fg isKindOfClass:[FeedGroup class]]) {
fg = [self insertFeedGroupAtSelection:(flag ? GROUP : FEED)]; fg = [self insertFeedGroupAtSelection:(flag ? GROUP : FEED)];
} else {
flag = (fg.type == GROUP);
} }
ModalEditDialog *editDialog = (fg.type == GROUP ? [ModalGroupEdit modalWith:fg] : [ModalFeedEdit modalWith:fg]); ModalEditDialog *editDialog = (flag ? [ModalGroupEdit modalWith:fg] : [ModalFeedEdit modalWith:fg]);
[self.view.window beginSheet:[editDialog getModalSheet] completionHandler:^(NSModalResponse returnCode) { [self.view.window beginSheet:[editDialog getModalSheet] completionHandler:^(NSModalResponse returnCode) {
if (returnCode == NSModalResponseOK) { if (returnCode == NSModalResponseOK) {
[editDialog applyChangesToCoreDataObject]; [editDialog applyChangesToCoreDataObject];
} }
if ([self endCoreDataChangeUndoEmpty:YES forceUndo:(returnCode != NSModalResponseOK)]) { if ([self endCoreDataChangeUndoEmpty:YES forceUndo:(returnCode != NSModalResponseOK)]) {
if (!flag) [self someDatesChangedScheduleUpdateTimer]; // only for feed edit if (!flag) [UpdateScheduler scheduleNextFeed]; // only for feed edit
[self.dataStore rearrangeObjects]; // update display, edited title or icon [self.dataStore rearrangeObjects]; // update display, edited title or icon
} }
}]; }];