Save and fetch attachments. Use a cache.

This commit is contained in:
Brent Simmons 2017-09-12 21:19:45 -07:00
parent 29c8badc34
commit 9341515926
7 changed files with 113 additions and 20 deletions

View File

@ -23,8 +23,18 @@ public struct Attachment: Hashable {
self.url = url
self.mimeType = mimeType
self.title = title
self.sizeInBytes = sizeInBytes
self.durationInSeconds = durationInSeconds
if let sizeInBytes = sizeInBytes, sizeInBytes > 0 {
self.sizeInBytes = sizeInBytes
}
else {
self.sizeInBytes = nil
}
if let durationInSeconds = durationInSeconds, durationInSeconds > 0 {
self.durationInSeconds = durationInSeconds
}
else {
self.durationInSeconds = nil
}
var s = url
s += mimeType ?? ""

View File

@ -14,13 +14,47 @@ final class AttachmentsTable: DatabaseRelatedObjectsTable {
let name: String
let databaseIDKey = DatabaseKey.attachmentID
var cache = [String: Attachment]()
init(name: String) {
self.name = name
}
// MARK: DatabaseTable Methods
// MARK: DatabaseRelatedObjectsTable
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, in database: FMDatabase) -> [DatabaseObject]? {
if databaseIDs.isEmpty {
return nil
}
var cachedAttachments = Set<Attachment>()
var databaseIDsToFetch = Set<String>()
for attachmentID in databaseIDs {
if let cachedAttachment = cache[attachmentID] {
cachedAttachments.insert(cachedAttachment)
}
else {
databaseIDsToFetch.insert(attachmentID)
}
}
if databaseIDsToFetch.isEmpty {
return cachedAttachments.databaseObjects()
}
guard let resultSet = selectRowsWhere(key: databaseIDKey, inValues: Array(databaseIDsToFetch), in: database) else {
return cachedAttachments.databaseObjects()
}
let fetchedDatabaseObjects = objectsWithResultSet(resultSet)
let fetchedAttachments = Set(fetchedDatabaseObjects.map { $0 as Attachment })
cacheAttachments(fetchedAttachments)
let allAttachments = cachedAttachments.union(fetchedAttachments)
return allAttachments.databaseObjects()
}
func objectWithRow(_ row: FMResultSet) -> DatabaseObject? {
@ -31,17 +65,37 @@ final class AttachmentsTable: DatabaseRelatedObjectsTable {
}
func save(_ objects: [DatabaseObject], in database: FMDatabase) {
// TODO
let attachments = objects.map { $0 as! Attachment }
// Attachments in cache must already exist in database. Filter them out.
let attachmentsToSave = Set(attachments.filter { (attachment) -> Bool in
if let _ = cache[attachment.attachmentID] {
return false
}
return true
})
cacheAttachments(attachmentsToSave)
insertRows(attachmentsToSave.databaseDictionaries(), insertType: .orIgnore, in: database)
}
}
private extension AttachmentsTable {
func cacheAttachments(_ attachments: Set<Attachment>) {
for attachment in attachments {
cache[attachment.attachmentID] = attachment
}
}
func attachmentWithRow(_ row: FMResultSet) -> Attachment? {
guard let attachmentID = row.string(forColumn: DatabaseKey.attachmentID) else {
return nil
}
// attachmentID is non-null in database schema.
let attachmentID = row.string(forColumn: DatabaseKey.attachmentID)!
return Attachment(attachmentID: attachmentID, row: row)
}
}

View File

@ -41,6 +41,29 @@ extension Attachment {
let attachments = parsedAttachments.flatMap{ Attachment(parsedAttachment: $0) }
return attachments.isEmpty ? nil : Set(attachments)
}
func databaseDictionary() -> NSDictionary {
var d = NSMutableDictionary()
d[DatabaseKey.attachmentID] = attachmentID
d[DatabaseKey.url] = url
if let mimeType = mimeType {
d[DatabaseKey.mimeType] = mimeType
}
if let title = title {
d[DatabaseKey.title] = title
}
if let sizeInBytes = sizeInBytes {
d[DatabaseKey.sizeInBytes] = NSNumber(sizeInBytes)
}
if let durationInSeconds = durationInSeconds {
d[DatabaseKey.durationInSeconds] = NSNumber(durationInSeconds)
}
return d.copy() as! NSDictionary
}
}
private func optionalIntForColumn(_ row: FMResultSet, _ columnName: String) -> Int? {
@ -60,3 +83,16 @@ extension Attachment: DatabaseObject {
}
}
}
extension Set where Element == Attachment {
func databaseDictionaries() -> [NSDictionary] {
return self.map<NSDictionary> { $0.databaseDictionary() }
}
func databaseObjects() -> [DatabaseObject] {
return self.map<DatabaseObject> { $0 as DatabaseObject }
}
}

View File

@ -28,7 +28,7 @@ final class TagsTable: DatabaseRelatedObjectsTable {
// MARK: DatabaseTable Methods
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, in database: FMDatabase) -> [DatabaseObject] {
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, in database: FMDatabase) -> [DatabaseObject]? {
// A tag is a string, and it is its own databaseID.
return databaseIDs.map{ $0 as DatabaseObject }
@ -43,6 +43,5 @@ final class TagsTable: DatabaseRelatedObjectsTable {
// Nothing to do, since tags are saved in the lookup table, not in a separate table.
}
}

View File

@ -12,16 +12,11 @@ public protocol DatabaseObject {
var databaseID: String { get }
func setRelatedObjects(_ objects: [DatabaseObject], name: String)
func relatedObjectsWithName(_ name: String) -> [DatabaseObject]?
}
public extension DatabaseObject {
func setRelatedObjects(_ objects: [DatabaseObject], name: String) {
// Do nothing
}
func relatedObjectsWithName(_ name: String) -> [DatabaseObject]? {
return nil

View File

@ -183,8 +183,7 @@ private extension DatabaseLookupTable {
func fetchRelatedObjectsWithIDs(_ relatedObjectIDs: Set<String>, _ database: FMDatabase) -> [DatabaseObject]? {
let relatedObjects = relatedTable.fetchObjectsWithIDs(relatedObjectIDs, in: database)
if relatedObjects.isEmpty {
guard let relatedObjects = relatedTable.fetchObjectsWithIDs(relatedObjectIDs, in: database), !relatedObjects.isEmpty else {
return nil
}
return relatedObjects

View File

@ -14,7 +14,7 @@ public protocol DatabaseRelatedObjectsTable: DatabaseTable {
var databaseIDKey: String { get}
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, in database: FMDatabase) -> [DatabaseObject]
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, in database: FMDatabase) -> [DatabaseObject]?
func objectsWithResultSet(_ resultSet: FMResultSet) -> [DatabaseObject]
func objectWithRow(_ row: FMResultSet) -> DatabaseObject?
@ -25,10 +25,10 @@ public extension DatabaseRelatedObjectsTable {
// MARK: Default implementations
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, in database: FMDatabase) -> [DatabaseObject] {
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, in database: FMDatabase) -> [DatabaseObject]? {
guard let resultSet = selectRowsWhere(key: databaseIDKey, inValues: Array(databaseIDs), in: database) else {
return [DatabaseObject]()
return nil
}
return objectsWithResultSet(resultSet)
}