2017-07-13 22:38:47 +02:00
|
|
|
|
//
|
2017-07-29 21:08:10 +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.
|
|
|
|
|
// There’s 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));
|
|
|
|
|
|
|
|
|
|
|
2017-07-29 21:08:10 +02:00
|
|
|
|
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 var articleIDsWithNoAuthors = Set<String>()
|
|
|
|
|
private let authorsLookupTable = LookupTable(name: DatabaseTableName.authorsLookup, primaryKey: DatabaseKey.authorID, foreignKey: DatabaseKey.articleID)
|
2017-07-29 21:08:10 +02:00
|
|
|
|
|
2017-07-29 21:13:38 +02:00
|
|
|
|
init(name: String, queue: RSDatabaseQueue) {
|
2017-07-29 21:08:10 +02:00
|
|
|
|
|
|
|
|
|
self.name = name
|
2017-07-29 21:13:38 +02:00
|
|
|
|
self.queue = queue
|
2017-07-29 21:08:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-06 21:37:47 +02:00
|
|
|
|
func attachAuthors(_ articles: Set<Article>, _ database: FMDatabase) {
|
|
|
|
|
|
|
|
|
|
attachCachedAuthors(articles)
|
|
|
|
|
|
|
|
|
|
let articlesNeedingAuthors = articlesMissingAuthors(articles)
|
|
|
|
|
if articlesNeedingAuthors.isEmpty {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let articleIDs = Set(articlesNeedingAuthors.map { $0.databaseID })
|
|
|
|
|
let authorTable = fetchAuthorsForArticleIDs(articleIDs, database)
|
|
|
|
|
|
|
|
|
|
for article in articlesNeedingAuthors {
|
|
|
|
|
|
|
|
|
|
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 articlesMissingAuthors(_ articles: Set<Article>) -> Set<Article> {
|
|
|
|
|
|
|
|
|
|
return articles.filter{ (article) -> Bool in
|
|
|
|
|
|
|
|
|
|
if let _ = article.authors {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if articleIDsWithNoAuthors.contains(article.databaseID) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
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>]? {
|
|
|
|
|
|
|
|
|
|
let lookupValues = authorsLookupTable.fetchLookupValues(articleIDs, database: database)
|
|
|
|
|
let authorIDs = Set(lookupValues.map { $0.primaryID })
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|