Make progress on attachments. Build still broken.
This commit is contained in:
parent
13f8c4f9b1
commit
8fc4c3041d
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */,
|
||||
);
|
||||
|
|
|
@ -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:
|
||||
// * It’s fast
|
||||
// * Collisions aren’t going to happen with feed data
|
||||
|
||||
public func databaseIDWithString(_ s: String) -> String {
|
||||
|
||||
return (s as NSString).rs_md5Hash()
|
||||
}
|
|
@ -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,
|
||||
// it’s 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 it’s 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 don’t 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 it’s 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 doesn’t 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 { // Don’t 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)
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
|
|
Loading…
Reference in New Issue