Fix status info message in feed settings
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<Feed*>*)list background:(BOOL)flag finally:(nullable os_block_t)block;
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -290,7 +290,6 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
||||
}
|
||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||
if (block) block();
|
||||
PostNotification(kNotificationBackgroundUpdateInProgress, @(_queueSize));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>10782</string>
|
||||
<string>10902</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.news</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -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];
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,5 @@
|
||||
|
||||
- (void)beginCoreDataChange;
|
||||
- (BOOL)endCoreDataChangeUndoEmpty:(BOOL)undoEmpty forceUndo:(BOOL)force;
|
||||
- (void)someDatesChangedScheduleUpdateTimer;
|
||||
- (void)restoreOrderingAndIndexPathStr:(NSArray<NSTreeNode*>*)parentsList;
|
||||
@end
|
||||
|
||||
@@ -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,43 +168,22 @@
|
||||
#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]];
|
||||
/// 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];
|
||||
} 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: (nextFire > 60 ? fmod(nextFire, 60) : 1)];
|
||||
NSDate *nextUpdate = [NSDate dateWithTimeIntervalSinceNow: (remaining > 60 ? fmod(remaining, 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 {
|
||||
[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 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
|
||||
}
|
||||
}];
|
||||
|
||||
Reference in New Issue
Block a user