From 5392ac8ab2a2d026a6351caff1f6b85b44eac728 Mon Sep 17 00:00:00 2001 From: relikd Date: Thu, 15 Aug 2019 01:38:57 +0200 Subject: [PATCH] Fix status info message in feed settings --- CHANGELOG.md | 4 +- baRSS/Constants.h | 5 ++ baRSS/Feed Import/UpdateScheduler.h | 4 ++ baRSS/Feed Import/UpdateScheduler.m | 46 +++++++++---- baRSS/Feed Import/WebFeed.m | 1 - baRSS/Info.plist | 2 +- .../Feeds Tab/SettingsFeeds+DragDrop.m | 2 +- baRSS/Preferences/Feeds Tab/SettingsFeeds.h | 1 - baRSS/Preferences/Feeds Tab/SettingsFeeds.m | 69 +++++++------------ 9 files changed, 68 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0e8b5b..67c91a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 paused will postpone download until unpaused - *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 - *UI:* If an error occurs, show document URL (path to file or web url) - Comparison of existing articles with nonexistent guid and link diff --git a/baRSS/Constants.h b/baRSS/Constants.h index 0476a57..1584bdb 100644 --- a/baRSS/Constants.h +++ b/baRSS/Constants.h @@ -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. */ 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. Called whenever a new feed group was created in @c autoDownloadAndParseURL: diff --git a/baRSS/Feed Import/UpdateScheduler.h b/baRSS/Feed Import/UpdateScheduler.h index 055f210..2c62685 100644 --- a/baRSS/Feed Import/UpdateScheduler.h +++ b/baRSS/Feed Import/UpdateScheduler.h @@ -31,6 +31,10 @@ @property (class, readonly) BOOL isUpdating; @property (class, setter=setPaused:) BOOL isPaused; +// Getter ++ (NSString*)remainingTimeTillNextUpdate:(nullable double*)remaining; ++ (NSString*)updatingXFeeds; +// Scheduling + (void)scheduleNextFeed; + (void)forceUpdateAllFeeds; + (void)downloadList:(NSArray*)list background:(BOOL)flag finally:(nullable os_block_t)block; diff --git a/baRSS/Feed Import/UpdateScheduler.m b/baRSS/Feed Import/UpdateScheduler.m index bdb7c45..4e5a999 100644 --- a/baRSS/Feed Import/UpdateScheduler.m +++ b/baRSS/Feed Import/UpdateScheduler.m @@ -25,6 +25,7 @@ #import "WebFeed.h" #import "Constants.h" #import "StoreCoordinator.h" +#import "NSDate+Ext.h" static NSTimer *_timer; static SCNetworkReachabilityRef _reachability = NULL; @@ -56,21 +57,33 @@ static BOOL _nextUpdateIsForced = NO; + (void)setPaused:(BOOL)flag { // TODO: should pause persist between app launches? _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) - [self pauseUpdates]; - else - [self resumeUpdates]; + return NSLocalizedString(@"Updates paused", nil); + if (time > 1e9) // distance future, over 31 years + 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. -+ (void)pauseUpdates { - [self scheduleTimer:nil]; -} - -/// Start normal (non forced) schedule if network is reachable. -+ (void)resumeUpdates { - if (_isReachable) - [self scheduleNextFeed]; +/// Update status. 'Updating X feeds …' or empty string if not updating. ++ (NSString*)updatingXFeeds { + 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]; + } } @@ -83,6 +96,8 @@ static BOOL _nextUpdateIsForced = NO; + (void)scheduleNextFeed { if (![self allowNetworkConnection]) // timer will restart once connection exists 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 if (nextTime && [nextTime timeIntervalSinceNow] < 1) { // mostly, if app was closed for a long time nextTime = [NSDate dateWithTimeIntervalSinceNow:1]; @@ -116,6 +131,7 @@ static BOOL _nextUpdateIsForced = NO; NSTimeInterval tolerance = [nextTime timeIntervalSinceNow] * 0.15; _timer.tolerance = (tolerance < 1 ? 1 : tolerance); // at least 1 sec _timer.fireDate = nextTime; + PostNotification(kNotificationScheduleTimerChanged, nil); } /** @@ -135,7 +151,7 @@ static BOOL _nextUpdateIsForced = NO; [self downloadList:list background:!updateAll finally:^{ [StoreCoordinator saveContext:moc andParent:YES]; // save parents too ... [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]; PostNotification(kNotificationNetworkStatusChanged, @(_isReachable)); if (_isReachable) { - [UpdateScheduler resumeUpdates]; + [UpdateScheduler scheduleNextFeed]; } else { - [UpdateScheduler pauseUpdates]; + [UpdateScheduler scheduleTimer:nil]; } } diff --git a/baRSS/Feed Import/WebFeed.m b/baRSS/Feed Import/WebFeed.m index b8a7714..45bd0c3 100644 --- a/baRSS/Feed Import/WebFeed.m +++ b/baRSS/Feed Import/WebFeed.m @@ -290,7 +290,6 @@ static _Atomic(NSUInteger) _queueSize = 0; } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ if (block) block(); - PostNotification(kNotificationBackgroundUpdateInProgress, @(_queueSize)); }); } diff --git a/baRSS/Info.plist b/baRSS/Info.plist index 70def67..bf340b3 100644 --- a/baRSS/Info.plist +++ b/baRSS/Info.plist @@ -60,7 +60,7 @@ CFBundleVersion - 10782 + 10902 LSApplicationCategoryType public.app-category.news LSMinimumSystemVersion diff --git a/baRSS/Preferences/Feeds Tab/SettingsFeeds+DragDrop.m b/baRSS/Preferences/Feeds Tab/SettingsFeeds+DragDrop.m index d14d0be..3f843e8 100644 --- a/baRSS/Preferences/Feeds Tab/SettingsFeeds+DragDrop.m +++ b/baRSS/Preferences/Feeds Tab/SettingsFeeds+DragDrop.m @@ -163,7 +163,7 @@ const NSPasteboardType dragReorder = @"de.relikd.baRSS.drag-reorder"; [UpdateScheduler downloadList:feedsList background:NO finally:^{ [self endCoreDataChangeUndoEmpty:NO forceUndo:NO]; - [self someDatesChangedScheduleUpdateTimer]; + [UpdateScheduler scheduleNextFeed]; }]; } diff --git a/baRSS/Preferences/Feeds Tab/SettingsFeeds.h b/baRSS/Preferences/Feeds Tab/SettingsFeeds.h index c3e4983..c03094b 100644 --- a/baRSS/Preferences/Feeds Tab/SettingsFeeds.h +++ b/baRSS/Preferences/Feeds Tab/SettingsFeeds.h @@ -38,6 +38,5 @@ - (void)beginCoreDataChange; - (BOOL)endCoreDataChangeUndoEmpty:(BOOL)undoEmpty forceUndo:(BOOL)force; -- (void)someDatesChangedScheduleUpdateTimer; - (void)restoreOrderingAndIndexPathStr:(NSArray*)parentsList; @end diff --git a/baRSS/Preferences/Feeds Tab/SettingsFeeds.m b/baRSS/Preferences/Feeds Tab/SettingsFeeds.m index f2afa58..87c0eeb 100644 --- a/baRSS/Preferences/Feeds Tab/SettingsFeeds.m +++ b/baRSS/Preferences/Feeds Tab/SettingsFeeds.m @@ -27,7 +27,6 @@ #import "FeedGroup+Ext.h" #import "UpdateScheduler.h" #import "SettingsFeedsView.h" -#import "NSDate+Ext.h" @interface SettingsFeeds () @property (strong) SettingsFeedsView *view; // override super @@ -51,7 +50,10 @@ RegisterNotification(kNotificationFeedUpdated, @selector(feedUpdated:), self); RegisterNotification(kNotificationFeedIconUpdated, @selector(feedUpdated:), 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 { @@ -62,10 +64,9 @@ - (void)viewWillAppear { // needed to scroll outline view to top (if prefs open on another tab) [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]; - // start spinner if update is in progress when preferences open - [self activateSpinner:[UpdateScheduler feedsInQueue]]; + [self updateStatusInfo]; } /// Timer cleanup @@ -143,12 +144,7 @@ [StoreCoordinator saveContext:self.dataStore.managedObjectContext andParent:YES]; [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationTotalUnreadCountReset object:nil]; [self.dataStore rearrangeObjects]; // update ordering -} - -/// Query core data for next update date and set bottom status message -- (void)someDatesChangedScheduleUpdateTimer { [UpdateScheduler scheduleNextFeed]; - [self.timerStatusInfo fire]; } /// Callback method fired when feed (or icon) has been updated in the background. @@ -172,45 +168,24 @@ #pragma mark - Activity Spinner & Status Info -/// Callback method to update status info. Will be called more often when interval is getting shorter. -- (void)keepTimerRunning { - NSDate *date = [UpdateScheduler dateScheduled]; - if (date) { - double nextFire = fabs(date.timeIntervalSinceNow); - if (nextFire > 1e9) { // distance future, over 31 years - self.view.status.stringValue = @""; - return; - } - self.view.status.stringValue = [NSString stringWithFormat:NSLocalizedString(@"Next update in %@", nil), - [NSDate stringForRemainingTime:date]]; - // Next update is aligned with minute (fmod) else update 1/sec - NSDate *nextUpdate = [NSDate dateWithTimeIntervalSinceNow: (nextFire > 60 ? fmod(nextFire, 60) : 1)]; - [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 { +/// Callback method to update status info. Called more often as the interval is getting shorter. +- (void)updateStatusInfo { + if ([UpdateScheduler feedsInQueue] > 0) { [self.timerStatusInfo setFireDate:[NSDate distantFuture]]; + self.view.status.stringValue = [UpdateScheduler updatingXFeeds]; [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]; + } else { + [self.view.spinner stopAnimation:nil]; + double remaining; + self.view.status.stringValue = [UpdateScheduler remainingTimeTillNextUpdate:&remaining]; + if (remaining < 1e5) { // keep timer running if < 28 hours + // Next update is aligned with minute (fmod) else update 1/sec + NSDate *nextUpdate = [NSDate dateWithTimeIntervalSinceNow: (remaining > 60 ? fmod(remaining, 60) : 1)]; + [self.timerStatusInfo setFireDate:nextUpdate]; } } } -/// Callback method fired when background feed update begins and ends. -- (void)updateInProgress:(NSNotification*)notify { - [self activateSpinner:[notify.object unsignedIntegerValue]]; -} - #pragma mark - UI Button Interaction @@ -252,7 +227,7 @@ [self.dataStore removeObjectsAtArrangedObjectIndexPaths:[nodes valueForKeyPath:@"indexPath"]]; [self restoreOrderingAndIndexPathStr:parentNodes]; [self endCoreDataChangeUndoEmpty:NO forceUndo:NO]; - [self someDatesChangedScheduleUpdateTimer]; + [UpdateScheduler scheduleNextFeed]; [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationTotalUnreadCountReset object:nil]; } @@ -325,16 +300,18 @@ [self beginCoreDataChange]; if (!fg || ![fg isKindOfClass:[FeedGroup class]]) { 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) { if (returnCode == NSModalResponseOK) { [editDialog applyChangesToCoreDataObject]; } 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 } }];