ChatSecure-iOS/ChatSecure/Classes/Controllers/PushStorage.swift

301 lines
13 KiB
Swift

//
// PushStorage.swift
// ChatSecure
//
// Created by David Chiles on 9/29/15.
// Copyright © 2015 Chris Ballinger. All rights reserved.
//
import Foundation
import ChatSecure_Push_iOS
import YapDatabase
@objc public protocol PushStorageProtocol: class {
func thisDevicePushAccount() -> Account?
func hasPushAccount() -> Bool
func saveThisAccount(account:Account)
func thisDevice() -> Device?
func saveThisDevice(device:Device)
func unusedToken() -> TokenContainer?
func removeUnusedToken(token: TokenContainer)
func removeToken(token: TokenContainer)
func associateBuddy(tokenContainer:TokenContainer, buddyKey:String)
func saveUnusedToken(tokenContainer:TokenContainer)
func saveUsedToken(tokenContainer:TokenContainer)
func numberUnusedTokens() -> UInt
func unusedTokenStoreMinimum() -> UInt
func tokensForBuddy(buddyKey:String, createdByThisAccount:Bool) throws -> [TokenContainer]
func numberOfTokensForBuddy(buddyKey:String, createdByThisAccount:Bool) -> Int
func buddy(username: String, accountName: String) -> OTRBuddy?
func account(accountUniqueID:String) -> OTRAccount?
func buddy(token:String) -> OTRBuddy?
/**
* Asynchronously remvoes all the unused tokens in the unsedTokenCollection that are missing an expires date. This was needed
* for when we moved from not having expires date to saving expires date in the database. This clears those tokens that have not been
* given out already.
*
* parameter timeBuffer: Destry tokens that expire this far into the future. This allows you to clear out tokens that may
* expire in the next few hours or days
* parameter completion: This is called once all the tokens have been removed and the count of total tokens remvoed
*/
func removeAllOurExpiredUnusedTokens(timeBuffer:NSTimeInterval, completion:((count:Int)->Void)?)
}
extension Account {
public class func yapCollection() -> String {
return "ChatSecurePushAccountColletion"
}
}
class PushStorage: NSObject, PushStorageProtocol {
let databaseConnection: YapDatabaseConnection
let workQueue = dispatch_queue_create("PushStorage_Work_Queue", DISPATCH_QUEUE_SERIAL)
static let unusedTokenStoreSize:UInt = 5
enum PushYapKeys: String {
case thisDeviceKey = "kYapThisDeviceKey"
case thisAccountKey = "kYapThisAccountKey"
}
enum PushYapCollections: String {
///Alternate Collection for tokens before they're 'attached' to a buddy. Just downloaded from the server
case unusedTokenCollection = "kYapUnusedTokenCollection"
}
init(databaseConnection:YapDatabaseConnection) {
self.databaseConnection = databaseConnection
}
func thisDevicePushAccount() -> Account? {
var account:Account? = nil
self.databaseConnection.readWithBlock { (transaction) -> Void in
account = transaction.objectForKey(PushYapKeys.thisAccountKey.rawValue, inCollection: Account.yapCollection()) as? Account
}
return account
}
func hasPushAccount() -> Bool {
var hasAccount = false
self.databaseConnection.readWithBlock { (transaction) -> Void in
hasAccount = transaction.hasObjectForKey(PushYapKeys.thisAccountKey.rawValue, inCollection: Account.yapCollection())
}
return hasAccount
}
func saveThisAccount(account: Account) {
self.databaseConnection.readWriteWithBlock { (transaction) -> Void in
transaction.setObject(account, forKey:PushYapKeys.thisAccountKey.rawValue, inCollection:Account.yapCollection())
}
}
func saveThisDevice(device: Device) {
let deviceContainer = DeviceContainer()
deviceContainer.pushDevice = device
deviceContainer.pushAccountKey = PushYapKeys.thisAccountKey.rawValue
self.databaseConnection.readWriteWithBlock({ (transaction) -> Void in
transaction.setObject(deviceContainer, forKey:PushYapKeys.thisDeviceKey.rawValue, inCollection:DeviceContainer.collection())
})
}
func thisDevice() -> Device? {
var device:Device? = nil
self.databaseConnection.readWithBlock { (transaction) -> Void in
if let deviceContainer = transaction.objectForKey(PushYapKeys.thisDeviceKey.rawValue, inCollection:DeviceContainer.collection()) as? DeviceContainer {
device = deviceContainer.pushDevice
}
}
return device
}
func unusedToken() -> TokenContainer? {
var tokenContainer:TokenContainer? = nil
self.databaseConnection.readWithBlock { (transaction) -> Void in
transaction.enumerateKeysAndObjectsInCollection(PushYapCollections.unusedTokenCollection.rawValue, usingBlock: { (key, object, stop) -> Void in
if let tc = object as? TokenContainer {
tokenContainer = tc
}
stop.initialize(true)
})
}
return tokenContainer
}
func removeUnusedToken(token: TokenContainer) {
self.databaseConnection.readWriteWithBlock { (transaction) -> Void in
transaction.removeObjectForKey(token.uniqueId, inCollection: PushYapCollections.unusedTokenCollection.rawValue)
}
}
func removeToken(token: TokenContainer) {
self.databaseConnection.readWriteWithBlock { (transaction) -> Void in
token.removeWithTransaction(transaction)
}
}
func associateBuddy(tokenContainer: TokenContainer, buddyKey: String) {
self.databaseConnection.readWriteWithBlock { (transaction) -> Void in
tokenContainer.buddyKey = buddyKey
tokenContainer.saveWithTransaction(transaction)
}
}
func saveUnusedToken(tokenContainer: TokenContainer) {
self.databaseConnection.readWriteWithBlock { (transaction) -> Void in
tokenContainer.accountKey = PushYapKeys.thisAccountKey.rawValue
transaction.setObject(tokenContainer, forKey:tokenContainer.uniqueId, inCollection:PushYapCollections.unusedTokenCollection.rawValue)
}
}
func saveUsedToken(tokenContainer: TokenContainer) {
self.databaseConnection.readWriteWithBlock { (transaction) -> Void in
tokenContainer.saveWithTransaction(transaction)
}
}
func numberUnusedTokens() -> UInt {
var unusedTokensCount:UInt = 0
self.databaseConnection.readWithBlock { (transaction) -> Void in
unusedTokensCount = transaction.numberOfKeysInCollection(PushYapCollections.unusedTokenCollection.rawValue)
}
return unusedTokensCount
}
func unusedTokenStoreMinimum() -> UInt {
return PushStorage.unusedTokenStoreSize
}
func tokensForBuddy(buddyKey: String, createdByThisAccount: Bool) throws -> [TokenContainer] {
var error:NSError? = nil
var tokens:[TokenContainer] = []
self.databaseConnection.readWriteWithBlock { (transaction) -> Void in
guard let buddy = transaction.objectForKey(buddyKey, inCollection: OTRBuddy.collection()) as? OTRBuddy else {
error = PushError.noBuddyFound.error()
return
}
if let relationshipTransaction = transaction.ext(DatabaseExtensionName.RelationshipExtensionName.name()) as? YapDatabaseRelationshipTransaction {
relationshipTransaction.enumerateEdgesWithName(kBuddyTokenRelationshipEdgeName, destinationKey: buddy.uniqueId, collection: OTRBuddy.collection(), usingBlock: { (edge, stop) -> Void in
if let tokenContainer = transaction.objectForKey(edge.sourceKey, inCollection: edge.sourceCollection) as? TokenContainer {
if tokenContainer.accountKey != nil && createdByThisAccount {
tokens.append(tokenContainer)
} else if tokenContainer.accountKey == nil && !createdByThisAccount {
tokens.append(tokenContainer)
}
}
})
}
tokens.sortInPlace({ (first, second) -> Bool in
switch first.date.compare(second.date) {
case .OrderedAscending:
return true
default:
return false
}
})
}
if let err = error {
throw err
}
return tokens
}
func removeAllOurExpiredUnusedTokens(timeBuffer: NSTimeInterval, completion: ((count: Int) -> Void)?) {
var count:Int = 0
self.databaseConnection.asyncReadWriteWithBlock({ (transaction) in
let collection = PushYapCollections.unusedTokenCollection.rawValue
var removeKeyArray:[String] = []
transaction.enumerateKeysAndObjectsInCollection(collection, usingBlock: { (key, object, stop) in
if let token = object as? TokenContainer {
//Check that there is an expires date otherwise remove
guard let expiresDate = token.pushToken?.expires else {
removeKeyArray.append(token.uniqueId)
return
}
// Check that the date is farther in the future than currentDate + timeBuffer
if (NSDate(timeIntervalSinceNow: timeBuffer).compare(expiresDate) == .OrderedDescending ) {
removeKeyArray.append(token.uniqueId)
}
}
})
count = removeKeyArray.count
transaction.removeObjectsForKeys(removeKeyArray, inCollection: collection)
}, completionQueue: self.workQueue) {
if let comp = completion {
dispatch_async(dispatch_get_main_queue(), {
comp(count: count)
})
}
}
}
/**
Quicker way of getting just the count of the number of tokens. This method may take a little time because of Yap Relationships
it iterates over all the relationship edges and counts them.
- parameter buddyKey: The uniqueID or yap key for the buddy
- parameter createdByThisAccount: A bool to check for the count of tokens created by this account (outgoing) or those created by the buddy (incoming)
- returns: The number of push tokens
*/
func numberOfTokensForBuddy(buddyKey: String, createdByThisAccount: Bool) -> Int {
var count = 0
self.databaseConnection.readWriteWithBlock { (transaction) -> Void in
guard let relationshipTransaction = transaction.ext(DatabaseExtensionName.RelationshipExtensionName.name()) as? YapDatabaseRelationshipTransaction else {
return
}
relationshipTransaction.enumerateEdgesWithName(kBuddyTokenRelationshipEdgeName, destinationKey: buddyKey, collection: OTRBuddy.collection(), usingBlock: { (edge, stop) -> Void in
if let tokenContainer = transaction.objectForKey(edge.sourceKey, inCollection: edge.sourceCollection) as? TokenContainer {
if tokenContainer.accountKey != nil && createdByThisAccount {
count++
} else if tokenContainer.accountKey == nil && !createdByThisAccount {
count++
}
}
})
}
return count
}
func buddy(username: String, accountName: String) -> OTRBuddy? {
var buddy:OTRBuddy? = nil
self.databaseConnection.readWithBlock { (transaction) -> Void in
buddy = OTRBuddy.fetchBuddyForUsername(username, accountName: accountName, transaction: transaction)
}
return buddy
}
func account(accountUniqueID: String) -> OTRAccount? {
var account:OTRAccount? = nil
self.databaseConnection.readWithBlock { (transaction) -> Void in
account = OTRAccount.fetchObjectWithUniqueID(accountUniqueID, transaction: transaction)
}
return account
}
func buddy(token: String) -> OTRBuddy? {
var buddy:OTRBuddy? = nil
self.databaseConnection.readWithBlock { (transaction) -> Void in
if let relationshipTransaction = transaction.ext(DatabaseExtensionName.RelationshipExtensionName.name()) as? YapDatabaseRelationshipTransaction {
relationshipTransaction.enumerateEdgesWithName(kBuddyTokenRelationshipEdgeName, sourceKey: token, collection: TokenContainer.collection(), usingBlock: { (edge, stop) -> Void in
buddy = transaction.objectForKey(edge.destinationKey, inCollection: edge.destinationCollection) as? OTRBuddy
if buddy != nil {
stop.initialize(true)
}
})
}
}
return buddy
}
}