Skip to content

Commit

Permalink
Merge pull request #1810 from barijaona/articleCache
Browse files Browse the repository at this point in the history
Improve management of folder's caches of articles

After PR 1770 <#1770> was merged,
 I noticed that the "Last Refresh" filter did not always have the expected behavior.

Investigation led to various discoveries regarding the use of NSCache and parts
of the code where article's status was lost.
This PR solves these issues and gives back a functional "Last Refresh" filter,
with a difference though: "Last Refresh" now refers to the last time each feed
was refreshed and got new articles, while previously it referred to the last time
"Refresh All Subscriptions" was triggered.ent of folder's caches of articles
  • Loading branch information
barijaona authored Sep 13, 2024
2 parents 79ad41e + 5e34e27 commit 196afbb
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 128 deletions.
6 changes: 6 additions & 0 deletions Vienna.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@
F696D23E2561F9FC003DF0C0 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = F696D23D2561F9FC003DF0C0 /* Sparkle */; };
F69964F71F13029600FC8493 /* OverlayStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F69964F61F13029500FC8493 /* OverlayStatusBar.swift */; };
F6A179D326B82BE3008DDA42 /* NSFileManagerExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A179D226B82BE3008DDA42 /* NSFileManagerExtensionTests.swift */; };
F6A243E12C69E534000A006F /* Article+Tags.m in Sources */ = {isa = PBXBuildFile; fileRef = F6A243E02C69E534000A006F /* Article+Tags.m */; };
F6A464BC272F47BE0071E3F6 /* NSKeyedUnarchiver+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = F6A464BA272F47BE0071E3F6 /* NSKeyedUnarchiver+Compatibility.m */; };
F6A464BD272F47BE0071E3F6 /* NSKeyedArchiver+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = F6A464BB272F47BE0071E3F6 /* NSKeyedArchiver+Compatibility.m */; };
F6A4E8481F040D58001C9191 /* PlugInToolbarItemButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6A4E8471F040D58001C9191 /* PlugInToolbarItemButton.swift */; };
Expand Down Expand Up @@ -564,6 +565,8 @@
F69964F61F13029500FC8493 /* OverlayStatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverlayStatusBar.swift; sourceTree = "<group>"; };
F69AE7E91F2224F500342640 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = da; path = da.lproj/RSSSources.plist; sourceTree = "<group>"; };
F6A179D226B82BE3008DDA42 /* NSFileManagerExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFileManagerExtensionTests.swift; sourceTree = "<group>"; };
F6A243DF2C69E534000A006F /* Article+Tags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Article+Tags.h"; sourceTree = "<group>"; };
F6A243E02C69E534000A006F /* Article+Tags.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "Article+Tags.m"; sourceTree = "<group>"; };
F6A464B8272F47BE0071E3F6 /* NSKeyedUnarchiver+Compatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "NSKeyedUnarchiver+Compatibility.h"; sourceTree = "<group>"; };
F6A464B9272F47BE0071E3F6 /* NSKeyedArchiver+Compatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "NSKeyedArchiver+Compatibility.h"; sourceTree = "<group>"; };
F6A464BA272F47BE0071E3F6 /* NSKeyedUnarchiver+Compatibility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = "NSKeyedUnarchiver+Compatibility.m"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -729,6 +732,8 @@
2F88B2A42545BB450067EEA6 /* ArticleConverter.h */,
2F88B2A52545BB450067EEA6 /* ArticleConverter.m */,
2FC4A1BE25852C0E005FF227 /* ArticleConverter.swift */,
F6A243DF2C69E534000A006F /* Article+Tags.h */,
F6A243E02C69E534000A006F /* Article+Tags.m */,
);
name = Common;
sourceTree = "<group>";
Expand Down Expand Up @@ -1808,6 +1813,7 @@
435028AC165DE9E00018EDB7 /* NewSubscription.m in Sources */,
2F88FF432969FC530076B99E /* DateWithUnitPredicateEditorRowTemplate.swift in Sources */,
F6EB26031E58D37100570B22 /* DirectoryMonitor.swift in Sources */,
F6A243E12C69E534000A006F /* Article+Tags.m in Sources */,
435028AD165DE9E00018EDB7 /* PluginManager.m in Sources */,
B81B536425565CD700C65459 /* Constants.swift in Sources */,
435028AE165DE9E00018EDB7 /* ProgressTextCell.m in Sources */,
Expand Down
23 changes: 9 additions & 14 deletions Vienna/Sources/Database/Database.m
Original file line number Diff line number Diff line change
Expand Up @@ -2251,7 +2251,8 @@ -(NSArray *)arrayOfArticles:(NSInteger)folderId filterString:(NSString *)filterS
results = [db executeQuery:queryString, filterString, filterString];
}
while ([results next]) {
Article * article = [[Article alloc] initWithGuid:[results stringForColumnIndex:0]];
NSString * guid = [results stringForColumnIndex:0];
Article * article = [[Article alloc] initWithGuid:guid];
article.folderId = [results intForColumnIndex:1];
article.parentId = [results intForColumnIndex:2];
[article markRead:[results intForColumnIndex:3]];
Expand All @@ -2267,6 +2268,8 @@ -(NSArray *)arrayOfArticles:(NSInteger)folderId filterString:(NSString *)filterS
[article markRevised:[results intForColumnIndex:12]];
article.hasEnclosure = [results intForColumnIndex:13];
article.enclosure = [results stringForColumnIndex:14];
Folder * articleFolder = [self folderFromID:article.folderId];
article.status = [articleFolder retrieveKnownStatusForGuid:guid];

if (folder == nil || !article.deleted || folder.type == VNAFolderTypeTrash) {
[newArray addObject:article];
Expand Down Expand Up @@ -2474,19 +2477,11 @@ -(void)markArticleDeleted:(Article *)article isDeleted:(BOOL)isDeleted
@(folderId),
guid];
}];
if (isDeleted && !article.deleted) {
[article markDeleted:YES];
if (folder.countOfCachedArticles > 0) {
// If we're in a smart folder, the cached article may be different.
Article * cachedArticle = [folder articleFromGuid:guid];
[cachedArticle markDeleted:YES];
[folder removeArticleFromCache:guid];
}
} else if (!isDeleted) {
// if we undelete, allow the RSS or OpenReader folder
// to get the restored article
[folder restoreArticleToCache:article];
[article markDeleted:NO];
[article markDeleted:isDeleted];
if (folder.countOfCachedArticles > 0) {
// If we're in a smart folder, the cached article may be different.
Article * cachedArticle = [folder articleFromGuid:guid];
[cachedArticle markDeleted:isDeleted];
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Vienna/Sources/Fetching/OpenReader.m
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ -(void)feedRequestDone:(NSMutableURLRequest *)request response:(NSURLResponse *)

// Here's where we add the articles to the database
if (articleArray.count > 0) {
[refreshedFolder resetArticleStatuses];
NSArray *guidHistory = [dbManager guidHistoryForFolderId:refreshedFolder.itemId];

for (Article *article in articleArray) {
Expand Down
1 change: 1 addition & 0 deletions Vienna/Sources/Fetching/RefreshManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,7 @@ -(void)finalizeFolderRefresh:(NSDictionary *)parameters

// Here's where we add the articles to the database
if (articleArray.count > 0u) {
[folder resetArticleStatuses];
NSArray *guidHistory = [dbManager guidHistoryForFolderId:folderId];
for (Article * article in articleArray) {
if ([folder createArticle:article
Expand Down
45 changes: 45 additions & 0 deletions Vienna/Sources/Main window/Article+Tags.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Article+Tags.h
// Vienna
//
// Copyright 2024 Eitot
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#import "Article.h"

NS_ASSUME_NONNULL_BEGIN

@interface Article (Tags)

// The following methods are called dynamically by the ArticleConverter class,
// specifically -expandTagsOfArticle:intoTemplate:withConditional:. It creates
// selectors by appending a tag name to the string "tag", e.g. $ArticleAuthor$
// becomes tagArticleAuthor. The method signatures must not be changed without
// changing the corresponding tag names elsewhere.

- (NSString *)tagArticleTitle;
- (NSString *)tagArticleAuthor;
- (NSString *)tagArticleBody;
- (NSString *)tagArticleDate;
- (NSString *)tagArticleLink;
- (NSString *)tagArticleEnclosureLink;
- (NSString *)tagArticleEnclosureFilename;
- (NSString *)tagFeedTitle;
- (NSString *)tagFeedDescription;
- (NSString *)tagFeedLink;

@end

NS_ASSUME_NONNULL_END
92 changes: 92 additions & 0 deletions Vienna/Sources/Main window/Article+Tags.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//
// Article+Tags.m
// Vienna
//
// Copyright 2024 Eitot
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#import "Article+Tags.h"

#import "Database.h"
#import "DateFormatterExtension.h"
#import "Folder.h"
#import "HelperFunctions.h"
#import "StringExtensions.h"

@implementation Article (Tags)

- (NSString *)tagArticleTitle
{
NSMutableString *articleTitle = [NSMutableString stringWithString:SafeString(self.title)];
[articleTitle vna_replaceString:@"$Article" withString:@"$_%$%_Article"];
[articleTitle vna_replaceString:@"$Feed" withString:@"$_%$%_Feed"];
return [NSString vna_stringByConvertingHTMLEntities:articleTitle];
}

- (NSString *)tagArticleAuthor
{
return SafeString(self.author);
}

- (NSString *)tagArticleBody
{
NSMutableString *articleBody = [NSMutableString stringWithString:SafeString(self.body)];
[articleBody vna_replaceString:@"$Article" withString:@"$_%$%_Article"];
[articleBody vna_replaceString:@"$Feed" withString:@"$_%$%_Feed"];
[articleBody vna_fixupRelativeImgTags:self.link];
[articleBody vna_fixupRelativeIframeTags:self.link];
[articleBody vna_fixupRelativeAnchorTags:self.link];
return articleBody;
}

- (NSString *)tagArticleDate
{
return [NSDateFormatter vna_relativeDateStringFromDate:self.lastUpdate];
}

- (NSString *)tagArticleLink
{
return cleanedUpUrlFromString(self.link).absoluteString;
}

- (NSString *)tagArticleEnclosureLink
{
return cleanedUpUrlFromString(self.enclosure).absoluteString;
}

- (NSString *)tagArticleEnclosureFilename
{
return self.enclosure.lastPathComponent.stringByRemovingPercentEncoding;
}

- (NSString *)tagFeedTitle
{
Folder *folder = [Database.sharedManager folderFromID:self.folderId];
return [NSString vna_stringByConvertingHTMLEntities:SafeString(folder.name)];
}

- (NSString *)tagFeedDescription
{
Folder *folder = [Database.sharedManager folderFromID:self.folderId];
return folder.feedDescription;
}

- (NSString *)tagFeedLink
{
Folder *folder = [Database.sharedManager folderFromID:self.folderId];
return cleanedUpUrlFromString(folder.homePage).absoluteString;
}

@end
2 changes: 2 additions & 0 deletions Vienna/Sources/Main window/ArticleConverter.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ - (instancetype)init
*/
-(NSString *)expandTagsOfArticle:(Article *)theArticle intoTemplate:(NSString *)theString withConditional:(BOOL)cond
{
NSAssert(theArticle.status != ArticleStatusDiscarded,
@"Attempting to access %@ with discarded elements", theArticle);
NSMutableString * newString = [NSMutableString stringWithString:SafeString(theString)];
NSUInteger tagStartIndex = 0;

Expand Down
5 changes: 3 additions & 2 deletions Vienna/Sources/Models/Article.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ typedef NS_ENUM(NSInteger, VNAArticleFieldTag) {
typedef NS_ENUM(NSInteger, ArticleStatus) {
ArticleStatusEmpty = 0,
ArticleStatusNew,
ArticleStatusUpdated
ArticleStatusUpdated,
ArticleStatusDiscarded
};

@interface Article : NSObject
@interface Article : NSObject<NSDiscardableContent>

// Accessor functions
-(instancetype _Nonnull)initWithGuid:(NSString * _Nonnull)theGuid /*NS_DESIGNATED_INITIALIZER*/;
Expand Down
101 changes: 18 additions & 83 deletions Vienna/Sources/Models/Article.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@
// limitations under the License.
//


#import "Article.h"

#import "Database.h"
#import "DateFormatterExtension.h"
#import "StringExtensions.h"
#import "HelperFunctions.h"
#import "Folder.h"
#import "StringExtensions.h"

// The names here are internal field names, not for localisation.
NSString * const MA_Field_GUID = @"GUID";
Expand Down Expand Up @@ -310,96 +308,33 @@ -(NSScriptObjectSpecifier *)objectSpecifier
return nil;
}

/* tagArticleLink
* Returns the article link as a safe string.
*/
-(NSString *)tagArticleLink
{
return cleanedUpUrlFromString(self.link).absoluteString;
}

/* tagArticleTitle
* Returns the article title.
*/
-(NSString *)tagArticleTitle
{
NSMutableString * articleTitle = [NSMutableString stringWithString:SafeString([self title])];
[articleTitle vna_replaceString:@"$Article" withString:@"$_%$%_Article"];
[articleTitle vna_replaceString:@"$Feed" withString:@"$_%$%_Feed"];
return [NSString vna_stringByConvertingHTMLEntities:articleTitle];
}

/* tagArticleBody
* Returns the article body.
*/
-(NSString *)tagArticleBody
{
NSMutableString * articleBody = [NSMutableString stringWithString:SafeString(self.body)];
[articleBody vna_replaceString:@"$Article" withString:@"$_%$%_Article"];
[articleBody vna_replaceString:@"$Feed" withString:@"$_%$%_Feed"];
[articleBody vna_fixupRelativeImgTags:self.link];
[articleBody vna_fixupRelativeIframeTags:self.link];
[articleBody vna_fixupRelativeAnchorTags:self.link];
return articleBody;
}
// MARK: - NSDiscardableContent

/* tagArticleAuthor
* Returns the article author as a safe string.
*/
-(NSString *)tagArticleAuthor
-(BOOL)beginContentAccess
{
return SafeString([self author]);
return self.status != ArticleStatusDiscarded;
}

/* tagArticleDate
* Returns the article date.
*/
-(NSString *)tagArticleDate
- (void)endContentAccess
{
return [NSDateFormatter vna_relativeDateStringFromDate:self.lastUpdate];
// do nothing special,
// as we are not attempting to retrieve discarded content by ourself
// and don't manage any access count
}

/* tagArticleEnclosureLink
* Returns the article enclosure link.
*/
-(NSString *)tagArticleEnclosureLink
-(void)discardContentIfPossible
{
return cleanedUpUrlFromString(self.enclosure).absoluteString;
}

/* tagArticleEnclosureFilename
* Returns the article enclosure file name.
*/
-(NSString *)tagArticleEnclosureFilename
{
return [self.enclosure.lastPathComponent stringByRemovingPercentEncoding];
}

/* tagFeedTitle
* Returns the article's feed title.
*/
-(NSString *)tagFeedTitle
{
Folder * folder = [[Database sharedManager] folderFromID:self.folderId];
return [NSString vna_stringByConvertingHTMLEntities:SafeString([folder name])];
}

/* tagFeedLink
* Returns the article's feed URL.
*/
-(NSString *)tagFeedLink
{
Folder * folder = [[Database sharedManager] folderFromID:self.folderId];
return cleanedUpUrlFromString(folder.homePage).absoluteString;
self.status = ArticleStatusDiscarded;
[articleData removeObjectForKey:MA_Field_Text];
[articleData removeObjectForKey:MA_Field_Summary];
[articleData removeObjectForKey:MA_Field_Author];
[articleData removeObjectForKey:MA_Field_LastUpdate];
[articleData removeObjectForKey:MA_Field_PublicationDate];
}

/* tagFeedDescription
* Returns the article's feed description.
*/
-(NSString *)tagFeedDescription
- (BOOL)isContentDiscarded
{
Folder * folder = [[Database sharedManager] folderFromID:self.folderId];
return folder.feedDescription;
return self.status == ArticleStatusDiscarded;
}

@end
3 changes: 2 additions & 1 deletion Vienna/Sources/Models/Folder.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,11 @@ typedef NS_OPTIONS(NSUInteger, VNAFolderFlag) {
-(void)clearNonPersistedFlag:(VNAFolderFlag)flagToClear;
-(NSUInteger)indexOfArticle:(Article *)article;
-(Article *)articleFromGuid:(NSString *)guid;
-(NSInteger)retrieveKnownStatusForGuid:(NSString *)guid;
-(BOOL)createArticle:(Article *)article guidHistory:(NSArray *)guidHistory;
-(void)removeArticleFromCache:(NSString *)guid;
-(void)restoreArticleToCache:(Article *)article;
-(void)markArticlesInCacheRead;
-(void)resetArticleStatuses;
-(NSArray<ArticleReference *> *)arrayOfUnreadArticlesRefs;
-(NSComparisonResult)folderNameCompare:(Folder *)otherObject;
-(NSComparisonResult)folderIDCompare:(Folder *)otherObject;
Expand Down
Loading

0 comments on commit 196afbb

Please sign in to comment.