Continue surgery. Still broken build.

This commit is contained in:
Brent Simmons 2017-07-03 15:04:31 -07:00
parent d47c60e6de
commit 41d8a7f3de
10 changed files with 239 additions and 88 deletions

View File

@ -21,20 +21,22 @@ public final class ArticleStatus: Hashable {
public var starred = false
public var userDeleted = false
public var dateArrived: Date
var accountInfo: AccountInfo?
public let articleID: String
public var accountInfo: AccountInfo?
public let hashValue: Int
init(read: Bool, starred: Bool, userDeleted: Bool, dateArrived: Date, accountInfo: AccountInfo?) {
public init(articleID: String, read: Bool, starred: Bool, userDeleted: Bool, dateArrived: Date, accountInfo: AccountInfo?) {
self.articleID = articleID
self.read = read
self.starred = starred
self.userDeleted = userDeleted
self.dateArrived = dateArrived
self.accountInfo = accountInfo
self.hashValue = dateArrived.hashValue
self.hashValue = articleID.hashValue
}
func boolStatusForKey(_ key: String) -> Bool {
public func boolStatus(forKey key: String) -> Bool {
if let articleStatusKey = ArticleStatusKey(rawValue: key) {
switch articleStatusKey {
@ -52,7 +54,7 @@ public final class ArticleStatus: Hashable {
return false
}
func setBoolStatusForKey(_ status: Bool, key: String) {
public func setBoolStatus(_ status: Bool, forKey key: String) {
if let articleStatusKey = ArticleStatusKey(rawValue: key) {
switch articleStatusKey {
@ -74,6 +76,6 @@ public final class ArticleStatus: Hashable {
public class func ==(lhs: ArticleStatus, rhs: ArticleStatus) -> Bool {
return lhs.dateArrived == rhs.dateArrived && lhs.read == rhs.read && lhs.starred == rhs.starred
return lhs.articleID == rhs.articleID && lhs.dateArrived == rhs.dateArrived && lhs.read == rhs.read && lhs.starred == rhs.starred
}
}

View File

@ -0,0 +1,28 @@
//
// Keys.swift
// Database
//
// Created by Brent Simmons on 7/3/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
public struct DatabaseTableName {
static let articles = "articles"
static let statuses = "statuses"
}
public struct DatabaseKey {
static let articleID = "articleID"
static let accountInfo = "accountInfo"
// ArticleStatus
static let read = "read"
static let starred = "starred"
static let userDeleted = "userDeleted"
static let dateArrived = "dateArrived"
}

View File

@ -1,9 +1,5 @@
/*articleID is a hash of [something]+feedID. When there's a guid, [something] is a guid. Otherwise it's a combination of non-null properties.*/
CREATE TABLE if not EXISTS articles (articleID TEXT NOT NULL PRIMARY KEY, feedID TEXT NOT NULL, guid TEXT, title TEXT, body TEXT, datePublished DATE, dateModified DATE, link TEXT, permalink TEXT, author TEXT);
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);
/*Indexes*/
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 INDEX if not EXISTS feedIndex on articles (feedID);

View File

@ -12,9 +12,9 @@ import RSDatabase
import RSParser
import Data
let sqlLogging = false
private let sqlLogging = false
func logSQL(_ sql: String) {
private func logSQL(_ sql: String) {
if sqlLogging {
print("SQL: \(sql)")
}
@ -22,9 +22,7 @@ func logSQL(_ sql: String) {
typealias ArticleResultBlock = (Set<Article>) -> Void
private let articlesTableName = "articles"
final class LocalDatabase {
final class Database {
fileprivate let queue: RSDatabaseQueue
private let databaseFile: String
@ -51,10 +49,6 @@ final class LocalDatabase {
func fetchArticlesForFeed(_ feed: Feed) -> Set<Article> {
// if let articles = articleCache.cachedArticlesForFeedID(feed.feedID) {
// return articles
// }
var fetchedArticles = Set<Article>()
let feedID = feed.feedID
@ -63,17 +57,12 @@ final class LocalDatabase {
fetchedArticles = self.fetchArticlesForFeedID(feedID, database: database)
}
let articles = articleCache.uniquedArticles(fetchedArticles)
let articles = articleCache.uniquedArticles(fetchedArticles, statusesManager: statusesManager)
return filteredArticles(articles, feedCounts: [feed.feedID: fetchedArticles.count])
}
func fetchArticlesForFeedAsync(_ feed: Feed, _ resultBlock: @escaping ArticleResultBlock) {
// if let articles = articleCache.cachedArticlesForFeedID(feed.feedID) {
// resultBlock(articles)
// return
// }
let feedID = feed.feedID
queue.fetch { (database: FMDatabase!) -> Void in
@ -82,7 +71,7 @@ final class LocalDatabase {
DispatchQueue.main.async() { () -> Void in
let articles = self.articleCache.uniquedArticles(fetchedArticles)
let articles = self.articleCache.uniquedArticles(fetchedArticles, statusesManager: self.statusesManager)
let filteredArticles = self.filteredArticles(articles, feedCounts: [feed.feedID: fetchedArticles.count])
resultBlock(filteredArticles)
}
@ -131,13 +120,13 @@ final class LocalDatabase {
func fetchUnreadArticlesForFolder(_ folder: Folder) -> Set<Article> {
return fetchUnreadArticlesForFeedIDs(Array(folder.flattenedFeedIDs))
return fetchUnreadArticlesForFeedIDs(folder.flattenedFeedIDs())
}
func fetchUnreadArticlesForFeedIDs(_ feedIDs: [String]) -> Set<Article> {
if feedIDs.isEmpty {
return Set<LocalArticle>()
return Set<Article>()
}
var fetchedArticles = Set<Article>()
@ -158,7 +147,7 @@ final class LocalDatabase {
}
}
let articles = articleCache.uniquedArticles(fetchedArticles)
let articles = articleCache.uniquedArticles(fetchedArticles, statusesManager: statusesManager)
return filteredArticles(articles, feedCounts: counts)
}
@ -193,7 +182,7 @@ final class LocalDatabase {
fetchArticlesForFeedAsync(feed) { (articles) -> Void in
let articlesDictionary = self.articlesDictionary(articles as NSSet) as! [String: LocalArticle]
let articlesDictionary = self.articlesDictionary(articles as NSSet) as! [String: Article]
self.updateArticles(articlesDictionary, parsedArticles: parsedArticlesDictionary, feed: feed, completionHandler: completionHandler)
}
}
@ -208,7 +197,7 @@ final class LocalDatabase {
// MARK: Private
private extension LocalDatabase {
private extension Database {
// MARK: Saving Articles
@ -222,7 +211,7 @@ private extension LocalDatabase {
articleCache.cacheArticles(newArticles)
let newArticleDictionaries = newArticles.map { (oneArticle) in
return oneArticle.databaseDictionary
return oneArticle.databaseDictionary()
}
queue.update { (database: FMDatabase!) -> Void in
@ -232,7 +221,7 @@ private extension LocalDatabase {
for oneDictionary in articleChanges {
let oneArticleDictionary = oneDictionary.mutableCopy() as! NSMutableDictionary
let articleID = oneArticleDictionary[articleIDKey]!
let articleID = oneArticleDictionary[DatabaseKey.articleID]!
oneArticleDictionary.removeObject(forKey: articleIDKey)
let _ = database.rs_updateRows(with: oneArticleDictionary as [NSObject: AnyObject], whereKey: articleIDKey, equalsValue: articleID, tableName: articlesTableName)
@ -292,7 +281,7 @@ private extension LocalDatabase {
func createNewArticlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<Article> {
return Set(parsedArticles.map { LocalArticle(account: account, feedID: feedID, parsedArticle: $0) })
return Set(parsedArticles.map { Article(account: account, feedID: feedID, parsedArticle: $0) })
}
func articlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<Article> {
@ -300,7 +289,7 @@ private extension LocalDatabase {
var localArticles = Set<Article>()
for oneParsedArticle in parsedArticles {
let oneLocalArticle = LocalArticle(account: self.account, feedID: feedID, parsedArticle: oneParsedArticle)
let oneLocalArticle = Article(account: self.account, feedID: feedID, parsedArticle: oneParsedArticle)
localArticles.insert(oneLocalArticle)
}
@ -343,7 +332,7 @@ private extension LocalDatabase {
return articlesWithResultSet(resultSet)
}
return Set<LocalArticle>()
return Set<Article>()
}
func articlesWithResultSet(_ resultSet: FMResultSet) -> Set<Article> {
@ -496,46 +485,4 @@ private extension LocalDatabase {
}
}
}
func deleteOldArticlesInFeed(_ feed: Feed) {
numberOfArticlesInFeedID(feed.feedID) { (numberOfArticlesInFeed) in
if numberOfArticlesInFeed <= LocalDatabase.minimumNumberOfArticlesInFeed {
return
}
}
}
// MARK: Deleting Articles
// func deleteOldArticles(_ articleIDsInFeeds: Set<String>) {
//
// queue.update { (database: FMDatabase!) -> Void in
//
//// let cutoffDate = NSDate.rs_dateWithNumberOfDaysInThePast(60)
//// let articles = self.fetchArticlesWithWhereClause(database, whereClause: "statuses.dateArrived < ? limit 200", parameters: [cutoffDate])
//
//// var articleIDsToDelete = Set<String>()
////
//// for oneArticle in articles {
// // TODO
//// if !localAccountShouldIncludeArticle(oneArticle, articleIDsInFeed: articleIDsInFeeds) {
//// articleIDsToDelete.insert(oneArticle.articleID)
//// }
//// }
//
//// if !articleIDsToDelete.isEmpty {
//// database.rs_deleteRowsWhereKey(articleIDKey, inValues: Array(articleIDsToDelete), tableName: articlesTableName)
//// }
// }
// }
}

View File

@ -9,6 +9,11 @@
/* Begin PBXBuildFile section */
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 */; };
845580721F0AEE49003CCFA1 /* PropertyListTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845580711F0AEE49003CCFA1 /* PropertyListTransformer.swift */; };
845580761F0AF670003CCFA1 /* Article+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845580751F0AF670003CCFA1 /* Article+Database.swift */; };
845580781F0AF678003CCFA1 /* Folder+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845580771F0AF678003CCFA1 /* Folder+Database.swift */; };
8455807A1F0AF67D003CCFA1 /* ArticleStatus+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845580791F0AF67D003CCFA1 /* ArticleStatus+Database.swift */; };
846146271F0ABC7B00870CB3 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846146241F0ABC7400870CB3 /* RSParser.framework */; };
84E156EA1F0AB80500F8CC05 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E156E91F0AB80500F8CC05 /* Database.swift */; };
84E156EC1F0AB80E00F8CC05 /* ArticlesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E156EB1F0AB80E00F8CC05 /* ArticlesManager.swift */; };
@ -83,6 +88,11 @@
844BEE401F0AB3AB004AB7CD /* DatabaseTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DatabaseTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
844BEE451F0AB3AB004AB7CD /* DatabaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseTests.swift; sourceTree = "<group>"; };
844BEE471F0AB3AB004AB7CD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
845580661F0AEBCD003CCFA1 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
845580711F0AEE49003CCFA1 /* PropertyListTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyListTransformer.swift; sourceTree = "<group>"; };
845580751F0AF670003CCFA1 /* Article+Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Article+Database.swift"; path = "Extensions/Article+Database.swift"; sourceTree = "<group>"; };
845580771F0AF678003CCFA1 /* Folder+Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Folder+Database.swift"; path = "Extensions/Folder+Database.swift"; sourceTree = "<group>"; };
845580791F0AF67D003CCFA1 /* ArticleStatus+Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "ArticleStatus+Database.swift"; path = "Extensions/ArticleStatus+Database.swift"; sourceTree = "<group>"; };
8461461E1F0ABC7300870CB3 /* RSParser.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSParser.xcodeproj; path = ../RSParser/RSParser.xcodeproj; sourceTree = "<group>"; };
84E156E81F0AB75600F8CC05 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
84E156E91F0AB80500F8CC05 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = "<group>"; };
@ -121,8 +131,11 @@
isa = PBXGroup;
children = (
84E156E91F0AB80500F8CC05 /* Database.swift */,
845580661F0AEBCD003CCFA1 /* Constants.swift */,
84E156EB1F0AB80E00F8CC05 /* ArticlesManager.swift */,
84E156ED1F0AB81400F8CC05 /* StatusesManager.swift */,
845580711F0AEE49003CCFA1 /* PropertyListTransformer.swift */,
8461462A1F0AC44100870CB3 /* Extensions */,
84E156EF1F0AB81F00F8CC05 /* CreateStatements.sql */,
84E156E81F0AB75600F8CC05 /* Info.plist */,
844BEE441F0AB3AB004AB7CD /* DatabaseTests */,
@ -158,6 +171,16 @@
name = Products;
sourceTree = "<group>";
};
8461462A1F0AC44100870CB3 /* Extensions */ = {
isa = PBXGroup;
children = (
845580771F0AF678003CCFA1 /* Folder+Database.swift */,
845580751F0AF670003CCFA1 /* Article+Database.swift */,
845580791F0AF67D003CCFA1 /* ArticleStatus+Database.swift */,
);
name = Extensions;
sourceTree = "<group>";
};
84E156F21F0AB83600F8CC05 /* Products */ = {
isa = PBXGroup;
children = (
@ -370,6 +393,11 @@
84E156EA1F0AB80500F8CC05 /* Database.swift in Sources */,
84E156EC1F0AB80E00F8CC05 /* ArticlesManager.swift in Sources */,
84E156EE1F0AB81400F8CC05 /* StatusesManager.swift in Sources */,
845580671F0AEBCD003CCFA1 /* Constants.swift in Sources */,
845580781F0AF678003CCFA1 /* Folder+Database.swift in Sources */,
845580761F0AF670003CCFA1 /* Article+Database.swift in Sources */,
845580721F0AEE49003CCFA1 /* PropertyListTransformer.swift in Sources */,
8455807A1F0AF67D003CCFA1 /* ArticleStatus+Database.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -0,0 +1,26 @@
//
// Article+Database.swift
// Database
//
// Created by Brent Simmons on 7/3/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import RSDatabase
import Data
extension Article {
convenience init?(row: FMResultSet) {
}
func databaseDictionary() -> NSDictionary {
var d = NSMutableDictionary()
return d.copy() as! NSDictionary
}
}

View File

@ -0,0 +1,52 @@
//
// ArticleStatus+Database.swift
// Database
//
// Created by Brent Simmons on 7/3/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import RSDatabase
import Data
extension ArticleStatus {
convenience init?(row: FMResultSet) {
let articleID = row.string(forColumn: DatabaseKey.articleID)
if (articleID == nil) {
return nil
}
let read = row.bool(forColumn: DatabaseKey.read)
let starred = row.bool(forColumn: DatabaseKey.starred)
let userDeleted = row.bool(forColumn: DatabaseKey.userDeleted)
var dateArrived = row.date(forColumn: DatabaseKey.dateArrived)
if (dateArrived == nil) {
dateArrived = NSDate.distantPast
}
let accountInfoPlist = PropertyListTransformer.accountInfoWithRow(row)
self.init(articleID: articleID!, read: read, starred: starred, userDeleted: userDeleted, dateArrived: dateArrived!, accountInfo: accountInfoPlist)
}
func databaseDictionary() -> NSDictionary {
let d = NSMutableDictionary()
d[DatabaseKey.articleID] = articleID
d[DatabaseKey.read] = read
d[DatabaseKey.starred] = starred
d[DatabaseKey.userDeleted] = userDeleted
d[DatabaseKey.dateArrived] = dateArrived
if let accountInfo = accountInfo, let data = PropertyListTransformer.data(withPropertyList: accountInfo) {
d[DatabaseKey.accountInfo] = data
}
return d.copy() as! NSDictionary
}
}

View File

@ -0,0 +1,18 @@
//
// Folder+Database.swift
// Database
//
// Created by Brent Simmons on 7/3/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import Data
extension Folder {
func flattenedFeedIDs() -> [String] {
return flattenedFeeds().map { $0.feedID }
}
}

View File

@ -0,0 +1,54 @@
//
// AccountInfo.swift
// Database
//
// Created by Brent Simmons on 7/3/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import RSDatabase
import Data
// This allows for serializing structures such as Author, Attachment, and AccountInfo
// without having to create separate tables and lookup tables.
// While there are good strong arguments for using separate tables,
// we decided that the relative simplicity this allows is worth it.
struct PropertyListTransformer {
static func accountInfoWithRow(_ row: FMResultSet) -> AccountInfo? {
guard let rawAccountInfo = row.data(forColumn: DatabaseKey.accountInfo) else {
return nil
}
return propertyList(withData: rawAccountInfo) as? AccountInfo
}
static func propertyListWithRow(_ row: FMResultSet, column: String) -> Any? {
guard let rawData = row.data(forColumn: column) else {
return nil
}
return propertyList(withData: rawData)
}
static func propertyList(withData data: Data) -> Any? {
do {
return try PropertyListSerialization.propertyList(fromData: rawAccountInfo, options: [], format: nil)
} catch {
return nil
}
}
static func data(withPropertyList plist: Any) -> Data? {
do {
return try PropertyListSerialization.data(from: plist, format: .binary, options: [])
}
catch {
return nil
}
}
}

View File

@ -22,7 +22,7 @@ final class StatusesManager {
self.queue = queue
}
func markArticles(_ articles: Set<Article>, statusKey: ArticleStatusKey, flag: Bool) {
func markArticles(_ articles: Set<Article>, statusKey: String, flag: Bool) {
assertNoMissingStatuses(articles)
let statuses = Set(articles.flatMap { $0.status })
@ -36,7 +36,7 @@ final class StatusesManager {
if let cachedStatus = cachedStatusForArticleID(oneArticle.articleID) {
oneArticle.status = cachedStatus
}
else if let oneArticleStatus = oneArticle.status as? ArticleStatus {
else if let oneArticleStatus = oneArticle.status {
cacheStatus(oneArticleStatus)
}
}
@ -85,7 +85,7 @@ private extension StatusesManager {
// MARK: Marking
func markArticleStatuses(_ statuses: Set<ArticleStatus>, statusKey: ArticleStatusKey, flag: Bool) {
func markArticleStatuses(_ statuses: Set<ArticleStatus>, statusKey: String, flag: Bool) {
// Ignore the statuses where status.[statusKey] == flag. Update the remainder and save in database.
@ -93,8 +93,8 @@ private extension StatusesManager {
statuses.forEach { (oneStatus) in
if oneStatus.boolStatusForKey(statusKey) != flag {
oneStatus.setBoolStatusForKey(flag, articleStatusKey: statusKey)
if oneStatus.boolStatus(forKey: statusKey) != flag {
oneStatus.setBoolStatus(flag, forKey: statusKey)
articleIDs.insert(oneStatus.articleID)
}
}
@ -112,7 +112,7 @@ private extension StatusesManager {
return Set<ArticleStatus>()
}
guard let resultSet = database.rs_selectRowsWhereKey(articleIDKey, inValues: Array(articleIDs), tableName: statusesTableName) else {
guard let resultSet = database.rs_selectRowsWhereKey(DatabaseKey.articleID, inValues: Array(articleIDs), tableName: statusesTableName) else {
return Set<ArticleStatus>()
}
@ -137,7 +137,7 @@ private extension StatusesManager {
func saveStatuses(_ statuses: Set<ArticleStatus>) {
let statusArray = statuses.map { (oneStatus) -> NSDictionary in
return oneStatus.databaseDictionary
return oneStatus.databaseDictionary()
}
queue.update { (database: FMDatabase!) -> Void in
@ -210,9 +210,9 @@ private extension StatusesManager {
extension ParsedItem {
var databaseID: String {
var articleID: String {
get {
return "\(feedURL) \(uniqueID)"
return "\(feedURL) \(uniqueID)" //Must be same as Article.articleID
}
}
}