Make progress fetching articles.
This commit is contained in:
parent
2cefb87f20
commit
2d1c63403d
@ -16,16 +16,20 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
let name: String
|
||||
let databaseIDKey = DatabaseKey.articleID
|
||||
private weak var account: Account?
|
||||
private let queue: RSDatabaseQueue
|
||||
private let statusesTable: StatusesTable
|
||||
private let authorsLookupTable: DatabaseLookupTable
|
||||
private let attachmentsLookupTable: DatabaseLookupTable
|
||||
private let tagsLookupTable: DatabaseLookupTable
|
||||
private let articleCache = ArticleCache()
|
||||
|
||||
init(name: String) {
|
||||
init(name: String, account: Account, queue: RSDatabaseQueue) {
|
||||
|
||||
self.name = name
|
||||
|
||||
self.account = account
|
||||
self.queue = queue
|
||||
|
||||
self.statusesTable = StatusesTable(name: DatabaseTableName.statuses)
|
||||
let authorsTable = AuthorsTable(name: DatabaseTableName.authors)
|
||||
self.authorsLookupTable = DatabaseLookupTable(name: DatabaseTableName.authorsLookup, objectIDKey: DatabaseKey.articleID, relatedObjectIDKey: DatabaseKey.authorID, relatedTable: authorsTable, relationshipName: RelationshipName.authors)
|
||||
@ -40,11 +44,11 @@ final class ArticlesTable: DatabaseTable {
|
||||
// MARK: DatabaseTable Methods
|
||||
|
||||
func objectWithRow(_ row: FMResultSet) -> DatabaseObject? {
|
||||
|
||||
// if let article = articleWithRow(row) {
|
||||
//
|
||||
// }
|
||||
return nil // TODO
|
||||
|
||||
if let article = articleWithRow(row) {
|
||||
return article as DatabaseObject
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func save(_ objects: [DatabaseObject], in database: FMDatabase) {
|
||||
@ -56,12 +60,31 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
func fetchArticles(_ feed: Feed) -> Set<Article> {
|
||||
|
||||
return Set<Article>() // TODO
|
||||
let feedID = feed.feedID
|
||||
|
||||
var fetchedArticles = Set<Article>()
|
||||
|
||||
queue.fetchSync { (database: FMDatabase!) -> Void in
|
||||
|
||||
fetchedArticles = self.fetchArticlesForFeedID(feedID, database: database)
|
||||
}
|
||||
|
||||
return articleCache.uniquedArticles(fetchedArticles)
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(_ feed: Feed, _ resultBlock: @escaping ArticleResultBlock) {
|
||||
|
||||
// TODO
|
||||
let feedID = feed.feedID
|
||||
|
||||
queue.fetch { (database: FMDatabase!) -> Void in
|
||||
|
||||
let fetchedArticles = self.fetchArticlesForFeedID(feedID, database: database)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let articles = self.articleCache.uniquedArticles(fetchedArticles)
|
||||
resultBlock(articles)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(_ feeds: Set<Feed>) -> Set<Article> {
|
||||
@ -138,7 +161,66 @@ final class ArticlesTable: DatabaseTable {
|
||||
// }
|
||||
}
|
||||
|
||||
//private extension ArticlesTable {
|
||||
// MARK: -
|
||||
|
||||
private extension ArticlesTable {
|
||||
|
||||
// MARK: Fetching
|
||||
|
||||
func attachRelatedObjects(_ articles: Set<Article>, _ database: FMDatabase) {
|
||||
|
||||
let articleArray = articles.map { $0 as DatabaseObject }
|
||||
|
||||
authorsLookupTable.attachRelatedObjects(to: articleArray, in: database)
|
||||
attachmentsLookupTable.attachRelatedObjects(to: articleArray, in: database)
|
||||
tagsLookupTable.attachRelatedObjects(to: articleArray, in: database)
|
||||
|
||||
// In theory, it’s impossible to have a fetched article without a status.
|
||||
// Let’s handle that impossibility anyway.
|
||||
// Remember that, if nothing else, the user can edit the SQLite database,
|
||||
// and thus could delete all their statuses.
|
||||
|
||||
statusesTable.ensureStatusesForArticles(articles, database)
|
||||
}
|
||||
|
||||
func articleWithRow(_ row: FMResultSet) -> Article? {
|
||||
|
||||
guard let account = account else {
|
||||
return nil
|
||||
}
|
||||
guard let article = Article(row: row, account: account) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: the row is a result of a JOIN query with the statuses table,
|
||||
// so we can get the status at the same time and avoid additional database lookups.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject]?) -> Set<Article> {
|
||||
|
||||
let sql = "select * from articles natural join statuses where \(whereClause);"
|
||||
|
||||
if let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) {
|
||||
return articlesWithResultSet(resultSet, database)
|
||||
}
|
||||
|
||||
return Set<Article>()
|
||||
}
|
||||
|
||||
func fetchArticlesForFeedID(_ feedID: String, database: FMDatabase) -> Set<Article> {
|
||||
|
||||
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject])
|
||||
}
|
||||
|
||||
// func cachedArticle(_ articleID: String) -> Article? {
|
||||
//
|
||||
@ -154,11 +236,11 @@ final class ArticlesTable: DatabaseTable {
|
||||
//
|
||||
// articles.forEach { cacheArticle($0) }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
private struct ArticleCache {
|
||||
|
||||
// Main thread only.
|
||||
// Main thread only — unlike the other object caches.
|
||||
// The cache contains a given article only until all outside references are gone.
|
||||
// Cache key is articleID.
|
||||
|
||||
@ -179,6 +261,9 @@ private struct ArticleCache {
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, every Article must have an attached Status.
|
||||
assert(articlesToReturn.eachHasAStatus())
|
||||
|
||||
return articlesToReturn
|
||||
}
|
||||
|
||||
|
@ -24,14 +24,16 @@ public final class Database {
|
||||
private var articleArrivalCutoffDate = NSDate.rs_dateWithNumberOfDays(inThePast: 3 * 31)!
|
||||
private let minimumNumberOfArticles = 10
|
||||
private weak var delegate: AccountDelegate?
|
||||
|
||||
public init(databaseFile: String, delegate: AccountDelegate) {
|
||||
private weak var account: Account?
|
||||
|
||||
public init(databaseFile: String, delegate: AccountDelegate, account: Account) {
|
||||
|
||||
self.delegate = delegate
|
||||
self.account = account
|
||||
self.databaseFile = databaseFile
|
||||
self.queue = RSDatabaseQueue(filepath: databaseFile, excludeFromBackup: false)
|
||||
|
||||
self.articlesTable = ArticlesTable(name: DatabaseTableName.articles)
|
||||
self.articlesTable = ArticlesTable(name: DatabaseTableName.articles, account: account, queue: queue)
|
||||
|
||||
let createStatementsPath = Bundle(for: type(of: self)).path(forResource: "CreateStatements", ofType: "sql")!
|
||||
let createStatements = try! NSString(contentsOfFile: createStatementsPath, encoding: String.Encoding.utf8.rawValue)
|
||||
|
@ -71,4 +71,19 @@ extension Set where Element == Article {
|
||||
|
||||
return Set<String>(map { $0.databaseID })
|
||||
}
|
||||
|
||||
func eachHasAStatus() -> Bool {
|
||||
|
||||
for article in self {
|
||||
if article.status == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func missingStatuses() -> Set<Article> {
|
||||
|
||||
return withNilProperty(\Article.status)
|
||||
}
|
||||
}
|
||||
|
@ -12,20 +12,15 @@ import Data
|
||||
|
||||
extension ArticleStatus {
|
||||
|
||||
convenience init?(articleID: String, row: FMResultSet) {
|
||||
convenience init(articleID: String, dateArrived: Date, row: FMResultSet) {
|
||||
|
||||
let read = row.bool(forColumn: DatabaseKey.read)
|
||||
let starred = row.bool(forColumn: DatabaseKey.starred)
|
||||
let userDeleted = row.bool(forColumn: DatabaseKey.userDeleted)
|
||||
|
||||
var dateArrived = row.date(forColumn: DatabaseKey.dateArrived)
|
||||
if (dateArrived == nil) {
|
||||
dateArrived = NSDate.distantPast
|
||||
}
|
||||
|
||||
// let accountInfoPlist = accountInfoWithRow(row)
|
||||
|
||||
self.init(articleID: articleID, read: read, starred: starred, userDeleted: userDeleted, dateArrived: dateArrived!, accountInfo: nil)
|
||||
self.init(articleID: articleID, read: read, starred: starred, userDeleted: userDeleted, dateArrived: dateArrived, accountInfo: nil)
|
||||
}
|
||||
|
||||
func databaseDictionary() -> NSDictionary {
|
||||
|
@ -40,9 +40,41 @@ final class StatusesTable: DatabaseTable {
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: Fetching
|
||||
|
||||
func statusWithRow(_ row: FMResultSet) -> ArticleStatus? {
|
||||
|
||||
guard let articleID = row.string(forColumn: DatabaseKey.articleID) else {
|
||||
return nil
|
||||
}
|
||||
if let cachedStatus = cache[articleID] as? ArticleStatus {
|
||||
return cachedStatus
|
||||
}
|
||||
|
||||
guard let dateArrived = row.date(forColumn: DatabaseKey.dateArrived) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let articleStatus = ArticleStatus(articleID: articleID, dateArrived: dateArrived, row: row)
|
||||
cache[articleID] = articleStatus
|
||||
return articleStatus
|
||||
}
|
||||
|
||||
// MARK: Creating
|
||||
|
||||
func ensureStatusesForArticles(_ articles: Set<Article>, _ database: FMDatabase) {
|
||||
|
||||
let articlesNeedingStatuses = articles.missingStatuses()
|
||||
if articlesNeedingStatuses.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
createAndSaveStatusesForArticles(articlesNeedingStatuses, database)
|
||||
|
||||
attachCachedStatuses(articlesNeedingStatuses)
|
||||
assert(articles.eachHasAStatus())
|
||||
}
|
||||
|
||||
// func markArticles(_ articles: Set<Article>, statusKey: String, flag: Bool) {
|
||||
//
|
||||
@ -113,33 +145,16 @@ final class StatusesTable: DatabaseTable {
|
||||
}
|
||||
|
||||
private extension StatusesTable {
|
||||
|
||||
// MARK: Fetching
|
||||
|
||||
func statusWithRow(_ row: FMResultSet) -> ArticleStatus? {
|
||||
|
||||
guard let articleID = row.string(forColumn: DatabaseKey.articleID) else {
|
||||
return nil
|
||||
func attachCachedStatuses(_ articles: Set<Article>) {
|
||||
|
||||
for article in articles {
|
||||
if let cachedStatus = cache[article.articleID] as? ArticleStatus {
|
||||
article.status = cachedStatus
|
||||
}
|
||||
}
|
||||
if let cachedStatus = cache[articleID] as? ArticleStatus {
|
||||
return cachedStatus
|
||||
}
|
||||
|
||||
let status = ArticleStatus(articleID: articleID, row: row)
|
||||
cache[articleID] = status
|
||||
return status
|
||||
}
|
||||
|
||||
// func attachCachedStatuses(_ articles: Set<Article>) {
|
||||
//
|
||||
// articles.forEach { (oneArticle) in
|
||||
//
|
||||
// if let cachedStatus = cache[oneArticle.databaseID] {
|
||||
// oneArticle.status = cachedStatus
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// func assertNoMissingStatuses(_ articles: Set<Article>) {
|
||||
//
|
||||
// for oneArticle in articles {
|
||||
@ -220,26 +235,27 @@ private extension StatusesTable {
|
||||
|
||||
// MARK: Creating
|
||||
|
||||
// func saveStatuses(_ statuses: Set<ArticleStatus>, _ database: FMDatabase) {
|
||||
//
|
||||
// let statusArray = statuses.map { $0.databaseDictionary() }
|
||||
// insertRows(statusArray, insertType: .orIgnore, in: database)
|
||||
// }
|
||||
//
|
||||
// func createAndSaveStatusesForArticles(_ articles: Set<Article>, _ database: FMDatabase) {
|
||||
//
|
||||
// let articleIDs = Set(articles.map { $0.databaseID })
|
||||
// createAndSaveStatusesForArticleIDs(articleIDs, database)
|
||||
// }
|
||||
//
|
||||
// func createAndSaveStatusesForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) {
|
||||
//
|
||||
// let now = Date()
|
||||
// let statuses = articleIDs.map { ArticleStatus(articleID: $0, dateArrived: now) }
|
||||
// cache.addObjectsNotCached(statuses)
|
||||
//
|
||||
// saveStatuses(Set(statuses), database)
|
||||
// }
|
||||
func saveStatuses(_ statuses: Set<ArticleStatus>, _ database: FMDatabase) {
|
||||
|
||||
let statusArray = statuses.map { $0.databaseDictionary() }
|
||||
insertRows(statusArray, insertType: .orIgnore, in: database)
|
||||
}
|
||||
|
||||
func createAndSaveStatusesForArticles(_ articles: Set<Article>, _ database: FMDatabase) {
|
||||
|
||||
let articleIDs = Set(articles.map { $0.articleID })
|
||||
createAndSaveStatusesForArticleIDs(articleIDs, database)
|
||||
}
|
||||
|
||||
func createAndSaveStatusesForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) {
|
||||
|
||||
let now = Date()
|
||||
let statuses = articleIDs.map { ArticleStatus(articleID: $0, dateArrived: now) }
|
||||
let databaseObjects = statuses.map { $0 as DatabaseObject }
|
||||
cache.addObjectsNotCached(databaseObjects)
|
||||
|
||||
saveStatuses(Set(statuses), database)
|
||||
}
|
||||
|
||||
// MARK: Utilities
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user