NetNewsWire/Frameworks/Database/AuthorsTable.swift
2017-08-06 21:46:47 -07:00

145 lines
3.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// AuthorsTable.swift
// Database
//
// Created by Brent Simmons on 7/13/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import RSDatabase
import Data
// 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
let queue: RSDatabaseQueue
private let cache = ObjectCache<Author>(keyPathForID: \Author.databaseID)
private var articleIDToAuthorsCache = [String: Set<Author>]()
private let authorsLookupTable = LookupTable(name: DatabaseTableName.authorsLookup, primaryKey: DatabaseKey.authorID, foreignKey: DatabaseKey.articleID)
init(name: String, queue: RSDatabaseQueue) {
self.name = name
self.queue = queue
}
func attachAuthors(_ articles: Set<Article>, _ database: FMDatabase) {
attachCachedAuthors(articles)
let articlesMissingAuthors = articlesNeedingAuthors(articles)
if articlesMissingAuthors.isEmpty {
return
}
let articleIDs = Set(articlesMissingAuthors.map { $0.databaseID })
let authorTable = fetchAuthorsForArticleIDs(articleIDs, database)
for article in articlesMissingAuthors {
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)
}
}
}
func articlesNeedingAuthors(_ articles: Set<Article>) -> Set<Article> {
// 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) })
}
func fetchAuthorsForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) -> [String: Set<Author>]? {
let lookupTableDictionary = authorsLookupTable.fetchLookupTableDictionary(articleIDs, database)
let authorIDs = authorsLookupTable.primaryIDsInLookupTableDictionary(lookupTableDictionary)
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? {
guard let databaseID = row.string(forColumn: DatabaseKey.databaseID) else {
return nil
}
if let cachedAuthor = cache[databaseID] {
return cachedAuthor
}
guard let author = Author(databaseID: databaseID, row: row) else {
return nil
}
cache[databaseID] = author
return author
}
}