add ProfileDatabase class to read and write profiles to disk
This commit is contained in:
@@ -261,6 +261,7 @@
|
|||||||
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; };
|
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; };
|
||||||
501F8C5529FF5EF6001AFC1D /* PersistedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */; };
|
501F8C5529FF5EF6001AFC1D /* PersistedProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */; };
|
||||||
501F8C5829FF5FC5001AFC1D /* Damus.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5629FF5FC5001AFC1D /* Damus.xcdatamodeld */; };
|
501F8C5829FF5FC5001AFC1D /* Damus.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5629FF5FC5001AFC1D /* Damus.xcdatamodeld */; };
|
||||||
|
501F8C5A29FF70F5001AFC1D /* ProfileDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */; };
|
||||||
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; };
|
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; };
|
||||||
50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; };
|
50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; };
|
||||||
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
|
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
|
||||||
@@ -686,6 +687,7 @@
|
|||||||
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; };
|
501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; };
|
||||||
501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistedProfile.swift; sourceTree = "<group>"; };
|
501F8C5429FF5EF6001AFC1D /* PersistedProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistedProfile.swift; sourceTree = "<group>"; };
|
||||||
501F8C5729FF5FC5001AFC1D /* Damus.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Damus.xcdatamodel; sourceTree = "<group>"; };
|
501F8C5729FF5FC5001AFC1D /* Damus.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Damus.xcdatamodel; sourceTree = "<group>"; };
|
||||||
|
501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDatabase.swift; sourceTree = "<group>"; };
|
||||||
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
|
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; };
|
||||||
50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; };
|
50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; };
|
||||||
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
|
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
|
||||||
@@ -1000,6 +1002,7 @@
|
|||||||
4C75EFBA2804A34C0006080F /* ProofOfWork.swift */,
|
4C75EFBA2804A34C0006080F /* ProofOfWork.swift */,
|
||||||
4CEE2AEC2805B22500AB5EEF /* NostrRequest.swift */,
|
4CEE2AEC2805B22500AB5EEF /* NostrRequest.swift */,
|
||||||
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */,
|
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */,
|
||||||
|
501F8C5929FF70F5001AFC1D /* ProfileDatabase.swift */,
|
||||||
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */,
|
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */,
|
||||||
4C363A8F28247A1D006E126D /* NostrLink.swift */,
|
4C363A8F28247A1D006E126D /* NostrLink.swift */,
|
||||||
50088DA029E8271A008A1FDF /* WebSocket.swift */,
|
50088DA029E8271A008A1FDF /* WebSocket.swift */,
|
||||||
@@ -1651,6 +1654,7 @@
|
|||||||
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */,
|
4CD348EF29C3659D00497EB2 /* ImageUploadModel.swift in Sources */,
|
||||||
4C7D096E2A0AEA0400943473 /* ScannerCoordinator.swift in Sources */,
|
4C7D096E2A0AEA0400943473 /* ScannerCoordinator.swift in Sources */,
|
||||||
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
||||||
|
501F8C5A29FF70F5001AFC1D /* ProfileDatabase.swift in Sources */,
|
||||||
4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */,
|
4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */,
|
||||||
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
|
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
|
||||||
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */,
|
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */,
|
||||||
|
|||||||
110
damus/Nostr/ProfileDatabase.swift
Normal file
110
damus/Nostr/ProfileDatabase.swift
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
//
|
||||||
|
// ProfileDatabase.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Bryan Montz on 4/30/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
|
||||||
|
enum ProfileDatabaseError: Error {
|
||||||
|
case missing_context
|
||||||
|
case outdated_input
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ProfileDatabase {
|
||||||
|
|
||||||
|
private let entity_name = "PersistedProfile"
|
||||||
|
private var persistent_container: NSPersistentContainer?
|
||||||
|
private var background_context: NSManagedObjectContext?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
set_up()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var profile_cache_url: URL {
|
||||||
|
(FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first?.appendingPathComponent("profiles"))!
|
||||||
|
}
|
||||||
|
|
||||||
|
private var persistent_store_description: NSPersistentStoreDescription {
|
||||||
|
let description = NSPersistentStoreDescription(url: profile_cache_url)
|
||||||
|
description.type = NSSQLiteStoreType
|
||||||
|
description.setOption(true as NSNumber, forKey: NSMigratePersistentStoresAutomaticallyOption)
|
||||||
|
description.setOption(true as NSNumber, forKey: NSInferMappingModelAutomaticallyOption)
|
||||||
|
description.setOption(true as NSNumber, forKey: NSSQLiteManualVacuumOption)
|
||||||
|
return description
|
||||||
|
}
|
||||||
|
|
||||||
|
private var object_model: NSManagedObjectModel? {
|
||||||
|
guard let url = Bundle.main.url(forResource: "Damus", withExtension: "momd") else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return NSManagedObjectModel(contentsOf: url)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func set_up() {
|
||||||
|
guard let object_model else {
|
||||||
|
print("⚠️ Warning: ProfileDatabase failed to load its object model")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
persistent_container = NSPersistentContainer(name: "Damus", managedObjectModel: object_model)
|
||||||
|
persistent_container?.persistentStoreDescriptions = [persistent_store_description]
|
||||||
|
persistent_container?.loadPersistentStores { _, error in
|
||||||
|
if let error {
|
||||||
|
print("WARNING: ProfileDatabase failed to load: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
persistent_container?.viewContext.automaticallyMergesChangesFromParent = true
|
||||||
|
persistent_container?.viewContext.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
|
||||||
|
|
||||||
|
background_context = persistent_container?.newBackgroundContext()
|
||||||
|
background_context?.mergePolicy = NSMergePolicy(merge: .mergeByPropertyObjectTrumpMergePolicyType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func get_persisted(id: String) -> PersistedProfile? {
|
||||||
|
let request = NSFetchRequest<PersistedProfile>(entityName: entity_name)
|
||||||
|
request.predicate = NSPredicate(format: "id == %@", id)
|
||||||
|
request.fetchLimit = 1
|
||||||
|
return try? persistent_container?.viewContext.fetch(request).first
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
/// Updates or inserts a new Profile into the local database. Rejects profiles whose update date
|
||||||
|
/// is older than one we already have. Database writes occur on a background context for best performance.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - id: Profile id (pubkey)
|
||||||
|
/// - profile: Profile object to be stored
|
||||||
|
/// - last_update: Date that the Profile was updated
|
||||||
|
func upsert(id: String, profile: Profile, last_update: Date) throws {
|
||||||
|
guard let context = background_context else {
|
||||||
|
throw ProfileDatabaseError.missing_context
|
||||||
|
}
|
||||||
|
|
||||||
|
var persisted_profile: PersistedProfile?
|
||||||
|
if let profile = get_persisted(id: id) {
|
||||||
|
if let existing_last_update = profile.last_update, last_update < existing_last_update {
|
||||||
|
throw ProfileDatabaseError.outdated_input
|
||||||
|
} else {
|
||||||
|
persisted_profile = profile
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
persisted_profile = NSEntityDescription.insertNewObject(forEntityName: entity_name, into: context) as? PersistedProfile
|
||||||
|
persisted_profile?.id = id
|
||||||
|
}
|
||||||
|
persisted_profile?.copyValues(from: profile)
|
||||||
|
persisted_profile?.last_update = last_update
|
||||||
|
|
||||||
|
try context.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(id: String) -> Profile? {
|
||||||
|
guard let profile = get_persisted(id: id) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Profile(persisted_profile: profile)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user