snikket-ios/Tigase iOS Messenger/database/DBRosterStore.swift

254 lines
11 KiB
Swift

//
// DBRosterStore.swift
//
// Tigase iOS Messenger
// 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 Affero 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. Look for COPYING file in the top folder.
// If not, see http://www.gnu.org/licenses/.
//
import Foundation
import TigaseSwift
public class DBRosterStoreWrapper: RosterStore {
let cache: NSCache?;
let sessionObject: SessionObject;
let store:DBRosterStore;
override public var count: Int {
return store.count(sessionObject);
}
init(sessionObject: SessionObject, store: DBRosterStore, useCache: Bool = true) {
self.sessionObject = sessionObject;
self.store = store;
self.cache = useCache ? NSCache() : nil;
self.cache?.countLimit = 100;
self.cache?.totalCostLimit = 1 * 1024 * 1024;
super.init();
}
override public func addItem(item:RosterItem) {
store.addItem(sessionObject, item: item);
cache?.setObject(item, forKey: item.jid.stringValue);
}
override public func get(jid:JID) -> RosterItem? {
if let item = cache?.objectForKey(jid.stringValue) as? RosterItem {
return item;
}
if let item = store.get(sessionObject, jid: jid) {
cache?.setObject(item, forKey: jid.stringValue);
return item;
}
return nil;
}
override public func removeAll() {
cache?.removeAllObjects();
store.removeAll(sessionObject);
}
override public func removeItem(jid:JID) {
cache?.removeObjectForKey(jid.stringValue);
store.removeItem(sessionObject, jid: jid);
}
}
public class DBRosterStore: RosterCacheProvider, LocalQueueDispatcher {
private let dbConnection: DBConnection;
private lazy var countItemsStmt: DBStatement! = try? self.dbConnection.prepareStatement("SELECT count(id) FROM roster_items WHERE account = :account");
private lazy var deleteItemStmt: DBStatement! = try? self.dbConnection.prepareStatement("DELETE FROM roster_items WHERE account = :account AND jid = :jid");
private lazy var deleteItemGroupsStmt: DBStatement! = try? self.dbConnection.prepareStatement("DELETE FROM roster_items_groups WHERE item_id IN (SELECT id FROM roster_items WHERE account = :account AND jid = :jid)");
private lazy var deleteItemsStmt: DBStatement! = try? self.dbConnection.prepareStatement("DELETE FROM roster_items WHERE account = :account");
private lazy var deleteItemsGroupsStmt: DBStatement! = try? self.dbConnection.prepareStatement("DELETE FROM roster_items_groups WHERE item_id IN (SELECT id FROM roster_items WHERE account = :account)");
private lazy var getGroupIdStmt: DBStatement! = try? self.dbConnection.prepareStatement("SELECT id from roster_groups WHERE name = :name");
private lazy var getItemGroupsStmt: DBStatement! = try? self.dbConnection.prepareStatement("SELECT name FROM roster_groups rg INNER JOIN roster_items_groups rig ON rig.group_id = rg.id WHERE rig.item_id = :item_id");
private lazy var getItemStmt: DBStatement! = try? self.dbConnection.prepareStatement("SELECT id, name, subscription, ask FROM roster_items WHERE account = :account AND jid = :jid");
private lazy var insertGroupStmt: DBStatement! = try? self.dbConnection.prepareStatement("INSERT INTO roster_groups (name) VALUES (:name)");
private lazy var insertItemStmt: DBStatement! = try? self.dbConnection.prepareStatement("INSERT INTO roster_items (account, jid, name, subscription, timestamp, ask) VALUES (:account, :jid, :name, :subscription, :timestamp, :ask)");
private lazy var insertItemGroupStmt: DBStatement! = try? self.dbConnection.prepareStatement("INSERT INTO roster_items_groups (item_id, group_id) VALUES (:item_id, :group_id)");
private lazy var updateItemStmt: DBStatement! = try? self.dbConnection.prepareStatement("UPDATE roster_items SET name = :name, subscription = :subscription, timestamp = :timestamp, ask = :ask WHERE account = :account AND jid = :jid");
public var queue: dispatch_queue_t = dispatch_queue_create("db_roster_store_queue", DISPATCH_QUEUE_SERIAL);
public var queueTag: UnsafeMutablePointer<Void>;
public init(dbConnection:DBConnection) {
self.dbConnection = dbConnection;
self.queueTag = UnsafeMutablePointer<Void>(Unmanaged.passUnretained(self.queue).toOpaque());
dispatch_queue_set_specific(queue, queueTag, queueTag, nil);
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(DBRosterStore.accountRemoved), name: "accountRemoved", object: nil);
}
public func count(sessionObject: SessionObject) -> Int {
do {
let params:[String:Any?] = ["account" : sessionObject.userBareJid!.stringValue];
return try countItemsStmt.scalar(params) ?? 0;
} catch _ {
}
return 0;
}
public func addItem(sessionObject: SessionObject, item:RosterItem) -> RosterItem? {
do {
let params:[String:Any?] = [ "account": sessionObject.userBareJid, "jid": item.jid, "name": item.name, "subscription": String(item.subscription.rawValue), "timestamp": NSDate(), "ask": item.ask ];
let dbItem = item as? DBRosterItem ?? DBRosterItem(rosterItem: item);
if dbItem.id == nil {
// adding roster item to DB
dbItem.id = try insertItemStmt.insert(params);
} else {
// updating roster item in DB
try updateItemStmt.update(params);
let itemGroupsDeleteParams:[String:Any?] = ["account": sessionObject.userBareJid, "jid": dbItem.jid];
try deleteItemGroupsStmt.update(itemGroupsDeleteParams);
}
for group in dbItem.groups {
let gparams:[String:Any?] = ["name": group];
var groupId = try! getGroupIdStmt.scalar(gparams);
if groupId == nil {
groupId = try! insertGroupStmt.insert(gparams);
}
let igparams:[String:Any?] = ["item_id": dbItem.id, "group_id": groupId];
try insertItemGroupStmt.insert(igparams);
}
return dbItem;
} catch _ {
return nil;
}
}
public func get(sessionObject: SessionObject, jid:JID) -> RosterItem? {
var item:DBRosterItem? = nil;
let params:[String:Any?] = [ "account" : sessionObject.userBareJid, "jid" : jid ];
var id: Int?;
var name: String?;
var subscription = RosterItem.Subscription.none;
var ask = false;
dispatch_sync_local_queue() {
try! self.getItemStmt.query(params) { cursor -> Void in
id = cursor["id"]!;
name = cursor["name"];
subscription = RosterItem.Subscription(rawValue: cursor["subscription"]!)!;
ask = cursor["ask"]!;
}
if (id != nil) {
var groups = [String]();
try! self.getItemGroupsStmt.query(["item_id": id]) { cursor -> Void in
groups.append(cursor["name"]!);
}
item = DBRosterItem(jid: jid, id: id, name: name, subscription: subscription, groups: groups, ask: ask);
}
}
return item;
}
public func removeAll(sessionObject: SessionObject) {
let params:[String:Any?] = ["account": sessionObject.userBareJid];
dbConnection.dispatch_async_db_queue() {
do {
try self.deleteItemsGroupsStmt.execute(params);
try self.deleteItemsStmt.execute(params);
} catch _ {
}
}
}
public func removeItem(sessionObject: SessionObject, jid:JID) {
let params:[String:Any?] = ["account": sessionObject.userBareJid, "jid": jid];
dbConnection.dispatch_async_db_queue() {
do {
try self.deleteItemGroupsStmt.execute(params);
try self.deleteItemStmt.execute(params);
} catch _ {
}
}
}
public func getCachedVersion(sessionObject: SessionObject) -> String? {
return AccountManager.getAccount(sessionObject.userBareJid!.stringValue)?.rosterVersion;
}
public func loadCachedRoster(sessionObject: SessionObject) -> [RosterItem] {
return [RosterItem]();
}
public func updateReceivedVersion(sessionObject: SessionObject, ver: String?) {
if let account = AccountManager.getAccount(sessionObject.userBareJid!.stringValue) {
account.rosterVersion = ver;
AccountManager.updateAccount(account, notifyChange: false);
}
}
@objc public func accountRemoved(notification: NSNotification) {
if let data = notification.userInfo {
let accountStr = data["account"] as! String;
let params:[String:Any?] = ["account": accountStr];
dbConnection.dispatch_async_db_queue() {
do {
try self.deleteItemsGroupsStmt.execute(params);
try self.deleteItemsStmt.execute(params);
} catch _ {
}
}
}
}
}
extension RosterItemProtocol {
var id:Int? {
switch self {
case let ri as DBRosterItem:
return ri.id;
default:
return nil;
}
}
}
class DBRosterItem: RosterItem {
var id:Int? = nil;
init(jid: JID, id: Int?, name: String?, subscription: RosterItem.Subscription, groups: [String], ask: Bool) {
self.id = id;
super.init(jid: jid, name: name, subscription: subscription, groups: groups, ask: ask);
}
init(rosterItem item: RosterItem) {
super.init(jid: item.jid, name: item.name, subscription: item.subscription, groups: item.groups, ask: item.ask);
}
override func update(name: String?, subscription: RosterItem.Subscription, groups: [String], ask: Bool) -> RosterItem {
return DBRosterItem(jid: self.jid, id: self.id, name: name, subscription: subscription, groups: groups, ask: ask);
}
}