Make progress on attachments. Build still broken.

This commit is contained in:
Brent Simmons 2017-07-16 19:36:38 -07:00
parent 13f8c4f9b1
commit 8fc4c3041d
12 changed files with 364 additions and 31 deletions

View File

@ -39,10 +39,9 @@ public final class Article: Hashable {
}
}
init(account: Account, databaseID: String, feedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: [Author]?, tags: Set<String>?, attachments: [Attachment]?, accountInfo: AccountInfo?) {
init(account: Account, databaseID: String?, feedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: [Author]?, tags: Set<String>?, attachments: [Attachment]?, accountInfo: AccountInfo?) {
self.account = account
self.databaseID = databaseID
self.feedID = feedID
self.uniqueID = uniqueID
self.title = title
@ -59,8 +58,17 @@ public final class Article: Hashable {
self.tags = tags
self.attachments = attachments
self.accountInfo = accountInfo
self.hashValue = account.hashValue ^ self.articleID.hashValue
let _databaseID: String
if let databaseID = databaseID {
_databaseID = databaseID
}
else {
_databaseID = databaseIDWithString("\(feedID) \(uniqueID)")
}
self.databaseID = _databaseID
self.hashValue = account.hashValue ^ _databaseID.hashValue
}
public class func ==(lhs: Article, rhs: Article) -> Bool {

View File

@ -8,21 +8,43 @@
import Foundation
public struct Attachment: Equatable {
public struct Attachment: Hashable {
public let databaseID: String // Calculated
public let articleID: String // Article.databaseID
public let url: String
public let mimeType: String?
public let title: String?
public let sizeInBytes: Int?
public let durationInSeconds: Int?
public let hashValue: Int
public init(url: String, mimeType: String?, title: String?, sizeInBytes: Int?, durationInSeconds: Int?) {
public init(databaseID: String?, articleID: String, url: String, mimeType: String?, title: String?, sizeInBytes: Int?, durationInSeconds: Int?) {
self.articleID = articleID
self.url = url
self.mimeType = mimeType
self.title = title
self.sizeInBytes = sizeInBytes
self.durationInSeconds = durationInSeconds
var s = articleID + url
s += mimeType ?? ""
s += title ?? ""
if let sizeInBytes = sizeInBytes {
s += "\(sizeInBytes)"
}
if let durationInSeconds = durationInSeconds {
s += "\(durationInSeconds)"
}
self.hashValue = s.hashValue
if let databaseID = databaseID {
self.databaseID = databaseID
}
else {
self.databaseID = databaseIDWithString(s)
}
}
public static func ==(lhs: Attachment, rhs: Attachment) -> Bool {

View File

@ -33,9 +33,12 @@ public struct Author: Hashable {
s += avatarURL ?? ""
s += emailAddress ?? ""
self.hashValue = s.hashValue
if databaseID == nil {
self.databaseID = (s as NSString).rs_md5Hash()
if let databaseID = databaseID {
self.databaseID = databaseID
}
else {
self.databaseID = databaseIDWithString(s)
}
}

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
840405CA1F1A8E4300DF0296 /* DatabaseID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840405C91F1A8E4300DF0296 /* DatabaseID.swift */; };
843079FA1F0AB57F00B4B7F7 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844BEEA31F0AB512004AB7CD /* RSCore.framework */; };
844BEE651F0AB3C9004AB7CD /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844BEE5B1F0AB3C8004AB7CD /* Data.framework */; };
844BEE6A1F0AB3C9004AB7CD /* DataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844BEE691F0AB3C9004AB7CD /* DataTests.swift */; };
@ -61,6 +62,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
840405C91F1A8E4300DF0296 /* DatabaseID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseID.swift; sourceTree = "<group>"; };
844BEE5B1F0AB3C8004AB7CD /* Data.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Data.framework; sourceTree = BUILT_PRODUCTS_DIR; };
844BEE641F0AB3C9004AB7CD /* DataTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DataTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
844BEE691F0AB3C9004AB7CD /* DataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTests.swift; sourceTree = "<group>"; };
@ -116,6 +118,7 @@
844BEE801F0AB4D0004AB7CD /* Author.swift */,
844BEE821F0AB4D6004AB7CD /* Attachment.swift */,
844BEE841F0AB4DB004AB7CD /* ArticleStatus.swift */,
840405C91F1A8E4300DF0296 /* DatabaseID.swift */,
844BEE861F0AB4E3004AB7CD /* BatchUpdates.swift */,
844BEE881F0AB4E7004AB7CD /* Notifications.swift */,
844BEE8A1F0AB4EF004AB7CD /* OPML */,
@ -342,6 +345,7 @@
844BEE871F0AB4E3004AB7CD /* BatchUpdates.swift in Sources */,
844BEE811F0AB4D0004AB7CD /* Author.swift in Sources */,
844BEE921F0AB4EF004AB7CD /* OPMLRepresentable.swift in Sources */,
840405CA1F1A8E4300DF0296 /* DatabaseID.swift in Sources */,
844BEE9B1F0AB4F8004AB7CD /* UnreadCountProvider.swift in Sources */,
844BEE851F0AB4DB004AB7CD /* ArticleStatus.swift in Sources */,
);

View File

@ -0,0 +1,18 @@
//
// DatabaseID.swift
// Data
//
// Created by Brent Simmons on 7/15/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
// MD5 works because:
// * Its fast
// * Collisions arent going to happen with feed data
public func databaseIDWithString(_ s: String) -> String {
return (s as NSString).rs_md5Hash()
}

View File

@ -0,0 +1,233 @@
//
// AttachmentsManager.swift
// Database
//
// Created by Brent Simmons on 7/15/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import RSDatabase
import Data
// Attachments are treated as atomic.
// If an attachment in a feed changes any of its values,
// its actually saved as a new attachment and the old one is deleted.
// (This is rare compared to an article in a feed changing its text, for instance.)
//
// Article -> Attachment is one-to-many.
// Attachment -> Article is one-to-one.
// A given attachment can be owned by one and only one Article.
// An attachment with the same exact values (except for articleID) might exist.
// (That would be quite rare. But its by design.)
//
// All the functions here must be called only from inside the database serial queue.
// (The serial queue makes locking unnecessary.)
//
// Attachments are cached, for the lifetime of the app run, once fetched or saved.
// Because:
// * They dont take up much space.
// * It seriously cuts down on the number of database reads and writes.
final class AttachmentsManager {
private var cachedAttachments = [String: Attachment]() // Attachment.databaseID key
private var cachedAttachmentsByArticle = [String: Set<Attachment>]() // Article.databaseID key
private var articlesWithNoAttachments = Set<String>() // Article.databaseID
private let table = DatabaseTable(name: DatabaseTableName.attachments)
func fetchAttachmentsForArticles(_ articles: Set<Article>, database: FMDatabase) {
}
func saveAttachmentsForArticles(_ articles: Set<Article>, database: FMDatabase) {
// This is complex and overly long because its optimized for fewest database hits.
var articlesWithPossiblyAllAttachmentsDeleted = Set<Article>()
var attachmentsToSave = Set<Attachment>()
var attachmentsToDelete = Set<Attachment>()
func reconcileAttachments(incomingAttachments: Set<Attachment>, existingAttachments: Set<Attachment>) {
for oneIncomingAttachment in incomingAttachments { // Add some.
if !existingAttachments.contains(oneIncomingAttachment) {
attachmentsToSave.insert(oneIncomingAttachment)
}
}
for oneExistingAttachment in existingAttachments { // Delete some.
if !incomingAttachments.contains(oneExistingAttachment) {
attachmentsToDelete.insert(oneExistingAttachment)
}
}
}
for oneArticle in articles {
if let oneAttachments = oneArticle.attachments, !oneAttachments.isEmpty {
// If it matches the cache, then do nothing.
if let oneCachedAttachments = cachedAttachmentsByArticle(oneArticle.databaseID) {
if oneCachedAttachments == oneAttachments {
continue
}
// There is a cache and it doesnt match.
reconcileAttachments(incomingAttachments: oneAttachments, existingAttachments: oneCachedAttachments)
}
else { // no cache, but article has attachments
if let resultSet = table.selectRowsWhere(key: DatabaseKey.articleID, equals: oneArticle.databaseID, in: database) {
let existingAttachments = attachmentsWithResultSet(resultSet)
if existingAttachments != oneAttachments { // Dont match?
reconcileAttachments(incomingAttachments: oneAttachments, existingAttachments: existingAttachments)
}
}
else {
// Nothing in database. Just save.
attachmentsToSave.formUnion(oneAttachments)
}
}
cacheAttachmentsForArticle(oneArticle)
}
else {
// No attachments: might need to delete them all from database
if !articlesWithNoAttachments.contains(oneArticle.databaseID) {
articlesWithPossiblyAllAttachmentsDeleted.insert(oneArticle)
uncacheAttachmentsForArticle(oneArticle)
}
}
}
if !articlesWithPossiblyAllAttachmentsDeleted.isEmpty {
deleteAttachmentsForArticles(articlesWithPossiblyAllAttachmentsDeleted)
}
}
}
private extension AttachmentsManager {
func deleteAttachmentsForArticles(_ articles: Set<Article>, database: FMDatabase) {
let articleIDs = articles.map { $0.databaseID }
articlesWithNoAttachments.formUnion(Set(articleIDs))
articleIDs.forEach { cachedAttachmentsByArticle[$0] = nil }
let _ = database.rs_deleteRowsWhereKey(DatabaseKey.articleID, inValues: articleIDs, tableName: DatabaseTableName.attachments)
}
func addCachedAttachmentsToArticle(_ article: Article) {
if let _ = article.attachments {
return
}
if let attachments = cachedAttachmentsByArticle[article.databaseID] {
article.attachments = attachments
}
}
func fetchAttachmentsForArticle(_ article: Article, database: FMDatabase) {
if articlesWithNoAttachments.contains(article.databaseID) {
return
}
addCachedAttachmentsToArticle(article)
if let _ = article.attachments {
return
}
}
func uncacheAttachmentsForArticle(_ article: Article) {
assert(article.attachments == nil || article.attachments.isEmpty)
articlesWithNoAttachments.insert(article.databaseID)
cachedAttachmentsByArticle[article.databaseID] = nil
var attachmentDatabaseIDsToUncache = Set<String>()
for (databaseID, attachment) in cachedAttachments {
if attachment.articleID == article.databaseID {
attachmentDatabaseIDsToUncache.insert(databaseID)
}
}
attachmentDatabaseIDsToUncache.forEach { uncacheAttachmentWithDatabaseID($0) }
}
func cacheAttachmentsForArticle(_ article: Article) {
guard let attachments = article.attachments, !attachments.isEmpty else {
assertionFailure("article.attachments must not be empty")
}
articlesWithNoAttachments.remove(article.databaseID)
cachedAttachmentsByArticle[article.databaseID] = attachments
cacheAttachment(attachments)
}
func cachedAttachmentForDatabaseID(_ databaseID: String) -> Attachment? {
return cachedAttachments[databaseID]
}
func cacheAttachments(_ attachments: Set<Attachment>) {
attachments.forEach { cacheAttachment($) }
}
func cacheAttachment(_ attachment: Attachment) {
cachedAttachments[attachment.databaseID] = attachment
}
func uncacheAttachmentWithDatabaseID(_ databaseID: String) {
cachedAttachments[databaseID] = nil
}
func saveAttachmentsForArticle(_ article: Article, database: FMDatabase) {
if let attachments = article.attachments {
}
else {
if articlesWithNoAttachments.contains(article.databaseID) {
return
}
articlesWithNoAttachments.insert(article.databaseID)
cachedAttachmentsByArticle[article.databaseID] = nil
deleteAttachmentsForArticleID(article.databaseID)
}
}
func attachmentsWithResultSet(_ resultSet: FMResultSet) -> Set<Attachment> {
var attachments = Set<Attachment>()
while (resultSet.next()) {
if let oneAttachment = attachmentWithRow(resultSet) {
attachments.insert(oneAttachment)
}
}
return attachments
}
func attachmentWithRow(_ row: FMResultSet) -> Attachment? {
let databaseID = row.string(forColumn: DatabaseKey.databaseID)
if let cachedAttachment = cachedAttachmentForDatabaseID(databaseID) {
return cachedAttachment
}
return Attachment(databaseID: databaseID, row: row)
}
}

View File

@ -13,11 +13,13 @@ public struct DatabaseTableName {
static let articles = "articles"
static let statuses = "statuses"
static let tags = "tags"
static let attachments = "attachments"
}
public struct DatabaseKey {
// Shared
static let databaseID = "databaseID"
static let articleID = "articleID"
static let accountInfo = "accountInfo"
static let url = "url"

View File

@ -7,9 +7,11 @@ CREATE TABLE if not EXISTS authorLookup (authorID TEXT NOT NULL, articleID TEXT
CREATE TABLE if not EXISTS tags(tagName TEXT NOT NULL, articleID TEXT NOT NULL, PRIMARY KEY(tagName, articleID));
CREATE TABLE if not EXISTS attachments(articleID TEXT NOT NULL, url TEXT NOT NULL, mimeType TEXT, title TEXT, sizeInBytes INTEGER, durationInSeconds INTEGER, PRIMARY KEY(articleID, url));
CREATE TABLE if not EXISTS attachments(databaseID TEXT NOT NULL PRIMARY KEY, articleID TEXT NOT NULL, url TEXT NOT NULL, mimeType TEXT, title TEXT, sizeInBytes INTEGER, durationInSeconds INTEGER);
CREATE INDEX if not EXISTS articles_feedID_index on articles (feedID);
CREATE INDEX if not EXISTS tags_tagName_index on tags(tagName COLLATE NOCASE);
CREATE INDEX if not EXISTS tags_tagName_index on tags (tagName COLLATE NOCASE);
CREATE INDEX if not EXISTS attachments_articleID_index on attachments (articleID);

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
840405CF1F1A963700DF0296 /* AttachmentsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840405CE1F1A963700DF0296 /* AttachmentsManager.swift */; };
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 */; };
@ -108,6 +109,7 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
840405CE1F1A963700DF0296 /* AttachmentsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentsManager.swift; sourceTree = "<group>"; };
844BEE371F0AB3AA004AB7CD /* Database.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Database.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
@ -163,6 +165,7 @@
84E156EB1F0AB80E00F8CC05 /* ArticlesManager.swift */,
84E156ED1F0AB81400F8CC05 /* StatusesManager.swift */,
84F20F8E1F180D8700D8E682 /* AuthorsManager.swift */,
840405CE1F1A963700DF0296 /* AttachmentsManager.swift */,
84BB4BA81F11A32800858766 /* TagsManager.swift */,
8461462A1F0AC44100870CB3 /* Extensions */,
84E156EF1F0AB81F00F8CC05 /* CreateStatements.sql */,
@ -463,6 +466,7 @@
8455807C1F0C0DBD003CCFA1 /* Attachment+Database.swift in Sources */,
84F20F8F1F180D8700D8E682 /* AuthorsManager.swift in Sources */,
845580671F0AEBCD003CCFA1 /* Constants.swift in Sources */,
840405CF1F1A963700DF0296 /* AttachmentsManager.swift in Sources */,
845580781F0AF678003CCFA1 /* Folder+Database.swift in Sources */,
845580761F0AF670003CCFA1 /* Article+Database.swift in Sources */,
845580721F0AEE49003CCFA1 /* AccountInfo.swift in Sources */,

View File

@ -8,29 +8,28 @@
import Foundation
import Data
import RSDatabase
extension Attachment {
init?(databaseDictionary d: [String: Any]) {
guard let url = d[DatabaseKey.url] as? String else {
return nil
}
let mimeType = d[DatabaseKey.mimeType] as? String
let title = d[DatabaseKey.title] as? String
let sizeInBytes = d[DatabaseKey.sizeInBytes] as? Int
let durationInSeconds = d[DatabaseKey.durationInSeconds] as? Int
self.init(url: url, mimeType: mimeType, title: title, sizeInBytes: sizeInBytes, durationInSeconds: durationInSeconds)
init?(databaseID: String, row: FMResultSet) {
let articleID = row.string(forColumn: DatabaseKey.articleID)
let url = row.string(forColumn: DatabaseKey.url)
let mimeType = row.string(forColumn: DatabaseKey.mimeType)
let title = row.string(forColumn: DatabaseKey.title)
let sizeInBytes = optionalIntForColumn(row, DatabaseKey.sizeInBytes)
let durationInSeconds = optionalIntForColumn(row, DatabaseKey.durationInSeconds)
init(databaseID: databaseID, articleID: articleID, url: url, mimeType: mimeType, title: title, sizeInBytes: sizeInBytes, durationInSeconds: durationInSeconds)
}
static func attachments(with plist: [Any]) -> [Attachment]? {
return plist.flatMap{ (oneDictionary) -> Attachment? in
if let d = oneDictionary as? [String: Any] {
return Attachment(databaseDictionary: d)
}
private func optionalIntForColumn(_ row: FMResultSet, _ columnName: String) -> Int? {
let intValue = row.long(forColumn: columnName)
if intValue < 1 {
return nil
}
return intValue
}
}

View File

@ -0,0 +1,25 @@
//
// DatabaseTable.swift
// RSDatabase
//
// Created by Brent Simmons on 7/16/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public struct DatabaseTable {
public let name: String
public init(name: String) {
self.name = name
}
public func selectRowsWhere(key: String, equals value: Any, in database: FMDatabase) -> FMResultSet? {
return database.rs_selectRowsWhereKey(key, equalsValue: value, tableName: self.name)
}
}

View File

@ -23,6 +23,8 @@
8400AC0C1E0CFC3100AA7C57 /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 84DDF1951C94FC45005E6CF5 /* FMResultSet.m */; };
8400AC0F1E0CFC5600AA7C57 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8400AC0E1E0CFC5600AA7C57 /* libsqlite3.tbd */; };
8400AC101E0CFD6B00AA7C57 /* RSDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 84F22C581B52E0D9000060CE /* RSDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; };
840405DB1F1C158C00DF0296 /* DatabaseTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840405DA1F1C158C00DF0296 /* DatabaseTable.swift */; };
840405DC1F1C15EA00DF0296 /* DatabaseTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840405DA1F1C158C00DF0296 /* DatabaseTable.swift */; };
84419AD61B5ABD6D00C26BB2 /* FMDatabase+RSExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 84419AD41B5ABD6D00C26BB2 /* FMDatabase+RSExtras.h */; settings = {ATTRIBUTES = (Public, ); }; };
84419AD71B5ABD6D00C26BB2 /* FMDatabase+RSExtras.m in Sources */ = {isa = PBXBuildFile; fileRef = 84419AD51B5ABD6D00C26BB2 /* FMDatabase+RSExtras.m */; };
84419ADA1B5ABD7400C26BB2 /* NSString+RSDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = 84419AD81B5ABD7400C26BB2 /* NSString+RSDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -57,6 +59,7 @@
8400ABF71E0CFBD800AA7C57 /* RSDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RSDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8400ABFA1E0CFBD800AA7C57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8400AC0E1E0CFC5600AA7C57 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; };
840405DA1F1C158C00DF0296 /* DatabaseTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseTable.swift; sourceTree = "<group>"; };
84419AD41B5ABD6D00C26BB2 /* FMDatabase+RSExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "FMDatabase+RSExtras.h"; path = "RSDatabase/FMDatabase+RSExtras.h"; sourceTree = "<group>"; };
84419AD51B5ABD6D00C26BB2 /* FMDatabase+RSExtras.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "FMDatabase+RSExtras.m"; path = "RSDatabase/FMDatabase+RSExtras.m"; sourceTree = "<group>"; };
84419AD81B5ABD7400C26BB2 /* NSString+RSDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSString+RSDatabase.h"; path = "RSDatabase/NSString+RSDatabase.h"; sourceTree = "<group>"; };
@ -150,6 +153,7 @@
84419B041B5ABFF700C26BB2 /* FMResultSet+RSExtras.m */,
84419AD81B5ABD7400C26BB2 /* NSString+RSDatabase.h */,
84419AD91B5ABD7400C26BB2 /* NSString+RSDatabase.m */,
840405DA1F1C158C00DF0296 /* DatabaseTable.swift */,
84DDF18A1C94FC45005E6CF5 /* FMDB */,
84F22C5A1B52E0D9000060CE /* Info.plist */,
849BF8C51C94FB8E0071D1DA /* libsqlite3.tbd */,
@ -285,7 +289,7 @@
};
84F22C541B52E0D9000060CE = {
CreatedOnToolsVersion = 7.0;
LastSwiftMigration = 0800;
LastSwiftMigration = 0900;
};
84F22C5E1B52E0D9000060CE = {
CreatedOnToolsVersion = 7.0;
@ -344,6 +348,7 @@
8400AC001E0CFC0700AA7C57 /* RSDatabaseQueue.m in Sources */,
8400AC061E0CFC0700AA7C57 /* NSString+RSDatabase.m in Sources */,
8400AC0C1E0CFC3100AA7C57 /* FMResultSet.m in Sources */,
840405DC1F1C15EA00DF0296 /* DatabaseTable.swift in Sources */,
8400AC021E0CFC0700AA7C57 /* FMDatabase+RSExtras.m in Sources */,
8400AC081E0CFC2000AA7C57 /* FMDatabase.m in Sources */,
8400AC041E0CFC0700AA7C57 /* FMResultSet+RSExtras.m in Sources */,
@ -358,6 +363,7 @@
84419AE71B5ABD7F00C26BB2 /* RSDatabaseQueue.m in Sources */,
84419AD71B5ABD6D00C26BB2 /* FMDatabase+RSExtras.m in Sources */,
84419ADB1B5ABD7400C26BB2 /* NSString+RSDatabase.m in Sources */,
840405DB1F1C158C00DF0296 /* DatabaseTable.swift in Sources */,
84DDF1971C94FC45005E6CF5 /* FMDatabase.m in Sources */,
84DDF1A01C94FC45005E6CF5 /* FMResultSet.m in Sources */,
84419B061B5ABFF700C26BB2 /* FMResultSet+RSExtras.m in Sources */,
@ -531,6 +537,7 @@
84F22C6A1B52E0D9000060CE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@ -543,12 +550,15 @@
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSDatabase;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
84F22C6B1B52E0D9000060CE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
COMBINE_HIDPI_IMAGES = YES;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
@ -561,12 +571,14 @@
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSDatabase;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Release;
};
84F22C6D1B52E0D9000060CE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = RSDatabaseTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
@ -579,6 +591,7 @@
84F22C6E1B52E0D9000060CE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = RSDatabaseTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";