Make progress on Database.framework.
This commit is contained in:
parent
2ace9ec0d2
commit
9d37d88c2f
|
@ -33,6 +33,8 @@ final class AttachmentsTable: DatabaseTable {
|
|||
|
||||
let name: String
|
||||
let queue: RSDatabaseQueue
|
||||
private let cacheByArticleID = ObjectCache<Attachment>(keyPathForID: \Attachment.articleID)
|
||||
private let cacheByDatabaseID = ObjectCache<Attachment>(keyPathForID: \Attachment.databaseID)
|
||||
|
||||
init(name: String, queue: RSDatabaseQueue) {
|
||||
|
||||
|
@ -115,7 +117,7 @@ final class AttachmentsTable: DatabaseTable {
|
|||
}
|
||||
}
|
||||
|
||||
private extension AttachmentsManager {
|
||||
private extension AttachmentsTable {
|
||||
|
||||
func deleteAttachmentsForArticles(_ articles: Set<Article>, _ database: FMDatabase) {
|
||||
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
//
|
||||
|
||||
import Foundation
|
||||
import RSDatabase
|
||||
import Data
|
||||
|
||||
final class AuthorsTable: DatabaseTable {
|
||||
|
||||
let name: String
|
||||
let queue: RSDatabaseQueue
|
||||
private let cache = ObjectCache<Author>(keyPathForID: \Author.databaseID)
|
||||
|
||||
init(name: String, queue: RSDatabaseQueue) {
|
||||
|
||||
|
@ -20,30 +22,27 @@ final class AuthorsTable: DatabaseTable {
|
|||
self.queue = queue
|
||||
}
|
||||
|
||||
var cachedAuthors = [String: Author]()
|
||||
|
||||
func cachedAuthor(_ databaseID: String) -> Author? {
|
||||
|
||||
return cachedAuthors[databaseID]
|
||||
}
|
||||
|
||||
func cacheAuthor(_ author: Author) {
|
||||
|
||||
cachedAuthors[author.databaseID] = author
|
||||
}
|
||||
|
||||
func authorWithRow(_ row: FMResultSet) -> Author? {
|
||||
|
||||
let databaseID = row.string(forColumn: DatabaseKey.databaseID)
|
||||
if let author = cachedAuthor(databaseID) {
|
||||
return author
|
||||
// Since:
|
||||
// 1. anything to do with an FMResultSet runs inside the database serial queue, and
|
||||
// 2. the cache is referenced only within this method,
|
||||
// this is safe.
|
||||
|
||||
guard let databaseID = row.string(forColumn: DatabaseKey.databaseID) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let cachedAuthor = cache[databaseID] {
|
||||
return cachedAuthor
|
||||
}
|
||||
|
||||
guard let author = Author(row: row) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
cacheAuthor(author)
|
||||
cache[databaseID] = author
|
||||
return author
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import Foundation
|
|||
public struct DatabaseTableName {
|
||||
|
||||
static let articles = "articles"
|
||||
static let authors = "authors"
|
||||
static let statuses = "statuses"
|
||||
static let tags = "tags"
|
||||
static let attachments = "attachments"
|
||||
|
|
|
@ -2,7 +2,7 @@ CREATE TABLE if not EXISTS articles (articleID TEXT NOT NULL PRIMARY KEY, feedID
|
|||
|
||||
CREATE TABLE if not EXISTS statuses (articleID TEXT NOT NULL PRIMARY KEY, read BOOL NOT NULL DEFAULT 0, starred BOOL NOT NULL DEFAULT 0, userDeleted BOOL NOT NULL DEFAULT 0, dateArrived DATE NOT NULL DEFAULT 0, accountInfo BLOB);
|
||||
|
||||
CREATE TABLE if not EXISTS authors (authorID TEXT NOT NULL PRIMARY KEY, name TEXT, url TEXT, avatarURL TEXT, emailAddress TEXT);
|
||||
CREATE TABLE if not EXISTS authors (databaseID TEXT NOT NULL PRIMARY KEY, name TEXT, url TEXT, avatarURL TEXT, emailAddress TEXT);
|
||||
CREATE TABLE if not EXISTS authorLookup (authorID TEXT NOT NULL, articleID TEXT NOT NULL, PRIMARY KEY(authorID, articleID));
|
||||
|
||||
CREATE TABLE if not EXISTS tags(tagName TEXT NOT NULL, articleID TEXT NOT NULL, PRIMARY KEY(tagName, articleID));
|
||||
|
|
|
@ -26,9 +26,11 @@ final class Database {
|
|||
|
||||
fileprivate let queue: RSDatabaseQueue
|
||||
private let databaseFile: String
|
||||
private let articlesTable: ArticlesTable
|
||||
private let authorsTable: AuthorsTable
|
||||
private let attachmentsTable: AttachmentsTable
|
||||
fileprivate let statusesManager: StatusesManager
|
||||
fileprivate let articleCache = ArticlesManager()
|
||||
private let statusesTable: StatusesTable
|
||||
private let tagsTable: TagsTable
|
||||
fileprivate var articleArrivalCutoffDate = NSDate.rs_dateWithNumberOfDays(inThePast: 3 * 31)!
|
||||
fileprivate let minimumNumberOfArticles = 10
|
||||
fileprivate weak var delegate: AccountDelegate?
|
||||
|
@ -38,8 +40,12 @@ final class Database {
|
|||
self.delegate = delegate
|
||||
self.databaseFile = databaseFile
|
||||
self.queue = RSDatabaseQueue(filepath: databaseFile, excludeFromBackup: false)
|
||||
|
||||
self.articlesTable = ArticlesTable(name: DatabaseTableName.articles, queue: queue)
|
||||
self.authorsTable = AuthorsTable(name: DatabaseTableName.authors, queue: queue)
|
||||
self.attachmentsTable = AttachmentsTable(name: DatabaseTableName.attachments, queue: queue)
|
||||
self.statusesManager = StatusesManager(queue: self.queue)
|
||||
self.statusesTable = StatusesTable(name: DatabaseTableName.statuses, queue: queue)
|
||||
self.tagsTable = TagsTable(name: DatabaseTableName.tags, 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)
|
||||
|
@ -327,7 +333,7 @@ private extension Database {
|
|||
|
||||
func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject]?) -> Set<Article> {
|
||||
|
||||
let sql = "select * from articles natural join statuses where \(whereClause);"
|
||||
let sql = "select * from articles where \(whereClause);"
|
||||
logSQL(sql)
|
||||
|
||||
if let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) {
|
||||
|
@ -344,10 +350,15 @@ private extension Database {
|
|||
while (resultSet.next()) {
|
||||
|
||||
if let oneArticle = Article(account: self.account, row: resultSet) {
|
||||
oneArticle.status = ArticleStatus(row: resultSet)
|
||||
fetchedArticles.insert(oneArticle)
|
||||
}
|
||||
}
|
||||
resultSet.close()
|
||||
|
||||
statusesTable.attachStatuses(fetchedArticles, database)
|
||||
authorsTable.attachAuthors(fetchedArticles, database)
|
||||
tagsTable.attachTags(fetchedArticles, database)
|
||||
attachmentsTable.attachAttachments(fetchedArticles, database)
|
||||
|
||||
return fetchedArticles
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
/* Begin PBXBuildFile section */
|
||||
840405CF1F1A963700DF0296 /* AttachmentsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840405CE1F1A963700DF0296 /* AttachmentsTable.swift */; };
|
||||
843CB9961F34174100EE6581 /* Author+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F20F901F1810DD00D8E682 /* Author+Database.swift */; };
|
||||
844BEE411F0AB3AB004AB7CD /* Database.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844BEE371F0AB3AA004AB7CD /* Database.framework */; };
|
||||
844BEE461F0AB3AB004AB7CD /* DatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844BEE451F0AB3AB004AB7CD /* DatabaseTests.swift */; };
|
||||
845580671F0AEBCD003CCFA1 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845580661F0AEBCD003CCFA1 /* Constants.swift */; };
|
||||
|
@ -163,9 +164,9 @@
|
|||
84E156E91F0AB80500F8CC05 /* Database.swift */,
|
||||
845580661F0AEBCD003CCFA1 /* Constants.swift */,
|
||||
84E156EB1F0AB80E00F8CC05 /* ArticlesTable.swift */,
|
||||
84E156ED1F0AB81400F8CC05 /* StatusesTable.swift */,
|
||||
84F20F8E1F180D8700D8E682 /* AuthorsTable.swift */,
|
||||
840405CE1F1A963700DF0296 /* AttachmentsTable.swift */,
|
||||
84E156ED1F0AB81400F8CC05 /* StatusesTable.swift */,
|
||||
84BB4BA81F11A32800858766 /* TagsTable.swift */,
|
||||
8461462A1F0AC44100870CB3 /* Extensions */,
|
||||
84E156EF1F0AB81F00F8CC05 /* CreateStatements.sql */,
|
||||
|
@ -467,6 +468,7 @@
|
|||
84F20F8F1F180D8700D8E682 /* AuthorsTable.swift in Sources */,
|
||||
845580671F0AEBCD003CCFA1 /* Constants.swift in Sources */,
|
||||
840405CF1F1A963700DF0296 /* AttachmentsTable.swift in Sources */,
|
||||
843CB9961F34174100EE6581 /* Author+Database.swift in Sources */,
|
||||
845580781F0AF678003CCFA1 /* Folder+Database.swift in Sources */,
|
||||
845580761F0AF670003CCFA1 /* Article+Database.swift in Sources */,
|
||||
845580721F0AEE49003CCFA1 /* AccountInfo.swift in Sources */,
|
||||
|
|
|
@ -21,7 +21,7 @@ extension Attachment {
|
|||
let sizeInBytes = optionalIntForColumn(row, DatabaseKey.sizeInBytes)
|
||||
let durationInSeconds = optionalIntForColumn(row, DatabaseKey.durationInSeconds)
|
||||
|
||||
init(databaseID: databaseID, articleID: articleID, url: url, mimeType: mimeType, title: title, sizeInBytes: sizeInBytes, durationInSeconds: durationInSeconds)
|
||||
self.init(databaseID: databaseID, articleID: articleID, url: url, mimeType: mimeType, title: title, sizeInBytes: sizeInBytes, durationInSeconds: durationInSeconds)
|
||||
}
|
||||
|
||||
private func optionalIntForColumn(_ row: FMResultSet, _ columnName: String) -> Int? {
|
||||
|
|
|
@ -29,6 +29,25 @@ final class StatusesTable: DatabaseTable {
|
|||
assertNoMissingStatuses(articles)
|
||||
let statuses = Set(articles.flatMap { $0.status })
|
||||
markArticleStatuses(statuses, statusKey: statusKey, flag: flag)
|
||||
}
|
||||
|
||||
func attachStatuses(_ articles: Set<Article>, _ database: FMDatabase) {
|
||||
|
||||
attachCachedStatuses(articles)
|
||||
let articlesNeedingStatuses = articlesMissingStatuses(articles)
|
||||
if articlesNeedingStatuses.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
fetchAndCacheStatusesForArticles(Set(articlesNeedingStatuses))
|
||||
attachCachedStatuses(articlesNeedingStatuses)
|
||||
|
||||
// It shouldn’t happen that an Article in the database has no corresponding ArticleStatus,
|
||||
// but the case should be handled anyway.
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
func attachCachedStatuses(_ articles: Set<Article>) {
|
||||
|
@ -90,7 +109,7 @@ private extension StatusesTable {
|
|||
|
||||
// MARK: Fetching
|
||||
|
||||
func fetchStatusesForArticleIDs(_ articleIDs: Set<String>, database: FMDatabase) -> Set<ArticleStatus> {
|
||||
func fetchStatusesForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) -> Set<ArticleStatus> {
|
||||
|
||||
if !articleIDs.isEmpty, let resultSet = selectRowsWhere(key: DatabaseKey.articleID, inValues: Array(articleIDs), in: database) {
|
||||
return articleStatusesWithResultSet(resultSet)
|
||||
|
@ -161,7 +180,18 @@ private extension StatusesTable {
|
|||
|
||||
func articleIDsMissingStatuses(_ articleIDs: Set<String>) -> Set<String> {
|
||||
|
||||
return Set(articleIDs.filter { !objectWithIDIsCached[$0] })
|
||||
return Set(articleIDs.filter { !cache.objectWithIDIsCached[$0] })
|
||||
}
|
||||
|
||||
func articlesMissingStatuses(_ articles: Set<Article>) -> Set<Article> {
|
||||
|
||||
let missing = articles.flatMap { (article) -> Article? in
|
||||
if article.status == nil {
|
||||
return article
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return Set(missing)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ final class TagsTable: DatabaseTable {
|
|||
}
|
||||
}
|
||||
|
||||
private extension TagsManager {
|
||||
private extension TagsTable {
|
||||
|
||||
func cacheTagsForArticle(_ article: Article, tags: TagNameSet) {
|
||||
|
||||
|
|
Loading…
Reference in New Issue