Show time of next update
This commit is contained in:
@@ -39,7 +39,7 @@ ToDo
|
||||
- [ ] Show statistics
|
||||
- [x] How often gets the feed updated (min, max, avg)
|
||||
- [ ] Automatically choose best interval?
|
||||
- [ ] Show time of next update
|
||||
- [x] Show time of next update
|
||||
- [ ] Feeds with authentication
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
@class Feed;
|
||||
|
||||
@interface FeedDownload : NSObject
|
||||
@property (class, readonly) NSDate *dateScheduled;
|
||||
@property (class, readonly) BOOL allowNetworkConnection;
|
||||
@property (class, readonly) BOOL isUpdating;
|
||||
@property (class, setter=setPaused:) BOOL isPaused;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
|
||||
static NSTimer *_timer;
|
||||
static SCNetworkReachabilityRef _reachability = NULL;
|
||||
static BOOL _isReachable = NO;
|
||||
static BOOL _isUpdating = NO;
|
||||
@@ -41,6 +42,9 @@ static BOOL _nextUpdateIsForced = NO;
|
||||
|
||||
#pragma mark - User Interaction -
|
||||
|
||||
/// @return Date when background update will fire. If updates are paused, date is @c distantFuture.
|
||||
+ (NSDate *)dateScheduled { return _timer.fireDate; }
|
||||
|
||||
/// @return @c YES if current network state is reachable and updates are not paused by user.
|
||||
+ (BOOL)allowNetworkConnection { return (_isReachable && !_updatePaused); }
|
||||
|
||||
@@ -80,9 +84,8 @@ static BOOL _nextUpdateIsForced = NO;
|
||||
+ (void)scheduleUpdateForUpcomingFeeds {
|
||||
if (![self allowNetworkConnection]) // timer will restart once connection exists
|
||||
return;
|
||||
NSDate *nextTime = [StoreCoordinator nextScheduledUpdate];
|
||||
if (!nextTime) return; // no timer means no feeds to update
|
||||
if ([nextTime timeIntervalSinceNow] < 1) { // mostly, if app was closed for a long time
|
||||
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];
|
||||
}
|
||||
[self scheduleTimer:nextTime];
|
||||
@@ -104,16 +107,16 @@ static BOOL _nextUpdateIsForced = NO;
|
||||
@param nextTime If @c nil timer will be disabled with a @c .fireDate very far in the future.
|
||||
*/
|
||||
+ (void)scheduleTimer:(NSDate*)nextTime {
|
||||
static NSTimer *timer;
|
||||
if (!timer) {
|
||||
timer = [NSTimer timerWithTimeInterval:NSTimeIntervalSince1970 target:[self class] selector:@selector(updateTimerCallback) userInfo:nil repeats:YES];
|
||||
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_timer = [NSTimer timerWithTimeInterval:NSTimeIntervalSince1970 target:[self class] selector:@selector(updateTimerCallback) userInfo:nil repeats:YES];
|
||||
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
|
||||
});
|
||||
if (!nextTime)
|
||||
nextTime = [NSDate dateWithTimeIntervalSinceNow:NSTimeIntervalSince1970];
|
||||
nextTime = [NSDate distantFuture];
|
||||
NSTimeInterval tolerance = [nextTime timeIntervalSinceNow] * 0.15;
|
||||
timer.tolerance = (tolerance < 1 ? 1 : tolerance); // at least 1 sec
|
||||
timer.fireDate = nextTime;
|
||||
_timer.tolerance = (tolerance < 1 ? 1 : tolerance); // at least 1 sec
|
||||
_timer.fireDate = nextTime;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
|
||||
@property (strong) NSArray<NSTreeNode*> *currentlyDraggedNodes;
|
||||
@property (strong) NSUndoManager *undoManager;
|
||||
|
||||
@property (strong) NSTimer *timerStatusInfo;
|
||||
@property (strong) NSDateComponentsFormatter *intervalFormatter;
|
||||
@end
|
||||
|
||||
@implementation SettingsFeeds
|
||||
@@ -47,7 +50,6 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
[self activateSpinner:([FeedDownload isUpdating] ? -1 : 0)]; // start spinner if update is in progress when preferences open
|
||||
[self.outlineView registerForDraggedTypes:[NSArray arrayWithObject:dragNodeType]];
|
||||
[self.dataStore setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"sortIndex" ascending:YES]]];
|
||||
|
||||
@@ -69,6 +71,64 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Activity Spinner & Status Info
|
||||
|
||||
|
||||
/// Initialize status info timer
|
||||
- (void)viewWillAppear {
|
||||
self.intervalFormatter = [[NSDateComponentsFormatter alloc] init];
|
||||
self.intervalFormatter.unitsStyle = NSDateComponentsFormatterUnitsStyleShort; // e.g., '30 min'
|
||||
self.intervalFormatter.maximumUnitCount = 1;
|
||||
self.timerStatusInfo = [NSTimer timerWithTimeInterval:NSTimeIntervalSince1970 target:self selector:@selector(keepTimerRunning) userInfo:nil repeats:YES];
|
||||
[[NSRunLoop mainRunLoop] addTimer:self.timerStatusInfo forMode:NSRunLoopCommonModes];
|
||||
// start spinner if update is in progress when preferences open
|
||||
[self activateSpinner:([FeedDownload isUpdating] ? -1 : 0)];
|
||||
}
|
||||
|
||||
/// Timer cleanup
|
||||
- (void)viewWillDisappear {
|
||||
// in viewWillDisappear otherwise dealloc will not be called
|
||||
[self.timerStatusInfo invalidate];
|
||||
self.timerStatusInfo = nil;
|
||||
self.intervalFormatter = nil;
|
||||
}
|
||||
|
||||
/// Callback method to update status info. Will be called more often when interval is getting shorter.
|
||||
- (void)keepTimerRunning {
|
||||
NSDate *date = [FeedDownload dateScheduled];
|
||||
if (date) {
|
||||
double nextFire = fabs(date.timeIntervalSinceNow);
|
||||
if (nextFire > 60) { // update 1/min
|
||||
nextFire = fmod(nextFire, 60); // next update will align with minute
|
||||
} else {
|
||||
nextFire = 1; // update 1/sec
|
||||
}
|
||||
NSString *str = [self.intervalFormatter stringFromTimeInterval: date.timeIntervalSinceNow];
|
||||
self.spinnerLabel.stringValue = [NSString stringWithFormat:NSLocalizedString(@"Next update in %@", nil), str];
|
||||
[self.timerStatusInfo setFireDate:[NSDate dateWithTimeIntervalSinceNow: nextFire]];
|
||||
}
|
||||
}
|
||||
|
||||
/// Start ( @c c @c > @c 0 ) or stop ( @c c @c = @c 0 ) activity spinner. Also, sets status info.
|
||||
- (void)activateSpinner:(NSInteger)c {
|
||||
if (c == 0) {
|
||||
[self.spinner stopAnimation:nil];
|
||||
self.spinnerLabel.stringValue = @"";
|
||||
[self.timerStatusInfo fire];
|
||||
} else {
|
||||
[self.timerStatusInfo setFireDate:[NSDate distantFuture]];
|
||||
[self.spinner startAnimation:nil];
|
||||
if (c == 1) { // exactly one feed
|
||||
self.spinnerLabel.stringValue = NSLocalizedString(@"Updating 1 feed …", nil);
|
||||
} else if (c < 0) { // unknown number of feeds
|
||||
self.spinnerLabel.stringValue = NSLocalizedString(@"Updating feeds …", nil);
|
||||
} else {
|
||||
self.spinnerLabel.stringValue = [NSString stringWithFormat:NSLocalizedString(@"Updating %lu feeds …", nil), c];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Notification callback methods
|
||||
|
||||
|
||||
@@ -89,25 +149,6 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
||||
[self activateSpinner:[notify.object integerValue]];
|
||||
}
|
||||
|
||||
/// Start or stop activity spinner (will run on main thread). If @c c @c == @c 0 stop spinner.
|
||||
- (void)activateSpinner:(NSInteger)c {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (c == 0) {
|
||||
[self.spinner stopAnimation:nil];
|
||||
self.spinnerLabel.stringValue = @"";
|
||||
} else {
|
||||
[self.spinner startAnimation:nil];
|
||||
if (c < 0) { // unknown number of feeds
|
||||
self.spinnerLabel.stringValue = NSLocalizedString(@"Updating feeds …", nil);
|
||||
} else if (c == 1) {
|
||||
self.spinnerLabel.stringValue = NSLocalizedString(@"Updating 1 feed …", nil);
|
||||
} else {
|
||||
self.spinnerLabel.stringValue = [NSString stringWithFormat:NSLocalizedString(@"Updating %lu feeds …", nil), c];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Persist state
|
||||
|
||||
@@ -133,6 +174,8 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
||||
[self.undoManager endUndoGrouping];
|
||||
if (!flag && self.dataStore.managedObjectContext.hasChanges) {
|
||||
[StoreCoordinator saveContext:self.dataStore.managedObjectContext andParent:YES];
|
||||
[FeedDownload scheduleUpdateForUpcomingFeeds];
|
||||
[self.timerStatusInfo fire];
|
||||
return YES;
|
||||
}
|
||||
[self.undoManager disableUndoRegistration];
|
||||
|
||||
@@ -221,19 +221,19 @@ CA
|
||||
<action selector="shareMenu:" target="-2" id="JJq-7D-Bti"/>
|
||||
</connections>
|
||||
</button>
|
||||
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="fos-vP-s2s">
|
||||
<rect key="frame" x="168" y="3" width="16" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
</progressIndicator>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="44U-lx-hnq">
|
||||
<rect key="frame" x="190" y="4" width="112" height="14"/>
|
||||
<rect key="frame" x="166" y="4" width="141" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="<string>" id="yyA-K6-M3v">
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="center" title="<string>" id="yyA-K6-M3v">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="fos-vP-s2s">
|
||||
<rect key="frame" x="301" y="3" width="16" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
|
||||
</progressIndicator>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="27" y="882.5"/>
|
||||
</customView>
|
||||
|
||||
Reference in New Issue
Block a user