NetNewsWire/Frameworks/Database/AuthorsTable.swift

145 lines
3.8 KiB
Swift
Raw Normal View History

2017-07-13 22:38:47 +02:00
//
// AuthorsTable.swift
2017-07-13 22:38:47 +02:00
// Database
//
// Created by Brent Simmons on 7/13/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
2017-08-04 06:10:01 +02:00
import RSDatabase
2017-07-13 22:38:47 +02:00
import Data
2017-08-06 21:37:47 +02:00
// article->authors is a many-to-many relationship.
// Theres a lookup table relating authorID and articleID.
//
// 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));
final class AuthorsTable: DatabaseTable {
let name: String
2017-07-29 21:13:38 +02:00
let queue: RSDatabaseQueue
2017-08-04 06:10:01 +02:00
private let cache = ObjectCache<Author>(keyPathForID: \Author.databaseID)
2017-08-06 21:37:47 +02:00
private var articleIDToAuthorsCache = [String: Set<Author>]()
private let authorsLookupTable = LookupTable(name: DatabaseTableName.authorsLookup, primaryKey: DatabaseKey.authorID, foreignKey: DatabaseKey.articleID)
2017-07-29 21:13:38 +02:00
init(name: String, queue: RSDatabaseQueue) {
self.name = name
2017-07-29 21:13:38 +02:00
self.queue = queue
}
2017-08-06 21:37:47 +02:00
func attachAuthors(_ articles: Set<Article>, _ database: FMDatabase) {
attachCachedAuthors(articles)
2017-08-07 06:16:13 +02:00
let articlesMissingAuthors = articlesNeedingAuthors(articles)
if articlesMissingAuthors.isEmpty {
2017-08-06 21:37:47 +02:00
return
}
2017-08-07 06:16:13 +02:00
let articleIDs = Set(articlesMissingAuthors.map { $0.databaseID })
2017-08-06 21:37:47 +02:00
let authorTable = fetchAuthorsForArticleIDs(articleIDs, database)
2017-08-07 06:16:13 +02:00
for article in articlesMissingAuthors {
2017-08-06 21:37:47 +02:00
let articleID = article.databaseID
if let authors = authorTable?[articleID] {
articleIDsWithNoAuthors.remove(articleID)
article.authors = Array(authors)
}
else {
articleIDsWithNoAuthors.insert(articleID)
}
}
}
}
private extension AuthorsTable {
func attachCachedAuthors(_ articles: Set<Article>) {
for article in articles {
if let authors = articleIDToAuthorsCache[article.databaseID] {
article.authors = Array(authors)
}
}
}
2017-08-07 06:16:13 +02:00
func articlesNeedingAuthors(_ articles: Set<Article>) -> Set<Article> {
2017-08-06 21:37:47 +02:00
2017-08-07 06:16:13 +02:00
// If article.authors is nil and article is not known to have zero authors, include it in the set.
let articlesWithNoAuthors = articles.withNilProperty(\Article.authors)
return Set(articlesWithNoAuthors.filter { !articleIDsWithNoAuthors.contains($0.databaseID) })
2017-08-06 21:37:47 +02:00
}
2017-08-04 06:10:01 +02:00
2017-08-06 21:37:47 +02:00
func fetchAuthorsForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) -> [String: Set<Author>]? {
2017-08-07 06:46:47 +02:00
let lookupTableDictionary = authorsLookupTable.fetchLookupTableDictionary(articleIDs, database)
let authorIDs = authorsLookupTable.primaryIDsInLookupTableDictionary(lookupTableDictionary)
2017-08-06 21:37:47 +02:00
if authorIDs.isEmpty {
return nil
}
guard let resultSet = selectRowsWhere(key: DatabaseKey.databaseID, inValues: Array(authorIDs), in: database) else {
return nil
}
let authors = authorsWithResultSet(resultSet)
if authors.isEmpty {
return nil
}
return authorTableWithLookupValues(lookupValues)
}
func authorTableWithLookupValues(_ lookupValues: Set<LookupValue>) -> [String: Set<Author>] {
var authorTable = [String: Set<Author>]()
for lookupValue in lookupValues {
let authorID = lookupValue.primaryID
guard let author = cache[authorID] else {
continue
}
let articleID = lookupValue.foreignID
if authorTable[articleID] == nil {
authorTable[articleID] = Set([author])
}
else {
authorTable[articleID]!.insert(author)
}
}
return authorTable
}
func authorsWithResultSet(_ resultSet: FMResultSet) -> Set<Author> {
return resultSet.mapToSet(authorWithRow)
}
func authorWithRow(_ row: FMResultSet) -> Author? {
2017-08-04 06:10:01 +02:00
guard let databaseID = row.string(forColumn: DatabaseKey.databaseID) else {
return nil
}
if let cachedAuthor = cache[databaseID] {
return cachedAuthor
2017-07-13 22:38:47 +02:00
}
2017-08-06 21:37:47 +02:00
guard let author = Author(databaseID: databaseID, row: row) else {
2017-07-13 22:38:47 +02:00
return nil
}
2017-08-04 06:10:01 +02:00
cache[databaseID] = author
2017-07-13 22:38:47 +02:00
return author
}
}