Get rid of DatabaseArticle entirely. Cache Article objects. This will make fetches faster *and* save memory.
This commit is contained in:
parent
4b361300f4
commit
aeaeac61fc
|
@ -11,7 +11,6 @@
|
||||||
841D4D742106B59F00DD04E6 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841D4D732106B59F00DD04E6 /* Articles.framework */; };
|
841D4D742106B59F00DD04E6 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841D4D732106B59F00DD04E6 /* Articles.framework */; };
|
||||||
84288A001F6A3C4400395871 /* DatabaseObject+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842889FF1F6A3C4400395871 /* DatabaseObject+Database.swift */; };
|
84288A001F6A3C4400395871 /* DatabaseObject+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842889FF1F6A3C4400395871 /* DatabaseObject+Database.swift */; };
|
||||||
84288A021F6A3D8000395871 /* RelatedObjectsMap+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84288A011F6A3D8000395871 /* RelatedObjectsMap+Database.swift */; };
|
84288A021F6A3D8000395871 /* RelatedObjectsMap+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84288A011F6A3D8000395871 /* RelatedObjectsMap+Database.swift */; };
|
||||||
843577161F744FC800F460AE /* DatabaseArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843577151F744FC800F460AE /* DatabaseArticle.swift */; };
|
|
||||||
843577221F749C6200F460AE /* ArticleChangesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843577211F749C6200F460AE /* ArticleChangesTests.swift */; };
|
843577221F749C6200F460AE /* ArticleChangesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843577211F749C6200F460AE /* ArticleChangesTests.swift */; };
|
||||||
843702C31F70D15D00B18807 /* ParsedArticle+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843702C21F70D15D00B18807 /* ParsedArticle+Database.swift */; };
|
843702C31F70D15D00B18807 /* ParsedArticle+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 843702C21F70D15D00B18807 /* ParsedArticle+Database.swift */; };
|
||||||
843CB9961F34174100EE6581 /* Author+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F20F901F1810DD00D8E682 /* Author+Database.swift */; };
|
843CB9961F34174100EE6581 /* Author+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F20F901F1810DD00D8E682 /* Author+Database.swift */; };
|
||||||
|
@ -115,7 +114,6 @@
|
||||||
841D4D732106B59F00DD04E6 /* Articles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Articles.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
841D4D732106B59F00DD04E6 /* Articles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Articles.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
842889FF1F6A3C4400395871 /* DatabaseObject+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatabaseObject+Database.swift"; sourceTree = "<group>"; };
|
842889FF1F6A3C4400395871 /* DatabaseObject+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatabaseObject+Database.swift"; sourceTree = "<group>"; };
|
||||||
84288A011F6A3D8000395871 /* RelatedObjectsMap+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelatedObjectsMap+Database.swift"; sourceTree = "<group>"; };
|
84288A011F6A3D8000395871 /* RelatedObjectsMap+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RelatedObjectsMap+Database.swift"; sourceTree = "<group>"; };
|
||||||
843577151F744FC800F460AE /* DatabaseArticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseArticle.swift; sourceTree = "<group>"; };
|
|
||||||
843577211F749C6200F460AE /* ArticleChangesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleChangesTests.swift; sourceTree = "<group>"; };
|
843577211F749C6200F460AE /* ArticleChangesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleChangesTests.swift; sourceTree = "<group>"; };
|
||||||
843702C21F70D15D00B18807 /* ParsedArticle+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ParsedArticle+Database.swift"; path = "Extensions/ParsedArticle+Database.swift"; sourceTree = "<group>"; };
|
843702C21F70D15D00B18807 /* ParsedArticle+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "ParsedArticle+Database.swift"; path = "Extensions/ParsedArticle+Database.swift"; sourceTree = "<group>"; };
|
||||||
844BEE371F0AB3AA004AB7CD /* ArticlesDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ArticlesDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
844BEE371F0AB3AA004AB7CD /* ArticlesDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ArticlesDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -176,7 +174,6 @@
|
||||||
845580661F0AEBCD003CCFA1 /* Constants.swift */,
|
845580661F0AEBCD003CCFA1 /* Constants.swift */,
|
||||||
84E156EB1F0AB80E00F8CC05 /* ArticlesTable.swift */,
|
84E156EB1F0AB80E00F8CC05 /* ArticlesTable.swift */,
|
||||||
8477ACBB2221E76F00DF7F37 /* SearchTable.swift */,
|
8477ACBB2221E76F00DF7F37 /* SearchTable.swift */,
|
||||||
843577151F744FC800F460AE /* DatabaseArticle.swift */,
|
|
||||||
84E156ED1F0AB81400F8CC05 /* StatusesTable.swift */,
|
84E156ED1F0AB81400F8CC05 /* StatusesTable.swift */,
|
||||||
84F20F8E1F180D8700D8E682 /* AuthorsTable.swift */,
|
84F20F8E1F180D8700D8E682 /* AuthorsTable.swift */,
|
||||||
8461462A1F0AC44100870CB3 /* Extensions */,
|
8461462A1F0AC44100870CB3 /* Extensions */,
|
||||||
|
@ -350,14 +347,14 @@
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
844BEE361F0AB3AA004AB7CD = {
|
844BEE361F0AB3AA004AB7CD = {
|
||||||
CreatedOnToolsVersion = 8.3.2;
|
CreatedOnToolsVersion = 8.3.2;
|
||||||
DevelopmentTeam = SHJK2V3AJG;
|
DevelopmentTeam = M8L2WTLA8W;
|
||||||
LastSwiftMigration = 0830;
|
LastSwiftMigration = 0830;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Manual;
|
||||||
};
|
};
|
||||||
844BEE3F1F0AB3AB004AB7CD = {
|
844BEE3F1F0AB3AB004AB7CD = {
|
||||||
CreatedOnToolsVersion = 8.3.2;
|
CreatedOnToolsVersion = 8.3.2;
|
||||||
DevelopmentTeam = SHJK2V3AJG;
|
DevelopmentTeam = M8L2WTLA8W;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Manual;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -522,7 +519,6 @@
|
||||||
84F20F8F1F180D8700D8E682 /* AuthorsTable.swift in Sources */,
|
84F20F8F1F180D8700D8E682 /* AuthorsTable.swift in Sources */,
|
||||||
84288A001F6A3C4400395871 /* DatabaseObject+Database.swift in Sources */,
|
84288A001F6A3C4400395871 /* DatabaseObject+Database.swift in Sources */,
|
||||||
8477ACBC2221E76F00DF7F37 /* SearchTable.swift in Sources */,
|
8477ACBC2221E76F00DF7F37 /* SearchTable.swift in Sources */,
|
||||||
843577161F744FC800F460AE /* DatabaseArticle.swift in Sources */,
|
|
||||||
843702C31F70D15D00B18807 /* ParsedArticle+Database.swift in Sources */,
|
843702C31F70D15D00B18807 /* ParsedArticle+Database.swift in Sources */,
|
||||||
84E156EC1F0AB80E00F8CC05 /* ArticlesTable.swift in Sources */,
|
84E156EC1F0AB80E00F8CC05 /* ArticlesTable.swift in Sources */,
|
||||||
84E156EE1F0AB81400F8CC05 /* StatusesTable.swift in Sources */,
|
84E156EE1F0AB81400F8CC05 /* StatusesTable.swift in Sources */,
|
||||||
|
|
|
@ -19,7 +19,7 @@ final class ArticlesTable: DatabaseTable {
|
||||||
private let queue: DatabaseQueue
|
private let queue: DatabaseQueue
|
||||||
private let statusesTable: StatusesTable
|
private let statusesTable: StatusesTable
|
||||||
private let authorsLookupTable: DatabaseLookupTable
|
private let authorsLookupTable: DatabaseLookupTable
|
||||||
private var databaseArticlesCache = [String: DatabaseArticle]()
|
private var articlesCache = [String: Article]()
|
||||||
|
|
||||||
private lazy var searchTable: SearchTable = {
|
private lazy var searchTable: SearchTable = {
|
||||||
return SearchTable(queue: queue, articlesTable: self)
|
return SearchTable(queue: queue, articlesTable: self)
|
||||||
|
@ -449,7 +449,7 @@ final class ArticlesTable: DatabaseTable {
|
||||||
|
|
||||||
func emptyCaches() {
|
func emptyCaches() {
|
||||||
queue.runInDatabase { _ in
|
queue.runInDatabase { _ in
|
||||||
self.databaseArticlesCache = [String: DatabaseArticle]()
|
self.articlesCache = [String: Article]()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,86 +527,57 @@ private extension ArticlesTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func articlesWithResultSet(_ resultSet: FMResultSet, _ database: FMDatabase) -> Set<Article> {
|
func articlesWithResultSet(_ resultSet: FMResultSet, _ database: FMDatabase) -> Set<Article> {
|
||||||
// 1. Create DatabaseArticles without related objects.
|
var cachedArticles = Set<Article>()
|
||||||
// 2. Then fetch the related objects, given the set of articleIDs.
|
var fetchedArticles = Set<Article>()
|
||||||
// 3. Then create set of Articles with DatabaseArticles and related objects and return it.
|
|
||||||
|
|
||||||
// 1. Create databaseArticles (intermediate representations).
|
while resultSet.next() {
|
||||||
|
|
||||||
let databaseArticles = makeDatabaseArticles(with: resultSet)
|
guard let articleID = resultSet.string(forColumn: DatabaseKey.articleID) else {
|
||||||
if databaseArticles.isEmpty {
|
|
||||||
return Set<Article>()
|
|
||||||
}
|
|
||||||
|
|
||||||
let articleIDs = databaseArticles.articleIDs()
|
|
||||||
|
|
||||||
// 2. Fetch related objects.
|
|
||||||
|
|
||||||
let authorsMap = authorsLookupTable.fetchRelatedObjects(for: articleIDs, in: database)
|
|
||||||
|
|
||||||
// 3. Create articles with related objects.
|
|
||||||
|
|
||||||
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 {
|
|
||||||
assertionFailure("Expected articleID.")
|
assertionFailure("Expected articleID.")
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Articles are removed from the cache when they’re updated.
|
// Articles are removed from the cache when they’re updated.
|
||||||
// See saveUpdatedArticles.
|
// See saveUpdatedArticles.
|
||||||
if let databaseArticle = databaseArticlesCache[articleID] {
|
if let article = articlesCache[articleID] {
|
||||||
return databaseArticle
|
cachedArticles.insert(article)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// The resultSet is a result of a JOIN query with the statuses table,
|
// The resultSet is a result of a JOIN query with the statuses table,
|
||||||
// so we can get the statuses at the same time and avoid additional database lookups.
|
// so we can get the statuses at the same time and avoid additional database lookups.
|
||||||
guard let status = statusesTable.statusWithRow(resultSet, articleID: articleID) else {
|
guard let status = statusesTable.statusWithRow(resultSet, articleID: articleID) else {
|
||||||
assertionFailure("Expected status.")
|
assertionFailure("Expected status.")
|
||||||
return nil
|
continue
|
||||||
}
|
|
||||||
guard let webFeedID = 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = row.string(forColumn: DatabaseKey.title)
|
guard let article = Article(accountID: accountID, row: resultSet, status: status) else {
|
||||||
let contentHTML = row.string(forColumn: DatabaseKey.contentHTML)
|
continue
|
||||||
let contentText = row.string(forColumn: DatabaseKey.contentText)
|
}
|
||||||
let url = row.string(forColumn: DatabaseKey.url)
|
fetchedArticles.insert(article)
|
||||||
let externalURL = row.string(forColumn: DatabaseKey.externalURL)
|
}
|
||||||
let summary = row.string(forColumn: DatabaseKey.summary)
|
resultSet.close()
|
||||||
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)
|
|
||||||
|
|
||||||
let databaseArticle = DatabaseArticle(articleID: articleID, webFeedID: webFeedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, status: status)
|
if fetchedArticles.isEmpty {
|
||||||
databaseArticlesCache[articleID] = databaseArticle
|
return cachedArticles
|
||||||
return databaseArticle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return articles
|
// Fetch authors for non-cached articles. (Articles from the cache already have authors.)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add fetchedArticles to cache, now that they have attached authors.
|
||||||
|
for article in articlesWithFetchedAuthors {
|
||||||
|
articlesCache[article.articleID] = article
|
||||||
|
}
|
||||||
|
|
||||||
|
return cachedArticles.union(articlesWithFetchedAuthors)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject], withLimits: Bool) -> Set<Article> {
|
func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject], withLimits: Bool) -> Set<Article> {
|
||||||
|
@ -872,7 +843,7 @@ private extension ArticlesTable {
|
||||||
|
|
||||||
|
|
||||||
func saveUpdatedArticles(_ updatedArticles: Set<Article>, _ fetchedArticles: [String: Article], _ database: FMDatabase) {
|
func saveUpdatedArticles(_ updatedArticles: Set<Article>, _ fetchedArticles: [String: Article], _ database: FMDatabase) {
|
||||||
removeArticlesFromDatabaseArticlesCache(updatedArticles)
|
removeArticlesFromArticlesCache(updatedArticles)
|
||||||
saveUpdatedRelatedObjects(updatedArticles, fetchedArticles, database)
|
saveUpdatedRelatedObjects(updatedArticles, fetchedArticles, database)
|
||||||
|
|
||||||
for updatedArticle in updatedArticles {
|
for updatedArticle in updatedArticles {
|
||||||
|
@ -897,10 +868,10 @@ private extension ArticlesTable {
|
||||||
updateRowsWithDictionary(changesDictionary, whereKey: DatabaseKey.articleID, matches: updatedArticle.articleID, database: database)
|
updateRowsWithDictionary(changesDictionary, whereKey: DatabaseKey.articleID, matches: updatedArticle.articleID, database: database)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeArticlesFromDatabaseArticlesCache(_ updatedArticles: Set<Article>) {
|
func removeArticlesFromArticlesCache(_ updatedArticles: Set<Article>) {
|
||||||
let articleIDs = updatedArticles.articleIDs()
|
let articleIDs = updatedArticles.articleIDs()
|
||||||
for articleID in articleIDs {
|
for articleID in articleIDs {
|
||||||
databaseArticlesCache[articleID] = nil
|
articlesCache[articleID] = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
//
|
|
||||||
// DatabaseArticle.swift
|
|
||||||
// NetNewsWire
|
|
||||||
//
|
|
||||||
// Created by Brent Simmons on 9/21/17.
|
|
||||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Articles
|
|
||||||
|
|
||||||
// Intermediate representation of an Article. Doesn’t include related objects.
|
|
||||||
// Used by ArticlesTable as part of fetching articles.
|
|
||||||
|
|
||||||
struct DatabaseArticle: Hashable {
|
|
||||||
|
|
||||||
let articleID: String
|
|
||||||
let webFeedID: String
|
|
||||||
let uniqueID: String
|
|
||||||
let title: String?
|
|
||||||
let contentHTML: String?
|
|
||||||
let contentText: String?
|
|
||||||
let url: String?
|
|
||||||
let externalURL: String?
|
|
||||||
let summary: String?
|
|
||||||
let imageURL: String?
|
|
||||||
let bannerImageURL: String?
|
|
||||||
let datePublished: Date?
|
|
||||||
let dateModified: Date?
|
|
||||||
let status: ArticleStatus
|
|
||||||
|
|
||||||
// MARK: - Hashable
|
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
|
||||||
hasher.combine(articleID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Set where Element == DatabaseArticle {
|
|
||||||
|
|
||||||
func articleIDs() -> Set<String> {
|
|
||||||
return Set<String>(map { $0.articleID })
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,8 +13,32 @@ import RSParser
|
||||||
|
|
||||||
extension Article {
|
extension Article {
|
||||||
|
|
||||||
init(databaseArticle: DatabaseArticle, accountID: String, authors: Set<Author>?) {
|
init?(accountID: String, row: FMResultSet, status: ArticleStatus) {
|
||||||
self.init(accountID: accountID, articleID: databaseArticle.articleID, webFeedID: databaseArticle.webFeedID, uniqueID: databaseArticle.uniqueID, title: databaseArticle.title, contentHTML: databaseArticle.contentHTML, contentText: databaseArticle.contentText, url: databaseArticle.url, externalURL: databaseArticle.externalURL, summary: databaseArticle.summary, imageURL: databaseArticle.imageURL, bannerImageURL: databaseArticle.bannerImageURL, datePublished: databaseArticle.datePublished, dateModified: databaseArticle.dateModified, authors: authors, status: databaseArticle.status)
|
guard let articleID = row.string(forColumn: DatabaseKey.articleID) else {
|
||||||
|
assertionFailure("Expected articleID.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let webFeedID = 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
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = 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)
|
||||||
|
|
||||||
|
self.init(accountID: accountID, articleID: articleID, webFeedID: webFeedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: nil, status: status)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, webFeedID: String, status: ArticleStatus) {
|
init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, webFeedID: String, status: ArticleStatus) {
|
||||||
|
@ -43,6 +67,13 @@ extension Article {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func byAdding(_ authors: Set<Author>) -> Article {
|
||||||
|
if authors.isEmpty {
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
return Article(accountID: self.accountID, articleID: self.articleID, webFeedID: self.webFeedID, uniqueID: self.uniqueID, title: self.title, contentHTML: self.contentHTML, contentText: self.contentText, url: self.url, externalURL: self.externalURL, summary: self.summary, imageURL: self.imageURL, bannerImageURL: self.bannerImageURL, datePublished: self.datePublished, dateModified: self.dateModified, authors: authors, status: self.status)
|
||||||
|
}
|
||||||
|
|
||||||
func changesFrom(_ existingArticle: Article) -> DatabaseDictionary? {
|
func changesFrom(_ existingArticle: Article) -> DatabaseDictionary? {
|
||||||
if self == existingArticle {
|
if self == existingArticle {
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Reference in New Issue