2017-07-03 20:20:14 +02:00
//
2017-07-29 21:08:10 +02:00
// A r t i c l e s T a b l e . s w i f t
2017-07-03 20:20:14 +02:00
// E v e r g r e e n
//
// C r e a t e d b y B r e n t S i m m o n s o n 5 / 9 / 1 6 .
// C o p y r i g h t © 2 0 1 6 R a n c h e r o S o f t w a r e , L L C . A l l r i g h t s r e s e r v e d .
//
import Foundation
2017-08-23 22:23:12 +02:00
import RSCore
2017-07-29 21:29:05 +02:00
import RSDatabase
2017-08-23 22:23:12 +02:00
import RSParser
2017-07-03 20:20:14 +02:00
import Data
2017-08-21 22:31:14 +02:00
final class ArticlesTable : DatabaseTable {
let name : String
2017-09-05 17:53:45 +02:00
private let accountID : String
2017-08-27 00:37:15 +02:00
private let queue : RSDatabaseQueue
2017-09-05 17:53:45 +02:00
private let statusesTable : StatusesTable
2017-08-23 22:23:12 +02:00
private let authorsLookupTable : DatabaseLookupTable
private let attachmentsLookupTable : DatabaseLookupTable
private let tagsLookupTable : DatabaseLookupTable
2017-09-05 03:29:02 +02:00
2017-08-31 22:35:48 +02:00
// 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 .
private var articleCutoffDate = NSDate . rs_dateWithNumberOfDays ( inThePast : 3 * 31 ) !
2017-09-05 02:10:02 +02:00
private var maximumArticleCutoffDate = NSDate . rs_dateWithNumberOfDays ( inThePast : 4 * 31 ) !
2017-08-31 22:35:48 +02:00
2017-09-05 17:53:45 +02:00
init ( name : String , accountID : String , queue : RSDatabaseQueue ) {
2017-07-29 21:29:05 +02:00
2017-08-21 22:31:14 +02:00
self . name = name
2017-09-05 17:53:45 +02:00
self . accountID = accountID
2017-08-27 00:37:15 +02:00
self . queue = queue
2017-09-05 17:53:45 +02:00
let statusesTable = StatusesTable ( queue : queue )
2017-08-23 22:23:12 +02:00
let authorsTable = AuthorsTable ( name : DatabaseTableName . authors )
self . authorsLookupTable = DatabaseLookupTable ( name : DatabaseTableName . authorsLookup , objectIDKey : DatabaseKey . articleID , relatedObjectIDKey : DatabaseKey . authorID , relatedTable : authorsTable , relationshipName : RelationshipName . authors )
let tagsTable = TagsTable ( name : DatabaseTableName . tags )
self . tagsLookupTable = DatabaseLookupTable ( name : DatabaseTableName . tags , objectIDKey : DatabaseKey . articleID , relatedObjectIDKey : DatabaseKey . tagName , relatedTable : tagsTable , relationshipName : RelationshipName . tags )
let attachmentsTable = AttachmentsTable ( name : DatabaseTableName . attachments )
self . attachmentsLookupTable = DatabaseLookupTable ( name : DatabaseTableName . attachmentsLookup , objectIDKey : DatabaseKey . articleID , relatedObjectIDKey : DatabaseKey . attachmentID , relatedTable : attachmentsTable , relationshipName : RelationshipName . attachments )
2017-08-21 22:31:14 +02:00
}
2017-07-29 21:29:05 +02:00
2017-08-21 22:31:14 +02:00
// MARK: F e t c h i n g
2017-08-23 22:23:12 +02:00
func fetchArticles ( _ feed : Feed ) -> Set < Article > {
2017-08-21 22:31:14 +02:00
2017-08-27 00:37:15 +02:00
let feedID = feed . feedID
2017-08-27 22:03:15 +02:00
var articles = Set < Article > ( )
2017-08-27 00:37:15 +02:00
2017-09-05 03:29:02 +02:00
queue . fetchSync { ( database ) in
2017-09-05 02:10:02 +02:00
articles = self . fetchArticlesForFeedID ( feedID , withLimits : true , database : database )
2017-08-27 00:37:15 +02:00
}
2017-09-05 03:29:02 +02:00
return articles
2017-08-21 22:31:14 +02:00
}
2017-09-05 02:10:02 +02:00
func fetchArticlesAsync ( _ feed : Feed , withLimits : Bool , _ resultBlock : @ escaping ArticleResultBlock ) {
2017-08-21 22:31:14 +02:00
2017-08-27 00:37:15 +02:00
let feedID = feed . feedID
2017-09-05 03:29:02 +02:00
queue . fetch { ( database ) in
2017-08-27 00:37:15 +02:00
2017-09-05 03:29:02 +02:00
let articles = self . fetchArticlesForFeedID ( feedID , withLimits : withLimits , database : database )
2017-08-27 00:37:15 +02:00
DispatchQueue . main . async {
resultBlock ( articles )
}
}
2017-08-21 22:31:14 +02:00
}
2017-08-27 22:03:15 +02:00
func fetchUnreadArticles ( for feeds : Set < Feed > ) -> Set < Article > {
return fetchUnreadArticles ( feeds . feedIDs ( ) )
2017-08-21 22:31:14 +02:00
}
2017-09-01 22:31:27 +02:00
2017-08-23 22:23:12 +02:00
// MARK: U p d a t i n g
2017-09-06 22:33:04 +02:00
func update ( _ feed : Feed , _ parsedFeed : ParsedFeed , _ completion : @ escaping UpdateArticlesWithFeedCompletionBlock ) {
2017-09-02 23:19:42 +02:00
if parsedFeed . items . isEmpty {
2017-09-06 22:33:04 +02:00
completion ( nil , nil )
2017-09-02 23:19:42 +02:00
return
}
2017-09-05 17:53:45 +02:00
// 1 . E n s u r e s t a t u s e s f o r a l l t h e p a r s e d I t e m s .
// 2 . I g n o r e p a r s e d I t e m s t h a t a r e u s e r D e l e t e d | | ( ! s t a r r e d a n d r e a l l y o l d )
// 3 . 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 .
// 4 . C r e a t e A r t i c l e s w i t h p a r s e d I t e m s .
2017-09-06 22:33:04 +02:00
// 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 .
2017-09-05 17:53:45 +02:00
let feedID = feed . feedID
let parsedItemArticleIDs = Set ( parsedFeed . items . map { $0 . databaseIdentifierWithFeed ( feed ) } )
let parsedItemsDictionary = parsedFeed . itemsDictionary ( with : feed )
2017-09-06 22:33:04 +02:00
statusesTable . ensureStatusesForArticleIDs ( parsedItemArticleIDs ) { // 1
2017-09-05 17:53:45 +02:00
2017-09-06 22:33:04 +02:00
let filteredParsedItems = self . filterParsedItems ( parsedItemsDictionary ) // 2
2017-09-05 17:53:45 +02:00
if filteredParsedItems . isEmpty {
2017-09-06 22:33:04 +02:00
completion ( nil , nil )
2017-09-05 17:53:45 +02:00
return
}
2017-09-06 22:33:04 +02:00
queue . update { ( database ) in
2017-09-05 17:53:45 +02:00
2017-09-06 22:33:04 +02:00
let fetchedArticles = self . fetchArticlesForFeedID ( feedID , withLimits : false , database : database ) // 3
let fetchedArticlesDictionary = fetchedArticles . dictionary ( )
2017-09-05 17:53:45 +02:00
2017-09-06 22:33:04 +02:00
let incomingArticles = Article . articlesWithParsedItems ( filteredParsedItems , accountID , feedID ) // 4
let incomingArticlesDictionary = incomingArticles . dictionary ( )
let newArticles = Set ( incomingArticles . filter { fetchedArticles [ $0 . articleID ] = = nil } ) // 5
if ! newArticles . isEmpty {
saveNewArticles ( newArticles , database )
}
let updatedArticles = incomingArticles . filter { ( incomingArticle ) -> Bool in // 6
if let existingArticle = fetchedArticles [ incomingArticle . articleID ] {
if existingArticle != incomingArticle {
return true
}
}
return false
}
if ! updatedArticles . isEmpty {
saveUpdatedArticles ( Set ( updatedArticles ) , fetchedArticlesDictionary , database )
}
DispatchQueue . main . async {
completion ( newArticles , updatedArticles ) // 7
}
2017-09-05 17:53:45 +02:00
}
2017-09-02 23:19:42 +02:00
}
2017-08-23 22:23:12 +02:00
}
2017-09-01 22:31:27 +02:00
2017-08-23 22:23:12 +02:00
// MARK: U n r e a d C o u n t s
func fetchUnreadCounts ( _ feeds : Set < Feed > , _ completion : @ escaping UnreadCountCompletionBlock ) {
2017-09-01 22:31:27 +02:00
let feedIDs = feeds . feedIDs ( )
var unreadCountTable = UnreadCountTable ( )
queue . fetch { ( database ) in
for feedID in feedIDs {
unreadCountTable [ feedID ] = self . fetchUnreadCount ( feedID , database )
}
DispatchQueue . main . async ( ) {
completion ( unreadCountTable )
}
}
2017-08-23 22:23:12 +02:00
}
2017-09-01 22:31:27 +02:00
2017-08-23 22:23:12 +02:00
// MARK: S t a t u s
func mark ( _ articles : Set < Article > , _ statusKey : String , _ flag : Bool ) {
2017-08-29 22:32:36 +02:00
// S e t s f l a g i n b o t h m e m o r y a n d i n d a t a b a s e .
let articleIDs = articles . flatMap { ( article ) -> String ? in
guard let status = article . status else {
assertionFailure ( " Each article must have a status. " )
return nil
}
if status . boolStatus ( forKey : statusKey ) = = flag {
return nil
}
status . setBoolStatus ( flag , forKey : statusKey )
return article . articleID
}
if articleIDs . isEmpty {
return
}
queue . update { ( database ) in
self . statusesTable . markArticleIDs ( Set ( articleIDs ) , statusKey , flag , database )
}
2017-08-23 22:23:12 +02:00
}
2017-08-21 22:31:14 +02:00
}
2017-07-29 21:29:05 +02:00
2017-09-02 23:19:42 +02:00
// MARK: - P r i v a t e
2017-08-27 00:37:15 +02:00
private extension ArticlesTable {
// MARK: F e t c h i n g
2017-09-06 22:33:04 +02:00
// f u n c a t t a c h R e l a t e d O b j e c t s ( _ a r t i c l e s : S e t < A r t i c l e > , _ d a t a b a s e : F M D a t a b a s e ) {
//
// l e t a r t i c l e A r r a y = a r t i c l e s . m a p { $ 0 a s D a t a b a s e O b j e c t }
//
// a u t h o r s L o o k u p T a b l e . a t t a c h R e l a t e d O b j e c t s ( t o : a r t i c l e A r r a y , i n : d a t a b a s e )
// a t t a c h m e n t s L o o k u p T a b l e . a t t a c h R e l a t e d O b j e c t s ( t o : a r t i c l e A r r a y , i n : d a t a b a s e )
// t a g s L o o k u p T a b l e . a t t a c h R e l a t e d O b j e c t s ( t o : a r t i c l e A r r a y , i n : d a t a b a s e )
//
// / / I n t h e o r y , i t ’ s i m p o s s i b l e t o h a v e a f e t c h e d a r t i c l e w i t h o u t a s t a t u s .
// / / L e t ’ s h a n d l e t h a t i m p o s s i b i l i t y a n y w a y .
// / / R e m e m b e r t h a t , i f n o t h i n g e l s e , t h e u s e r c a n e d i t t h e S Q L i t e d a t a b a s e ,
// / / a n d t h u s c o u l d d e l e t e a l l t h e i r s t a t u s e s .
//
// s t a t u s e s T a b l e . e n s u r e S t a t u s e s F o r A r t i c l e s ( a r t i c l e s , d a t a b a s e )
// }
2017-08-27 00:37:15 +02:00
func articleWithRow ( _ row : FMResultSet ) -> Article ? {
guard let account = account else {
return nil
}
guard let article = Article ( row : row , account : account ) else {
return nil
}
// N o t e : t h e r o w 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 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 .
article . status = statusesTable . statusWithRow ( row )
return article
}
func articlesWithResultSet ( _ resultSet : FMResultSet , _ database : FMDatabase ) -> Set < Article > {
let articles = resultSet . mapToSet ( articleWithRow )
attachRelatedObjects ( articles , database )
return articles
}
2017-09-05 02:10:02 +02:00
func fetchArticlesWithWhereClause ( _ database : FMDatabase , whereClause : String , parameters : [ AnyObject ] , withLimits : Bool ) -> Set < Article > {
2017-08-27 00:37:15 +02:00
2017-08-31 22:35:48 +02:00
// 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 .
2017-09-05 02:10:02 +02:00
let sql = withLimits ? " select * from articles natural join statuses where \( whereClause ) and userDeleted=0 and (starred=1 or dateArrived>?); " : " select * from articles natural join statuses where \( whereClause ) ; "
2017-08-31 22:35:48 +02:00
return articlesWithSQL ( sql , parameters + [ articleCutoffDate as AnyObject ] , database )
2017-08-27 22:03:15 +02:00
}
2017-08-31 22:35:48 +02:00
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 userDeleted=0 and (starred=1 or dateArrived>?); "
return numberWithSQLAndParameters ( sql , [ feedID , articleCutoffDate ] , in : database )
}
2017-09-05 02:10:02 +02:00
func fetchArticlesForFeedID ( _ feedID : String , withLimits : Bool , database : FMDatabase ) -> Set < Article > {
2017-08-27 22:03:15 +02:00
2017-09-05 02:10:02 +02:00
return fetchArticlesWithWhereClause ( database , whereClause : " articles.feedID = ? " , parameters : [ feedID as AnyObject ] , withLimits : withLimits )
2017-08-27 22:03:15 +02:00
}
2017-08-27 00:37:15 +02:00
2017-08-27 22:03:15 +02:00
func fetchUnreadArticles ( _ feedIDs : Set < String > ) -> Set < Article > {
if feedIDs . isEmpty {
return Set < Article > ( )
2017-08-27 00:37:15 +02:00
}
2017-08-27 22:03:15 +02:00
var articles = Set < Article > ( )
queue . fetchSync { ( database ) in
// 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
let parameters = feedIDs . map { $0 as AnyObject }
let placeholders = NSString . rs_SQLValueList ( withPlaceholders : UInt ( feedIDs . count ) ) !
let whereClause = " feedID in \( placeholders ) and read=0 "
2017-09-05 02:10:02 +02:00
articles = self . fetchArticlesWithWhereClause ( database , whereClause : whereClause , parameters : parameters , withLimits : true )
2017-08-27 22:03:15 +02:00
}
return articleCache . uniquedArticles ( articles )
2017-08-27 00:37:15 +02:00
}
2017-08-27 22:03:15 +02:00
func articlesWithSQL ( _ sql : String , _ parameters : [ AnyObject ] , _ database : FMDatabase ) -> Set < Article > {
2017-08-27 00:37:15 +02:00
2017-08-27 22:03:15 +02:00
guard let resultSet = database . executeQuery ( sql , withArgumentsIn : parameters ) else {
return Set < Article > ( )
}
return articlesWithResultSet ( resultSet , database )
2017-08-27 00:37:15 +02:00
}
2017-09-02 23:19:42 +02:00
// MARK: S a v i n g / U p d a t i n g
func updateArticles ( _ articlesDictionary : [ String : Article ] , _ parsedItemsDictionary : [ String : ParsedItem ] , _ feed : Feed , _ completion : @ escaping RSVoidCompletionBlock ) {
2017-09-05 02:10:02 +02:00
// 1 . F e t c h s t a t u s e s f o r p a r s e d I t e m s .
// 2 . F i l t e r o u t p a r s e d I t e m s w h e r e u s e r D e l e t e d = = 1 o r ( a r r i v a l d a t e > 4 m o n t h s a n d n o t s t a r r e d ) .
// ( U n d e r n o u s e r s e t t i n g d o w e r e t a i n a r t i c l e s o l d e r w i t h a n a r r i v a l d a t e > 4 m o n t h s . )
// 3 . F i n d p a r s e d I t e m s w i t h n o s t a t u s a n d n o m a t c h i n g a r t i c l e : s a v e t h e m a s e n t i r e l y n e w a r t i c l e s .
// 4 . C o m p a r e r e m a i n i n g p a r s e d I t e m s w i t h a r t i c l e s , a n d u p d a t e d a t a b a s e w i t h a n y c h a n g e s .
assert ( Thread . isMainThread )
queue . fetch { ( database ) in
let parsedItemArticleIDs = Set ( parsedItemsDictionary . keys )
let fetchedStatuses = self . statusesTable . fetchStatusesForArticleIDs ( parsedItemArticleIDs , database )
DispatchQueue . main . async {
// # 2 . D r o p a n y p a r s e d I t e m s t h a t c a n b e i g n o r e d .
// I f t h a t ’ s a l l o f t h e m , t h e n g r e a t — n o t h i n g t o d o .
let filteredParsedItems = self . filterParsedItems ( parsedItemsDictionary , fetchedStatuses )
if filteredParsedItems . isEmpty {
completion ( )
return
}
// # 3 . S a v e e n t i r e l y n e w p a r s e d I t e m s .
let newParsedItems = self . findNewParsedItems ( parsedItemsDictionary , fetchedStatuses , articlesDictionary )
if ! newParsedItems . isEmpty {
self . saveNewParsedItems ( newParsedItems , feed )
}
// # 4 . U p d a t e e x i s t i n g p a r s e d I t e m s .
let parsedItemsToUpdate = self . findExistingParsedItems ( parsedItemsDictionary , fetchedStatuses , articlesDictionary )
if ! parsedItemsToUpdate . isEmpty {
self . updateParsedItems ( parsedItemsToUpdate , articlesDictionary , feed )
}
completion ( )
}
}
}
func updateParsedItems ( _ parsedItems : [ String : ParsedItem ] , _ articles : [ String : Article ] , _ feed : Feed ) {
assert ( Thread . isMainThread )
2017-09-08 22:36:30 +02:00
// u p d a t e R e l a t e d O b j e c t s ( _ p a r s e d I t e m s : [ S t r i n g : P a r s e d I t e m ] , _ a r t i c l e s : [ S t r i n g : A r t i c l e ] )
2017-09-05 02:10:02 +02:00
}
2017-09-09 20:02:02 +02:00
// f u n c u p d a t e R e l a t e d O b j e c t s ( _ p a r s e d I t e m s : [ S t r i n g : P a r s e d I t e m ] , _ a r t i c l e s : [ S t r i n g : A r t i c l e ] ) {
//
// / / U p d a t e t h e i n - m e m o r y A r t i c l e s w h e n n e e d e d .
// / / S a v e o n l y w h e n t h e r e a r e c h a n g e s , w h i c h s h o u l d b e p r e t t y i n f r e q u e n t .
//
// a s s e r t ( T h r e a d . i s M a i n T h r e a d )
//
// v a r a r t i c l e s W i t h T a g C h a n g e s = S e t < A r t i c l e > ( )
// v a r a r t i c l e s W i t h A t t a c h m e n t C h a n g e s = S e t < A r t i c l e > ( )
// v a r a r t i c l e s W i t h A u t h o r C h a n g e s = S e t < A r t i c l e > ( )
//
// f o r ( a r t i c l e I D , p a r s e d I t e m ) i n p a r s e d I t e m s {
//
// g u a r d l e t a r t i c l e = a r t i c l e s [ a r t i c l e I D ] e l s e {
// c o n t i n u e
// }
//
// i f a r t i c l e . u p d a t e T a g s W i t h P a r s e d T a g s ( p a r s e d I t e m . t a g s ) {
// a r t i c l e s W i t h T a g C h a n g e s . i n s e r t ( a r t i c l e )
// }
// i f a r t i c l e . u p d a t e A t t a c h m e n t s W i t h P a r s e d A t t a c h m e n t s ( p a r s e d I t e m . a t t a c h m e n t s ) {
// a r t i c l e s W i t h A t t a c h m e n t C h a n g e s . i n s e r t ( a r t i c l e )
// }
// i f a r t i c l e . u p d a t e A u t h o r s W i t h P a r s e d A u t h o r s ( p a r s e d I t e m . a u t h o r s ) {
// a r t i c l e s W i t h A u t h o r C h a n g e s . i n s e r t ( a r t i c l e )
// }
// }
//
// i f a r t i c l e s W i t h T a g C h a n g e s . i s E m p t y & & a r t i c l e s W i t h A t t a c h m e n t C h a n g e s . i s E m p t y & & a r t i c l e s W i t h A u t h o r C h a n g e s . i s E m p t y {
// / / S h o u l d b e p r e t t y c o m m o n .
// r e t u r n
// }
//
// / / W e u s e d d e t a c h e d C o p y b e c a u s e t h e A r t i c l e o b j e c t s b e i n g u p d a t e d a r e m a i n - t h r e a d o b j e c t s .
//
// a r t i c l e s W i t h T a g C h a n g e s = S e t ( a r t i c l e s W i t h T a g C h a n g e s . m a p { $ 0 . d e t a c h e d C o p y ( ) } )
// a r t i c l e s W i t h A t t a c h m e n t C h a n g e s = S e t ( a r t i c l e s W i t h A t t a c h m e n t C h a n g e s . m a p { $ 0 . d e t a c h e d C o p y ( ) } )
// a r t i c l e s W i t h A u t h o r C h a n g e s = S e t ( a r t i c l e s W i t h A u t h o r C h a n g e s . m a p { $ 0 . d e t a c h e d C o p y ( ) } )
//
// q u e u e . u p d a t e { ( d a t a b a s e ) i n
// i f ! a r t i c l e s W i t h T a g C h a n g e s . i s E m p t y {
// t a g s L o o k u p T a b l e . s a v e R e l a t e d O b j e c t s ( f o r : a r t i c l e s W i t h T a g C h a n g e s . d a t a b a s e O b j e c t s ( ) , i n : d a t a b a s e )
// }
// i f ! a r t i c l e s W i t h A t t a c h m e n t C h a n g e s . i s E m p t y {
// a t t a c h m e n t s L o o k u p T a b l e . s a v e R e l a t e d O b j e c t s ( f o r : a r t i c l e s W i t h A t t a c h m e n t C h a n g e s . d a t a b a s e O b j e c t s ( ) , i n : d a t a b a s e )
// }
// i f ! a r t i c l e s W i t h A u t h o r C h a n g e s . i s E m p t y {
// a u t h o r s L o o k u p T a b l e . s a v e R e l a t e d O b j e c t s ( f o r : a r t i c l e s W i t h A u t h o r C h a n g e s . d a t a b a s e O b j e c t s ( ) , i n : d a t a b a s e )
// }
// }
// }
2017-09-05 02:10:02 +02:00
2017-09-08 22:36:30 +02:00
// MARK: S a v e N e w A r t i c l e s
2017-09-05 02:10:02 +02:00
2017-09-08 22:36:30 +02:00
func saveNewArticles ( _ articles : Set < Article > , _ database : FMDatabase ) {
2017-09-05 02:10:02 +02:00
2017-09-08 22:36:30 +02:00
saveRelatedObjectsForNewArticles ( articles , database )
2017-09-05 02:10:02 +02:00
2017-09-08 22:36:30 +02:00
let databaseDictionaries = articles . map { $0 . databaseDictionary ( ) }
insertRows ( databaseDictionaries , insertType : . orReplace , in : database )
}
2017-09-05 02:10:02 +02:00
2017-09-08 22:36:30 +02:00
func saveRelatedObjectsForNewArticles ( _ articles : Set < Article > , _ database : FMDatabase ) {
let databaseObjects = articles . databaseObjects ( )
authorsLookupTable . saveRelatedObjects ( for : databaseObjects , in : database )
attachmentsLookupTable . saveRelatedObjects ( for : databaseObjects , in : database )
tagsLookupTable . saveRelatedObjects ( for : databaseObjects , in : database )
2017-09-05 02:10:02 +02:00
}
2017-09-08 22:36:30 +02:00
// MARK: U p d a t e E x i s t i n g A r t i c l e s
2017-09-05 02:10:02 +02:00
2017-09-09 20:02:02 +02:00
func articlesWithRelatedObjectChanges ( _ comparisonKeyPath : Keypath < Article , Set < AnyHashable > > , _ updatedArticles : Set < Article > , _ fetchedArticles : [ String : Article ] ) -> Set < Article > {
2017-09-08 22:36:30 +02:00
return updatedArticles . filter { ( updatedArticle ) -> Bool in
if let fetchedArticle = fetchedArticles [ updatedArticle . articleID ] {
2017-09-09 20:02:02 +02:00
return updatedArticle [ keyPath : comparisonKeyPath ] != fetchedArticle [ keyPath : comparisonKeyPath ]
2017-09-08 22:36:30 +02:00
}
assertionFailure ( " Expected to find matching fetched article. " ) ;
2017-09-05 02:10:02 +02:00
return true
}
}
2017-09-09 20:02:02 +02:00
func updateRelatedObjects ( _ comparisonKeyPath : Keypath < Article , Set < AnyHashable > > , _ updatedArticles : Set < Article > , _ fetchedArticles : [ String : Article ] , _ lookupTable : DatabaseLookupTable , _ database : FMDatabase ) {
2017-09-08 22:36:30 +02:00
2017-09-09 20:02:02 +02:00
let articlesWithChanges = articlesWithRelatedObjectChanges ( comparisonKeyPath , updatedArticles , fetchedArticles )
2017-09-08 22:36:30 +02:00
if ! articlesWithChanges . isEmpty {
2017-09-09 20:02:02 +02:00
lookupTable . saveRelatedObjects ( for : articlesWithChanges . databaseObjects ( ) , in : database )
2017-09-08 22:36:30 +02:00
}
}
2017-09-03 01:08:02 +02:00
2017-09-09 20:02:02 +02:00
func saveUpdatedRelatedObjects ( _ updatedArticles : Set < Article > , _fetchedArticles : [ String : Article ] , _ database : FMDatabase ) {
2017-09-05 02:10:02 +02:00
2017-09-09 20:02:02 +02:00
updateRelatedObjects ( \ Article . tags , updatedArticles , fetchedArticles , tagsLookupTable , database )
updateRelatedObjects ( \ Article . authors , updatedArticles , fetchedArticles , authorsLookupTable , database )
updateRelatedObjects ( \ Article . attachments , updatedArticles , fetchedArticles , attachmentsLookupTable , database )
2017-09-05 02:10:02 +02:00
}
2017-09-08 22:36:30 +02:00
func saveUpdatedArticles ( _ updatedArticles : Set < Article > , _ fetchedArticles : [ String : Article ] , _ database : FMDatabase ) {
2017-09-09 20:10:15 +02:00
saveUpdatedRelatedObjects ( updatedArticles , fetchedArticles , database )
2017-09-08 22:36:30 +02:00
}
2017-09-05 02:10:02 +02:00
func articlesWithParsedItems ( _ parsedItems : Set < ParsedItem > , _ feed : Feed ) -> Set < Article > {
2017-09-09 20:02:02 +02:00
assert ( ! Thread . isMainThread )
2017-09-05 02:10:02 +02:00
let feedID = feed . feedID
return Set ( parsedItems . flatMap { articleWithParsedItem ( $0 , feedID ) } )
}
func articleWithParsedItem ( _ parsedItem : ParsedItem , _ feedID : String ) -> Article ? {
2017-09-08 22:36:30 +02:00
return Article ( parsedItem : parsedItem , feedID : feedID , accountID : accountID )
2017-09-05 02:10:02 +02:00
}
func statusIndicatesArticleIsIgnorable ( _ status : ArticleStatus ) -> Bool {
// I g n o r a b l e a r t i c l e s : e i t h e r u s e r D e l e t e d = = 1 o r ( 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 . userDeleted {
return true
}
if status . starred {
return false
}
return status . dateArrived < maximumArticleCutoffDate
}
func filterParsedItems ( _ parsedItems : [ String : ParsedItem ] , _ statuses : [ String : ArticleStatus ] ) -> [ String : ParsedItem ] {
2017-09-03 01:08:02 +02:00
2017-09-05 02:10:02 +02:00
// D r o p p a r s e d I t e m s t h a t w e c a n i g n o r e .
var d = [ String : ParsedItem ] ( )
for ( articleID , parsedItem ) in parsedItems {
if let status = statuses [ articleID ] {
if statusIndicatesArticleIsIgnorable ( status ) {
continue
}
}
d [ articleID ] = parsedItem
2017-09-03 01:08:02 +02:00
}
2017-09-05 02:10:02 +02:00
return d
}
func findNewParsedItems ( _ parsedItems : [ String : ParsedItem ] , _ statuses : [ String : ArticleStatus ] , _ articles : [ String : Article ] ) -> [ String : ParsedItem ] {
// I f t h e r e ’ s n o e x i s t i n g s t a t u s o r A r t i c l e , t h e n i t ’ s c o m p l e t e l y n e w .
2017-09-03 01:08:02 +02:00
2017-09-05 02:10:02 +02:00
assert ( Thread . isMainThread )
var d = [ String : ParsedItem ] ( )
for ( articleID , parsedItem ) in parsedItems {
if statuses [ articleID ] = = nil && articles [ articleID ] = = nil {
d [ articleID ] = parsedItem
}
}
return d
2017-09-02 23:19:42 +02:00
}
2017-09-05 02:10:02 +02:00
func findExistingParsedItems ( _ parsedItems : [ String : ParsedItem ] , _ statuses : [ String : ArticleStatus ] , _ articles : [ String : Article ] ) -> [ String : ParsedItem ] {
return [ String : ParsedItem ] ( ) // T O D O
}
2017-08-27 00:37:15 +02:00
}
2017-07-29 21:29:05 +02:00