Separate FeedDownload into UpdateScheduler & WebFeed
This commit is contained in:
@@ -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 */,
|
||||||
|
|||||||
@@ -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!)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
38
baRSS/Feed Import/UpdateScheduler.h
Normal file
38
baRSS/Feed Import/UpdateScheduler.h
Normal 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
|
||||||
210
baRSS/Feed Import/UpdateScheduler.m
Normal file
210
baRSS/Feed Import/UpdateScheduler.m
Normal 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
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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];
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
@@ -398,7 +291,7 @@ static BOOL _nextUpdateIsForced = NO;
|
|||||||
@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
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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];
|
||||||
}];
|
}];
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user