Refactoring Part 4: Update Timer and Pausing
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
- (void)calculateAndSetScheduled;
|
- (void)calculateAndSetScheduled;
|
||||||
|
|
||||||
- (void)setEtag:(NSString*)etag modified:(NSString*)modified;
|
- (void)setEtag:(NSString*)etag modified:(NSString*)modified;
|
||||||
|
- (void)setEtagAndModified:(NSHTTPURLResponse*)http;
|
||||||
- (BOOL)setURL:(NSString*)url refresh:(int32_t)refresh unit:(int16_t)unit;
|
- (BOOL)setURL:(NSString*)url refresh:(int32_t)refresh unit:(int16_t)unit;
|
||||||
|
|
||||||
- (NSString*)readableRefreshString;
|
- (NSString*)readableRefreshString;
|
||||||
|
|||||||
@@ -24,12 +24,16 @@
|
|||||||
|
|
||||||
@implementation FeedMeta (Ext)
|
@implementation FeedMeta (Ext)
|
||||||
|
|
||||||
/// Increment @c errorCount (max. 19) and set new @c scheduled (2^N seconds, max. 6 days).
|
/// Increment @c errorCount and set new @c scheduled date (2^N minutes, max. 5.7 days).
|
||||||
- (void)setErrorAndPostponeSchedule {
|
- (void)setErrorAndPostponeSchedule {
|
||||||
int16_t n = self.errorCount + 1;
|
if (self.errorCount < 0)
|
||||||
self.errorCount = (n < 1 ? 1 : (n > 19 ? 19 : n)); // between: 2 sec and 6 days
|
self.errorCount = 0;
|
||||||
NSTimeInterval retryWaitTime = pow(2, self.errorCount); // 2^n seconds
|
int16_t n = self.errorCount + 1; // always increment errorCount (can be used to indicate bad feeds)
|
||||||
|
NSTimeInterval retryWaitTime = pow(2, (n > 13 ? 13 : n)) * 60; // 2^N (between: 2 minutes and 5.7 days)
|
||||||
|
self.errorCount = n;
|
||||||
self.scheduled = [NSDate dateWithTimeIntervalSinceNow:retryWaitTime];
|
self.scheduled = [NSDate dateWithTimeIntervalSinceNow:retryWaitTime];
|
||||||
|
// TODO: remove logging
|
||||||
|
NSLog(@"ERROR: Feed download failed: %@ (errorCount: %d)", self.url, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate date from @c refreshNum and @c refreshUnit and set as next scheduled feed update.
|
/// Calculate date from @c refreshNum and @c refreshUnit and set as next scheduled feed update.
|
||||||
@@ -44,6 +48,12 @@
|
|||||||
if (![self.modified isEqualToString:modified]) self.modified = modified;
|
if (![self.modified isEqualToString:modified]) self.modified = modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read header field "Etag" and "Date" and set @c .etag and @c .modified.
|
||||||
|
- (void)setEtagAndModified:(NSHTTPURLResponse*)http {
|
||||||
|
NSDictionary *header = [http allHeaderFields];
|
||||||
|
[self setEtag:header[@"Etag"] modified:header[@"Date"]]; // @"Expires", @"Last-Modified"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Set download url and refresh interval (popup button selection). @note Only values that differ will be updated.
|
Set download url and refresh interval (popup button selection). @note Only values that differ will be updated.
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,10 @@
|
|||||||
#ifndef Constants_h
|
#ifndef Constants_h
|
||||||
#define Constants_h
|
#define Constants_h
|
||||||
|
|
||||||
|
// TODO: Add support for media player?
|
||||||
|
// <enclosure url="https://url.mp3" length="63274022" type="audio/mpeg" />
|
||||||
|
// TODO: Disable 'update all' menu item during update?
|
||||||
|
|
||||||
static NSString *kNotificationFeedUpdated = @"baRSS-notification-feed-updated";
|
static NSString *kNotificationFeedUpdated = @"baRSS-notification-feed-updated";
|
||||||
static NSString *kNotificationNetworkStatusChanged = @"baRSS-notification-network-status-changed";
|
static NSString *kNotificationNetworkStatusChanged = @"baRSS-notification-network-status-changed";
|
||||||
static NSString *kNotificationTotalUnreadCountChanged = @"baRSS-notification-total-unread-count-changed";
|
static NSString *kNotificationTotalUnreadCountChanged = @"baRSS-notification-total-unread-count-changed";
|
||||||
|
|||||||
@@ -24,9 +24,15 @@
|
|||||||
#import <RSXML/RSXML.h>
|
#import <RSXML/RSXML.h>
|
||||||
|
|
||||||
@interface FeedDownload : NSObject
|
@interface FeedDownload : NSObject
|
||||||
+ (void)newFeed:(NSString *)url block:(void(^)(RSParsedFeed *feed, NSError* error, NSHTTPURLResponse* response))block;
|
// Register for network change notifications
|
||||||
+ (void)registerNetworkChangeNotification;
|
+ (void)registerNetworkChangeNotification;
|
||||||
+ (void)unregisterNetworkChangeNotification;
|
+ (void)unregisterNetworkChangeNotification;
|
||||||
+ (BOOL)isNetworkReachable;
|
// Scheduled feed update
|
||||||
+ (void)scheduleNextUpdateForced:(BOOL)flag;
|
+ (void)newFeed:(NSString *)url block:(void(^)(RSParsedFeed *feed, NSError* error, NSHTTPURLResponse* response))block;
|
||||||
|
+ (void)scheduleUpdateForUpcomingFeeds;
|
||||||
|
+ (void)forceUpdateAllFeeds;
|
||||||
|
// User interaction
|
||||||
|
+ (BOOL)allowNetworkConnection;
|
||||||
|
+ (BOOL)isPaused;
|
||||||
|
+ (void)setPaused:(BOOL)flag;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -30,10 +30,126 @@
|
|||||||
|
|
||||||
static SCNetworkReachabilityRef _reachability = NULL;
|
static SCNetworkReachabilityRef _reachability = NULL;
|
||||||
static BOOL _isReachable = NO;
|
static BOOL _isReachable = NO;
|
||||||
|
static BOOL _updatePaused = NO;
|
||||||
|
static BOOL _nextUpdateIsForced = NO;
|
||||||
|
|
||||||
|
|
||||||
@implementation FeedDownload
|
@implementation FeedDownload
|
||||||
|
|
||||||
|
#pragma mark - User Interaction -
|
||||||
|
|
||||||
|
/// @return @c YES if current network state is reachable and updates are not paused by user.
|
||||||
|
+ (BOOL)allowNetworkConnection {
|
||||||
|
return (_isReachable && !_updatePaused);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return @c YES if update is paused by user.
|
||||||
|
+ (BOOL)isPaused {
|
||||||
|
return _updatePaused;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set paused flag and cancel timer regardless of network connectivity.
|
||||||
|
+ (void)setPaused:(BOOL)flag {
|
||||||
|
_updatePaused = flag;
|
||||||
|
if (_updatePaused)
|
||||||
|
[self pauseUpdates];
|
||||||
|
else
|
||||||
|
[self resumeUpdates];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 scheduleUpdateForUpcomingFeeds];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - Update Feed Timer -
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get date of next up feed and start the timer.
|
||||||
|
*/
|
||||||
|
+ (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
|
||||||
|
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.2]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set new @c .fireDate and @c .tolerance for update timer.
|
||||||
|
|
||||||
|
@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];
|
||||||
|
}
|
||||||
|
if (!nextTime)
|
||||||
|
nextTime = [NSDate dateWithTimeIntervalSinceNow:NSTimeIntervalSince1970];
|
||||||
|
NSTimeInterval tolerance = [nextTime timeIntervalSinceNow] * 0.15;
|
||||||
|
timer.tolerance = (tolerance < 1 ? 1 : tolerance); // at least 1 sec
|
||||||
|
timer.fireDate = nextTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Called when schedule timer runs out (earliest @c .schedule date). Or if forced by user request.
|
||||||
|
*/
|
||||||
|
+ (void)updateTimerCallback {
|
||||||
|
if (![self allowNetworkConnection])
|
||||||
|
return;
|
||||||
|
NSLog(@"fired");
|
||||||
|
|
||||||
|
__block NSManagedObjectContext *childContext = [StoreCoordinator createChildContext];
|
||||||
|
NSArray<Feed*> *list = [StoreCoordinator getListOfFeedsThatNeedUpdate:_nextUpdateIsForced inContext:childContext];
|
||||||
|
_nextUpdateIsForced = NO;
|
||||||
|
if (list.count == 0) {
|
||||||
|
NSLog(@"ERROR: Something went wrong, timer fired too early.");
|
||||||
|
[childContext reset];
|
||||||
|
childContext = nil;
|
||||||
|
// thechnically should never happen, anyway we need to reset the timer
|
||||||
|
[self resumeUpdates];
|
||||||
|
return; // nothing to do here
|
||||||
|
}
|
||||||
|
dispatch_group_t group = dispatch_group_create();
|
||||||
|
for (Feed *feed in list) {
|
||||||
|
[self downloadFeed:feed group:group];
|
||||||
|
}
|
||||||
|
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||||
|
[StoreCoordinator saveContext:childContext andParent:YES];
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFeedUpdated object:[list valueForKeyPath:@"objectID"]];
|
||||||
|
[childContext reset];
|
||||||
|
childContext = nil;
|
||||||
|
[self resumeUpdates];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma mark - Download RSS Feed -
|
||||||
|
|
||||||
|
|
||||||
/// @return New request with no caching policy and timeout interval of 30 seconds.
|
/// @return New request with no caching policy and timeout interval of 30 seconds.
|
||||||
+ (NSMutableURLRequest*)newRequestURL:(NSString*)url {
|
+ (NSMutableURLRequest*)newRequestURL:(NSString*)url {
|
||||||
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
|
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
|
||||||
@@ -76,72 +192,6 @@ static BOOL _isReachable = NO;
|
|||||||
}] resume];
|
}] resume];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Update existing feeds -
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
Get date of next update schedule and start @c updateTimer.
|
|
||||||
|
|
||||||
@param forceUpdate If @c YES all feeds will be downloaded regardless of scheduled date.
|
|
||||||
*/
|
|
||||||
+ (void)scheduleNextUpdateForced:(BOOL)forceUpdate {
|
|
||||||
static NSTimer *_updateTimer;
|
|
||||||
@synchronized (_updateTimer) { // TODO: dig into analyzer warning
|
|
||||||
if (_updateTimer) {
|
|
||||||
[_updateTimer invalidate];
|
|
||||||
_updateTimer = nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!_isReachable) return; // cancel timer entirely (will be restarted once connection exists)
|
|
||||||
NSDate *nextTime = [NSDate dateWithTimeIntervalSinceNow:0.2];
|
|
||||||
if (!forceUpdate) {
|
|
||||||
nextTime = [StoreCoordinator nextScheduledUpdate];
|
|
||||||
if (!nextTime) return; // no timer means no feeds to update
|
|
||||||
if ([nextTime timeIntervalSinceNow] < 0) { // mostly, if app was closed for a long time
|
|
||||||
nextTime = [NSDate dateWithTimeIntervalSinceNow:2]; // TODO: retry in 2 sec?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NSTimeInterval tolerance = [nextTime timeIntervalSinceNow] * 0.15;
|
|
||||||
_updateTimer = [NSTimer timerWithTimeInterval:0 target:[self class] selector:@selector(scheduledUpdateTimer:) userInfo:@(forceUpdate) repeats:NO];
|
|
||||||
_updateTimer.fireDate = nextTime;
|
|
||||||
_updateTimer.tolerance = (tolerance < 1 ? 1 : tolerance); // at least 1 sec
|
|
||||||
[[NSRunLoop mainRunLoop] addTimer:_updateTimer forMode:NSRunLoopCommonModes];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Called when schedule timer has run out (earliest scheduled date). Or if forced by user request.
|
|
||||||
|
|
||||||
@param timer @c NSTimer @c .userInfo should contain a @c BOOL value whether to force an update of all feeds @c (YES).
|
|
||||||
*/
|
|
||||||
+ (void)scheduledUpdateTimer:(NSTimer*)timer {
|
|
||||||
NSLog(@"fired");
|
|
||||||
BOOL forceAll = [timer.userInfo boolValue];
|
|
||||||
// TODO: check internet connection
|
|
||||||
// TODO: disable menu item 'update all' during update
|
|
||||||
__block NSManagedObjectContext *childContext = [StoreCoordinator createChildContext];
|
|
||||||
NSArray<Feed*> *list = [StoreCoordinator getListOfFeedsThatNeedUpdate:forceAll inContext:childContext];
|
|
||||||
if (list.count == 0) {
|
|
||||||
NSLog(@"ERROR: Something went wrong, timer fired too early.");
|
|
||||||
[childContext reset];
|
|
||||||
childContext = nil;
|
|
||||||
// thechnically should never happen, anyway we need to reset the timer
|
|
||||||
[self scheduleNextUpdateForced:NO]; // NO, since forceAll will get ALL items and shouldn't be 0
|
|
||||||
return; // nothing to do here
|
|
||||||
}
|
|
||||||
dispatch_group_t group = dispatch_group_create();
|
|
||||||
for (Feed *feed in list) {
|
|
||||||
[self downloadFeed:feed group:group];
|
|
||||||
}
|
|
||||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
|
||||||
[StoreCoordinator saveContext:childContext andParent:YES];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFeedUpdated object:[list valueForKeyPath:@"objectID"]];
|
|
||||||
[childContext reset];
|
|
||||||
childContext = nil;
|
|
||||||
[self scheduleNextUpdateForced:NO]; // after forced update, continue regular cycle
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Start download request with existing @c Feed object. Reuses etag and modified headers.
|
Start download request with existing @c Feed object. Reuses etag and modified headers.
|
||||||
|
|
||||||
@@ -149,56 +199,41 @@ static BOOL _isReachable = NO;
|
|||||||
@param group Mutex to count completion of all downloads.
|
@param group Mutex to count completion of all downloads.
|
||||||
*/
|
*/
|
||||||
+ (void)downloadFeed:(Feed*)feed group:(dispatch_group_t)group {
|
+ (void)downloadFeed:(Feed*)feed group:(dispatch_group_t)group {
|
||||||
if (!_isReachable) return;
|
if (![self allowNetworkConnection])
|
||||||
|
return;
|
||||||
dispatch_group_enter(group);
|
dispatch_group_enter(group);
|
||||||
[[[NSURLSession sharedSession] dataTaskWithRequest:[self newRequest:feed.meta] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
[[[NSURLSession sharedSession] dataTaskWithRequest:[self newRequest:feed.meta] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
|
||||||
[feed.managedObjectContext performBlock:^{
|
[feed.managedObjectContext performBlock:^{
|
||||||
// core data block inside of url session block; otherwise access will EXC_BAD_INSTRUCTION
|
// core data block inside of url session block; otherwise access will EXC_BAD_INSTRUCTION
|
||||||
if (error) {
|
if (error) {
|
||||||
[feed.meta setErrorAndPostponeSchedule];
|
[feed.meta setErrorAndPostponeSchedule];
|
||||||
// TODO: remove logging
|
|
||||||
NSLog(@"Error loading: %@ (%d)", response.URL, feed.meta.errorCount);
|
|
||||||
} else {
|
} else {
|
||||||
feed.meta.errorCount = 0; // reset counter
|
[feed.meta setEtagAndModified:(NSHTTPURLResponse*)response];
|
||||||
[self downloadSuccessful:data forFeed:feed response:(NSHTTPURLResponse*)response];
|
[feed.meta calculateAndSetScheduled];
|
||||||
|
|
||||||
|
if ([(NSHTTPURLResponse*)response statusCode] != 304) { // only parse if modified
|
||||||
|
// should be fine to call synchronous since dataTask is already in the background (always? proof?)
|
||||||
|
RSXMLData *xml = [[RSXMLData alloc] initWithData:data urlString:feed.meta.url];
|
||||||
|
RSParsedFeed *parsed = RSParseFeedSync(xml, NULL);
|
||||||
|
if (parsed && parsed.articles.count > 0) {
|
||||||
|
[feed updateWithRSS:parsed postUnreadCountChange:YES];
|
||||||
|
feed.meta.errorCount = 0; // reset counter
|
||||||
|
} else {
|
||||||
|
[feed.meta setErrorAndPostponeSchedule]; // replaces date of 'calculateAndSetScheduled'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: save changes for this feed only?
|
||||||
|
//[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFeedUpdated object:feed.objectID];
|
||||||
}
|
}
|
||||||
dispatch_group_leave(group);
|
dispatch_group_leave(group);
|
||||||
}];
|
}];
|
||||||
}] resume];
|
}] resume];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
Parse RSS feed data and save to persistent store. If HTTP 304 (not modified) skip feed evaluation.
|
|
||||||
|
|
||||||
@param data Raw data from request.
|
#pragma mark - Network Connection & Reachability -
|
||||||
@param feed @c Feed on which the update is executed.
|
|
||||||
@param http Download response containing the statusCode and etag / modified headers.
|
|
||||||
*/
|
|
||||||
+ (void)downloadSuccessful:(NSData*)data forFeed:(Feed*)feed response:(NSHTTPURLResponse*)http {
|
|
||||||
if ([http statusCode] != 304) {
|
|
||||||
// should be fine to call synchronous since dataTask is already in the background (always? proof?)
|
|
||||||
RSXMLData *xml = [[RSXMLData alloc] initWithData:data urlString:feed.meta.url];
|
|
||||||
RSParsedFeed *parsed = RSParseFeedSync(xml, NULL);
|
|
||||||
if (parsed) {
|
|
||||||
// TODO: add support for media player?
|
|
||||||
// <enclosure url="https://url.mp3" length="63274022" type="audio/mpeg" />
|
|
||||||
[feed updateWithRSS:parsed postUnreadCountChange:YES];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[feed.meta setEtag:[http allHeaderFields][@"Etag"] modified:[http allHeaderFields][@"Date"]]; // @"Expires", @"Last-Modified"
|
|
||||||
// Don't update redirected url since it happened in the background; User may not recognize url
|
|
||||||
[feed.meta calculateAndSetScheduled];
|
|
||||||
// TODO: save changes for this feed only?
|
|
||||||
// [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFeedUpdated object:feed.objectID];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Network Connection -
|
|
||||||
|
|
||||||
|
|
||||||
/// External getter to check wheter current network state is reachable.
|
|
||||||
+ (BOOL)isNetworkReachable { return _isReachable; }
|
|
||||||
|
|
||||||
/// Set callback on @c self to listen for network reachability changes.
|
/// Set callback on @c self to listen for network reachability changes.
|
||||||
+ (void)registerNetworkChangeNotification {
|
+ (void)registerNetworkChangeNotification {
|
||||||
// https://stackoverflow.com/questions/11240196/notification-when-wifi-connected-os-x
|
// https://stackoverflow.com/questions/11240196/notification-when-wifi-connected-os-x
|
||||||
@@ -229,18 +264,16 @@ static BOOL _isReachable = NO;
|
|||||||
|
|
||||||
/// Called when network interface or reachability changes.
|
/// Called when network interface or reachability changes.
|
||||||
static void networkReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkConnectionFlags flags, void *object) {
|
static void networkReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkConnectionFlags flags, void *object) {
|
||||||
if (_reachability == NULL)
|
if (_reachability == NULL) return;
|
||||||
return;
|
|
||||||
_isReachable = [FeedDownload hasConnectivity:flags];
|
_isReachable = [FeedDownload hasConnectivity:flags];
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationNetworkStatusChanged
|
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationNetworkStatusChanged object:@(_isReachable)];
|
||||||
object:[NSNumber numberWithBool:_isReachable]];
|
if (_isReachable) {
|
||||||
if (_isReachable) {
|
|
||||||
NSLog(@"reachable");
|
NSLog(@"reachable");
|
||||||
|
[FeedDownload resumeUpdates];
|
||||||
} else {
|
} else {
|
||||||
NSLog(@"not reachable");
|
NSLog(@"not reachable");
|
||||||
|
[FeedDownload pauseUpdates];
|
||||||
}
|
}
|
||||||
// schedule regardless of state (if not reachable timer will be canceled)
|
|
||||||
[FeedDownload scheduleNextUpdateForced:NO];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @return @c YES if network connection established.
|
/// @return @c YES if network connection established.
|
||||||
|
|||||||
@@ -181,7 +181,6 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
|||||||
FeedGroup *fg = [children objectAtIndex:i].representedObject;
|
FeedGroup *fg = [children objectAtIndex:i].representedObject;
|
||||||
if (fg.sortIndex != (int32_t)i)
|
if (fg.sortIndex != (int32_t)i)
|
||||||
fg.sortIndex = (int32_t)i;
|
fg.sortIndex = (int32_t)i;
|
||||||
NSLog(@"%@ - %d", fg.name, fg.sortIndex);
|
|
||||||
[fg iterateSorted:NO overDescendantFeeds:^(Feed *feed, BOOL *cancel) {
|
[fg iterateSorted:NO overDescendantFeeds:^(Feed *feed, BOOL *cancel) {
|
||||||
[feed calculateAndSetIndexPathString];
|
[feed calculateAndSetIndexPathString];
|
||||||
}];
|
}];
|
||||||
@@ -259,13 +258,14 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
|||||||
BOOL isFeed = (fg.typ == FEED);
|
BOOL isFeed = (fg.typ == FEED);
|
||||||
BOOL isSeperator = (fg.typ == SEPARATOR);
|
BOOL isSeperator = (fg.typ == SEPARATOR);
|
||||||
BOOL isRefreshColumn = [tableColumn.identifier isEqualToString:@"RefreshColumn"];
|
BOOL isRefreshColumn = [tableColumn.identifier isEqualToString:@"RefreshColumn"];
|
||||||
|
BOOL refreshDisabled = (!isFeed || fg.refreshStr.length == 0 || [fg.refreshStr characterAtIndex:0] == '0');
|
||||||
|
|
||||||
NSString *cellIdent = (isRefreshColumn ? @"cellRefresh" : (isSeperator ? @"cellSeparator" : @"cellFeed"));
|
NSString *cellIdent = (isRefreshColumn ? @"cellRefresh" : (isSeperator ? @"cellSeparator" : @"cellFeed"));
|
||||||
// owner is nil to prohibit repeated awakeFromNib calls
|
// owner is nil to prohibit repeated awakeFromNib calls
|
||||||
NSTableCellView *cellView = [self.outlineView makeViewWithIdentifier:cellIdent owner:nil];
|
NSTableCellView *cellView = [self.outlineView makeViewWithIdentifier:cellIdent owner:nil];
|
||||||
|
|
||||||
if (isRefreshColumn) {
|
if (isRefreshColumn) {
|
||||||
cellView.textField.stringValue = (isFeed && fg.refreshStr.length > 0 ? fg.refreshStr : @"");
|
cellView.textField.stringValue = (refreshDisabled ? (isFeed ? @"--" : @"") : fg.refreshStr);
|
||||||
} else if (isSeperator) {
|
} else if (isSeperator) {
|
||||||
return cellView; // the refresh cell is already skipped with the above if condition
|
return cellView; // the refresh cell is already skipped with the above if condition
|
||||||
} else {
|
} else {
|
||||||
@@ -281,10 +281,8 @@ static NSString *dragNodeType = @"baRSS-feed-drag";
|
|||||||
cellView.imageView.image = defaultRSSIcon;
|
cellView.imageView.image = defaultRSSIcon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isFeed) {// also for refresh column
|
// also for refresh column
|
||||||
BOOL feedDisbaled = (fg.refreshStr.length == 0 || [fg.refreshStr characterAtIndex:0] == '0');
|
cellView.textField.textColor = (isFeed && refreshDisabled ? [NSColor disabledControlTextColor] : [NSColor controlTextColor]);
|
||||||
cellView.textField.textColor = (feedDisbaled ? [NSColor disabledControlTextColor] : [NSColor controlTextColor]);
|
|
||||||
}
|
|
||||||
return cellView;
|
return cellView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
} else {
|
} else {
|
||||||
self.barItem.title = @"";
|
self.barItem.title = @"";
|
||||||
}
|
}
|
||||||
// BOOL hasNet = [FeedDownload isNetworkReachable];
|
// BOOL hasNet = [FeedDownload allowNetworkConnection];
|
||||||
if (self.unreadCountTotal > 0 && [UserPrefs defaultYES:@"tintMenuBarIcon"]) {
|
if (self.unreadCountTotal > 0 && [UserPrefs defaultYES:@"tintMenuBarIcon"]) {
|
||||||
self.barItem.image = [RSSIcon templateIcon:16 tint:[NSColor rssOrange]];
|
self.barItem.image = [RSSIcon templateIcon:16 tint:[NSColor rssOrange]];
|
||||||
} else {
|
} else {
|
||||||
@@ -281,13 +281,14 @@
|
|||||||
Insert default menu items for the main menu only. Like 'Pause Updates', 'Update all feeds', 'Preferences' and 'Quit'.
|
Insert default menu items for the main menu only. Like 'Pause Updates', 'Update all feeds', 'Preferences' and 'Quit'.
|
||||||
*/
|
*/
|
||||||
- (void)insertMainMenuHeader:(NSMenu*)menu {
|
- (void)insertMainMenuHeader:(NSMenu*)menu {
|
||||||
NSMenuItem *item1 = [NSMenuItem itemWithTitle:NSLocalizedString(@"Pause Updates", nil)
|
NSMenuItem *item1 = [NSMenuItem itemWithTitle:@"" action:@selector(pauseUpdates:) target:self tag:TagPauseUpdates];
|
||||||
action:@selector(pauseUpdates:) target:self tag:TagPauseUpdates];
|
|
||||||
NSMenuItem *item2 = [NSMenuItem itemWithTitle:NSLocalizedString(@"Update all feeds", nil)
|
NSMenuItem *item2 = [NSMenuItem itemWithTitle:NSLocalizedString(@"Update all feeds", nil)
|
||||||
action:@selector(updateAllFeeds:) target:self tag:TagUpdateFeed];
|
action:@selector(updateAllFeeds:) target:self tag:TagUpdateFeed];
|
||||||
|
item1.title = ([FeedDownload isPaused] ?
|
||||||
|
NSLocalizedString(@"Resume Updates", nil) : NSLocalizedString(@"Pause Updates", nil));
|
||||||
if ([UserPrefs defaultYES:@"globalUpdateAll"] == NO)
|
if ([UserPrefs defaultYES:@"globalUpdateAll"] == NO)
|
||||||
item2.hidden = YES;
|
item2.hidden = YES;
|
||||||
if (![FeedDownload isNetworkReachable])
|
if (![FeedDownload allowNetworkConnection])
|
||||||
item2.enabled = NO;
|
item2.enabled = NO;
|
||||||
[menu insertItem:item1 atIndex:0];
|
[menu insertItem:item1 atIndex:0];
|
||||||
[menu insertItem:item2 atIndex:1];
|
[menu insertItem:item2 atIndex:1];
|
||||||
@@ -325,22 +326,21 @@
|
|||||||
- (void)preferencesClosed:(id)sender {
|
- (void)preferencesClosed:(id)sender {
|
||||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowWillCloseNotification object:self.prefWindow.window];
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowWillCloseNotification object:self.prefWindow.window];
|
||||||
self.prefWindow = nil;
|
self.prefWindow = nil;
|
||||||
[FeedDownload scheduleNextUpdateForced:NO];
|
[FeedDownload scheduleUpdateForUpcomingFeeds];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Called when user clicks on 'Pause Updates' in the main menu (only).
|
Called when user clicks on 'Pause Updates' in the main menu (only).
|
||||||
*/
|
*/
|
||||||
- (void)pauseUpdates:(NSMenuItem*)sender {
|
- (void)pauseUpdates:(NSMenuItem*)sender {
|
||||||
NSLog(@"1pause");
|
[FeedDownload setPaused:![FeedDownload isPaused]];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Called when user clicks on 'Update all feeds' in the main menu (only).
|
Called when user clicks on 'Update all feeds' in the main menu (only).
|
||||||
*/
|
*/
|
||||||
- (void)updateAllFeeds:(NSMenuItem*)sender {
|
- (void)updateAllFeeds:(NSMenuItem*)sender {
|
||||||
// TODO: Disable 'update all' menu item during update?
|
[FeedDownload forceUpdateAllFeeds];
|
||||||
[FeedDownload scheduleNextUpdateForced:YES];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user