2017-07-11 05:54:00 +02:00
|
|
|
|
//
|
|
|
|
|
// TagsManager.swift
|
|
|
|
|
// Database
|
|
|
|
|
//
|
|
|
|
|
// Created by Brent Simmons on 7/8/17.
|
|
|
|
|
// Copyright © 2017 Ranchero Software. All rights reserved.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
import RSDatabase
|
2017-07-12 22:25:10 +02:00
|
|
|
|
import Data
|
2017-07-11 05:54:00 +02:00
|
|
|
|
|
2017-08-06 21:37:47 +02:00
|
|
|
|
// Article->tags is a many-to-many relationship.
|
|
|
|
|
// Since a tag is just a simple string, the tags table and the lookup table are the same table.
|
|
|
|
|
//
|
|
|
|
|
// CREATE TABLE if not EXISTS tags(tagName TEXT NOT NULL, articleID TEXT NOT NULL, PRIMARY KEY(tagName, articleID));
|
|
|
|
|
// CREATE INDEX if not EXISTS tags_tagName_index on tags (tagName COLLATE NOCASE);
|
2017-07-11 05:54:00 +02:00
|
|
|
|
|
2017-07-12 22:25:10 +02:00
|
|
|
|
typealias TagNameSet = Set<String>
|
|
|
|
|
|
2017-07-29 21:08:10 +02:00
|
|
|
|
final class TagsTable: DatabaseTable {
|
|
|
|
|
|
|
|
|
|
let name: String
|
2017-07-29 21:13:38 +02:00
|
|
|
|
let queue: RSDatabaseQueue
|
2017-08-07 06:16:13 +02:00
|
|
|
|
let lookupTable: LookupTable
|
2017-07-11 05:54:00 +02:00
|
|
|
|
|
2017-07-29 21:13:38 +02:00
|
|
|
|
init(name: String, queue: RSDatabaseQueue) {
|
2017-07-11 05:54:00 +02:00
|
|
|
|
|
2017-07-29 21:08:10 +02:00
|
|
|
|
self.name = name
|
2017-07-29 21:13:38 +02:00
|
|
|
|
self.queue = queue
|
2017-08-07 06:16:13 +02:00
|
|
|
|
self.lookupTable = LookupTable(name: DatabaseTableName.tags, primaryKey: DatabaseKey.tagName, foreignKey: DatabaseKey.articleID)
|
2017-07-11 05:54:00 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
func attachTags(_ articles: Set<Article>, _ database: FMDatabase) {
|
2017-07-11 05:54:00 +02:00
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
guard let lookupTableDictionary = lookupTable.fetchLookupTableDictionary(articleIDs, database) else {
|
2017-07-11 05:54:00 +02:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
for article in articles {
|
|
|
|
|
if let lookupValues = lookupTableDictionary[article.databaseID] {
|
|
|
|
|
article.tags = lookupValues.tags()
|
2017-07-11 05:54:00 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
// func saveTagsForArticles(_ articles: Set<Article>) {
|
|
|
|
|
//
|
|
|
|
|
// var articlesToSaveTags = Set<Article>()
|
|
|
|
|
// var articlesToRemoveTags = Set<Article>()
|
|
|
|
|
//
|
|
|
|
|
// articles.forEach { (oneArticle) in
|
|
|
|
|
//
|
|
|
|
|
// if articleTagsMatchCache(oneArticle) {
|
|
|
|
|
// return
|
|
|
|
|
// }
|
|
|
|
|
// if let tags = oneArticle.tags {
|
|
|
|
|
// articlesToSaveTags.insert(oneArticle)
|
|
|
|
|
// }
|
|
|
|
|
// else {
|
|
|
|
|
// articlesToRemoveTags.insert(oneArticle)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// if !articlesToSaveTags.isEmpty {
|
|
|
|
|
// updateTagsForArticles(articlesToSaveTags)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// if !articlesToRemoveTags.isEmpty {
|
|
|
|
|
// removeArticleFromTags(articlesToRemoveTags)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
}
|
2017-07-11 05:54:00 +02:00
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
private extension TagsTable {
|
2017-07-11 05:54:00 +02:00
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
// func cacheTagsForArticle(_ article: Article, tags: TagNameSet) {
|
|
|
|
|
//
|
|
|
|
|
// articleIDsWithNoTags.remove(article.articleID)
|
|
|
|
|
// articleIDCache[article.articleID] = tags
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// func cachedTagsForArticleID(_ articleID: String) -> TagNameSet? {
|
|
|
|
|
//
|
|
|
|
|
// return articleIDsCache[articleID]
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// func articleTagsMatchCache(_ article: Article) -> Bool {
|
|
|
|
|
//
|
|
|
|
|
// if let tags = article.tags {
|
|
|
|
|
// return tags == articleIDCache[article.articleID]
|
|
|
|
|
// }
|
|
|
|
|
// return articleIDIsKnowToHaveNoTags(article.articleID)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// func articleIDIsKnownToHaveNoTags(_ articleID: String) -> Bool {
|
|
|
|
|
//
|
|
|
|
|
// return articleIDsWithNoTags.contains(articleID)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// func removeTagsFromCacheForArticleID(_ articleID: String) {
|
|
|
|
|
//
|
|
|
|
|
// articleIDsCache[oneArticleID] = nil
|
|
|
|
|
// articleIDsWithNoTags.insert(oneArticleID)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// func removeArticleFromTags(_ articles: Set<Article>) {
|
|
|
|
|
//
|
|
|
|
|
// var articleIDsToRemove = [String]()
|
|
|
|
|
//
|
|
|
|
|
// articles.forEach { (oneArticle) in
|
|
|
|
|
// let oneArticleID = oneArticle.articleID
|
|
|
|
|
// if articleIDIsKnownToHaveNoTags(oneArticle) {
|
|
|
|
|
// return
|
|
|
|
|
// }
|
|
|
|
|
// articleIDsToRemove += oneArticleID
|
|
|
|
|
// removeTagsFromCacheForArticleID(oneArticleID)
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// if !articleIDsToRemove.isEmpty {
|
|
|
|
|
// queue.update { (database) in
|
|
|
|
|
// database.rs_deleteRowsWhereKey(DatabaseKey.articleID, inValues: articleIDsToRemove, tableName: DatabaseTableName.tags)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// typealias TagsTable = [String: TagNameSet] // [articleID: Set<tagName>]
|
|
|
|
|
//
|
|
|
|
|
// func updateTagsForArticles(_ articles: Set<Article>) {
|
|
|
|
|
//
|
|
|
|
|
// var tagsForArticleIDs = TagsTable()
|
|
|
|
|
// articles.forEach { (oneArticle)
|
|
|
|
|
// if let tags = oneArticle.tags {
|
|
|
|
|
// cacheTagsForArticle(oneArticle, tags)
|
|
|
|
|
// tagsForArticleIDs[oneArticle.articleID] = oneArticle.tags
|
|
|
|
|
// }
|
|
|
|
|
// else {
|
|
|
|
|
// assertionFailure("article must have tags")
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// if tagsForArticleIDs.isEmpty { // Shouldn’t be empty
|
|
|
|
|
// return
|
|
|
|
|
// }
|
|
|
|
|
// let articleIDs = tagsForArticleIDs.keys
|
|
|
|
|
//
|
|
|
|
|
// queue.update { (database) in
|
|
|
|
|
//
|
|
|
|
|
// let existingTags = self.fetchTagsForArticleIDs(articleIDs, database: database)
|
|
|
|
|
// self.syncIncomingAndExistingTags(incomingTags: tagsForArticleIDs, existingTags: existingTags, database: database)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// func syncIncomingAndExistingTags(incomingTags: TagsTable, existingTags: TagsTable, database: database) {
|
|
|
|
|
//
|
|
|
|
|
// for (oneArticleID, oneTagNames) in incomingTags {
|
|
|
|
|
// if let existingTagNames = existingTags[oneArticleID] {
|
|
|
|
|
// syncIncomingAndExistingTagsForArticleID(oneArticleID, incomingTagNames: oneTagNames, existingTagNames: existingTagNames, database: database)
|
|
|
|
|
// }
|
|
|
|
|
// else {
|
|
|
|
|
// saveIncomingTagsForArticleID(oneArticleID, tagNames: oneTagNames, database: database)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// func saveIncomingTagsForArticleID(_ articleID: String, tagNames: TagNameSet, database: FMDatabase) {
|
|
|
|
|
//
|
|
|
|
|
// // No existing tags in database. Simple save.
|
|
|
|
|
//
|
|
|
|
|
// for oneTagName in tagNames {
|
|
|
|
|
// let oneDictionary = [DatabaseTableName.articleID: articleID, DatabaseTableName.tagName: oneTagName]
|
|
|
|
|
// database.rs_insertRow(with: oneDictionary, insertType: .OrIgnore, tableName: DatabaseTableName.tags)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// func syncingIncomingAndExistingTagsForArticleID(_ articleID: String, incomingTagNames: TagNameSet, existingTagNames: TagNameSet, database: FMDatabase) {
|
|
|
|
|
//
|
|
|
|
|
// if incomingTagNames == existingTagNames {
|
|
|
|
|
// return
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// var tagsToRemove = TagNameSet()
|
|
|
|
|
// for oneExistingTagName in existingTagNames {
|
|
|
|
|
// if !incomingTagNames.contains(oneExistingTagName) {
|
|
|
|
|
// tagsToRemove.insert(oneExistingTagName)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// var tagsToAdd = TagNameSet()
|
|
|
|
|
// for oneIncomingTagName in incomingTagNames {
|
|
|
|
|
// if !existingTagNames.contains(oneIncomingTagName) {
|
|
|
|
|
// tagsToAdd.insert(oneIncomingTagName)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// if !tagsToRemove.isEmpty {
|
|
|
|
|
// let placeholders = NSString.rs_SQLValueListWithPlaceholders
|
|
|
|
|
// let sql = "delete from \(DatabaseTableName.tags) where \(DatabaseKey.articleID) = ? and \(DatabaseKey.tagName) in "
|
|
|
|
|
// database.executeUpdate(sql, withArgumentsIn: [articleID, ])
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// func fetchTagsForArticleIDs(_ articleIDs: Set<String>, database: FMDatabase) -> TagsTable {
|
|
|
|
|
//
|
|
|
|
|
// var tagSpecifiers = TagsTable()
|
|
|
|
|
//
|
|
|
|
|
// guard let rs = database.rs_selectRowsWhereKey(DatabaseKey.articleID, inValues: Array(articleIDs), tableName: DatabaseTableName.tags) else {
|
|
|
|
|
// return tagSpecifiers
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// while rs.next() {
|
|
|
|
|
//
|
|
|
|
|
// guard let oneTagName = rs.string(forColumn: DatabaseKey.tagName), let oneArticleID = rs.string(forColumn: DatabaseKey.articleID) else {
|
|
|
|
|
// continue
|
|
|
|
|
// }
|
|
|
|
|
// if tagSpecifiers[oneArticleID] == nil {
|
|
|
|
|
// tagSpecifiers[oneArticleID] = Set([oneTagName])
|
|
|
|
|
// }
|
|
|
|
|
// else {
|
|
|
|
|
// tagSpecifiers[oneArticleID]!.insert(oneTagName)
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// return tagSpecifiers
|
|
|
|
|
// }
|
|
|
|
|
}
|
2017-07-11 05:54:00 +02:00
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
private extension Set where Element == LookupValue {
|
2017-07-11 05:54:00 +02:00
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
func tags() -> Set<String> {
|
2017-07-11 05:54:00 +02:00
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
return Set(flatMap{ $0.primaryID })
|
2017-07-11 05:54:00 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|