Fix adding feeds when offline or paused
This commit is contained in:
13
CHANGELOG.md
13
CHANGELOG.md
@@ -25,20 +25,25 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
|||||||
- *Adding feed:* Show users any 5xx server error response and extracted failure reason
|
- *Adding feed:* Show users any 5xx server error response and extracted failure reason
|
||||||
- *Adding feed:* If URLs can't be resolved in the first run (5xx error), try a second time. E.g., `Done` click (issue: #5)
|
- *Adding feed:* If URLs can't be resolved in the first run (5xx error), try a second time. E.g., `Done` click (issue: #5)
|
||||||
- *Adding feed:* Prefer favicons with size `32x32`
|
- *Adding feed:* Prefer favicons with size `32x32`
|
||||||
|
- *Adding feed:* Inserting feeds when offline will postpone download until network is reachable again
|
||||||
|
- *Adding feed:* Inserting feeds when paused will postpone download until unpaused
|
||||||
- *Settings, Feeds:* Actions `delete` and `edit` use clicked items instead of selected items
|
- *Settings, Feeds:* Actions `delete` and `edit` use clicked items instead of selected items
|
||||||
- *Settings, Feeds:* Accurate download count instead of `Updating feeds …`
|
- *Settings, Feeds:* Accurate download count instead of `Updating feeds …`
|
||||||
|
- *Status Bar Menu*: Feed title is updated properly
|
||||||
- *UI:* If an error occurs, show document URL (path to file or web url)
|
- *UI:* If an error occurs, show document URL (path to file or web url)
|
||||||
- Comparison of existing articles with nonexistent guid and link
|
- Comparison of existing articles with nonexistent guid and link
|
||||||
- Don't mark articles read if opening URLs failed
|
- Don't mark articles read if opening URLs failed
|
||||||
- HTML tag removal keeps structure intact
|
- HTML tag removal keeps structure intact
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- *UI:* Interface builder files replaced with code equivalent
|
|
||||||
- *UI:* Mark unread articles with blue dot, instead of tick mark
|
|
||||||
- *Settings, Feeds:* Single add button for feeds, groups, and separators
|
|
||||||
- *Settings, Feeds:* Always append new items at the end
|
|
||||||
- *Adding feed:* Display error reason if user cancels the creation of a new feed item
|
- *Adding feed:* Display error reason if user cancels the creation of a new feed item
|
||||||
- *Adding feed:* Refresh interval hotkeys set to: `⌘1` … `⌘6`
|
- *Adding feed:* Refresh interval hotkeys set to: `⌘1` … `⌘6`
|
||||||
|
- *Settings, Feeds:* Single add button for feeds, groups, and separators
|
||||||
|
- *Settings, Feeds:* Always append new items at the end
|
||||||
|
- *Status Bar Menu*: Show `(no title)` instead of `(error)`
|
||||||
|
- *Status Bar Menu*: `Update all feeds` will show error alerts for broken URLs
|
||||||
|
- *UI:* Interface builder files replaced with code equivalent
|
||||||
|
- *UI:* Mark unread articles with blue dot, instead of tick mark
|
||||||
- *DB*: New table for options. E.g., what app version modified the database
|
- *DB*: New table for options. E.g., what app version modified the database
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
/// Instantiates new @c Feed and @c FeedMeta entities in context.
|
/// Instantiates new @c Feed and @c FeedMeta entities in context.
|
||||||
+ (instancetype)newFeedAndMetaInContext:(NSManagedObjectContext*)moc {
|
+ (instancetype)newFeedAndMetaInContext:(NSManagedObjectContext*)moc {
|
||||||
Feed *feed = [[Feed alloc] initWithEntity:Feed.entity insertIntoManagedObjectContext:moc];
|
Feed *feed = [[Feed alloc] initWithEntity:Feed.entity insertIntoManagedObjectContext:moc];
|
||||||
feed.meta = [[FeedMeta alloc] initWithEntity:FeedMeta.entity insertIntoManagedObjectContext:moc];
|
feed.meta = [FeedMeta newMetaInContext:moc];
|
||||||
return feed;
|
return feed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,6 @@
|
|||||||
NSUInteger lastIndex = [StoreCoordinator countRootItemsInContext:moc];
|
NSUInteger lastIndex = [StoreCoordinator countRootItemsInContext:moc];
|
||||||
FeedGroup *fg = [FeedGroup newGroup:FEED inContext:moc];
|
FeedGroup *fg = [FeedGroup newGroup:FEED inContext:moc];
|
||||||
[fg setParent:nil andSortIndex:(int32_t)lastIndex];
|
[fg setParent:nil andSortIndex:(int32_t)lastIndex];
|
||||||
[fg.feed.meta setRefreshAndSchedule:kDefaultFeedRefreshInterval];
|
|
||||||
return fg.feed;
|
return fg.feed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,9 @@
|
|||||||
|
|
||||||
#pragma mark - Properties
|
#pragma mark - Properties
|
||||||
|
|
||||||
/// @return Returns "(error)" if @c self.name is @c nil.
|
/// @return Returns "(no title)" if @c self.name is @c nil.
|
||||||
- (nonnull NSString*)nameOrError {
|
- (nonnull NSString*)nameOrError {
|
||||||
return (self.name ? self.name : NSLocalizedString(@"(error)", nil));
|
return (self.name ? self.name : NSLocalizedString(@"(no title)", nil));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @return Return @c 16x16px NSImageNameFolder image.
|
/// @return Return @c 16x16px NSImageNameFolder image.
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
static int32_t const kDefaultFeedRefreshInterval = 30 * 60;
|
static int32_t const kDefaultFeedRefreshInterval = 30 * 60;
|
||||||
|
|
||||||
@interface FeedMeta (Ext)
|
@interface FeedMeta (Ext)
|
||||||
|
+ (instancetype)newMetaInContext:(NSManagedObjectContext*)moc;
|
||||||
// HTTP response
|
// HTTP response
|
||||||
- (void)setErrorAndPostponeSchedule;
|
- (void)setErrorAndPostponeSchedule;
|
||||||
- (void)setSucessfulWithResponse:(NSHTTPURLResponse*)response;
|
- (void)setSucessfulWithResponse:(NSHTTPURLResponse*)response;
|
||||||
@@ -33,4 +34,5 @@ static int32_t const kDefaultFeedRefreshInterval = 30 * 60;
|
|||||||
- (void)setUrlIfChanged:(NSString*)url;
|
- (void)setUrlIfChanged:(NSString*)url;
|
||||||
- (void)setEtag:(NSString*)etag modified:(NSString*)modified;
|
- (void)setEtag:(NSString*)etag modified:(NSString*)modified;
|
||||||
- (BOOL)setRefreshAndSchedule:(int32_t)refresh;
|
- (BOOL)setRefreshAndSchedule:(int32_t)refresh;
|
||||||
|
- (void)scheduleNow:(NSTimeInterval)future;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -26,6 +26,14 @@
|
|||||||
|
|
||||||
@implementation FeedMeta (Ext)
|
@implementation FeedMeta (Ext)
|
||||||
|
|
||||||
|
/// Create new instance with default @c refresh interval and set @c scheduled to distant past.
|
||||||
|
+ (instancetype)newMetaInContext:(NSManagedObjectContext*)moc {
|
||||||
|
FeedMeta *meta = [[FeedMeta alloc] initWithEntity:FeedMeta.entity insertIntoManagedObjectContext:moc];
|
||||||
|
meta.refresh = kDefaultFeedRefreshInterval;
|
||||||
|
meta.scheduled = [NSDate distantPast]; // will cause update to refresh as soon as possible
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - HTTP response
|
#pragma mark - HTTP response
|
||||||
|
|
||||||
/// Increment @c errorCount and set new @c scheduled date (2^N minutes, max. 5.7 days).
|
/// Increment @c errorCount and set new @c scheduled date (2^N minutes, max. 5.7 days).
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ NS_INLINE NSInteger RadioGroupSelection(NSView *view) {
|
|||||||
interval = (int32_t)[refresh integerValue];
|
interval = (int32_t)[refresh integerValue];
|
||||||
|
|
||||||
newFeed.feed.meta.url = [item attributeForKey:OPMLXMLURLKey];
|
newFeed.feed.meta.url = [item attributeForKey:OPMLXMLURLKey];
|
||||||
[newFeed.feed.meta setRefreshAndSchedule:interval];
|
newFeed.feed.meta.refresh = interval;
|
||||||
} else { // GROUP
|
} else { // GROUP
|
||||||
for (NSUInteger i = 0; i < item.children.count; i++) {
|
for (NSUInteger i = 0; i < item.children.count; i++) {
|
||||||
[self importFeed:item.children[i] parent:newFeed index:(int32_t)i inContext:moc];
|
[self importFeed:item.children[i] parent:newFeed index:(int32_t)i inContext:moc];
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
@import Cocoa;
|
@import Cocoa;
|
||||||
|
|
||||||
|
@class Feed;
|
||||||
|
|
||||||
@interface UpdateScheduler : NSObject
|
@interface UpdateScheduler : NSObject
|
||||||
@property (class, readonly) NSUInteger feedsInQueue;
|
@property (class, readonly) NSUInteger feedsInQueue;
|
||||||
@property (class, readonly) NSDate *dateScheduled;
|
@property (class, readonly) NSDate *dateScheduled;
|
||||||
@@ -29,10 +31,9 @@
|
|||||||
@property (class, readonly) BOOL isUpdating;
|
@property (class, readonly) BOOL isUpdating;
|
||||||
@property (class, setter=setPaused:) BOOL isPaused;
|
@property (class, setter=setPaused:) BOOL isPaused;
|
||||||
|
|
||||||
+ (void)beginUpdate;
|
|
||||||
+ (void)endUpdate;
|
|
||||||
+ (void)scheduleNextFeed;
|
+ (void)scheduleNextFeed;
|
||||||
+ (void)forceUpdateAllFeeds;
|
+ (void)forceUpdateAllFeeds;
|
||||||
|
+ (void)downloadList:(NSArray<Feed*>*)list background:(BOOL)flag finally:(nullable os_block_t)block;
|
||||||
// Register for network change notifications
|
// Register for network change notifications
|
||||||
+ (void)registerNetworkChangeNotification;
|
+ (void)registerNetworkChangeNotification;
|
||||||
+ (void)unregisterNetworkChangeNotification;
|
+ (void)unregisterNetworkChangeNotification;
|
||||||
|
|||||||
@@ -28,8 +28,7 @@
|
|||||||
|
|
||||||
static NSTimer *_timer;
|
static NSTimer *_timer;
|
||||||
static SCNetworkReachabilityRef _reachability = NULL;
|
static SCNetworkReachabilityRef _reachability = NULL;
|
||||||
static BOOL _isReachable = NO;
|
static BOOL _isReachable = YES;
|
||||||
static BOOL _isUpdating = NO;
|
|
||||||
static BOOL _updatePaused = NO;
|
static BOOL _updatePaused = NO;
|
||||||
static BOOL _nextUpdateIsForced = NO;
|
static BOOL _nextUpdateIsForced = NO;
|
||||||
|
|
||||||
@@ -48,13 +47,14 @@ static BOOL _nextUpdateIsForced = NO;
|
|||||||
+ (BOOL)allowNetworkConnection { return (_isReachable && !_updatePaused); }
|
+ (BOOL)allowNetworkConnection { return (_isReachable && !_updatePaused); }
|
||||||
|
|
||||||
/// @return @c YES if batch update is running
|
/// @return @c YES if batch update is running
|
||||||
+ (BOOL)isUpdating { return _isUpdating; }
|
+ (BOOL)isUpdating { return [WebFeed feedsInQueue] > 0; }
|
||||||
|
|
||||||
/// @return @c YES if update is paused by user.
|
/// @return @c YES if update is paused by user.
|
||||||
+ (BOOL)isPaused { return _updatePaused; }
|
+ (BOOL)isPaused { return _updatePaused; }
|
||||||
|
|
||||||
/// Set paused flag and cancel timer regardless of network connectivity.
|
/// Set paused flag and cancel timer regardless of network connectivity.
|
||||||
+ (void)setPaused:(BOOL)flag {
|
+ (void)setPaused:(BOOL)flag {
|
||||||
|
// TODO: should pause persist between app launches?
|
||||||
_updatePaused = flag;
|
_updatePaused = flag;
|
||||||
if (_updatePaused)
|
if (_updatePaused)
|
||||||
[self pauseUpdates];
|
[self pauseUpdates];
|
||||||
@@ -73,12 +73,6 @@ static BOOL _nextUpdateIsForced = NO;
|
|||||||
[self scheduleNextFeed];
|
[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
|
#pragma mark - Update Feed Timer
|
||||||
|
|
||||||
@@ -133,25 +127,34 @@ static BOOL _nextUpdateIsForced = NO;
|
|||||||
#endif
|
#endif
|
||||||
BOOL updateAll = _nextUpdateIsForced;
|
BOOL updateAll = _nextUpdateIsForced;
|
||||||
_nextUpdateIsForced = NO;
|
_nextUpdateIsForced = NO;
|
||||||
if (updateAll)
|
|
||||||
[WebFeed setRequestsAreUrgent:YES];
|
|
||||||
|
|
||||||
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
||||||
NSArray<Feed*> *list = [StoreCoordinator getListOfFeedsThatNeedUpdate:updateAll inContext:moc];
|
NSArray<Feed*> *list = [StoreCoordinator getListOfFeedsThatNeedUpdate:updateAll inContext:moc];
|
||||||
//NSAssert(list.count > 0, @"ERROR: Something went wrong, timer fired too early.");
|
//NSAssert(list.count > 0, @"ERROR: Something went wrong, timer fired too early.");
|
||||||
if (![self allowNetworkConnection]) {
|
|
||||||
[WebFeed setRequestsAreUrgent:NO];
|
[self downloadList:list background:!updateAll finally:^{
|
||||||
[moc reset];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
[WebFeed batchDownloadFeeds:list favicons:updateAll showErrorAlert:NO finally:^{
|
|
||||||
[WebFeed setRequestsAreUrgent:NO];
|
|
||||||
[StoreCoordinator saveContext:moc andParent:YES]; // save parents too ...
|
[StoreCoordinator saveContext:moc andParent:YES]; // save parents too ...
|
||||||
[moc reset];
|
[moc reset];
|
||||||
[self resumeUpdates]; // always reset the timer
|
[self resumeUpdates]; // always reset the timer
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Download list of feeds. Either silently in background or in foreground with alerts.
|
||||||
|
+ (void)downloadList:(NSArray<Feed*>*)list background:(BOOL)flag finally:(nullable os_block_t)block {
|
||||||
|
if (![self allowNetworkConnection]) {
|
||||||
|
if (block) block();
|
||||||
|
} else if (flag) {
|
||||||
|
[WebFeed batchDownloadFeeds:list showErrorAlert:NO finally:block];
|
||||||
|
} else {
|
||||||
|
// TODO: add undo grouping?
|
||||||
|
[WebFeed setRequestsAreUrgent:YES];
|
||||||
|
[WebFeed batchDownloadFeeds:list showErrorAlert:YES finally:^{
|
||||||
|
[WebFeed setRequestsAreUrgent:NO];
|
||||||
|
if (block) block();
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Network Connection & Reachability
|
#pragma mark - Network Connection & Reachability
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
+ (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;
|
||||||
+ (void)autoDownloadAndParseUpdateURL;
|
+ (void)autoDownloadAndParseUpdateURL;
|
||||||
+ (void)batchDownloadFeeds:(NSArray<Feed*> *)list favicons:(BOOL)fav showErrorAlert:(BOOL)alert finally:(nullable os_block_t)block;
|
+ (void)batchDownloadFeeds:(NSArray<Feed*>*)list showErrorAlert:(BOOL)alert finally:(nullable os_block_t)block;
|
||||||
// Favicon image download
|
// Favicon image download
|
||||||
+ (void)downloadFavicon:(NSString*)urlStr finished:(void(^)(NSImage * _Nullable img))block;
|
+ (void)downloadFavicon:(NSString*)urlStr finished:(void(^)(NSImage * _Nullable img))block;
|
||||||
+ (void)downloadImage:(NSString*)url finished:(void(^)(NSImage * _Nullable img))block;
|
+ (void)downloadImage:(NSString*)url finished:(void(^)(NSImage * _Nullable img))block;
|
||||||
|
|||||||
@@ -233,18 +233,12 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
|||||||
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
||||||
Feed *f = [Feed appendToRootWithDefaultIntervalInContext:moc];
|
Feed *f = [Feed appendToRootWithDefaultIntervalInContext:moc];
|
||||||
f.meta.url = url;
|
f.meta.url = url;
|
||||||
[self backgroundUpdateBoth:f favicon:YES alert:!flag finally:^(BOOL successful){
|
if (block) block(f);
|
||||||
if (!flag && !successful) {
|
[StoreCoordinator saveContext:moc andParent:YES];
|
||||||
[moc deleteObject:f.group];
|
[UpdateScheduler downloadList:@[f] background:flag finally:^{
|
||||||
} else if (block) {
|
PostNotification(kNotificationGroupInserted, f.group.objectID);
|
||||||
block(f); // only on success
|
|
||||||
}
|
|
||||||
[StoreCoordinator saveContext:moc andParent:YES];
|
|
||||||
[moc reset];
|
[moc reset];
|
||||||
if (successful) {
|
[UpdateScheduler scheduleNextFeed];
|
||||||
PostNotification(kNotificationGroupInserted, f.group.objectID);
|
|
||||||
[UpdateScheduler scheduleNextFeed];
|
|
||||||
}
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,20 +246,20 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
|||||||
+ (void)autoDownloadAndParseUpdateURL {
|
+ (void)autoDownloadAndParseUpdateURL {
|
||||||
[self autoDownloadAndParseURL:versionUpdateURL addAnyway:YES modify:^(Feed *feed) {
|
[self autoDownloadAndParseURL:versionUpdateURL addAnyway:YES modify:^(Feed *feed) {
|
||||||
feed.group.name = NSLocalizedString(@"baRSS releases", nil);
|
feed.group.name = NSLocalizedString(@"baRSS releases", nil);
|
||||||
[feed.meta setRefreshAndSchedule:2 * TimeUnitDays];
|
feed.meta.refresh = 2 * TimeUnitDays;
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Start download of feed xml, then continue with favicon download (optional).
|
Start download of feed xml, then continue with favicon (if newly added or 'Update all').
|
||||||
|
|
||||||
@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 Parameter @c success is @c YES if xml download succeeded (regardless of favicon result).
|
@param block Parameter @c success is @c YES if xml download succeeded (regardless of favicon result).
|
||||||
*/
|
*/
|
||||||
+ (void)backgroundUpdateBoth:(Feed*)feed favicon:(BOOL)fav alert:(BOOL)alert finally:(nullable void(^)(BOOL success))block {
|
+ (void)backgroundUpdateBoth:(Feed*)feed alert:(BOOL)alert finally:(nullable void(^)(BOOL success))block {
|
||||||
|
BOOL recentlyAdded = (feed.articles.count == 0);
|
||||||
[self backgroundUpdateFeed:feed showErrorAlert:alert finally:^(BOOL success) {
|
[self backgroundUpdateFeed:feed showErrorAlert:alert finally:^(BOOL success) {
|
||||||
if (fav && success) {
|
if (success && (recentlyAdded || _requestsAreUrgent)) {
|
||||||
[self backgroundUpdateFavicon:feed replaceExisting:NO finally:^{
|
[self backgroundUpdateFavicon:feed replaceExisting:NO finally:^{
|
||||||
if (block) block(YES);
|
if (block) block(YES);
|
||||||
}];
|
}];
|
||||||
@@ -276,21 +270,19 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Start download of all feeds in list. Either with or without favicons.
|
Start download of all feeds in list. Favicons will be loaded for new feeds and for 'Update all'.
|
||||||
|
|
||||||
@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 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 showErrorAlert:(BOOL)alert finally:(nullable os_block_t)block {
|
||||||
[UpdateScheduler beginUpdate];
|
|
||||||
atomic_fetch_add_explicit(&_queueSize, list.count, memory_order_relaxed);
|
atomic_fetch_add_explicit(&_queueSize, list.count, memory_order_relaxed);
|
||||||
PostNotification(kNotificationBackgroundUpdateInProgress, @(_queueSize));
|
PostNotification(kNotificationBackgroundUpdateInProgress, @(_queueSize));
|
||||||
dispatch_group_t group = dispatch_group_create();
|
dispatch_group_t group = dispatch_group_create();
|
||||||
for (Feed *f in list) {
|
for (Feed *f in list) {
|
||||||
dispatch_group_enter(group);
|
dispatch_group_enter(group);
|
||||||
[self backgroundUpdateBoth:f favicon:fav alert:alert finally:^(BOOL success){
|
[self backgroundUpdateBoth:f alert:alert finally:^(BOOL success){
|
||||||
atomic_fetch_sub_explicit(&_queueSize, 1, memory_order_relaxed);
|
atomic_fetch_sub_explicit(&_queueSize, 1, memory_order_relaxed);
|
||||||
PostNotification(kNotificationBackgroundUpdateInProgress, @(_queueSize));
|
PostNotification(kNotificationBackgroundUpdateInProgress, @(_queueSize));
|
||||||
dispatch_group_leave(group);
|
dispatch_group_leave(group);
|
||||||
@@ -298,8 +290,7 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
|||||||
}
|
}
|
||||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||||
if (block) block();
|
if (block) block();
|
||||||
[UpdateScheduler endUpdate];
|
PostNotification(kNotificationBackgroundUpdateInProgress, @(_queueSize));
|
||||||
PostNotification(kNotificationBackgroundUpdateInProgress, @(0));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>10678</string>
|
<string>10782</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>
|
||||||
|
|||||||
@@ -72,11 +72,10 @@
|
|||||||
@property (strong) RefreshStatisticsView *statisticsView;
|
@property (strong) RefreshStatisticsView *statisticsView;
|
||||||
|
|
||||||
@property (copy) NSString *previousURL; // check if changed and avoid multiple download
|
@property (copy) NSString *previousURL; // check if changed and avoid multiple download
|
||||||
@property (copy) NSString *httpDate;
|
|
||||||
@property (copy) NSString *httpEtag;
|
|
||||||
@property (copy) NSString *faviconURL;
|
@property (copy) NSString *faviconURL;
|
||||||
@property (strong) NSError *feedError; // download error or xml parser error
|
@property (strong) NSError *feedError; // download error or xml parser error
|
||||||
@property (strong) RSParsedFeed *feedResult; // parsed result
|
@property (strong) RSParsedFeed *feedResult; // parsed result
|
||||||
|
@property (strong) NSHTTPURLResponse *httpResponse;
|
||||||
@property (assign) BOOL didDownloadFeed; // check if feed articles need update
|
@property (assign) BOOL didDownloadFeed; // check if feed articles need update
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -119,7 +118,7 @@
|
|||||||
[meta setRefreshAndSchedule:[NSDate intervalForPopup:self.view.refreshUnit andField:self.view.refreshNum]];
|
[meta setRefreshAndSchedule:[NSDate intervalForPopup:self.view.refreshUnit andField:self.view.refreshNum]];
|
||||||
// updateTimer will be scheduled once preferences is closed
|
// updateTimer will be scheduled once preferences is closed
|
||||||
if (self.didDownloadFeed) {
|
if (self.didDownloadFeed) {
|
||||||
[meta setEtag:self.httpEtag modified:self.httpDate];
|
[meta setSucessfulWithResponse:self.httpResponse];
|
||||||
[feed updateWithRSS:self.feedResult postUnreadCountChange:YES];
|
[feed updateWithRSS:self.feedResult postUnreadCountChange:YES];
|
||||||
[feed setIconImage:self.view.favicon.image];
|
[feed setIconImage:self.view.favicon.image];
|
||||||
}
|
}
|
||||||
@@ -141,10 +140,9 @@
|
|||||||
if ([self.view.name.stringValue isEqualToString:self.feedResult.title]) {
|
if ([self.view.name.stringValue isEqualToString:self.feedResult.title]) {
|
||||||
self.view.name.stringValue = @"";
|
self.view.name.stringValue = @"";
|
||||||
}
|
}
|
||||||
self.feedResult = nil;
|
|
||||||
self.feedError = nil;
|
self.feedError = nil;
|
||||||
self.httpEtag = nil;
|
self.feedResult = nil;
|
||||||
self.httpDate = nil;
|
self.httpResponse = nil;
|
||||||
self.faviconURL = nil;
|
self.faviconURL = nil;
|
||||||
self.previousURL = self.view.url.stringValue;
|
self.previousURL = self.view.url.stringValue;
|
||||||
}
|
}
|
||||||
@@ -167,8 +165,7 @@
|
|||||||
self.didDownloadFeed = YES;
|
self.didDownloadFeed = YES;
|
||||||
self.feedResult = result;
|
self.feedResult = result;
|
||||||
self.feedError = error;
|
self.feedError = error;
|
||||||
self.httpEtag = [response allHeaderFields][@"Etag"];
|
self.httpResponse = response;
|
||||||
self.httpDate = [response allHeaderFields][@"Date"]; // @"Expires", @"Last-Modified"
|
|
||||||
[self postDownload:response.URL.absoluteString];
|
[self postDownload:response.URL.absoluteString];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 "WebFeed.h"
|
#import "UpdateScheduler.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,8 @@ 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 sortedArrayUsingSelector:@selector(compare:)]];
|
[self.dataStore setSelectionIndexPaths:[selection sortedArrayUsingSelector:@selector(compare:)]];
|
||||||
[WebFeed batchDownloadFeeds:feedsList favicons:YES showErrorAlert:YES finally:^{
|
|
||||||
|
[UpdateScheduler downloadList:feedsList background:NO finally:^{
|
||||||
[self endCoreDataChangeUndoEmpty:NO forceUndo:NO];
|
[self endCoreDataChangeUndoEmpty:NO forceUndo:NO];
|
||||||
[self someDatesChangedScheduleUpdateTimer];
|
[self someDatesChangedScheduleUpdateTimer];
|
||||||
}];
|
}];
|
||||||
|
|||||||
@@ -154,6 +154,8 @@
|
|||||||
[self.unreadMap updateAllCounts:updated forPath:feed.indexPath];
|
[self.unreadMap updateAllCounts:updated forPath:feed.indexPath];
|
||||||
// 2. rebuild articles menu if it is open
|
// 2. rebuild articles menu if it is open
|
||||||
if (item.submenu.isFeedMenu) { // menu item is visible
|
if (item.submenu.isFeedMenu) { // menu item is visible
|
||||||
|
if (feed.group.name)
|
||||||
|
item.title = feed.group.name; // will replace (no title)
|
||||||
item.image = [feed iconImage16];
|
item.image = [feed iconImage16];
|
||||||
item.enabled = (feed.articles.count > 0);
|
item.enabled = (feed.articles.count > 0);
|
||||||
if (item.submenu.numberOfItems > 0) { // replace articles menu
|
if (item.submenu.numberOfItems > 0) { // replace articles menu
|
||||||
|
|||||||
Reference in New Issue
Block a user