Separate FeedDownload into UpdateScheduler & WebFeed

This commit is contained in:
relikd
2019-08-11 20:23:32 +02:00
parent a1f191789d
commit 48578ea211
13 changed files with 317 additions and 228 deletions

View File

@@ -29,8 +29,9 @@
54A07A82220E723D00082C51 /* MapUnreadTotal.m in Sources */ = {isa = PBXBuildFile; fileRef = 54A07A81220E723D00082C51 /* MapUnreadTotal.m */; }; 54A07A82220E723D00082C51 /* MapUnreadTotal.m in Sources */ = {isa = PBXBuildFile; fileRef = 54A07A81220E723D00082C51 /* MapUnreadTotal.m */; };
54A2D63922EF81A4007C61F3 /* QLOPML.qlgenerator in CopyFiles */ = {isa = PBXBuildFile; fileRef = 54A2D63822EF8193007C61F3 /* QLOPML.qlgenerator */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 54A2D63922EF81A4007C61F3 /* QLOPML.qlgenerator in CopyFiles */ = {isa = PBXBuildFile; fileRef = 54A2D63822EF8193007C61F3 /* QLOPML.qlgenerator */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
54ACC28C21061B3C0020715F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28B21061B3C0020715F /* main.m */; }; 54ACC28C21061B3C0020715F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC28B21061B3C0020715F /* main.m */; };
54ACC29521061E270020715F /* FeedDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29421061E270020715F /* FeedDownload.m */; }; 54ACC29521061E270020715F /* UpdateScheduler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29421061E270020715F /* UpdateScheduler.m */; };
54ACC29821061FBA0020715F /* Preferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29721061FBA0020715F /* Preferences.m */; }; 54ACC29821061FBA0020715F /* Preferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 54ACC29721061FBA0020715F /* Preferences.m */; };
54AD4E0023005297000AE386 /* WebFeed.m in Sources */ = {isa = PBXBuildFile; fileRef = 54AD4DFF23005297000AE386 /* WebFeed.m */; };
54B51704226DC339006C1B29 /* ModalFeedEditView.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B51703226DC339006C1B29 /* ModalFeedEditView.m */; }; 54B51704226DC339006C1B29 /* ModalFeedEditView.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B51703226DC339006C1B29 /* ModalFeedEditView.m */; };
54B517072270E990006C1B29 /* NSView+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B517062270E92A006C1B29 /* NSView+Ext.m */; }; 54B517072270E990006C1B29 /* NSView+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B517062270E92A006C1B29 /* NSView+Ext.m */; };
54B749DA2204A85C0022CC6D /* BarStatusItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B749D92204A85C0022CC6D /* BarStatusItem.m */; }; 54B749DA2204A85C0022CC6D /* BarStatusItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 54B749D92204A85C0022CC6D /* BarStatusItem.m */; };
@@ -135,10 +136,12 @@
54ACC28321061B3B0020715F /* DBv1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DBv1.xcdatamodel; sourceTree = "<group>"; }; 54ACC28321061B3B0020715F /* DBv1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DBv1.xcdatamodel; sourceTree = "<group>"; };
54ACC28A21061B3C0020715F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 54ACC28A21061B3C0020715F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54ACC28B21061B3C0020715F /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; 54ACC28B21061B3C0020715F /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
54ACC29321061E270020715F /* FeedDownload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeedDownload.h; sourceTree = "<group>"; }; 54ACC29321061E270020715F /* UpdateScheduler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UpdateScheduler.h; sourceTree = "<group>"; };
54ACC29421061E270020715F /* FeedDownload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeedDownload.m; sourceTree = "<group>"; }; 54ACC29421061E270020715F /* UpdateScheduler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UpdateScheduler.m; sourceTree = "<group>"; };
54ACC29621061FBA0020715F /* Preferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Preferences.h; sourceTree = "<group>"; }; 54ACC29621061FBA0020715F /* Preferences.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Preferences.h; sourceTree = "<group>"; };
54ACC29721061FBA0020715F /* Preferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Preferences.m; sourceTree = "<group>"; }; 54ACC29721061FBA0020715F /* Preferences.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Preferences.m; sourceTree = "<group>"; };
54AD4DFE23005297000AE386 /* WebFeed.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WebFeed.h; sourceTree = "<group>"; };
54AD4DFF23005297000AE386 /* WebFeed.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WebFeed.m; sourceTree = "<group>"; };
54B51702226DC339006C1B29 /* ModalFeedEditView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ModalFeedEditView.h; sourceTree = "<group>"; }; 54B51702226DC339006C1B29 /* ModalFeedEditView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ModalFeedEditView.h; sourceTree = "<group>"; };
54B51703226DC339006C1B29 /* ModalFeedEditView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ModalFeedEditView.m; sourceTree = "<group>"; }; 54B51703226DC339006C1B29 /* ModalFeedEditView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ModalFeedEditView.m; sourceTree = "<group>"; };
54B517052270E8C6006C1B29 /* NSView+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSView+Ext.h"; sourceTree = "<group>"; }; 54B517052270E8C6006C1B29 /* NSView+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSView+Ext.h"; sourceTree = "<group>"; };
@@ -201,8 +204,6 @@
children = ( children = (
54209E922117325100F3B5EF /* DrawImage.h */, 54209E922117325100F3B5EF /* DrawImage.h */,
54209E932117325100F3B5EF /* DrawImage.m */, 54209E932117325100F3B5EF /* DrawImage.m */,
54ACC29321061E270020715F /* FeedDownload.h */,
54ACC29421061E270020715F /* FeedDownload.m */,
54BB048721FD2AB500C303A5 /* NSDate+Ext.h */, 54BB048721FD2AB500C303A5 /* NSDate+Ext.h */,
54BB048821FD2AB500C303A5 /* NSDate+Ext.m */, 54BB048821FD2AB500C303A5 /* NSDate+Ext.m */,
54B517052270E8C6006C1B29 /* NSView+Ext.h */, 54B517052270E8C6006C1B29 /* NSView+Ext.h */,
@@ -290,6 +291,7 @@
544936F721F1E51E00DEE9AA /* Helper */, 544936F721F1E51E00DEE9AA /* Helper */,
541A90EF21257D4F002680A6 /* Status Bar Menu */, 541A90EF21257D4F002680A6 /* Status Bar Menu */,
54A07A8322105E0800082C51 /* Core Data */, 54A07A8322105E0800082C51 /* Core Data */,
54AD4E04230084FD000AE386 /* Feed Import */,
546FC44D2118B357007CC3A3 /* Preferences */, 546FC44D2118B357007CC3A3 /* Preferences */,
54ACC28A21061B3C0020715F /* Info.plist */, 54ACC28A21061B3C0020715F /* Info.plist */,
54F7101322EE0DDA006985D1 /* Artwork */, 54F7101322EE0DDA006985D1 /* Artwork */,
@@ -299,6 +301,19 @@
path = baRSS; path = baRSS;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
54AD4E04230084FD000AE386 /* Feed Import */ = {
isa = PBXGroup;
children = (
54ACC29321061E270020715F /* UpdateScheduler.h */,
54ACC29421061E270020715F /* UpdateScheduler.m */,
54AD4DFE23005297000AE386 /* WebFeed.h */,
54AD4DFF23005297000AE386 /* WebFeed.m */,
54F6025B21C1D4170006D338 /* OpmlFile.h */,
54F6025C21C1D4170006D338 /* OpmlFile.m */,
);
path = "Feed Import";
sourceTree = "<group>";
};
54D857CF228022AB001BA1C8 /* General Tab */ = { 54D857CF228022AB001BA1C8 /* General Tab */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -339,8 +354,6 @@
546FC43C21188AD5007CC3A3 /* SettingsFeeds.m */, 546FC43C21188AD5007CC3A3 /* SettingsFeeds.m */,
54D55D7122E624CD00057B98 /* SettingsFeeds+DragDrop.h */, 54D55D7122E624CD00057B98 /* SettingsFeeds+DragDrop.h */,
54D55D7222E624CD00057B98 /* SettingsFeeds+DragDrop.m */, 54D55D7222E624CD00057B98 /* SettingsFeeds+DragDrop.m */,
54F6025B21C1D4170006D338 /* OpmlFile.h */,
54F6025C21C1D4170006D338 /* OpmlFile.m */,
54E8831D211B509D00064188 /* ModalFeedEdit.h */, 54E8831D211B509D00064188 /* ModalFeedEdit.h */,
54E8831E211B509D00064188 /* ModalFeedEdit.m */, 54E8831E211B509D00064188 /* ModalFeedEdit.m */,
5478DF02225A7AE200D30C64 /* SettingsFeedsView.h */, 5478DF02225A7AE200D30C64 /* SettingsFeedsView.h */,
@@ -491,6 +504,7 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
54AD4E0023005297000AE386 /* WebFeed.m in Sources */,
54B51704226DC339006C1B29 /* ModalFeedEditView.m in Sources */, 54B51704226DC339006C1B29 /* ModalFeedEditView.m in Sources */,
546A6A2C22C584AF0034E806 /* SettingsAppearanceView.m in Sources */, 546A6A2C22C584AF0034E806 /* SettingsAppearanceView.m in Sources */,
54E9CF32225914300023696F /* SettingsAbout.m in Sources */, 54E9CF32225914300023696F /* SettingsAbout.m in Sources */,
@@ -500,7 +514,7 @@
544B011D2114EE9100386E5C /* AppHook.m in Sources */, 544B011D2114EE9100386E5C /* AppHook.m in Sources */,
546FC44321189975007CC3A3 /* SettingsGeneral.m in Sources */, 546FC44321189975007CC3A3 /* SettingsGeneral.m in Sources */,
546A6A2922C583390034E806 /* SettingsGeneralView.m in Sources */, 546A6A2922C583390034E806 /* SettingsGeneralView.m in Sources */,
54ACC29521061E270020715F /* FeedDownload.m in Sources */, 54ACC29521061E270020715F /* UpdateScheduler.m in Sources */,
54BB048921FD2AB500C303A5 /* NSDate+Ext.m in Sources */, 54BB048921FD2AB500C303A5 /* NSDate+Ext.m in Sources */,
5477D34E21233C62002BA27F /* FeedGroup+Ext.m in Sources */, 5477D34E21233C62002BA27F /* FeedGroup+Ext.m in Sources */,
5478DF04225A7AE200D30C64 /* SettingsFeedsView.m in Sources */, 5478DF04225A7AE200D30C64 /* SettingsFeedsView.m in Sources */,

View File

@@ -22,7 +22,8 @@
#import "AppHook.h" #import "AppHook.h"
#import "BarStatusItem.h" #import "BarStatusItem.h"
#import "FeedDownload.h" #import "WebFeed.h"
#import "UpdateScheduler.h"
#import "Preferences.h" #import "Preferences.h"
#import "DrawImage.h" #import "DrawImage.h"
#import "SettingsFeeds+DragDrop.h" #import "SettingsFeeds+DragDrop.h"
@@ -52,15 +53,15 @@
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[_statusItem asyncReloadUnreadCount]; [_statusItem asyncReloadUnreadCount];
[FeedDownload registerNetworkChangeNotification]; // will call update scheduler [UpdateScheduler registerNetworkChangeNotification]; // will call update scheduler
if ([StoreCoordinator isEmpty]) { if ([StoreCoordinator isEmpty]) {
[_statusItem showWelcomeMessage]; [_statusItem showWelcomeMessage];
[FeedDownload autoDownloadAndParseUpdateURL]; [WebFeed autoDownloadAndParseUpdateURL];
} }
} }
- (void)applicationWillTerminate:(NSNotification *)aNotification { - (void)applicationWillTerminate:(NSNotification *)aNotification {
[FeedDownload unregisterNetworkChangeNotification]; [UpdateScheduler unregisterNetworkChangeNotification];
} }
- (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent {
@@ -72,7 +73,7 @@
url = [url substringFromIndex:2]; url = [url substringFromIndex:2];
} }
if ([scheme isEqualToString:@"feed"]) { if ([scheme isEqualToString:@"feed"]) {
[FeedDownload autoDownloadAndParseURL:url addAnyway:NO modify:nil]; [WebFeed autoDownloadAndParseURL:url addAnyway:NO modify:nil];
} }
} }
@@ -107,7 +108,7 @@
- (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 scheduleUpdateForUpcomingFeeds]; [UpdateScheduler scheduleNextFeed];
} }
/// Close previous preferences window and re-open at the same position (will drop undo manager stack!) /// Close previous preferences window and re-open at the same position (will drop undo manager stack!)

