@@ -20,7 +20,8 @@ final class ArticlesTable: DatabaseTable {
private let statusesTable : StatusesTable
private let authorsLookupTable : DatabaseLookupTable
private let retentionStyle : ArticlesDatabase . RetentionStyle
private var databaseArticlesCache = [ String : DatabaseArticle ] ( )
private var articlesCache = [ String : Article ] ( )
private lazy var searchTable : SearchTable = {
return SearchTable ( queue : queue , articlesTable : self )
@@ -28,7 +29,6 @@ final class ArticlesTable: DatabaseTable {
// TODO: u p d a t e a r t i c l e C u t o f f D a t e a s t i m e p a s s e s a n d b a s e d o n u s e r p r e f e r e n c e s .
let articleCutoffDate = Date ( ) . bySubtracting ( days : 90 )
private var maximumArticleCutoffDate = Date ( ) . bySubtracting ( days : 4 * 31 )
private typealias ArticlesFetchMethod = ( FMDatabase ) -> Set < Article >
@@ -47,15 +47,11 @@ final class ArticlesTable: DatabaseTable {
// MARK: - F e t c h i n g A r t i c l e s f o r F e e d
func fetchArticles ( _ feedID : String ) -> Set < Article > {
return fetchArticles { self . fetchArticlesForFeedID ( feedID , withLimits : true , $0 ) }
return fetchArticles { self . fetchArticlesForFeedID ( feedID , $0 ) }
}
func fetchArticlesAsync ( _ feedID : String , _ callback : @ escaping ArticleSetBlock ) {
fetchArticlesAsync ( { self . fetchArticlesForFeedID ( feedID , withLimits : true , $0 ) } , callback )
}
private func fetchArticlesForFeedID ( _ feedID : String , withLimits : Bool , _ database : FMDatabase ) -> Set < Article > {
return fetchArticlesWithWhereClause ( database , whereClause : " articles.feedID = ? " , parameters : [ feedID as AnyObject ] , withLimits : withLimits )
fetchArticlesAsync ( { self . fetchArticlesForFeedID ( feedID , $0 ) } , callback )
}
// MARK: - F e t c h i n g A r t i c l e s b y a r t i c l e I D
@@ -68,16 +64,6 @@ final class ArticlesTable: DatabaseTable {
return fetchArticlesAsync ( { self . fetchArticles ( articleIDs : articleIDs , $0 ) } , callback )
}
private func fetchArticles ( articleIDs : Set < String > , _ database : FMDatabase ) -> Set < Article > {
if articleIDs . isEmpty {
return Set < Article > ( )
}
let parameters = articleIDs . map { $0 as AnyObject }
let placeholders = NSString . rs_SQLValueList ( withPlaceholders : UInt ( articleIDs . count ) ) !
let whereClause = " articleID in \( placeholders ) "
return fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters , withLimits : false )
}
// MARK: - F e t c h i n g U n r e a d A r t i c l e s
func fetchUnreadArticles ( _ feedIDs : Set < String > ) -> Set < Article > {
@@ -88,17 +74,6 @@ final class ArticlesTable: DatabaseTable {
fetchArticlesAsync ( { self . fetchUnreadArticles ( feedIDs , $0 ) } , callback )
}
private func fetchUnreadArticles ( _ feedIDs : Set < String > , _ database : FMDatabase ) -> Set < Article > {
// s e l e c t * f r o m a r t i c l e s n a t u r a l j o i n s t a t u s e s w h e r e f e e d I D i n ( ' h t t p : / / r a n c h e r o . c o m / x m l / r s s . x m l ' ) a n d r e a d = 0
if feedIDs . isEmpty {
return Set < Article > ( )
}
let parameters = feedIDs . map { $0 as AnyObject }
let placeholders = NSString . rs_SQLValueList ( withPlaceholders : UInt ( feedIDs . count ) ) !
let whereClause = " feedID in \( placeholders ) and read=0 "
return fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters , withLimits : true )
}
// MARK: - F e t c h i n g T o d a y A r t i c l e s
func fetchArticlesSince ( _ feedIDs : Set < String > , _ cutoffDate : Date ) -> Set < Article > {
@@ -109,19 +84,6 @@ final class ArticlesTable: DatabaseTable {
fetchArticlesAsync ( { self . fetchArticlesSince ( feedIDs , cutoffDate , $0 ) } , callback )
}
private func fetchArticlesSince ( _ feedIDs : Set < String > , _ cutoffDate : Date , _ database : FMDatabase ) -> Set < Article > {
// s e l e c t * f r o m a r t i c l e s n a t u r a l j o i n s t a t u s e s w h e r e f e e d I D i n ( ' h t t p : / / r a n c h e r o . c o m / x m l / r s s . x m l ' ) a n d ( d a t e P u b l i s h e d > ? | | ( d a t e P u b l i s h e d i s n u l l a n d d a t e A r r i v e d > ? )
//
// d a t e P u b l i s h e d m a y b e n i l , s o w e f a l l b a c k t o d a t e A r r i v e d .
if feedIDs . isEmpty {
return Set < Article > ( )
}
let parameters = feedIDs . map { $0 as AnyObject } + [ cutoffDate as AnyObject , cutoffDate as AnyObject ]
let placeholders = NSString . rs_SQLValueList ( withPlaceholders : UInt ( feedIDs . count ) ) !
let whereClause = " feedID in \( placeholders ) and (datePublished > ? or (datePublished is null and dateArrived > ?)) "
return fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters , withLimits : false )
}
// MARK: - F e t c h i n g S t a r r e d A r t i c l e s
func fetchStarredArticles ( _ feedIDs : Set < String > ) -> Set < Article > {
@@ -132,17 +94,6 @@ final class ArticlesTable: DatabaseTable {
fetchArticlesAsync ( { self . fetchStarredArticles ( feedIDs , $0 ) } , callback )
}
private func fetchStarredArticles ( _ feedIDs : Set < String > , _ database : FMDatabase ) -> Set < Article > {
// s e l e c t * f r o m a r t i c l e s n a t u r a l j o i n s t a t u s e s w h e r e f e e d I D i n ( ' h t t p : / / r a n c h e r o . c o m / x m l / r s s . x m l ' ) a n d s t a r r e d = 1 ;
if feedIDs . isEmpty {
return Set < Article > ( )
}
let parameters = feedIDs . map { $0 as AnyObject }
let placeholders = NSString . rs_SQLValueList ( withPlaceholders : UInt ( feedIDs . count ) ) !
let whereClause = " feedID in \( placeholders ) and starred = 1 "
return fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters , withLimits : false )
}
// MARK: - F e t c h i n g S e a r c h A r t i c l e s
func fetchArticlesMatching ( _ searchString : String , _ feedIDs : Set < String > ) -> Set < Article > {
@@ -173,7 +124,7 @@ final class ArticlesTable: DatabaseTable {
let placeholders = NSString . rs_SQLValueList ( withPlaceholders : UInt ( searchRowIDs . count ) ) !
let whereClause = " searchRowID in \( placeholders ) "
let parameters : [ AnyObject ] = Array ( searchRowIDs ) as [ AnyObject ]
let articles = fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters , withLimits : true )
let articles = fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters )
// TODO: i n c l u d e t h e f e e d I D s i n t h e S Q L r a t h e r t h a n f i l t e r i n g h e r e .
return articles . filter { feedIDs . contains ( $0 . feedID ) }
}
@@ -207,9 +158,74 @@ final class ArticlesTable: DatabaseTable {
// MARK: - U p d a t i n g
func update ( _ parsedItems : Set < ParsedItem > , _ webFeedID : String , _ completion : @ escaping UpdateArticlesCompletionBlock ) {
precondition ( retentionStyle = = . feedBased )
if parsedItems . isEmpty {
callUpdateArticlesCompletionBlock ( nil , nil , nil , completion )
return
}
// 1 . E n s u r e s t a t u s e s f o r a l l t h e i n c o m i n g a r t i c l e s .
// 2 . C r e a t e i n c o m i n g a r t i c l e s w i t h p a r s e d I t e m s .
// 3 . [ D e l e t e d - t h i s s t e p i s n o l o n g e r n e e d e d ]
// 4 . F e t c h a l l a r t i c l e s f o r t h e f e e d .
// 5 . C r e a t e a r r a y o f A r t i c l e s n o t i n d a t a b a s e a n d s a v e t h e m .
// 6 . C r e a t e a r r a y o f u p d a t e d A r t i c l e s a n d s a v e w h a t ’ s c h a n g e d .
// 7 . C a l l b a c k w i t h n e w a n d u p d a t e d A r t i c l e s .
// 8 . D e l e t e A r t i c l e s i n d a t a b a s e n o l o n g e r p r e s e n t i n t h e f e e d .
// 9 . U p d a t e s e a r c h i n d e x .
self . queue . runInTransaction { database in
let articleIDs = parsedItems . articleIDs ( )
let statusesDictionary = self . statusesTable . ensureStatusesForArticleIDs ( articleIDs , false , database ) // 1
assert ( statusesDictionary . count = = articleIDs . count )
let incomingArticles = Article . articlesWithParsedItems ( parsedItems , webFeedID , self . accountID , statusesDictionary ) // 2
if incomingArticles . isEmpty {
self . callUpdateArticlesCompletionBlock ( nil , nil , nil , completion )
return
}
let fetchedArticles = self . fetchArticlesForFeedID ( webFeedID , database ) // 4
let fetchedArticlesDictionary = fetchedArticles . dictionary ( )
let newArticles = self . findAndSaveNewArticles ( incomingArticles , fetchedArticlesDictionary , database ) // 5
let updatedArticles = self . findAndSaveUpdatedArticles ( incomingArticles , fetchedArticlesDictionary , database ) // 6
// A r t i c l e s t o d e l e t e a r e 1 ) n o t s t a r r e d a n d 2 ) o l d e r t h a n 3 0 d a y s a n d 3 ) n o l o n g e r i n f e e d .
let cutoffDate = Date ( ) . bySubtracting ( days : 30 )
let articlesToDelete = fetchedArticles . filter { ( article ) -> Bool in
return ! article . status . starred && article . status . dateArrived < cutoffDate && ! articleIDs . contains ( article . articleID )
}
self . callUpdateArticlesCompletionBlock ( newArticles , updatedArticles , articlesToDelete , completion ) // 7
self . addArticlesToCache ( newArticles )
self . addArticlesToCache ( updatedArticles )
// 8 . D e l e t e a r t i c l e s n o l o n g e r i n f e e d .
let articleIDsToDelete = articlesToDelete . articleIDs ( )
if ! articleIDsToDelete . isEmpty {
self . removeArticles ( articleIDsToDelete , database )
self . removeArticleIDsFromCache ( articleIDsToDelete )
}
// 9 . U p d a t e s e a r c h i n d e x .
if let newArticles = newArticles {
self . searchTable . indexNewArticles ( newArticles , database )
}
if let updatedArticles = updatedArticles {
self . searchTable . indexUpdatedArticles ( updatedArticles , database )
}
}
}
func update ( _ feedIDsAndItems : [ String : Set < ParsedItem > ] , _ read : Bool , _ completion : @ escaping UpdateArticlesCompletionBlock ) {
precondition ( retentionStyle = = . syncSystem )
if feedIDsAndItems . isEmpty {
completion ( nil , nil )
callUpdateArticlesC ompletionBlock ( nil , nil , nil , completion )
return
}
@@ -233,13 +249,13 @@ final class ArticlesTable: DatabaseTable {
let allIncomingArticles = Article . articlesWithFeedIDsAndItems ( feedIDsAndItems , self . accountID , statusesDictionary ) // 2
if allIncomingArticles . isEmpty {
self . callUpdateArticlesCompletionBlock ( nil , nil , completion )
self . callUpdateArticlesCompletionBlock ( nil , nil , nil , completion )
return
}
let incomingArticles = self . filterIncomingArticles ( allIncomingArticles ) // 3
if incomingArticles . isEmpty {
self . callUpdateArticlesCompletionBlock ( nil , nil , completion )
self . callUpdateArticlesCompletionBlock ( nil , nil , nil , completion )
return
}
@@ -250,22 +266,17 @@ final class ArticlesTable: DatabaseTable {
let newArticles = self . findAndSaveNewArticles ( incomingArticles , fetchedArticlesDictionary , database ) // 5
let updatedArticles = self . findAndSaveUpdatedArticles ( incomingArticles , fetchedArticlesDictionary , database ) // 6
self . callUpdateArticlesCompletionBlock ( newArticles , updatedArticles , completion ) // 7
self . callUpdateArticlesCompletionBlock ( newArticles , updatedArticles , nil , completion ) // 7
self . addArticlesToCache ( newArticles )
self . addArticlesToCache ( updatedArticles )
// 8 . U p d a t e s e a r c h i n d e x .
var articlesToIndex = Set < Article > ( )
if let newArticles = newArticles {
articlesToIndex . formUnion ( newArticles )
self . searchTable . indexNewArticles ( newArticles , database )
}
if let updatedArticles = updatedArticles {
articlesToIndex . formUnion ( updatedArticles )
}
let articleIDsToIndex = articlesToIndex . articleIDs ( )
if articleIDsToIndex . isEmpty {
return
}
DispatchQueue . main . async {
self . searchTable . ensureIndexedArticles ( for : articleIDsToIndex )
self . searchTable . indexUpdatedArticles ( updatedArticles , database )
}
}
}
@@ -277,7 +288,7 @@ final class ArticlesTable: DatabaseTable {
self . statusesTable . mark ( statuses , statusKey , flag , database )
}
}
// MARK: - U n r e a d C o u n t s
func fetchUnreadCounts ( _ feedIDs : Set < String > , _ completion : @ escaping UnreadCountCompletionBlock ) {
@@ -330,7 +341,7 @@ final class ArticlesTable: DatabaseTable {
let cutoffDate = articleCutoffDate
queue . runInDatabase { ( database ) in
let sql = " select distinct feedID, count(*) from articles natural join statuses where read=0 and (starred=1 or dateArrived>?) group by feedID; "
let sql = " select distinct feedID, count(*) from articles natural join statuses where read=0 group by feedID; "
guard let resultSet = database . executeQuery ( sql , withArgumentsIn : [ cutoffDate ] ) else {
DispatchQueue . main . async {
@@ -418,7 +429,7 @@ final class ArticlesTable: DatabaseTable {
func emptyCaches ( ) {
queue . runInDatabase { _ in
self . databaseA rticlesCache = [ String : Database Article] ( )
self . a rticlesCache = [ String : Article ] ( )
}
}
@@ -541,110 +552,64 @@ private extension ArticlesTable {
}
func articlesWithResultSet ( _ resultSet : FMResultSet , _ database : FMDatabase ) -> Set < Article > {
// 1 . C r e a t e D a t a b a s e A r t i c l e s w i t h o u t r e l a t e d o b j e c t s .
// 2 . T h e n f e t c h t h e r e l a t e d o b j e c t s , g i v e n t h e s e t o f a r t i c l e I D s .
// 3 . T h e n c r e a t e s e t o f A r t i c l e s w i t h D a t a b a s e A r t i c l e s a n d r e l a t e d o b j e c t s a n d r e t u r n i t .
var cachedArticles = Set < Article > ( )
var fetchedArticles = Set < Article > ( )
// 1 . C r e a t e d a t a b a s e A r t i c l e s ( i n t e r m e d i a t e r e p r e s e n t a t i o n s ) .
while resultSet . next ( ) {
let databaseA rticles = makeDatabaseArticles ( with : resultSet )
if databaseArticles . isEmpty {
return Set < Article > ( )
}
let articleIDs = databaseArticles . articleIDs ( )
// 2 . F e t c h r e l a t e d o b j e c t s .
let authorsMap = authorsLookupTable . fetchRelatedObjects ( for : articleIDs , in : database )
// 3 . C r e a t e a r t i c l e s w i t h r e l a t e d o b j e c t s .
let articles = databaseArticles . map { ( databaseArticle ) -> Article in
return articleWithDatabaseArticle ( databaseArticle , authorsMap )
}
return Set ( articles )
}
func articleWithDatabaseArticle ( _ databaseArticle : DatabaseArticle , _ authorsMap : RelatedObjectsMap ? ) -> Article {
let articleID = databaseArticle . articleID
let authors = authorsMap ? . authors ( for : articleID )
return Article ( databaseArticle : databaseArticle , accountID : accountID , authors : authors )
}
func makeDatabaseArticles ( with resultSet : FMResultSet ) -> Set < DatabaseArticle > {
let articles = resultSet . mapToSet { ( row ) -> DatabaseArticle ? in
guard let articleID = row . string ( forColumn : DatabaseKey . articleID ) else {
guard let a rticleID = resultSet . string ( forColumn : DatabaseKey . articleID ) else {
assertionFailure ( " Expected articleID. " )
return nil
continue
}
// A r t i c l e s a r e r e m o v e d f r o m t h e c a c h e w h e n t h e y ’ r e u p d a t e d .
// S e e s a v e U p d a t e d A r t i c l e s .
if let databaseArticle = databaseArticlesCache [ articleID ] {
return databaseArticle
if let article = articlesCache [ articleID ] {
cachedArticles . insert ( article )
continue
}
// T h e r e s u l t S e t i s a r e s u l t o f a J O I N q u e r y w i t h t h e s t a t u s e s t a b l e ,
// s o w e c a n g e t t h e s t a t u s e s a t t h e s a m e t i m e a n d a v o i d a d d i t i o n a l d a t a b a s e l o o k u p s .
guard let status = statusesTable . statusWithRow ( resultSet , articleID : articleID ) else {
assertionFailure ( " Expected status. " )
return nil
}
guard let feedID = row . string ( forColumn : DatabaseKey . feedID ) else {
assertionFailure ( " Expected feedID. " )
return nil
}
guard let uniqueID = row . string ( forColumn : DatabaseKey . uniqueID ) else {
assertionFailure ( " Expected uniqueID. " )
return nil
continue
}
let tit le = row . string ( forColumn : DatabaseKey . title )
let contentHTML = row . string ( forColumn : DatabaseKey . contentHTML )
let contentText = row . string ( forColumn : DatabaseKey . contentText )
let url = row . string ( forColumn : DatabaseKey . url )
let externalURL = row . string ( forColumn : DatabaseKey . externalURL )
let summary = row . string ( forColumn : DatabaseKey . summary )
let imageURL = row . string ( forColumn : DatabaseKey . imageURL )
let bannerImageURL = row . string ( forColumn : DatabaseKey . bannerImageURL )
let datePublished = row . date ( forColumn : DatabaseKey . datePublished )
let dateModified = row . date ( forColumn : DatabaseKey . dateModified )
guard let ar tic le = Article ( accountID : accountID , row : resultSet , status : status ) else {
continue
}
fetchedArticles . insert ( article )
}
resultSet . close ( )
let databaseArticle = DatabaseArticle ( articleID : articleID , feedID : feedID , uniqueID : uniqueID , title : title , contentHTML : contentHTML , contentText : contentText , url : url , externalURL : externalURL , summary : summary , imageURL : imageURL , bannerImageURL : bannerImageURL , datePublished : datePublished , dateModified : dateModified , status : status )
databaseArticlesCache [ articleID ] = database Article
return databaseArticle
if fetchedArticles . isEmpty {
return cached Articles
}
return articles
// F e t c h a u t h o r s f o r n o n - c a c h e d a r t i c l e s . ( A r t i c l e s f r o m t h e c a c h e a l r e a d y h a v e a u t h o r s . )
let fetchedArticleIDs = fetchedArticles . articleIDs ( )
let authorsMap = authorsLookupTable . fetchRelatedObjects ( for : fetchedArticleIDs , in : database )
let articlesWithFetchedAuthors = fetchedArticles . map { ( article ) -> Article in
if let authors = authorsMap ? . authors ( for : article . articleID ) {
return article . byAdding ( authors )
}
return article
}
// A d d f e t c h e d A r t i c l e s t o c a c h e , n o w t h a t t h e y h a v e a t t a c h e d a u t h o r s .
for article in articlesWithFetchedAuthors {
articlesCache [ article . articleID ] = article
}
return cachedArticles . union ( articlesWithFetchedAuthors )
}
func fetchArticlesWithWhereClause ( _ database : FMDatabase , whereClause : String , parameters : [ AnyObject ] , withLimits : Bool ) -> Set < Article > {
// D o n ’ t f e t c h a r t i c l e s t h a t s h o u l d n ’ t a p p e a r i n t h e U I . T h e r u l e s :
// * M u s t n o t b e d e l e t e d .
// * M u s t b e e i t h e r 1 ) s t a r r e d o r 2 ) d a t e A r r i v e d m u s t b e n e w e r t h a n c u t o f f d a t e .
if withLimits {
let sql = " select * from articles natural join statuses where \( whereClause ) and (starred=1 or dateArrived>?); "
return articlesWithSQL ( sql , parameters + [ articleCutoffDate as AnyObject ] , database )
}
else {
let sql = " select * from articles natural join statuses where \( whereClause ) ; "
return articlesWithSQL ( sql , parameters , database )
}
func fetchArticlesWithWhereClause ( _ database : FMDatabase , whereClause : String , parameters : [ AnyObject ] ) -> Set < Article > {
let sql = " select * from articles natural join statuses where \( whereClause ) ; "
return articlesWithSQL ( sql , parameters , database )
}
func fetchUnreadCount ( _ feedID : String , _ database : FMDatabase ) -> Int {
// C o u n t o n l y t h e a r t i c l e s t h a t w o u l d a p p e a r i n t h e U I .
// * M u s t b e u n r e a d .
// * M u s t n o t b e d e l e t e d .
// * M u s t b e e i t h e r 1 ) s t a r r e d o r 2 ) d a t e A r r i v e d m u s t b e n e w e r t h a n c u t o f f d a t e .
let sql = " select count(*) from articles natural join statuses where feedID=? and read=0 and (starred=1 or dateArrived>?); "
let sql = " select count(*) from articles natural join statuses where feedID=? and read=0; "
return numberWithSQLAndParameters ( sql , [ feedID , articleCutoffDate ] , in : database )
}
@@ -663,7 +628,7 @@ private extension ArticlesTable {
let placeholders = NSString . rs_SQLValueList ( withPlaceholders : UInt ( searchRowIDs . count ) ) !
let whereClause = " searchRowID in \( placeholders ) "
let parameters : [ AnyObject ] = Array ( searchRowIDs ) as [ AnyObject ]
return fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters , withLimits : true )
return fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters )
}
func sqliteSearchString ( with searchString : String ) -> String {
@@ -688,14 +653,64 @@ private extension ArticlesTable {
return articlesWithResultSet ( resultSet , database )
}
func fetchUnreadArticles ( _ feedIDs : Set < String > , _ database : FMDatabase ) -> Set < Article > {
// s e l e c t * f r o m a r t i c l e s n a t u r a l j o i n s t a t u s e s w h e r e f e e d I D i n ( ' h t t p : / / r a n c h e r o . c o m / x m l / r s s . x m l ' ) a n d r e a d = 0
if feedIDs . isEmpty {
return Set < Article > ( )
}
let parameters = feedIDs . map { $0 as AnyObject }
let placeholders = NSString . rs_SQLValueList ( withPlaceholders : UInt ( feedIDs . count ) ) !
let whereClause = " feedID in \( placeholders ) and read=0 "
return fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters )
}
func fetchArticlesForFeedID ( _ feedID : String , _ database : FMDatabase ) -> Set < Article > {
return fetchArticlesWithWhereClause ( database , whereClause : " articles.feedID = ? " , parameters : [ feedID as AnyObject ] )
}
func fetchArticles ( articleIDs : Set < String > , _ database : FMDatabase ) -> Set < Article > {
if articleIDs . isEmpty {
return Set < Article > ( )
}
let parameters = articleIDs . map { $0 as AnyObject }
let placeholders = NSString . rs_SQLValueList ( withPlaceholders : UInt ( articleIDs . count ) ) !
let whereClause = " articleID in \( placeholders ) "
return fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters )
}
func fetchArticlesSince ( _ feedIDs : Set < String > , _ cutoffDate : Date , _ database : FMDatabase ) -> Set < Article > {
// s e l e c t * f r o m a r t i c l e s n a t u r a l j o i n s t a t u s e s w h e r e f e e d I D i n ( ' h t t p : / / r a n c h e r o . c o m / x m l / r s s . x m l ' ) a n d ( d a t e P u b l i s h e d > ? | | ( d a t e P u b l i s h e d i s n u l l a n d d a t e A r r i v e d > ? )
//
// d a t e P u b l i s h e d m a y b e n i l , s o w e f a l l b a c k t o d a t e A r r i v e d .
if feedIDs . isEmpty {
return Set < Article > ( )
}
let parameters = feedIDs . map { $0 as AnyObject } + [ cutoffDate as AnyObject , cutoffDate as AnyObject ]
let placeholders = NSString . rs_SQLValueList ( withPlaceholders : UInt ( feedIDs . count ) ) !
let whereClause = " feedID in \( placeholders ) and (datePublished > ? or (datePublished is null and dateArrived > ?)) "
return fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters )
}
func fetchStarredArticles ( _ feedIDs : Set < String > , _ database : FMDatabase ) -> Set < Article > {
// s e l e c t * f r o m a r t i c l e s n a t u r a l j o i n s t a t u s e s w h e r e f e e d I D i n ( ' h t t p : / / r a n c h e r o . c o m / x m l / r s s . x m l ' ) a n d s t a r r e d = 1 ;
if feedIDs . isEmpty {
return Set < Article > ( )
}
let parameters = feedIDs . map { $0 as AnyObject }
let placeholders = NSString . rs_SQLValueList ( withPlaceholders : UInt ( feedIDs . count ) ) !
let whereClause = " feedID in \( placeholders ) and starred=1 "
return fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters )
}
// MARK: - S a v i n g P a r s e d I t e m s
func callUpdateArticlesCompletionBlock ( _ newArticles : Set < Article > ? , _ updatedArticles : Set < Article > ? , _ completion : @ escaping UpdateArticlesCompletionBlock ) {
func callUpdateArticlesCompletionBlock ( _ newArticles : Set < Article > ? , _ updatedArticles : Set < Article > ? , _ deletedArticles : Set < Article > ? , _ completion : @ escaping UpdateArticlesCompletionBlock ) {
let articleChanges = ArticleChanges ( newArticles : newArticles , updatedArticles : updatedArticles , deletedArticles : deletedArticles )
DispatchQueue . main . async {
completion ( newA rticles , updatedArticl es)
completion ( a rticleChang es)
}
}
// MARK: - S a v i n g N e w A r t i c l e s
func findNewArticles ( _ incomingArticles : Set < Article > , _ fetchedArticlesDictionary : [ String : Article ] ) -> Set < Article > ? {
@@ -770,7 +785,6 @@ private extension ArticlesTable {
func saveUpdatedArticles ( _ updatedArticles : Set < Article > , _ fetchedArticles : [ String : Article ] , _ database : FMDatabase ) {
removeArticlesFromDatabaseArticlesCache ( updatedArticles )
saveUpdatedRelatedObjects ( updatedArticles , fetchedArticles , database )
for updatedArticle in updatedArticles {
@@ -795,24 +809,32 @@ private extension ArticlesTable {
updateRowsWithDictionary ( changesDictionary , whereKey : DatabaseKey . articleID , matches : updatedArticle . articleID , database : database )
}
func remove ArticlesFromDatabaseArticles Cache( _ updatedA rticles: Set < Article > ) {
let articleID s = updatedArticles . articleIDs ( )
for articleID in articleIDs {
databaseArticlesCache [ articleID ] = nil
func add ArticlesTo Cache( _ a rticles: Set < Article > ? ) {
guard let articles = articles else {
return
}
for article in articles {
articlesCache [ article . articleID ] = article
}
}
func statusIndicatesArticleIsIgnorable ( _ status : ArticleStatus ) -> Bool {
// I g n o r a b l e a r t i c l e s : n o t s t a r r e d a n d a r r i v a l d a t e > 4 m o n t h s .
if status . starred {
func removeArticleIDsFromCache ( _ articleIDs : Set < String > ) {
for articleID in articleIDs {
articlesCache [ articleID ] = nil
}
}
func articleIsIgnorable ( _ article : Article ) -> Bool {
if article . status . starred || ! article . status . read {
return false
}
return status . dateArrived < maximumA rticleCutoffDate
return article . status. dateArrived < a rticleCutoffDate
}
func filterIncomingArticles ( _ articles : Set < Article > ) -> Set < Article > {
// D r o p A r t i c l e s t h a t w e c a n i g n o r e .
return Set ( articles . filter { ! statusIndicatesArticleIsIgnorable ( $0 . status ) } )
precondition ( retentionStyle = = . syncSystem )
return Set ( articles . filter { ! articleIsIgnorable ( $0 ) } )
}
func removeArticles ( _ articleIDs : Set < String > , _ database : FMDatabase ) {