Files
raspberry/iOS/Wallet/Sources/CryptoChat/Service/CryptoChatServiceMsgsHistory.swift
T
2022-08-31 14:21:25 +03:00

273 lines
11 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// CryptoChatServiceMsgsHistory.swift
// Wallet
//
// Created by Saveliy Stavitsky on 8/17/20.
// Copyright © 2020 List. All rights reserved.
//
import Foundation
import RealmSwift
import eosswift
extension String {
fileprivate static var didFirstSync = "didFirstSync"
}
extension Account.Model {
var didFirstSync: Bool {
get { Settings()[.key("\(username)@\(keyType.rawValue)", suffix: .didFirstSync)] }
set { Settings()[.key("\(username)@\(keyType.rawValue)", suffix: .didFirstSync)] = newValue }
}
}
extension CryptoChat.Service {
class MsgsHistory {
private let realm: Realm
private let username: String
private var historyService: Account.Service.AccountHistory
init(username: String, encryptionKey: String) throws {
guard let key = try? Data(eosioPrivateKey: encryptionKey), key.count == 32,
let publicKey = try? EOSPrivateKey(base58: encryptionKey).publicKey.base58
else { throw "EncryptionKey wrong or to small" }
guard username.count > 0 else { throw "username wrong or to small" }
guard let historyService = Account.Service.History.shared.current else { throw "No history service was found for username" }
self.historyService = historyService
self.username = username
var config = Realm.Configuration(// Set the new schema version. This must be greater than the previously used
// version (if you've never set a schema version before, the version is 0).
schemaVersion: 4,
// Set the block which will be called automatically when opening a Realm with
// a schema version lower than the one set above
migrationBlock: { migration, oldSchemaVersion in
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if oldSchemaVersion < 1 {
// Nothing to do!
// Realm will automatically detect new properties and removed properties
// And will update the schema on disk automatically
}
}
)
// Use the default directory, but replace the filename with the username
config.fileURL = config.fileURL!.deletingLastPathComponent()
.appendingPathComponent("\(username)_\(publicKey)_msgs.realm")
config.encryptionKey = (key.bytes + Array(repeating: UInt8(0), count: 64 - key.bytes.count)).toData()
config.objectTypes = [CryptoChatModelRealmChat.self, CryptoChatModelRealmMessage.self]
// config.schemaVersion = 2
// config.deleteRealmIfMigrationNeeded = true
realm = try Realm(configuration: config)
}
private var isLoading = false
var didUpdate: (() -> Void)?
lazy var chats: Results<CryptoChatModelRealmChat> = {
realm.objects(CryptoChatModelRealmChat.self)
.sorted(by: \.timestamp, ascending: false)
}()
func filterChats(query: String) -> Results<CryptoChatModelRealmChat> {
realm.objects(CryptoChatModelRealmChat.self)
.where({ $0.chatName.contains(query) })
.sorted(by: \.timestamp, ascending: false)
}
func filterMsgs(query: String) -> Results<CryptoChatModelRealmMessage> {
if query.isEmpty { // || query.count < 3 {
return realm.objects(CryptoChatModelRealmMessage.self)
.filter(NSPredicate(value: false))
}
return realm.objects(CryptoChatModelRealmMessage.self)
.where({ $0.deleted == false })
.where({ $0.searchText.contains(query.lowercased()) })
.sorted(by: \.timestamp, ascending: false)
}
func deleteMsg(ephemPublicKey: String) {
try? realm.write {
if let msg = realm.object(ofType: CryptoChatModelRealmMessage.self,
forPrimaryKey: ephemPublicKey) {
realm.delete(msg)
}
}
}
func hideMsg(ephemPublicKey: String) {
try? realm.write {
if let msg = realm.object(ofType: CryptoChatModelRealmMessage.self,
forPrimaryKey: ephemPublicKey) {
msg.deleted = true
}
}
}
func hideChatMsgs(chatName: String) {
try? realm.write {
for msg in realm.objects(CryptoChatModelRealmMessage.self)
.filter("chatName = '\(chatName)'") {
msg.deleted = true
}
if let chat = realm.object(ofType: CryptoChatModelRealmChat.self,
forPrimaryKey: chatName) {
realm.delete(chat)
}
saveReadCount()
didUpdate?()
}
}
func markChatAsRead(username: String) {
try? realm.write {
if let chat = realm.object(ofType: CryptoChatModelRealmChat.self, forPrimaryKey: username) {
chat.unreadCount = 0
}
}
saveReadCount()
}
func saveReadCount() {
var readDict = (UserDefaults.standard.dictionary(forKey: "chatsReadCount") as? [String: Int]) ?? [:]
readDict[username] = readCount
UserDefaults.standard.setValue(readDict, forKey: "chatsReadCount")
}
private func markAllChatsAsRead() {
try? realm.write {
for chat in realm.objects(CryptoChatModelRealmChat.self) {
print("\(chat.chatName) - \(chat.unreadCount)")
chat.unreadCount = 0
}
}
saveReadCount()
}
private var unreadCount: Int {
realm.objects(CryptoChatModelRealmChat.self)
.reduce(0, { $0 + $1.unreadCount })
}
var readCount: Int {
historyService.actions
.filter("account = '\(Network.servers.current.contract(.chat))'")
.filter("name = '\(Network.Model.Blockchain.Action.chatSendDm.rawValue)'")
.filter("receiver = '\(self.username)'").count - unreadCount
}
func openChatWith(_ username: String) -> Results<CryptoChatModelRealmMessage> {
realm.objects(CryptoChatModelRealmMessage.self)
.filter("chatName = '\(username)'")
.filter("deleted = \(false)")
.sorted(by: \.timestamp, ascending: true)
}
func saveMsg(_ msg: CryptoChatModelRealmMessage, ignoreTimestamp: Bool = false) {
try? realm.write {
realm.add(msg)
if let chat = realm.object(ofType: CryptoChatModelRealmChat.self, forPrimaryKey: msg.chatName) {
if ignoreTimestamp {
chat.timestamp = msg.timestamp
chat.data = msg.data
} else {
if chat.timestamp < msg.timestamp {
chat.timestamp = msg.timestamp
chat.data = msg.data
}
if msg.chatName == msg.from {
chat.unreadCount += 1
}
}
} else {
realm.add(CryptoChatModelRealmChat(chatName: msg.chatName, timestamp: msg.timestamp, data: msg.data, unreadCount: msg.chatName == msg.from ? 1 : 0))
}
}
}
var lastSyncTimestamp: Date {
realm.objects(CryptoChatModelRealmMessage.self)
.filter("isInSyncWithBlockchain = \(true)")
.sorted(by: \.timestamp, ascending: false)
.first?.timestamp ?? Date(timeIntervalSince1970: 0)
}
private var chatActions: Results<AccountAction> {
historyService.actions
.filter("account = '\(Network.servers.current.contract(.chat))'")
.filter("name = '\(Network.Model.Blockchain.Action.chatSendDm.rawValue)'")
.filter("receiver = '\(self.username)'")
.filter("blockTime >= %@", lastSyncTimestamp)
.sorted(by: \.accountActionId, ascending: true)
}
func setMsgSynced(ephemPublicKey: String) {
try? realm.write {
if let msg = realm.object(ofType: CryptoChatModelRealmMessage.self,
forPrimaryKey: ephemPublicKey) {
msg.isInSyncWithBlockchain = true
msg.isUnsent = false
}
}
}
func markMsgUnsent(ephemPublicKey: String) {
try? realm.write {
if let msg = realm.object(ofType: CryptoChatModelRealmMessage.self,
forPrimaryKey: ephemPublicKey) {
msg.isUnsent = true
}
}
}
func fetchFromLocalHistory() {
guard !isLoading else { return }
guard let privateKey = realm.configuration.encryptionKey?.dropLast(32).toEosioK1PrivateKey.split(separator: "_").last.map(String.init) else {
return
}
isLoading = true
for action in self.chatActions {
if let serverMsgData = action.data(as: CryptoChat.Model.Msg.self),
let ephemPubK1Key = (try? Data(eosioPublicKey: serverMsgData.ephemPublicKey).toEosioK1PublicKey) {
if let msg = realm.object(ofType: CryptoChatModelRealmMessage.self,
forPrimaryKey: ephemPubK1Key) {
setMsgSynced(ephemPublicKey: msg.id)
} else {
saveMsg(CryptoChatModelRealmMessage(msg: serverMsgData, username: username, privateKey: privateKey, timestamp: action.timestamp))
}
}
}
if !UserDefaults.standard.bool(forKey: "\(username)_firstMsgsSyncDone") {
self.markAllChatsAsRead()
UserDefaults.standard.setValue(true, forKey: "\(username)_firstMsgsSyncDone")
}
self.isLoading = false
self.saveReadCount()
self.didUpdate?()
}
func copy(username: String, privateKey: String, publicKey: String) {
guard let key = try? Data(eosioPrivateKey: privateKey), key.count == 32 else { return }
let fileURL = realm.configuration.fileURL!.deletingLastPathComponent()
.appendingPathComponent("\(username)_\(publicKey)_msgs.realm")
let encryptionKey = (key.bytes + Array(repeating: UInt8(0), count: 64 - key.bytes.count)).toData()
try? realm.writeCopy(toFile: fileURL, encryptionKey: encryptionKey)
}
func removeDatabase() {
try? FileManager.default.removeItem(at: realm.configuration.fileURL!)
}
}
}