Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6192f76605 | ||
|
|
3ace169549 | ||
|
|
039c7bc734 | ||
|
|
0c9b128cfe |
57
CHANGELOG.md
Normal file
57
CHANGELOG.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project does NOT adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
|
||||||
|
## [0.9.3] - 2019-03-14
|
||||||
|
### Added
|
||||||
|
- Changelog
|
||||||
|
- UI: Show body tag in article tooltip if abstract tag is empty
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 'Update all feeds' will shows unread items count properly during update
|
||||||
|
- Fixed update for feeds where all article URLs point to the same resource (issue: #3)
|
||||||
|
|
||||||
|
|
||||||
|
## [0.9.2] - 2019-03-07
|
||||||
|
### Added
|
||||||
|
- Limit number of articles that are displayed in feed menu (issue: #2)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Cmd+Q in preferences will close the window instead of quitting the application
|
||||||
|
- Crash when libxml2 encountered and set an error
|
||||||
|
- libxml2 will ignore lower ascii characters (0x00–0x1F)
|
||||||
|
|
||||||
|
|
||||||
|
## [0.9.1] - 2019-02-14
|
||||||
|
### Added
|
||||||
|
- Mark single article as un/read (hold down option key and click on article)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Mouse click on 'Done' button, while entering a new feed URL, will start download properly
|
||||||
|
- Use guid url if link is not set (issue: #1)
|
||||||
|
- Issue with feeds not being detected if XML tags start after 4kb
|
||||||
|
- Support uppercase schemes (e.g., 'FEED:')
|
||||||
|
- UI: Hide 'Next update in -25yrs'
|
||||||
|
- UI: Show alert after click on 'Fix Cache'
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Auto increment build number
|
||||||
|
- Removed static images for group icon, default feed icon, and warning icon
|
||||||
|
- Remove html tags from abstract on save (not on display)
|
||||||
|
|
||||||
|
|
||||||
|
## [0.9] - 2019-02-11
|
||||||
|
Initial release
|
||||||
|
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/relikd/baRSS/compare/v0.9.2...HEAD
|
||||||
|
[0.9.3]: https://github.com/relikd/baRSS/compare/v0.9.2...v0.9.3
|
||||||
|
[0.9.2]: https://github.com/relikd/baRSS/compare/v0.9.1...v0.9.2
|
||||||
|
[0.9.1]: https://github.com/relikd/baRSS/compare/v0.9...v0.9.1
|
||||||
|
[0.9]: https://github.com/relikd/baRSS/compare/e1f36514a8aa2d5fb9a575b6eb19adc2ce4a04d9...v0.9
|
||||||
@@ -106,6 +106,7 @@
|
|||||||
546FC4462118A8E6007CC3A3 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = "<group>"; };
|
546FC4462118A8E6007CC3A3 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = "<group>"; };
|
||||||
5477D34C21233C62002BA27F /* FeedGroup+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FeedGroup+Ext.h"; sourceTree = "<group>"; };
|
5477D34C21233C62002BA27F /* FeedGroup+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FeedGroup+Ext.h"; sourceTree = "<group>"; };
|
||||||
5477D34D21233C62002BA27F /* FeedGroup+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FeedGroup+Ext.m"; sourceTree = "<group>"; };
|
5477D34D21233C62002BA27F /* FeedGroup+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FeedGroup+Ext.m"; sourceTree = "<group>"; };
|
||||||
|
54892F1D2235285700271CBA /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
|
||||||
5496B50F214D6275003ED4ED /* UserPrefs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserPrefs.h; sourceTree = "<group>"; };
|
5496B50F214D6275003ED4ED /* UserPrefs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserPrefs.h; sourceTree = "<group>"; };
|
||||||
5496B510214D6275003ED4ED /* UserPrefs.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UserPrefs.m; sourceTree = "<group>"; };
|
5496B510214D6275003ED4ED /* UserPrefs.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UserPrefs.m; sourceTree = "<group>"; };
|
||||||
54A07A7D220E04CF00082C51 /* NSFetchRequest+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSFetchRequest+Ext.h"; sourceTree = "<group>"; };
|
54A07A7D220E04CF00082C51 /* NSFetchRequest+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSFetchRequest+Ext.h"; sourceTree = "<group>"; };
|
||||||
@@ -251,6 +252,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
540CD14821C094A2004AB594 /* README.md */,
|
540CD14821C094A2004AB594 /* README.md */,
|
||||||
|
54892F1D2235285700271CBA /* CHANGELOG.md */,
|
||||||
54ACC27E21061B3B0020715F /* baRSS */,
|
54ACC27E21061B3B0020715F /* baRSS */,
|
||||||
54CC042D2162532800A48795 /* baRSS-Helper */,
|
54CC042D2162532800A48795 /* baRSS-Helper */,
|
||||||
54ACC27D21061B3B0020715F /* Products */,
|
54ACC27D21061B3B0020715F /* Products */,
|
||||||
|
|||||||
@@ -92,9 +92,9 @@
|
|||||||
self.group.name = obj.title;
|
self.group.name = obj.title;
|
||||||
|
|
||||||
// Add and remove articles
|
// Add and remove articles
|
||||||
NSMutableSet<NSString*> *urls = [[self.articles valueForKeyPath:@"link"] mutableCopy];
|
NSMutableSet<FeedArticle*> *oldSet = [self.articles mutableCopy];
|
||||||
NSInteger diff = [self addMissingArticles:obj updateLinks:urls]; // will remove links in 'urls' that should be kept
|
NSInteger diff = [self addMissingArticles:obj withOldSet:oldSet]; // will remove items that should be kept
|
||||||
diff -= [self deleteArticlesWithLink:urls]; // remove old, outdated articles
|
diff -= [self deleteArticlesWithOldSet:oldSet]; // remove old, outdated articles
|
||||||
// Get new total article count and post unread-count-change notification
|
// Get new total article count and post unread-count-change notification
|
||||||
if (flag && diff != 0) {
|
if (flag && diff != 0) {
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationTotalUnreadCountChanged object:@(diff)];
|
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationTotalUnreadCountChanged object:@(diff)];
|
||||||
@@ -108,9 +108,10 @@
|
|||||||
New articles should be in ascending order without any gaps in between.
|
New articles should be in ascending order without any gaps in between.
|
||||||
If new article is disjunct from the article before, assume a deleted article re-appeared and mark it as read.
|
If new article is disjunct from the article before, assume a deleted article re-appeared and mark it as read.
|
||||||
|
|
||||||
@param urls Input will be used to identify new articles. Output will contain URLs that aren't present in the feed anymore.
|
@param oldSet Input will be used to identify new articles.
|
||||||
|
Output contains articles that aren't present in the feed anymore and should be deleted.
|
||||||
*/
|
*/
|
||||||
- (NSInteger)addMissingArticles:(RSParsedFeed*)obj updateLinks:(NSMutableSet<NSString*>*)urls {
|
- (NSInteger)addMissingArticles:(RSParsedFeed*)obj withOldSet:(NSMutableSet<FeedArticle*>*)oldSet {
|
||||||
NSInteger newOnes = 0;
|
NSInteger newOnes = 0;
|
||||||
int32_t currentIndex = [[self.articles valueForKeyPath:@"@min.sortIndex"] intValue];
|
int32_t currentIndex = [[self.articles valueForKeyPath:@"@min.sortIndex"] intValue];
|
||||||
FeedArticle *lastInserted = nil;
|
FeedArticle *lastInserted = nil;
|
||||||
@@ -118,10 +119,10 @@
|
|||||||
|
|
||||||
for (RSParsedArticle *article in [obj.articles reverseObjectEnumerator]) {
|
for (RSParsedArticle *article in [obj.articles reverseObjectEnumerator]) {
|
||||||
// reverse enumeration ensures correct article order
|
// reverse enumeration ensures correct article order
|
||||||
if ([urls containsObject:article.link]) {
|
FeedArticle *storedArticle = [self findArticle:article inSet:oldSet];
|
||||||
[urls removeObject:article.link];
|
if (storedArticle) {
|
||||||
FeedArticle *storedArticle = [self findArticleWithLink:article.link]; // TODO: use two synced arrays?
|
[oldSet removeObject:storedArticle];
|
||||||
if (storedArticle && storedArticle.sortIndex != currentIndex) {
|
if (storedArticle.sortIndex != currentIndex) {
|
||||||
storedArticle.sortIndex = currentIndex;
|
storedArticle.sortIndex = currentIndex;
|
||||||
}
|
}
|
||||||
hasGapBetweenNewArticles = YES;
|
hasGapBetweenNewArticles = YES;
|
||||||
@@ -146,26 +147,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Delete all items where @c link matches one of the URLs in the @c NSSet.
|
Delete all articles from core data, that are still in the oldSet.
|
||||||
*/
|
*/
|
||||||
- (NSUInteger)deleteArticlesWithLink:(NSMutableSet<NSString*>*)urls {
|
- (NSUInteger)deleteArticlesWithOldSet:(NSMutableSet<FeedArticle*>*)oldSet {
|
||||||
if (!urls || urls.count == 0)
|
if (!oldSet || oldSet.count == 0)
|
||||||
return 0;
|
return 0;
|
||||||
NSUInteger c = 0;
|
NSUInteger c = 0;
|
||||||
for (FeedArticle *fa in self.articles) {
|
for (FeedArticle *fa in oldSet) {
|
||||||
if ([urls containsObject:fa.link]) {
|
|
||||||
[urls removeObject:fa.link];
|
|
||||||
if (fa.unread) ++c;
|
if (fa.unread) ++c;
|
||||||
// TODO: keep unread articles?
|
// TODO: keep unread articles?
|
||||||
[self.managedObjectContext deleteObject:fa];
|
[self.managedObjectContext deleteObject:fa];
|
||||||
if (urls.count == 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NSSet<FeedArticle*> *delArticles = [self.managedObjectContext deletedObjects];
|
|
||||||
if (delArticles.count > 0) {
|
|
||||||
[self removeArticles:delArticles];
|
|
||||||
}
|
}
|
||||||
|
if (oldSet.count > 0)
|
||||||
|
[self removeArticles:oldSet];
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,12 +177,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Iterate over all Articles and return the one where @c .link matches. Or @c nil if no matching article found.
|
Iterate over oldSet and return the one where @c link and @c guid matches. Or @c nil if no matching article found.
|
||||||
*/
|
*/
|
||||||
- (FeedArticle*)findArticleWithLink:(NSString*)url {
|
- (FeedArticle*)findArticle:(RSParsedArticle*)article inSet:(NSSet<FeedArticle*>*)oldSet {
|
||||||
for (FeedArticle *a in self.articles) {
|
NSString *searchLink = article.link;
|
||||||
if ([a.link isEqualToString:url])
|
NSString *searchGuid = article.guid;
|
||||||
return a;
|
BOOL linkIsNil = (searchLink == nil);
|
||||||
|
BOOL guidIsNil = (searchGuid == nil);
|
||||||
|
for (FeedArticle *old in oldSet) {
|
||||||
|
if ((linkIsNil && old.link == nil) || [old.link isEqualToString:searchLink]) {
|
||||||
|
if ((guidIsNil && old.guid == nil) || [old.guid isEqualToString:searchGuid])
|
||||||
|
return old;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
item.title = [self shortArticleName];
|
item.title = [self shortArticleName];
|
||||||
item.enabled = (self.link.length > 0);
|
item.enabled = (self.link.length > 0);
|
||||||
item.state = (self.unread && [UserPrefs defaultYES:@"feedTickMark"] ? NSControlStateValueOn : NSControlStateValueOff);
|
item.state = (self.unread && [UserPrefs defaultYES:@"feedTickMark"] ? NSControlStateValueOn : NSControlStateValueOff);
|
||||||
item.toolTip = self.abstract;
|
item.toolTip = (self.abstract ? self.abstract : self.body); // fall back to body (html)
|
||||||
item.representedObject = self.objectID;
|
item.representedObject = self.objectID;
|
||||||
item.target = [self class];
|
item.target = [self class];
|
||||||
item.action = @selector(didClickOnMenuItem:);
|
item.action = @selector(didClickOnMenuItem:);
|
||||||
|
|||||||
@@ -34,7 +34,9 @@
|
|||||||
self.errorCount = 0;
|
self.errorCount = 0;
|
||||||
int16_t n = self.errorCount + 1; // always increment errorCount (can be used to indicate bad feeds)
|
int16_t n = self.errorCount + 1; // always increment errorCount (can be used to indicate bad feeds)
|
||||||
// TODO: remove logging
|
// TODO: remove logging
|
||||||
|
#ifdef DEBUG
|
||||||
NSLog(@"ERROR: Feed download failed: %@ (errorCount: %d)", self.url, n);
|
NSLog(@"ERROR: Feed download failed: %@ (errorCount: %d)", self.url, n);
|
||||||
|
#endif
|
||||||
if ([self.scheduled timeIntervalSinceNow] > 30) // forced, early update. Scheduled is still in the futute.
|
if ([self.scheduled timeIntervalSinceNow] > 30) // forced, early update. Scheduled is still in the futute.
|
||||||
return; // Keep error counter low. Not enough time has passed (e.g., temporary server outage)
|
return; // Keep error counter low. Not enough time has passed (e.g., temporary server outage)
|
||||||
NSTimeInterval retryWaitTime = pow(2, (n > 13 ? 13 : n)) * 60; // 2^N (between: 2 minutes and 5.7 days)
|
NSTimeInterval retryWaitTime = pow(2, (n > 13 ? 13 : n)) * 60; // 2^N (between: 2 minutes and 5.7 days)
|
||||||
|
|||||||
@@ -123,7 +123,9 @@ static BOOL _nextUpdateIsForced = NO;
|
|||||||
Called when schedule timer runs out (earliest @c .schedule date). Or if forced by user request.
|
Called when schedule timer runs out (earliest @c .schedule date). Or if forced by user request.
|
||||||
*/
|
*/
|
||||||
+ (void)updateTimerCallback {
|
+ (void)updateTimerCallback {
|
||||||
|
#ifdef DEBUG
|
||||||
NSLog(@"fired");
|
NSLog(@"fired");
|
||||||
|
#endif
|
||||||
BOOL updateAll = _nextUpdateIsForced;
|
BOOL updateAll = _nextUpdateIsForced;
|
||||||
_nextUpdateIsForced = NO;
|
_nextUpdateIsForced = NO;
|
||||||
|
|
||||||
@@ -303,7 +305,7 @@ static BOOL _nextUpdateIsForced = NO;
|
|||||||
needsNotification = YES;
|
needsNotification = YES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[StoreCoordinator saveContext:moc andParent:NO];
|
[StoreCoordinator saveContext:moc andParent:YES];
|
||||||
if (needsNotification)
|
if (needsNotification)
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFeedUpdated object:oid];
|
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFeedUpdated object:oid];
|
||||||
if (block) block(success);
|
if (block) block(success);
|
||||||
@@ -400,7 +402,7 @@ static BOOL _nextUpdateIsForced = NO;
|
|||||||
[self downloadFavicon:faviconURL finished:^(NSImage *img) {
|
[self downloadFavicon:faviconURL finished:^(NSImage *img) {
|
||||||
Feed *f = [moc objectWithID:oid];
|
Feed *f = [moc objectWithID:oid];
|
||||||
if (f && [f setIconImage:img]) {
|
if (f && [f setIconImage:img]) {
|
||||||
[StoreCoordinator saveContext:moc andParent:NO];
|
[StoreCoordinator saveContext:moc andParent:YES];
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFeedIconUpdated object:oid];
|
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationFeedIconUpdated object:oid];
|
||||||
}
|
}
|
||||||
if (block) block();
|
if (block) block();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.9.2</string>
|
<string>0.9.3</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1122</string>
|
<string>1153</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||||
<key>LSUIElement</key>
|
<key>LSUIElement</key>
|
||||||
|
|||||||
@@ -154,6 +154,7 @@
|
|||||||
[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
|
||||||
|
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
|
||||||
[item.submenu removeAllItems];
|
[item.submenu removeAllItems];
|
||||||
|
|||||||
Reference in New Issue
Block a user