View File

@@ -24,11 +24,11 @@
#import "FeedMeta+Ext.h" #import "FeedMeta+Ext.h"
#import "FeedGroup+Ext.h" #import "FeedGroup+Ext.h"
#import "StoreCoordinator.h" #import "StoreCoordinator.h"
#import "FeedDownload.h"
#import "Constants.h" #import "Constants.h"
#import "NSDate+Ext.h" #import "NSDate+Ext.h"
#import "NSView+Ext.h" #import "NSView+Ext.h"
#import <RSXML/RSXML.h>
#pragma mark - Helper #pragma mark - Helper

View File

@@ -0,0 +1,38 @@
//
// The MIT License (MIT)
// Copyright (c) 2019 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import <Cocoa/Cocoa.h>
@interface UpdateScheduler : NSObject
@property (class, readonly) NSDate *dateScheduled;
@property (class, readonly) BOOL allowNetworkConnection;
@property (class, readonly) BOOL isUpdating;
@property (class, setter=setPaused:) BOOL isPaused;
+ (void)beginUpdate;
+ (void)endUpdate;
+ (void)scheduleNextFeed;
+ (void)forceUpdateAllFeeds;
// Register for network change notifications
+ (void)registerNetworkChangeNotification;
+ (void)unregisterNetworkChangeNotification;
@end

