280 lines
11 KiB
Swift
280 lines
11 KiB
Swift
//
|
|
// DBRosterStore.swift
|
|
//
|
|
// Siskin IM
|
|
// Copyright (C) 2016 "Tigase, Inc." <office@tigase.com>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. Look for COPYING file in the top folder.
|
|
// If not, see https://www.gnu.org/licenses/.
|
|
//
|
|
|
|
import Foundation
|
|
import Shared
|
|
import TigaseSwift
|
|
|
|
class DBRosterStoreWrapper: RosterStore {
|
|
|
|
fileprivate var roster = [JID: DBRosterItem]();
|
|
|
|
fileprivate var queue = DispatchQueue(label: "db_roster_store_wrapper", attributes: DispatchQueue.Attributes.concurrent);
|
|
|
|
fileprivate let sessionObject: SessionObject;
|
|
fileprivate let store = DBRosterStore.instance;
|
|
|
|
open override var count:Int {
|
|
get {
|
|
return queue.sync {
|
|
return self.roster.count;
|
|
}
|
|
}
|
|
}
|
|
|
|
public init(sessionObject: SessionObject) {
|
|
self.sessionObject = sessionObject;
|
|
}
|
|
|
|
open func initialize() {
|
|
queue.sync(flags: .barrier) {
|
|
self.store.getAll(for: sessionObject.userBareJid!).forEach { item in
|
|
roster[item.jid] = item;
|
|
}
|
|
}
|
|
}
|
|
|
|
open override func addItem(_ item:RosterItem) {
|
|
queue.async(flags: .barrier, execute: {
|
|
let dbItem = self.store.set(for: self.sessionObject.userBareJid!, item: item);
|
|
self.roster[item.jid] = dbItem;
|
|
})
|
|
}
|
|
|
|
open func getJids() -> [JID] {
|
|
var result = [JID]();
|
|
queue.sync {
|
|
self.roster.keys.forEach({ (jid) in
|
|
result.append(jid);
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
open func getAll() -> [RosterItem] {
|
|
return queue.sync {
|
|
return self.roster.values.map({ (item) -> RosterItem in
|
|
return item;
|
|
})
|
|
}
|
|
}
|
|
|
|
open override func get(for jid:JID) -> RosterItem? {
|
|
return queue.sync {
|
|
return self.roster[jid];
|
|
}
|
|
}
|
|
|
|
open override func removeItem(for jid:JID) {
|
|
queue.async(flags: .barrier, execute: {
|
|
guard let item = self.roster.removeValue(forKey: jid) else {
|
|
return;
|
|
}
|
|
self.store.remove(for: self.sessionObject.userBareJid!, item: item)
|
|
})
|
|
}
|
|
|
|
open override func removeAll() {
|
|
queue.async(flags: .barrier) {
|
|
self.store.removeAll(for: self.sessionObject.userBareJid!);
|
|
self.roster.removeAll();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
open class DBRosterStore: RosterCacheProvider {
|
|
|
|
static let ITEM_UPDATED = Notification.Name("rosterItemUpdated");
|
|
static let instance: DBRosterStore = DBRosterStore.init();
|
|
|
|
public let dispatcher: QueueDispatcher;
|
|
|
|
fileprivate let insertItemStmt: DBStatement;
|
|
fileprivate let updateItemStmt: DBStatement;
|
|
fileprivate let deleteItemStmt: DBStatement;
|
|
|
|
fileprivate let getAllItemsGroupsStmt: DBStatement;
|
|
fileprivate let getAllItemsStmt: DBStatement;
|
|
|
|
fileprivate let insertGroupStmt: DBStatement;
|
|
fileprivate let getGroupIdStmt: DBStatement;
|
|
fileprivate let insertItemGroupStmt: DBStatement;
|
|
fileprivate let deleteItemGroupsStmt: DBStatement;
|
|
|
|
public init() {
|
|
self.dispatcher = QueueDispatcher(label: "db_roster_store");
|
|
|
|
insertItemStmt = try! DBConnection.main.prepareStatement("INSERT INTO roster_items (account, jid, name, subscription, timestamp, ask, annotations) VALUES (:account, :jid, :name, :subscription, :timestamp, :ask, :annotations)");
|
|
updateItemStmt = try! DBConnection.main.prepareStatement("UPDATE roster_items SET name = :name, subscription = :subscription, timestamp = :timestamp, ask = :ask, annotations = :annotations WHERE id = :id");
|
|
deleteItemStmt = try! DBConnection.main.prepareStatement("DELETE FROM roster_items WHERE id = :id");
|
|
|
|
getAllItemsGroupsStmt = try! DBConnection.main.prepareStatement("SELECT rig.item_id as item_id, rg.name as name FROM roster_items ri INNER JOIN roster_items_groups rig ON ri.id = rig.item_id INNER JOIN roster_groups rg ON rig.group_id = rg.id WHERE ri.account = :account");
|
|
getAllItemsStmt = try! DBConnection.main.prepareStatement("SELECT id, jid, name, subscription, ask, annotations FROM roster_items WHERE account = :account");
|
|
|
|
getGroupIdStmt = try! DBConnection.main.prepareStatement("SELECT id from roster_groups WHERE name = :name");
|
|
insertGroupStmt = try! DBConnection.main.prepareStatement("INSERT INTO roster_groups (name) VALUES (:name)");
|
|
insertItemGroupStmt = try! DBConnection.main.prepareStatement("INSERT INTO roster_items_groups (item_id, group_id) VALUES (:item_id, :group_id)");
|
|
deleteItemGroupsStmt = try! DBConnection.main.prepareStatement("DELETE FROM roster_items_groups WHERE item_id = :item_id");
|
|
|
|
NotificationCenter.default.addObserver(self, selector: #selector(DBRosterStore.accountRemoved), name: NSNotification.Name(rawValue: "accountRemoved"), object: nil);
|
|
}
|
|
|
|
func getAll(for account: BareJID) -> [DBRosterItem] {
|
|
return dispatcher.sync {
|
|
let params: [String: Any?] = ["account": account];
|
|
var groups: [Int: [String]] = [:];
|
|
try! self.getAllItemsGroupsStmt.query(params) { cursor in
|
|
let itemId: Int = cursor["item_id"]!;
|
|
let group: String = cursor["name"]!;
|
|
|
|
var tmp = groups[itemId] ?? [];
|
|
tmp.append(group);
|
|
groups[itemId] = tmp;
|
|
}
|
|
|
|
return try! self.getAllItemsStmt.query(params, map: { (cursor) -> DBRosterItem? in
|
|
let itemId: Int = cursor["id"]!;
|
|
let jid: JID = cursor["jid"]!;
|
|
let name: String? = cursor["name"];
|
|
let subscription = RosterItem.Subscription(rawValue: cursor["subscription"]!)!;
|
|
let ask: Bool = cursor["ask"]!;
|
|
var annotations: [RosterItemAnnotation] = [];
|
|
if let annotationsStr: String = cursor["annotations"], let annotationsData = annotationsStr.data(using: .utf8) {
|
|
if let val = try? JSONDecoder().decode([RosterItemAnnotation].self, from: annotationsData) {
|
|
annotations = val;
|
|
}
|
|
}
|
|
|
|
let itemGroups = groups[itemId] ?? [];
|
|
|
|
return DBRosterItem(id: itemId, jid: jid, name: name, subscription: subscription, groups: itemGroups, ask: ask, annotations: annotations);
|
|
});
|
|
}
|
|
}
|
|
|
|
func remove(for account: BareJID, item: DBRosterItem) {
|
|
dispatcher.sync {
|
|
deleteItemGroups(item: item);
|
|
let params: [String: Any?] = ["id": item.id];
|
|
_ = try! self.deleteItemStmt.update(params);
|
|
}
|
|
}
|
|
|
|
func removeAll(for account: BareJID) {
|
|
dispatcher.sync {
|
|
getAll(for: account).forEach { item in
|
|
self.remove(for: account, item: item);
|
|
}
|
|
}
|
|
}
|
|
|
|
func set(for account: BareJID, item: RosterItem) -> DBRosterItem {
|
|
return dispatcher.sync {
|
|
guard let i = item as? DBRosterItem else {
|
|
let annotations = String(data: (try? JSONEncoder().encode(item.annotations)) ?? Data(), encoding: .utf8);
|
|
let params: [String: Any?] = ["account": account, "jid": item.jid, "name": item.name, "subscription": item.subscription.rawValue, "timestamp": Date(), "ask": item.ask, "annotations": annotations];
|
|
|
|
let id = try! self.insertItemStmt.insert(params)!;
|
|
let dbItem = DBRosterItem(id: id, item: item);
|
|
self.insertItemGroups(item: dbItem);
|
|
return dbItem;
|
|
}
|
|
|
|
let annotations = String(data: (try? JSONEncoder().encode(item.annotations)) ?? Data(), encoding: .utf8);
|
|
let params: [String: Any?] = ["id": i.id, "name": i.name, "subscription": item.subscription.rawValue, "timestamp": Date(), "ask": item.ask, "annotations": annotations];
|
|
|
|
_ = try! self.updateItemStmt.update(params);
|
|
|
|
deleteItemGroups(item: i);
|
|
insertItemGroups(item: i);
|
|
|
|
return i;
|
|
}
|
|
}
|
|
|
|
fileprivate func insertItemGroups(item: DBRosterItem) {
|
|
item.groups.forEach({ group in
|
|
let groupId = ensure(group: group);
|
|
let params: [String: Any?] = ["item_id": item.id, "group_id": groupId];
|
|
_ = try! self.insertItemGroupStmt.insert(params);
|
|
})
|
|
}
|
|
|
|
fileprivate func deleteItemGroups(item: DBRosterItem) {
|
|
let params: [String: Any?] = ["item_id": item.id];
|
|
_ = try! deleteItemGroupsStmt.update(params);
|
|
}
|
|
|
|
fileprivate func ensure(group: String) -> Int {
|
|
let params: [String: Any?] = ["name": group];
|
|
guard let groupId = try! getGroupIdStmt.scalar(params) else {
|
|
return try! insertGroupStmt.insert(params)!;
|
|
}
|
|
return groupId;
|
|
}
|
|
|
|
|
|
public func getCachedVersion(_ sessionObject: SessionObject) -> String? {
|
|
return AccountManager.getAccount(for: sessionObject.userBareJid!)?.rosterVersion;
|
|
}
|
|
|
|
public func loadCachedRoster(_ sessionObject: SessionObject) -> [RosterItem] {
|
|
return [RosterItem]();
|
|
}
|
|
|
|
public func updateReceivedVersion(_ sessionObject: SessionObject, ver: String?) {
|
|
if let account = AccountManager.getAccount(for: sessionObject.userBareJid!) {
|
|
account.rosterVersion = ver;
|
|
AccountManager.save(account: account);
|
|
}
|
|
}
|
|
|
|
@objc open func accountRemoved(_ notification: NSNotification) {
|
|
if let data = notification.userInfo {
|
|
let accountStr = data["account"] as! String;
|
|
removeAll(for: BareJID(accountStr));
|
|
}
|
|
}
|
|
|
|
class RosterItemUpdated {
|
|
|
|
}
|
|
}
|
|
|
|
class DBRosterItem: RosterItem {
|
|
|
|
let id: Int;
|
|
|
|
init(id: Int, jid: JID, name: String?, subscription: RosterItem.Subscription, groups: [String], ask: Bool, annotations: [RosterItemAnnotation]) {
|
|
self.id = id;
|
|
super.init(jid: jid, name: name, subscription: subscription, groups: groups, ask: ask, annotations: annotations);
|
|
}
|
|
|
|
convenience init(id: Int, item: RosterItem) {
|
|
self.init(id: id, jid: item.jid, name: item.name, subscription: item.subscription, groups: item.groups, ask: item.ask, annotations: item.annotations);
|
|
}
|
|
|
|
override func update(name: String?, subscription: RosterItem.Subscription, groups: [String], ask: Bool, annotations: [RosterItemAnnotation]) -> RosterItem {
|
|
return DBRosterItem(id: id, jid: jid, name: name, subscription: subscription, groups: groups, ask: ask, annotations: annotations);
|
|
}
|
|
}
|