317 lines
8.2 KiB
Swift
317 lines
8.2 KiB
Swift
import Combine
|
|
import CoreData
|
|
|
|
protocol Database {
|
|
var viewContext: NSManagedObjectContext { get }
|
|
func has(context: NSManagedObjectContext) -> Bool
|
|
|
|
func room(id: String) -> Room?
|
|
func room(rid: String) -> Room?
|
|
func remove(_ message: Message)
|
|
|
|
func handleRoomsResponse(_ subscriptionsResponse: SubscriptionsResponse, _ roomsResponse: RoomsResponse)
|
|
func handleHistoryResponse(_ historyResponse: HistoryResponse, in roomID: String)
|
|
func handleMessagesResponse(_ messagesResponse: MessagesResponse, in roomID: String, newUpdatedSince: Date)
|
|
func handleSendMessageResponse(_ sendMessageResponse: SendMessageResponse, in roomID: String)
|
|
func handleSendMessageRequest(_ newMessage: MergedRoom.Message, in roomID: String)
|
|
func handleReadResponse(_ readResponse: ReadResponse, in roomID: String)
|
|
func handleSendMessageError(_ messageID: String)
|
|
|
|
func remove()
|
|
}
|
|
|
|
final class RocketChatDatabase: Database {
|
|
private let server: Server
|
|
|
|
init(server: Server) {
|
|
self.server = server
|
|
}
|
|
|
|
var viewContext: NSManagedObjectContext {
|
|
container.viewContext
|
|
}
|
|
|
|
func has(context: NSManagedObjectContext) -> Bool {
|
|
context == backgroundContext
|
|
}
|
|
|
|
private static let model: NSManagedObjectModel = {
|
|
guard let url = Bundle.main.url(forResource: "RocketChat", withExtension: "momd"),
|
|
let managedObjectModel = NSManagedObjectModel(contentsOf: url) else {
|
|
fatalError("Can't find Core Data Model")
|
|
}
|
|
|
|
return managedObjectModel
|
|
}()
|
|
|
|
private lazy var container: NSPersistentContainer = {
|
|
let name = server.url.host ?? "default"
|
|
|
|
let container = NSPersistentContainer(name: name, managedObjectModel: Self.model)
|
|
|
|
container.loadPersistentStores { _, error in
|
|
if let error { fatalError("Can't load persistent stores: \(error)") }
|
|
}
|
|
|
|
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
|
container.viewContext.automaticallyMergesChangesFromParent = true
|
|
|
|
return container
|
|
}()
|
|
|
|
private lazy var backgroundContext = container.newBackgroundContext()
|
|
|
|
func remove(_ message: Message) {
|
|
viewContext.delete(message)
|
|
|
|
do {
|
|
try viewContext.save()
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
|
|
func room(id: String) -> Room? {
|
|
let request = Room.fetchRequest()
|
|
request.predicate = NSPredicate(format: "id == %@", id)
|
|
|
|
return try? viewContext.fetch(request).first
|
|
}
|
|
|
|
func room(rid: String) -> Room? {
|
|
let request = Room.fetchRequest()
|
|
request.predicate = NSPredicate(format: "rid == %@", rid)
|
|
|
|
return try? viewContext.fetch(request).first
|
|
}
|
|
|
|
func remove() {
|
|
guard let url = container.persistentStoreDescriptions.first?.url else {
|
|
return
|
|
}
|
|
|
|
do {
|
|
try container.persistentStoreCoordinator.destroyPersistentStore(at: url, type: .sqlite)
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension RocketChatDatabase {
|
|
func handleReadResponse(_ readResponse: ReadResponse, in roomID: String) {
|
|
backgroundContext.performBackgroundTask { context in
|
|
let roomDatabase = RoomModel(context: context)
|
|
|
|
let room = roomDatabase.fetch(id: roomID)
|
|
|
|
room.alert = false
|
|
room.unread = 0
|
|
|
|
do {
|
|
try context.save()
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleSendMessageError(_ messageID: String) {
|
|
backgroundContext.performBackgroundTask { context in
|
|
let messageDatabase = MessageModel(context: context)
|
|
|
|
if let message = messageDatabase.fetch(id: messageID) {
|
|
message.status = "error"
|
|
}
|
|
|
|
do {
|
|
try context.save()
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleSendMessageRequest(_ newMessage: MergedRoom.Message, in roomID: String) {
|
|
backgroundContext.performBackgroundTask { context in
|
|
let roomDatabase = RoomModel(context: context)
|
|
let messageDatabase = MessageModel(context: context)
|
|
|
|
let room = roomDatabase.fetch(id: roomID)
|
|
|
|
let message = messageDatabase.upsert(newMessage)
|
|
|
|
message.status = "temp"
|
|
message.room = room
|
|
|
|
do {
|
|
try context.save()
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleSendMessageResponse(_ sendMessageResponse: SendMessageResponse, in roomID: String) {
|
|
let message = sendMessageResponse.message
|
|
|
|
backgroundContext.performBackgroundTask { context in
|
|
let messageDatabase = MessageModel(context: context)
|
|
let roomDatabase = RoomModel(context: context)
|
|
|
|
let room = roomDatabase.fetch(id: roomID)
|
|
|
|
if let newMessage = MergedRoom.Message(from: message) {
|
|
let message = messageDatabase.upsert(newMessage)
|
|
|
|
message.room = room
|
|
}
|
|
|
|
do {
|
|
try context.save()
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleMessagesResponse(_ messagesResponse: MessagesResponse, in roomID: String, newUpdatedSince: Date) {
|
|
let messages = messagesResponse.result.updated
|
|
|
|
backgroundContext.performBackgroundTask { context in
|
|
let messageDatabase = MessageModel(context: context)
|
|
let roomDatabase = RoomModel(context: context)
|
|
|
|
let room = roomDatabase.fetch(id: roomID)
|
|
|
|
for message in messages {
|
|
if let newMessage = MergedRoom.Message(from: message) {
|
|
let message = messageDatabase.upsert(newMessage)
|
|
|
|
message.room = room
|
|
}
|
|
}
|
|
|
|
room.updatedSince = newUpdatedSince
|
|
|
|
do {
|
|
try context.save()
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleHistoryResponse(_ historyResponse: HistoryResponse, in roomID: String) {
|
|
let messages = historyResponse.messages
|
|
|
|
backgroundContext.performBackgroundTask { context in
|
|
let messageDatabase = MessageModel(context: context)
|
|
let roomDatabase = RoomModel(context: context)
|
|
|
|
let room = roomDatabase.fetch(id: roomID)
|
|
|
|
room.hasMoreMessages = messages.count == HISTORY_MESSAGE_COUNT
|
|
room.synced = true
|
|
|
|
for message in historyResponse.messages {
|
|
if let newMessage = MergedRoom.Message(from: message) {
|
|
let message = messageDatabase.upsert(newMessage)
|
|
|
|
message.room = room
|
|
}
|
|
}
|
|
|
|
do {
|
|
try context.save()
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
func handleRoomsResponse(_ subscriptionsResponse: SubscriptionsResponse, _ roomsResponse: RoomsResponse) {
|
|
let rooms = roomsResponse.update
|
|
let subscriptions = subscriptionsResponse.update
|
|
|
|
backgroundContext.performBackgroundTask { context in
|
|
let roomDatabase = RoomModel(context: context)
|
|
|
|
let roomIds = rooms.filter { room in !subscriptions.contains { room._id == $0.rid } }.map { $0._id }
|
|
|
|
let existingSubs = roomDatabase.fetch(ids: roomIds)
|
|
let mappedExistingSubs = subscriptions + existingSubs.compactMap { $0.response }
|
|
|
|
let mergedSubscriptions = mappedExistingSubs.compactMap { subscription in
|
|
let index = rooms.firstIndex { $0._id == subscription.rid }
|
|
|
|
guard let index else {
|
|
return MergedRoom(subscription, nil)
|
|
}
|
|
|
|
let room = rooms[index]
|
|
return MergedRoom(subscription, room)
|
|
}
|
|
|
|
let subsIds = mergedSubscriptions.compactMap { $0.id } + subscriptionsResponse.remove.compactMap { $0._id }
|
|
|
|
if subsIds.count > 0 {
|
|
let existingSubscriptions = roomDatabase.fetch(ids: subsIds)
|
|
let subsToUpdate = existingSubscriptions.filter { subscription in mergedSubscriptions.contains { subscription.id == $0.id } }
|
|
let subsToCreate = mergedSubscriptions.filter { subscription in !existingSubscriptions.contains { subscription.id == $0.id } }
|
|
let subsToDelete = existingSubscriptions.filter { subscription in !mergedSubscriptions.contains { subscription.id == $0.id } }
|
|
|
|
subsToCreate.forEach { subscription in
|
|
roomDatabase.upsert(subscription)
|
|
}
|
|
|
|
subsToUpdate.forEach { subscription in
|
|
if let newRoom = mergedSubscriptions.first(where: { $0.id == subscription.id }) {
|
|
roomDatabase.upsert(newRoom)
|
|
}
|
|
}
|
|
|
|
subsToDelete.forEach { subscription in
|
|
roomDatabase.delete(subscription)
|
|
}
|
|
|
|
do {
|
|
try context.save()
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension Room {
|
|
var response: SubscriptionsResponse.Subscription? {
|
|
guard let id, let fname, let t, let rid else {
|
|
return nil
|
|
}
|
|
|
|
return .init(
|
|
_id: id,
|
|
rid: rid,
|
|
name: name,
|
|
fname: fname,
|
|
t: t,
|
|
unread: Int(unread),
|
|
alert: alert,
|
|
lr: lr,
|
|
open: open,
|
|
_updatedAt: ts,
|
|
hideUnreadStatus: hideUnreadStatus
|
|
)
|
|
}
|
|
}
|
|
|
|
extension NSManagedObjectContext {
|
|
func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
|
|
perform {
|
|
block(self)
|
|
}
|
|
}
|
|
}
|