View File

@@ -0,0 +1,210 @@
//
// The MIT License (MIT)
// Copyright (c) 2019 Oleg Geier
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "UpdateScheduler.h"
#import "WebFeed.h"
#import "Constants.h"
#import "StoreCoordinator.h"
#import <SystemConfiguration/SystemConfiguration.h>
static NSTimer *_timer;
static SCNetworkReachabilityRef _reachability = NULL;
static BOOL _isReachable = NO;
static BOOL _isUpdating = NO;
static BOOL _updatePaused = NO;
static BOOL _nextUpdateIsForced = NO;
@implementation UpdateScheduler
#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); }
/// @return @c YES if batch update is running
+ (BOOL)isUpdating { return _isUpdating; }
/// @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 scheduleNextFeed];
}
/// Set @c isUpdating @c = @c YES
+ (void)beginUpdate { _isUpdating = YES; }
/// Set @c isUpdating @c = @c NO
+ (void)endUpdate { _isUpdating = NO; }
#pragma mark - Update Feed Timer
/**
Get date of next up feed and start the timer.
*/
+ (void)scheduleNextFeed {
if (![self allowNetworkConnection]) // timer will restart once connection exists
return;
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];
}
/**
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.05]];
}
/**
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 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 distantFuture];
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 {
#ifdef DEBUG
NSLog(@"fired");
#endif
BOOL updateAll = _nextUpdateIsForced;
_nextUpdateIsForced = NO;
if (updateAll)
[WebFeed setRequestsAreUrgent:YES];
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
NSArray<Feed*> *list = [StoreCoordinator getListOfFeedsThatNeedUpdate:updateAll inContext:moc];
//NSAssert(list.count > 0, @"ERROR: Something went wrong, timer fired too early.");
if (![self allowNetworkConnection]) {
[WebFeed setRequestsAreUrgent:NO];
[moc reset];
return;
}
[WebFeed batchDownloadFeeds:list favicons:updateAll showErrorAlert:NO finally:^{
[WebFeed setRequestsAreUrgent:NO];
[StoreCoordinator saveContext:moc andParent:YES]; // save parents too ...
[moc reset];
[self resumeUpdates]; // always reset the timer
}];
}
#pragma mark - Network Connection & Reachability
/// Set callback on @c self to listen for network reachability changes.
+ (void)registerNetworkChangeNotification {
// https://stackoverflow.com/questions/11240196/notification-when-wifi-connected-os-x
if (_reachability != NULL) return;
_reachability = SCNetworkReachabilityCreateWithName(NULL, "1.1.1.1");
if (_reachability == NULL) return;
// If reachability information is available now, we don't get a callback later
SCNetworkConnectionFlags flags;
if (SCNetworkReachabilityGetFlags(_reachability, &flags))
networkReachabilityCallback(_reachability, flags, NULL);
if (!SCNetworkReachabilitySetCallback(_reachability, networkReachabilityCallback, NULL) ||
!SCNetworkReachabilityScheduleWithRunLoop(_reachability, [[NSRunLoop currentRunLoop] getCFRunLoop], kCFRunLoopCommonModes))
{
CFRelease(_reachability);
_reachability = NULL;
}
}
/// Remove @c self callback (network reachability changes).
+ (void)unregisterNetworkChangeNotification {
if (_reachability != NULL) {
SCNetworkReachabilitySetCallback(_reachability, nil, nil);
SCNetworkReachabilitySetDispatchQueue(_reachability, nil);
CFRelease(_reachability);
_reachability = NULL;
}
}
/// Called when network interface or reachability changes.
static void networkReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkConnectionFlags flags, void *object) {
if (_reachability == NULL) return;
_isReachable = [UpdateScheduler hasConnectivity:flags];
PostNotification(kNotificationNetworkStatusChanged, @(_isReachable));
if (_isReachable) {
[UpdateScheduler resumeUpdates];
} else {
[UpdateScheduler pauseUpdates];
}
}
/// @return @c YES if network connection established.
+ (BOOL)hasConnectivity:(SCNetworkReachabilityFlags)flags {
if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
return NO;
if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
return YES;
if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0 &&
((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0 ||
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
return YES; // no-intervention AND ( on-demand OR on-traffic )
return NO;
}
@end

View File

@@ -20,23 +20,13 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
#import <Cocoa/Cocoa.h> #import <Foundation/Foundation.h>
#import <RSXML/RSXML.h> #import <RSXML/RSXML.h>
@class Feed; @class Feed;
@interface FeedDownload : NSObject @interface WebFeed : NSObject
@property (class, readonly) NSDate *dateScheduled; + (void)setRequestsAreUrgent:(BOOL)flag;
@property (class, readonly) BOOL allowNetworkConnection;
@property (class, readonly) BOOL isUpdating;
@property (class, setter=setPaused:) BOOL isPaused;
// Register for network change notifications
+ (void)registerNetworkChangeNotification;
+ (void)unregisterNetworkChangeNotification;
// Scheduling
+ (void)scheduleUpdateForUpcomingFeeds;
+ (void)forceUpdateAllFeeds;
// Downloading // Downloading
+ (void)newFeed:(NSString *)urlStr askUser:(nonnull NSString*(^)(RSHTMLMetadata *meta))askUser block:(nonnull void(^)(RSParsedFeed *parsed, NSError *error, NSHTTPURLResponse *response))block; + (void)newFeed:(NSString *)urlStr askUser:(nonnull NSString*(^)(RSHTMLMetadata *meta))askUser block:(nonnull void(^)(RSParsedFeed *parsed, NSError *error, NSHTTPURLResponse *response))block;
+ (void)autoDownloadAndParseURL:(NSString*)urlStr addAnyway:(BOOL)flag modify:(nullable void(^)(Feed *feed))block; + (void)autoDownloadAndParseURL:(NSString*)urlStr addAnyway:(BOOL)flag modify:(nullable void(^)(Feed *feed))block;

View File

@@ -20,7 +20,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
#import "FeedDownload.h" #import "WebFeed.h"
#import "UpdateScheduler.h"
#import "Constants.h" #import "Constants.h"
#import "StoreCoordinator.h" #import "StoreCoordinator.h"
#import "Feed+Ext.h" #import "Feed+Ext.h"
@@ -28,124 +29,16 @@
#import "FeedGroup+Ext.h" #import "FeedGroup+Ext.h"
#import "NSDate+Ext.h" #import "NSDate+Ext.h"
#import <SystemConfiguration/SystemConfiguration.h> static BOOL _requestsAreUrgent = NO;
static NSTimer *_timer;
static SCNetworkReachabilityRef _reachability = NULL;
static BOOL _isReachable = NO;
static BOOL _isUpdating = NO;
static BOOL _updatePaused = NO;
static BOOL _nextUpdateIsForced = NO;
@implementation FeedDownload @implementation WebFeed
/// Disables @c NSURLNetworkServiceTypeBackground (ideally only temporarily)
+ (void)setRequestsAreUrgent:(BOOL)flag { _requestsAreUrgent = flag; }
#pragma mark - User Interaction - #pragma mark - Request Generator
/// @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); }
/// @return @c YES if batch update is running
+ (BOOL)isUpdating { return _isUpdating; }
/// @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 = 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];
}
/**
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.05]];
}
/**
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 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 distantFuture];
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 {
#ifdef DEBUG
NSLog(@"fired");
#endif
BOOL updateAll = _nextUpdateIsForced;
_nextUpdateIsForced = NO;
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
NSArray<Feed*> *list = [StoreCoordinator getListOfFeedsThatNeedUpdate:updateAll inContext:moc];
//NSAssert(list.count > 0, @"ERROR: Something went wrong, timer fired too early.");
if (![self allowNetworkConnection]) {
[moc reset];
return;
}
[self batchDownloadFeeds:list favicons:updateAll showErrorAlert:NO finally:^{
[StoreCoordinator saveContext:moc andParent:YES]; // save parents too ...
[moc reset];
[self resumeUpdates]; // always reset the timer
}];
}
#pragma mark - Request Generator -
/// @return Base URL part. E.g., https://stackoverflow.com/a/15897956/10616114 ==> https://stackoverflow.com/ /// @return Base URL part. E.g., https://stackoverflow.com/a/15897956/10616114 ==> https://stackoverflow.com/
@@ -176,7 +69,7 @@ static BOOL _nextUpdateIsForced = NO;
else if (meta.modified.length > 0) else if (meta.modified.length > 0)
[req setValue:meta.modified forHTTPHeaderField:@"If-Modified-Since"]; [req setValue:meta.modified forHTTPHeaderField:@"If-Modified-Since"];
} }
if (!_nextUpdateIsForced) // any request that is not forced, is a background update if (!_requestsAreUrgent) // any request that is not forced, is a background update
req.networkServiceType = NSURLNetworkServiceTypeBackground; req.networkServiceType = NSURLNetworkServiceTypeBackground;
return req; return req;
} }
@@ -229,7 +122,7 @@ static BOOL _nextUpdateIsForced = NO;
} }
#pragma mark - Download RSS Feed - #pragma mark - Download RSS Feed
/** /**
@@ -264,7 +157,7 @@ static BOOL _nextUpdateIsForced = NO;
/** /**
Perform feed download request from URL alone. Not updating any @c Feed item. Perform feed download request from URL alone. Not updating any @c Feed item.
@note @c askUser will not be called if url is XML already. @note @c askUser will not be called if url is XML already.
@param urlStr XML URL or HTTP URL that will be parsed to find feed URLs. @param urlStr XML URL or HTTP URL that will be parsed to find feed URLs.
@@ -357,7 +250,7 @@ static BOOL _nextUpdateIsForced = NO;
[moc reset]; [moc reset];
if (successful) { if (successful) {
PostNotification(kNotificationGroupInserted, f.group.objectID); PostNotification(kNotificationGroupInserted, f.group.objectID);
[self scheduleUpdateForUpcomingFeeds]; [UpdateScheduler scheduleNextFeed];
} }
}]; }];
} }
@@ -391,14 +284,14 @@ static BOOL _nextUpdateIsForced = NO;
/** /**
Start download of all feeds in list. Either with or without favicons. Start download of all feeds in list. Either with or without favicons.
@param list Download list using @c feed.meta.url as download url. (while reusing etag and modified headers) @param list Download list using @c feed.meta.url as download url. (while reusing etag and modified headers)
@param fav If @c YES continue with favicon download after xml download finished. @param fav If @c YES continue with favicon download after xml download finished.
@param alert If @c YES display Error Popup to user. @param alert If @c YES display Error Popup to user.
@param block Called after all downloads finished. @param block Called after all downloads finished.
*/ */
+ (void)batchDownloadFeeds:(NSArray<Feed*> *)list favicons:(BOOL)fav showErrorAlert:(BOOL)alert finally:(nullable os_block_t)block { + (void)batchDownloadFeeds:(NSArray<Feed*> *)list favicons:(BOOL)fav showErrorAlert:(BOOL)alert finally:(nullable os_block_t)block {
_isUpdating = YES; [UpdateScheduler beginUpdate];
PostNotification(kNotificationBackgroundUpdateInProgress, @(list.count)); PostNotification(kNotificationBackgroundUpdateInProgress, @(list.count));
dispatch_group_t group = dispatch_group_create(); dispatch_group_t group = dispatch_group_create();
for (Feed *f in list) { for (Feed *f in list) {
@@ -409,13 +302,13 @@ static BOOL _nextUpdateIsForced = NO;
} }
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if (block) block(); if (block) block();
_isUpdating = NO; [UpdateScheduler endUpdate];
PostNotification(kNotificationBackgroundUpdateInProgress, @(0)); PostNotification(kNotificationBackgroundUpdateInProgress, @(0));
}); });
} }
#pragma mark - Download Favicon - #pragma mark - Download Favicon
/** /**
@@ -457,7 +350,7 @@ static BOOL _nextUpdateIsForced = NO;
}]; }];
} }
/// Download html page and parse all icon urls. Starting a successive request on the url of the smallest icon. /// Download html page and parse all icon urls. Starting a successive request on the favicon url.
+ (void)downloadFaviconByParsingHTML:(NSString*)hostURL finished:(void(^)(NSImage * _Nullable img))block { + (void)downloadFaviconByParsingHTML:(NSString*)hostURL finished:(void(^)(NSImage * _Nullable img))block {
[self asyncRequest:[self newRequestURL:hostURL] block:^(NSData * _Nullable htmlData, NSError * _Nullable error, NSHTTPURLResponse *response) { [self asyncRequest:[self newRequestURL:hostURL] block:^(NSData * _Nullable htmlData, NSError * _Nullable error, NSHTTPURLResponse *response) {
if (htmlData) { if (htmlData) {
@@ -524,61 +417,4 @@ static BOOL _nextUpdateIsForced = NO;
}]; }];
} }
#pragma mark - Network Connection & Reachability -
/// Set callback on @c self to listen for network reachability changes.
+ (void)registerNetworkChangeNotification {
// https://stackoverflow.com/questions/11240196/notification-when-wifi-connected-os-x
if (_reachability != NULL) return;
_reachability = SCNetworkReachabilityCreateWithName(NULL, "1.1.1.1");
if (_reachability == NULL) return;
// If reachability information is available now, we don't get a callback later
SCNetworkConnectionFlags flags;
if (SCNetworkReachabilityGetFlags(_reachability, &flags))
networkReachabilityCallback(_reachability, flags, NULL);
if (!SCNetworkReachabilitySetCallback(_reachability, networkReachabilityCallback, NULL) ||
!SCNetworkReachabilityScheduleWithRunLoop(_reachability, [[NSRunLoop currentRunLoop] getCFRunLoop], kCFRunLoopCommonModes))
{
CFRelease(_reachability);
_reachability = NULL;
}
}
/// Remove @c self callback (network reachability changes).
+ (void)unregisterNetworkChangeNotification {
if (_reachability != NULL) {
SCNetworkReachabilitySetCallback(_reachability, nil, nil);
SCNetworkReachabilitySetDispatchQueue(_reachability, nil);
CFRelease(_reachability);
_reachability = NULL;
}
}
/// Called when network interface or reachability changes.
static void networkReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkConnectionFlags flags, void *object) {
if (_reachability == NULL) return;
_isReachable = [FeedDownload hasConnectivity:flags];
PostNotification(kNotificationNetworkStatusChanged, @(_isReachable));
if (_isReachable) {
[FeedDownload resumeUpdates];
} else {
[FeedDownload pauseUpdates];
}
}
/// @return @c YES if network connection established.
+ (BOOL)hasConnectivity:(SCNetworkReachabilityFlags)flags {
if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
return NO;
if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
return YES;
if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0 &&
((flags & kSCNetworkReachabilityFlagsConnectionOnDemand) != 0 ||
(flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
return YES; // no-intervention AND ( on-demand OR on-traffic )
return NO;
}
@end @end

View File

@@ -60,7 +60,7 @@
</dict> </dict>
</array> </array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>10180</string> <string>10198</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

@@ -21,7 +21,7 @@
// SOFTWARE. // SOFTWARE.
#import "ModalFeedEdit.h" #import "ModalFeedEdit.h"
#import "FeedDownload.h" #import "WebFeed.h"
#import "StoreCoordinator.h" #import "StoreCoordinator.h"
#import "Feed+Ext.h" #import "Feed+Ext.h"
#import "FeedMeta+Ext.h" #import "FeedMeta+Ext.h"
@@ -158,8 +158,8 @@
if (self.modalSheet.didCloseAndCancel) if (self.modalSheet.didCloseAndCancel)
return; return;
[self preDownload]; [self preDownload];
[FeedDownload newFeed:self.previousURL askUser:^NSString *(RSHTMLMetadata *meta) { [WebFeed newFeed:self.previousURL askUser:^NSString *(RSHTMLMetadata *meta) {
self.faviconURL = [FeedDownload faviconUrlForMetadata:meta]; // we can re-use favicon url if we find one self.faviconURL = [WebFeed faviconUrlForMetadata:meta]; // we can re-use favicon url if we find one
return [self letUserChooseXmlUrlFromList:meta.feedLinks]; return [self letUserChooseXmlUrlFromList:meta.feedLinks];
} block:^(RSParsedFeed *result, NSError *error, NSHTTPURLResponse* response) { } block:^(RSParsedFeed *result, NSError *error, NSHTTPURLResponse* response) {
if (self.modalSheet.didCloseAndCancel) if (self.modalSheet.didCloseAndCancel)
@@ -239,7 +239,7 @@
self.faviconURL = self.feedResult.link; self.faviconURL = self.feedResult.link;
if (self.faviconURL.length == 0) if (self.faviconURL.length == 0)
self.faviconURL = responseURL; self.faviconURL = responseURL;
[FeedDownload downloadFavicon:self.faviconURL finished:^(NSImage * _Nullable img) { [WebFeed downloadFavicon:self.faviconURL finished:^(NSImage * _Nullable img) {
if (self.modalSheet.didCloseAndCancel) if (self.modalSheet.didCloseAndCancel)
return; return;
self.view.favicon.image = img; self.view.favicon.image = img;

View File

@@ -23,7 +23,7 @@
#import "SettingsFeeds+DragDrop.h" #import "SettingsFeeds+DragDrop.h"
#import "StoreCoordinator.h" #import "StoreCoordinator.h"
#import "Constants.h" #import "Constants.h"
#import "FeedDownload.h" #import "WebFeed.h"
#import "FeedGroup+Ext.h" #import "FeedGroup+Ext.h"
// Pasteboard type used during internal row reordering // Pasteboard type used during internal row reordering
@@ -160,7 +160,7 @@ const NSPasteboardType dragReorder = @"de.relikd.baRSS.drag-reorder";
[StoreCoordinator saveContext:moc andParent:YES]; [StoreCoordinator saveContext:moc andParent:YES];
if (selection.count > 0) if (selection.count > 0)
[self.dataStore setSelectionIndexPaths:selection]; [self.dataStore setSelectionIndexPaths:selection];
[FeedDownload batchDownloadFeeds:feedsList favicons:YES showErrorAlert:YES finally:^{ [WebFeed batchDownloadFeeds:feedsList favicons:YES showErrorAlert:YES finally:^{
[self endCoreDataChangeUndoEmpty:NO forceUndo:NO]; [self endCoreDataChangeUndoEmpty:NO forceUndo:NO];
[self someDatesChangedScheduleUpdateTimer]; [self someDatesChangedScheduleUpdateTimer];
}]; }];

View File

@@ -25,7 +25,7 @@
#import "StoreCoordinator.h" #import "StoreCoordinator.h"
#import "ModalFeedEdit.h" #import "ModalFeedEdit.h"
#import "FeedGroup+Ext.h" #import "FeedGroup+Ext.h"
#import "FeedDownload.h" #import "UpdateScheduler.h"
#import "SettingsFeedsView.h" #import "SettingsFeedsView.h"
#import "NSDate+Ext.h" #import "NSDate+Ext.h"
@@ -65,7 +65,7 @@
self.timerStatusInfo = [NSTimer timerWithTimeInterval:NSTimeIntervalSince1970 target:self selector:@selector(keepTimerRunning) userInfo:nil repeats:YES]; self.timerStatusInfo = [NSTimer timerWithTimeInterval:NSTimeIntervalSince1970 target:self selector:@selector(keepTimerRunning) 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 // start spinner if update is in progress when preferences open
[self activateSpinner:([FeedDownload isUpdating] ? -1 : 0)]; [self activateSpinner:([UpdateScheduler isUpdating] ? -1 : 0)];
} }
/// Timer cleanup /// Timer cleanup
@@ -147,7 +147,7 @@
/// Query core data for next update date and set bottom status message /// Query core data for next update date and set bottom status message
- (void)someDatesChangedScheduleUpdateTimer { - (void)someDatesChangedScheduleUpdateTimer {
[FeedDownload scheduleUpdateForUpcomingFeeds]; [UpdateScheduler scheduleNextFeed];
[self.timerStatusInfo fire]; [self.timerStatusInfo fire];
} }
@@ -174,7 +174,7 @@
/// Callback method to update status info. Will be called more often when interval is getting shorter. /// Callback method to update status info. Will be called more often when interval is getting shorter.
- (void)keepTimerRunning { - (void)keepTimerRunning {
NSDate *date = [FeedDownload dateScheduled]; NSDate *date = [UpdateScheduler dateScheduled];
if (date) { if (date) {
double nextFire = fabs(date.timeIntervalSinceNow); double nextFire = fabs(date.timeIntervalSinceNow);
if (nextFire > 1e9) { // distance future, over 31 years if (nextFire > 1e9) { // distance future, over 31 years
@@ -280,9 +280,9 @@
/// Returning @c NO will result in a Action-Not-Available-Buzzer sound /// Returning @c NO will result in a Action-Not-Available-Buzzer sound
- (BOOL)respondsToSelector:(SEL)aSelector { - (BOOL)respondsToSelector:(SEL)aSelector {
if (aSelector == @selector(undo:)) if (aSelector == @selector(undo:))
return [self.undoManager canUndo] && self.undoManager.groupingLevel == 0 && ![FeedDownload isUpdating]; return [self.undoManager canUndo] && self.undoManager.groupingLevel == 0 && ![UpdateScheduler isUpdating];
if (aSelector == @selector(redo:)) if (aSelector == @selector(redo:))
return [self.undoManager canRedo] && self.undoManager.groupingLevel == 0 && ![FeedDownload isUpdating]; return [self.undoManager canRedo] && self.undoManager.groupingLevel == 0 && ![UpdateScheduler isUpdating];
if (aSelector == @selector(copy:) || aSelector == @selector(remove:)) if (aSelector == @selector(copy:) || aSelector == @selector(remove:))
return ([self userSelectionFirst] != nil); return ([self userSelectionFirst] != nil);
if (aSelector == @selector(editSelectedItem)) { if (aSelector == @selector(editSelectedItem)) {

View File

@@ -22,7 +22,7 @@
#import "BarStatusItem.h" #import "BarStatusItem.h"
#import "Constants.h" #import "Constants.h"
#import "FeedDownload.h" #import "UpdateScheduler.h"
#import "StoreCoordinator.h" #import "StoreCoordinator.h"
#import "UserPrefs.h" #import "UserPrefs.h"
#import "BarMenu.h" #import "BarMenu.h"
@@ -117,7 +117,7 @@
/// Update menu bar icon and text according to unread count and user preferences. /// Update menu bar icon and text according to unread count and user preferences.
- (void)updateBarIcon { - (void)updateBarIcon {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
BOOL hasNet = [FeedDownload allowNetworkConnection]; BOOL hasNet = [UpdateScheduler allowNetworkConnection];
BOOL tint = (self.unreadCountTotal > 0 && hasNet && [UserPrefs defaultYES:@"globalTintMenuBarIcon"]); BOOL tint = (self.unreadCountTotal > 0 && hasNet && [UserPrefs defaultYES:@"globalTintMenuBarIcon"]);
self.statusItem.image = [NSImage imageNamed:(hasNet ? RSSImageMenuBarIconActive : RSSImageMenuBarIconPaused)]; self.statusItem.image = [NSImage imageNamed:(hasNet ? RSSImageMenuBarIconActive : RSSImageMenuBarIconPaused)];
self.statusItem.image.template = !tint; self.statusItem.image.template = !tint;
@@ -169,13 +169,13 @@
// 'Pause Updates' item // 'Pause Updates' item
NSMenuItem *pause = [menu addItemWithTitle:NSLocalizedString(@"Pause Updates", nil) action:@selector(pauseUpdates) keyEquivalent:@""]; NSMenuItem *pause = [menu addItemWithTitle:NSLocalizedString(@"Pause Updates", nil) action:@selector(pauseUpdates) keyEquivalent:@""];
pause.target = self; pause.target = self;
if ([FeedDownload isPaused]) if ([UpdateScheduler isPaused])
pause.title = NSLocalizedString(@"Resume Updates", nil); pause.title = NSLocalizedString(@"Resume Updates", nil);
// 'Update all feeds' item // 'Update all feeds' item
if ([UserPrefs defaultYES:@"globalUpdateAll"]) { if ([UserPrefs defaultYES:@"globalUpdateAll"]) {
NSMenuItem *updateAll = [menu addItemWithTitle:NSLocalizedString(@"Update all feeds", nil) action:@selector(updateAllFeeds) keyEquivalent:@""]; NSMenuItem *updateAll = [menu addItemWithTitle:NSLocalizedString(@"Update all feeds", nil) action:@selector(updateAllFeeds) keyEquivalent:@""];
updateAll.target = self; updateAll.target = self;
updateAll.enabled = [FeedDownload allowNetworkConnection]; updateAll.enabled = [UpdateScheduler allowNetworkConnection];
self.updateAllItem = updateAll; self.updateAllItem = updateAll;
} }
// Separator between main header and default header // Separator between main header and default header
@@ -184,14 +184,14 @@
/// Called when user clicks on 'Pause Updates' (main menu only). /// Called when user clicks on 'Pause Updates' (main menu only).
- (void)pauseUpdates { - (void)pauseUpdates {
[FeedDownload setPaused:![FeedDownload isPaused]]; [UpdateScheduler setPaused:![UpdateScheduler isPaused]];
[self updateBarIcon]; [self updateBarIcon];
} }
/// Called when user clicks on 'Update all feeds' (main menu only). /// Called when user clicks on 'Update all feeds' (main menu only).
- (void)updateAllFeeds { - (void)updateAllFeeds {
// [self asyncReloadUnreadCount]; // should not be necessary // [self asyncReloadUnreadCount]; // should not be necessary
[FeedDownload forceUpdateAllFeeds]; [UpdateScheduler forceUpdateAllFeeds];
} }
@end @end