Statistics
| Branch: | Tag: | Revision:

chatsecureios / ChatSecure / Classes / Model / Yap Storage / SecondaryIndexes.swift @ 8d76e2e3

History | View | Annotate | Download (11.8 KB)

1
//
2
//  SecondaryIndexes.swift
3
//  ChatSecureCore
4
//
5
//  Created by Chris Ballinger on 11/7/17.
6
//  Copyright © 2017 Chris Ballinger. All rights reserved.
7
//
8

    
9
import Foundation
10
import YapDatabase
11

    
12
extension YapDatabaseSecondaryIndexOptions {
13
    convenience init(whitelist: [String]) {
14
        let set = Set(whitelist)
15
        let whitelist = YapWhitelistBlacklist(whitelist: set)
16
        self.init()
17
        self.allowedCollections = whitelist
18
    }
19
}
20

    
21
public extension YapDatabaseSecondaryIndex {
22
    @objc public static var buddyIndex: YapDatabaseSecondaryIndex {
23
        let columns: [String:YapDatabaseSecondaryIndexType] = [
24
            BuddyIndexColumnName.accountKey: .text,
25
            BuddyIndexColumnName.username: .text
26
        ]
27
        let setup = YapDatabaseSecondaryIndexSetup(capacity: UInt(columns.count))
28
        columns.forEach { (key, value) in
29
            setup.addColumn(key, with: value)
30
        }
31
        let handler = YapDatabaseSecondaryIndexHandler.withObjectBlock { (transaction, dict, collection, key, object) in
32
            guard let buddy = object as? OTRXMPPBuddy else {
33
                return
34
            }
35
            dict[BuddyIndexColumnName.accountKey] = buddy.accountUniqueId
36
            dict[BuddyIndexColumnName.username] = buddy.username
37
        }
38
        let options = YapDatabaseSecondaryIndexOptions(whitelist: [OTRXMPPBuddy.collection])
39
        let secondaryIndex = YapDatabaseSecondaryIndex(setup: setup, handler: handler, versionTag: "2", options: options)
40
        return secondaryIndex
41
    }
42
    
43
    
44
    @objc public static var messageIndex: YapDatabaseSecondaryIndex {
45
        let columns: [String:YapDatabaseSecondaryIndexType] = [
46
            MessageIndexColumnName.messageKey: .text,
47
            MessageIndexColumnName.remoteMessageId: .text,
48
            MessageIndexColumnName.threadId: .text,
49
            MessageIndexColumnName.isMessageRead: .integer,
50
            MessageIndexColumnName.originId: .text,
51
            MessageIndexColumnName.stanzaId: .text
52
        ]
53
        let setup = YapDatabaseSecondaryIndexSetup(capacity: UInt(columns.count))
54
        columns.forEach { (key, value) in
55
            setup.addColumn(key, with: value)
56
        }
57
        
58
        let handler = YapDatabaseSecondaryIndexHandler.withObjectBlock { (transaction, dict, collection, key, object) in
59
            guard let message = object as? OTRMessageProtocol else {
60
                return
61
            }
62
            if let remoteMessageId = message.remoteMessageId,
63
                remoteMessageId.count > 0 {
64
                dict[MessageIndexColumnName.remoteMessageId] = remoteMessageId
65
            }
66
            if message.messageKey.count > 0 {
67
                dict[MessageIndexColumnName.messageKey] = message.messageKey
68
            }
69
            dict[MessageIndexColumnName.isMessageRead] = message.isMessageRead
70
            if message.threadId.count > 0 {
71
                dict[MessageIndexColumnName.threadId] = message.threadId
72
            }
73
            if let originId = message.originId, originId.count > 0 {
74
                dict[MessageIndexColumnName.originId] = originId
75
            }
76
            if let stanzaId = message.stanzaId, stanzaId.count > 0 {
77
                dict[MessageIndexColumnName.stanzaId] = stanzaId
78
            }
79
        }
80
        // These are actually the same collection
81
        let options = YapDatabaseSecondaryIndexOptions(whitelist: [OTRBaseMessage.collection, OTRXMPPRoomMessage.collection])
82
        let secondaryIndex = YapDatabaseSecondaryIndex(setup: setup, handler: handler, versionTag: "6", options: options)
83
        return secondaryIndex
84
    }
85
    
86
    @objc public static var signalIndex: YapDatabaseSecondaryIndex {
87
        let columns: [String:YapDatabaseSecondaryIndexType] = [
88
            SignalIndexColumnName.session: .text,
89
            SignalIndexColumnName.preKeyId: .integer,
90
            SignalIndexColumnName.preKeyAccountKey: .text
91
        ]
92
        let setup = YapDatabaseSecondaryIndexSetup(capacity: UInt(columns.count))
93
        columns.forEach { (key, value) in
94
            setup.addColumn(key, with: value)
95
        }
96
        
97
        let handler = YapDatabaseSecondaryIndexHandler.withObjectBlock { (transaction, dict, collection, key, object) in
98
            if let session = object as? OTRSignalSession {
99
                if session.name.count > 0 {
100
                    dict[SignalIndexColumnName.session] = session.sessionKey
101
                }
102
            } else if let preKey = object as? OTRSignalPreKey {
103
                dict[SignalIndexColumnName.preKeyId] = preKey.keyId
104
                if preKey.accountKey.count > 0 {
105
                    dict[SignalIndexColumnName.preKeyAccountKey] = preKey.accountKey
106
                }
107
            }
108
        }
109
        let options = YapDatabaseSecondaryIndexOptions(whitelist: [OTRSignalPreKey.collection,OTRSignalSession.collection])
110
        let secondaryIndex = YapDatabaseSecondaryIndex(setup: setup, handler: handler, versionTag: "6", options: options)
111
        return secondaryIndex
112
    }
113
    
114
    @objc public static var roomOccupantIndex: YapDatabaseSecondaryIndex {
115
        let columns: [String:YapDatabaseSecondaryIndexType] = [
116
            RoomOccupantIndexColumnName.jid: .text,
117
            RoomOccupantIndexColumnName.realJID: .text,
118
            RoomOccupantIndexColumnName.roomUniqueId: .text,
119
            RoomOccupantIndexColumnName.buddyUniqueId: .text,
120
        ]
121
        let setup = YapDatabaseSecondaryIndexSetup(capacity: UInt(columns.count))
122
        columns.forEach { (key, value) in
123
            setup.addColumn(key, with: value)
124
        }
125
        
126
        let handler = YapDatabaseSecondaryIndexHandler.withObjectBlock { (transaction, dict, collection, key, object) in
127
            guard let occupant = object as? OTRXMPPRoomOccupant else {
128
                return
129
            }
130
            if let jid = occupant.jid, jid.count > 0 {
131
                dict[RoomOccupantIndexColumnName.jid] = jid
132
            }
133
            if let realJID = occupant.realJID, realJID.count > 0 {
134
                dict[RoomOccupantIndexColumnName.realJID] = realJID
135
            }
136
            if let roomUniqueId = occupant.roomUniqueId, roomUniqueId.count > 0 {
137
                dict[RoomOccupantIndexColumnName.roomUniqueId] = roomUniqueId
138
            }
139
            if let buddyUniqueId = occupant.buddyUniqueId, buddyUniqueId.count > 0 {
140
                dict[RoomOccupantIndexColumnName.buddyUniqueId] = buddyUniqueId
141
            }
142
        }
143
        let options = YapDatabaseSecondaryIndexOptions(whitelist: [OTRXMPPRoomOccupant.collection])
144
        let secondaryIndex = YapDatabaseSecondaryIndex(setup: setup, handler: handler, versionTag: "4", options: options)
145
        return secondaryIndex
146
    }
147
}
148

    
149
// MARK: - Extensions
150

    
151
public extension OTRSignalSession {
152
    /// "\(accountKey)-\(name)", only used for SecondaryIndex lookups
153
    public var sessionKey: String {
154
        return OTRSignalSession.sessionKey(accountKey: accountKey, name: name)
155
    }
156
    
157
    /// "\(accountKey)-\(name)", only used for SecondaryIndex lookups
158
    public static func sessionKey(accountKey: String, name: String) -> String {
159
        return "\(accountKey)-\(name)"
160
    }
161
}
162

    
163
public extension OTRXMPPBuddy {
164
    /// This function should only be used when the secondary index is not ready
165
    private static func slowLookup(jid: XMPPJID,
166
                                   accountUniqueId: String,
167
                                   transaction: YapDatabaseReadTransaction) -> OTRXMPPBuddy? {
168
        DDLogWarn("WARN: Using slow O(n) lookup for OTRXMPPBuddy: \(jid)")
169
        var buddy: OTRXMPPBuddy? = nil
170
        transaction.enumerateKeysAndObjects(inCollection: OTRXMPPBuddy.collection) { (key, object, stop) in
171
            if let potentialMatch = object as? OTRXMPPBuddy,
172
                potentialMatch.username == jid.bare {
173
                buddy = potentialMatch
174
                stop.pointee = true
175
            }
176
        }
177
        return buddy
178
    }
179
    
180
    
181
    /// Fetch buddy matching JID using secondary index
182
    @objc public static func fetchBuddy(jid: XMPPJID,
183
                                        accountUniqueId: String,
184
                                        transaction: YapDatabaseReadTransaction) -> OTRXMPPBuddy? {
185
        guard let indexTransaction = transaction.ext(SecondaryIndexName.buddy) as? YapDatabaseSecondaryIndexTransaction else {
186
            DDLogError("Error looking up OTRXMPPBuddy via SecondaryIndex: Extension not ready.")
187
            return self.slowLookup(jid:jid, accountUniqueId:accountUniqueId, transaction: transaction)
188
        }
189
        let queryString = "Where \(BuddyIndexColumnName.accountKey) == ? AND \(BuddyIndexColumnName.username) == ?"
190
        let query = YapDatabaseQuery(string: queryString, parameters: [accountUniqueId, jid.bare])
191
        
192
        var matchingBuddies: [OTRXMPPBuddy] = []
193
        let success = indexTransaction.enumerateKeysAndObjects(matching: query) { (collection, key, object, stop) in
194
            if let matchingBuddy = object as? OTRXMPPBuddy {
195
                matchingBuddies.append(matchingBuddy)
196
            }
197
        }
198
        if !success {
199
            DDLogError("Error looking up OTRXMPPBuddy with query \(query) \(jid) \(accountUniqueId)")
200
            return nil
201
        }
202
        if matchingBuddies.count > 1 {
203
            DDLogWarn("WARN: More than one OTRXMPPBuddy matching query \(query) \(jid) \(accountUniqueId): \(matchingBuddies.count)")
204
        }
205
//        #if DEBUG
206
//            if matchingBuddies.count == 0 {
207
//                DDLogWarn("WARN: No OTRXMPPBuddy matching query \(jid) \(accountUniqueId)")
208
//                let buddy = slowLookup(jid: jid, accountUniqueId: accountUniqueId, transaction: transaction)
209
//                if buddy != nil {
210
//                    DDLogWarn("WARN: Found buddy using O(n) lookup that wasn't found in secondary index: \(jid) \(accountUniqueId)")
211
//                }
212
//            }
213
//        #endif
214
        return matchingBuddies.first
215
    }
216
}
217

    
218
// MARK: - Constants
219

    
220
/// YapDatabase extension names for Secondary Indexes
221
@objc public class SecondaryIndexName: NSObject {
222
    @objc public static let messages = "OTRMessagesSecondaryIndex"
223
    @objc public static let signal = "OTRYapDatabseMessageIdSecondaryIndexExtension"
224
    @objc public static let roomOccupants = "SecondaryIndexName_roomOccupantIndex"
225
    @objc public static let buddy = "SecondaryIndexName_buddy"
226
}
227

    
228
@objc public class BuddyIndexColumnName: NSObject {
229
    @objc public static let accountKey = "BuddyIndexColumnName_accountKey"
230
    @objc public static let username = "BuddyIndexColumnName_username"
231
}
232

    
233
@objc public class MessageIndexColumnName: NSObject {
234
    @objc public static let messageKey = "OTRYapDatabaseMessageIdSecondaryIndexColumnName"
235
    @objc public static let remoteMessageId = "OTRYapDatabaseRemoteMessageIdSecondaryIndexColumnName"
236
    @objc public static let threadId = "OTRYapDatabaseMessageThreadIdSecondaryIndexColumnName"
237
    @objc public static let isMessageRead = "OTRYapDatabaseUnreadMessageSecondaryIndexColumnName"
238
    
239
    
240
    /// XEP-0359 origin-id
241
    @objc public static let originId = "SecondaryIndexNameOriginId"
242
    /// XEP-0359 stanza-id
243
    @objc public static let stanzaId = "SecondaryIndexNameStanzaId"
244
}
245

    
246
@objc public class RoomOccupantIndexColumnName: NSObject {
247
    /// jid
248
    @objc public static let jid = "OTRYapDatabaseRoomOccupantJidSecondaryIndexColumnName"
249
    @objc public static let realJID = "RoomOccupantIndexColumnName_realJID"
250
    @objc public static let roomUniqueId = "RoomOccupantIndexColumnName_roomUniqueId"
251
    @objc public static let buddyUniqueId = "RoomOccupantIndexColumnName_buddyUniqueId"
252
}
253

    
254
@objc public class SignalIndexColumnName: NSObject {
255
    @objc public static let session = "OTRYapDatabaseSignalSessionSecondaryIndexColumnName"
256
    @objc public static let preKeyId = "OTRYapDatabaseSignalPreKeyIdSecondaryIndexColumnName"
257
    @objc public static let preKeyAccountKey = "OTRYapDatabaseSignalPreKeyAccountKeySecondaryIndexColumnName"
258
}