Skip to content

Commit 36dc113

Browse files
Merge pull request #734 from zom/add_invites_to_queue
Change message queue to also handle add/remove buddy
2 parents 9c72c86 + a6e1747 commit 36dc113

File tree

5 files changed

+227
-70
lines changed

5 files changed

+227
-70
lines changed

ChatSecure/Classes/Controllers/MessageQueueHandler.swift

Lines changed: 155 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//
1+
//
22
// MessageQueueHandler.swift
33
// ChatSecure
44
//
@@ -8,7 +8,30 @@
88

99
import Foundation
1010
import YapTaskQueue
11-
11+
12+
private class OutstandingActionInfo: Hashable, Equatable {
13+
let action:YapTaskQueueAction
14+
let timer:Timer?
15+
let completion:((_ success: Bool, _ retryTimeout: TimeInterval) -> Void)
16+
17+
public init(action:YapTaskQueueAction,timer:Timer?,completion:@escaping ((_ success: Bool, _ retryTimeout: TimeInterval) -> Void)) {
18+
self.action = action
19+
self.timer = timer
20+
self.completion = completion
21+
}
22+
23+
/// Needed so we can store the struct in a dictionary
24+
var hashValue: Int {
25+
get {
26+
return action.yapKey().hashValue
27+
}
28+
}
29+
}
30+
31+
private func ==(lhs: OutstandingActionInfo, rhs: OutstandingActionInfo) -> Bool {
32+
return lhs.action.yapKey() == rhs.action.yapKey()
33+
}
34+
1235
/// This is just small struct to store the necessary inormation about a message while we wait for delegate callbacks from the XMPPStream
1336
private struct OutstandingMessageInfo {
1437
let messageKey:String
@@ -18,7 +41,7 @@ private struct OutstandingMessageInfo {
1841
let completion:((_ success: Bool, _ retryTimeout: TimeInterval) -> Void)
1942
}
2043

21-
/// Needed so we can store the struct in a dictionary
44+
/// Needed so we can store the struct in a dictionary
2245
extension OutstandingMessageInfo: Hashable {
2346
var hashValue: Int {
2447
get {
@@ -43,7 +66,7 @@ public class MessageQueueHandler:NSObject {
4366
let databaseConnection:YapDatabaseConnection
4467
fileprivate var outstandingMessages = [String:OutstandingMessageInfo]()
4568
fileprivate var outstandingBuddies = [String:OutstandingMessageInfo]()
46-
fileprivate var outstandingAccounts = [String:Set<OutstandingMessageInfo>]()
69+
fileprivate var outstandingAccounts = [String:Set<OutstandingActionInfo>]()
4770
fileprivate let isolationQueue = DispatchQueue(label: "MessageQueueHandler-IsolationQueue", attributes: [])
4871
fileprivate var accountLoginNotificationObserver:NSObjectProtocol?
4972
fileprivate var messageStateDidChangeNotificationObserver:NSObjectProtocol?
@@ -73,50 +96,48 @@ public class MessageQueueHandler:NSObject {
7396

7497
//MARK: Access to outstanding messages and account
7598

76-
fileprivate func waitingForAccount(_ accountString:String,messageKey:String,messageCollection:String,messageSecurity:OTRMessageTransportSecurity,completion:@escaping (_ success: Bool, _ retryTimeout: TimeInterval) -> Void) {
99+
fileprivate func waitingForAccount(_ accountString:String,action:OutstandingActionInfo) {
77100

78101
self.isolationQueue.async {
79102

80103
// Get the set out or create a new one
81-
var messageSet = self.outstandingAccounts[accountString]
82-
if messageSet == nil {
83-
messageSet = Set<OutstandingMessageInfo>()
104+
var actionSet = self.outstandingAccounts[accountString]
105+
if actionSet == nil {
106+
actionSet = Set<OutstandingActionInfo>()
84107
}
85108

86109
// Guarantee set is real
87-
guard var set = messageSet else {
110+
guard var set = actionSet else {
88111
return
89112
}
90113
// Add new item
91-
set.insert(OutstandingMessageInfo(messageKey: messageKey, messageCollection: messageCollection,messageSecurity:messageSecurity,timer:nil, completion: completion))
114+
set.insert(action)
92115
//Insert back into dictionary
93116
self.outstandingAccounts.updateValue(set, forKey: accountString)
94117
}
95-
96-
97118
}
98119

99-
fileprivate func popWaitingAccount(_ accountString:String) -> Set<OutstandingMessageInfo>? {
100-
var messageInfoSet:Set<OutstandingMessageInfo>? = nil
120+
fileprivate func popWaitingAccount(_ accountString:String) -> Set<OutstandingActionInfo>? {
121+
var actionSet:Set<OutstandingActionInfo>? = nil
101122
self.isolationQueue.sync {
102-
messageInfoSet = self.outstandingAccounts.removeValue(forKey: accountString)
123+
actionSet = self.outstandingAccounts.removeValue(forKey: accountString)
103124
}
104125

105-
return messageInfoSet
126+
return actionSet
106127
}
107128

108129
fileprivate func waitingForBuddy(_ buddyKey:String,messageKey:String, messageCollection:String, messageSecurity:OTRMessageTransportSecurity, timer:Timer,completion:@escaping (_ success: Bool, _ retryTimeout: TimeInterval) -> Void) {
109130

110131
let messageInfo = OutstandingMessageInfo(messageKey: messageKey, messageCollection: messageCollection,messageSecurity:messageSecurity, timer:nil, completion: completion)
111132

112-
self.isolationQueue.async {
133+
self.isolationQueue.async {
113134
self.outstandingBuddies.updateValue(messageInfo, forKey: buddyKey)
114135
}
115136
}
116137

117138
fileprivate func popWaitingBuddy(_ buddyKey:String) -> OutstandingMessageInfo? {
118139
var messageInfo:OutstandingMessageInfo? = nil
119-
self.isolationQueue.sync {
140+
self.isolationQueue.sync {
120141
messageInfo = self.outstandingBuddies.removeValue(forKey: buddyKey)
121142
}
122143
return messageInfo
@@ -126,23 +147,23 @@ public class MessageQueueHandler:NSObject {
126147
let messageInfo = OutstandingMessageInfo(messageKey: messageKey, messageCollection: messageCollection, messageSecurity:messageSecurity, timer:nil, completion: completion)
127148
let key = "\(messageKey)\(messageCollection)"
128149

129-
self.isolationQueue.async {
150+
self.isolationQueue.async {
130151
self.outstandingMessages.updateValue(messageInfo, forKey: key)
131152
}
132153
}
133154

134-
/**
155+
/**
135156
* Remove a waiting message info from the outstaning message dictionary. After the message info is removed the completion block should be called.
136157
* This ensures that the outstandingMessages dictionary is accessed from the correct queue.
137-
*
158+
*
138159
* - parameter messageKey: The yap database messsage key.
139160
* - parameter messageCollection: The yap database message key.
140161
* - returns: The OutstandingMessageInfo if one exists. Removed from the waiting dictioanry.
141162
*/
142163
fileprivate func popWaitingMessage(_ messageKey:String,messageCollection:String) -> OutstandingMessageInfo? {
143164
var messageInfo:OutstandingMessageInfo? = nil
144165
let key = "\(messageKey)\(messageCollection)"
145-
self.isolationQueue.sync {
166+
self.isolationQueue.sync {
146167
messageInfo = self.outstandingMessages.removeValue(forKey: key)
147168
}
148169

@@ -172,21 +193,35 @@ public class MessageQueueHandler:NSObject {
172193
fileprivate func sendMessage(_ outstandingMessage:OutstandingMessageInfo) {
173194
self.operationQueue.addOperation { [weak self] in
174195
guard let strongSelf = self else { return }
175-
var msg:OTROutgoingMessage? = nil
196+
var msgAction:OTRYapMessageSendAction? = nil
176197
strongSelf.databaseConnection.read({ (transaction) in
177-
msg = transaction.object(forKey: outstandingMessage.messageKey, inCollection: outstandingMessage.messageCollection) as? OTROutgoingMessage
198+
msgAction = strongSelf.fetchSendingAction(outstandingMessage.messageKey, messageCollection: outstandingMessage.messageCollection, transaction: transaction)
178199
})
179200

180-
guard let message = msg else {
201+
guard let action = msgAction else {
181202
outstandingMessage.completion(true, 0.0)
182203
return
183204
}
184205

185-
strongSelf.sendMessage(message, completion: outstandingMessage.completion)
206+
strongSelf.sendMessage(action, completion: outstandingMessage.completion)
186207
}
187208
}
188209

189-
fileprivate func sendMessage(_ message:OTROutgoingMessage, completion:@escaping (_ success: Bool, _ retryTimeout: TimeInterval) -> Void) {
210+
fileprivate func sendMessage(_ messageSendingAction:OTRYapMessageSendAction, completion:@escaping (_ success: Bool, _ retryTimeout: TimeInterval) -> Void) {
211+
212+
let messageKey = messageSendingAction.messageKey
213+
let messageCollection = messageSendingAction.messageCollection
214+
var msg:OTROutgoingMessage? = nil
215+
self.databaseConnection.read { (transaction) in
216+
msg = self.fetchMessage(messageKey, collection: messageCollection, transaction: transaction)
217+
}
218+
219+
guard let message = msg else {
220+
// Somehow we have an action without a message. This is very strange. Do not like.
221+
// We tell the queue broker that we handle it successfully so it will be rmeoved and go on to the next action.
222+
completion(true, 0.0)
223+
return
224+
}
190225

191226
var bud:OTRBuddy? = nil
192227
var acc:OTRAccount? = nil
@@ -214,8 +249,6 @@ public class MessageQueueHandler:NSObject {
214249
* a msesage to be sent.
215250
*/
216251
//Some way to store a message dictionary with the key and block
217-
let messageCollection = OTROutgoingMessage.collection()
218-
219252

220253
//Ensure protocol is connected or if not and autologin then connnect
221254
if (accountProtocol.connectionStatus == .connected) {
@@ -236,7 +269,7 @@ public class MessageQueueHandler:NSObject {
236269
break
237270
}
238271
} else if (account.autologin == true) {
239-
self.waitingForAccount(account.uniqueId, messageKey: message.uniqueId, messageCollection: messageCollection, messageSecurity:message.messageSecurity(), completion: completion)
272+
self.waitingForAccount(account.uniqueId, action: OutstandingActionInfo(action: messageSendingAction, timer: nil, completion: completion))
240273
accountProtocol.connectUserInitiated(false)
241274
} else {
242275
// The account might be connected then? even if not auto connecting we might just start up faster then the
@@ -247,8 +280,79 @@ public class MessageQueueHandler:NSObject {
247280

248281
completion(false, self.accountRetryTimeout)
249282
}
283+
}
284+
285+
fileprivate func addBuddyToRoster(_ addBuddyAction:OTRYapAddBuddyAction, completion:@escaping (_ success: Bool, _ retryTimeout: TimeInterval) -> Void) {
286+
287+
var bud:OTRBuddy? = nil
288+
var acc:OTRAccount? = nil
289+
self.databaseConnection.read({ (transaction) in
290+
bud = OTRBuddy.fetchObject(withUniqueID: addBuddyAction.buddyKey, transaction: transaction)
291+
if let accountKey = bud?.accountUniqueId {
292+
acc = OTRAccount.fetchObject(withUniqueID: accountKey, transaction: transaction)
293+
}
294+
295+
})
296+
guard let buddy = bud,let account = acc else {
297+
completion(true, 0.0)
298+
return
299+
}
300+
301+
//Get the XMPP procol manager associated with this message and therefore account
302+
guard let accountProtocol = OTRProtocolManager.sharedInstance().protocol(for: account) as? OTRXMPPManager else {
303+
completion(true, 0.0)
304+
return
305+
}
306+
307+
//Ensure protocol is connected or if not and autologin then connnect
308+
if (accountProtocol.connectionStatus == .connected) {
309+
// Add the buddy to our roster
310+
let jid = XMPPJID(string: buddy.username)
311+
accountProtocol.xmppRoster.addUser(jid, withNickname:buddy.displayName)
312+
completion(true, 0.0)
313+
} else if (account.autologin == true) {
314+
self.waitingForAccount(account.uniqueId, action: OutstandingActionInfo(action: addBuddyAction, timer: nil, completion: completion))
315+
accountProtocol.connectUserInitiated(false)
316+
} else {
317+
// Retry later
318+
completion(false, self.accountRetryTimeout)
319+
}
320+
}
250321

322+
fileprivate func removeBuddyFromRoster(_ removeBuddyAction:OTRYapRemoveBuddyAction, completion:@escaping (_ success: Bool, _ retryTimeout: TimeInterval) -> Void) {
323+
324+
var acc:OTRAccount? = nil
325+
self.databaseConnection.read({ (transaction) in
326+
if let accountKey = removeBuddyAction.accountKey {
327+
acc = OTRAccount.fetchObject(withUniqueID: accountKey, transaction: transaction)
328+
}
329+
})
330+
guard let account = acc else {
331+
completion(true, 0.0)
332+
return
333+
}
334+
335+
//Get the XMPP procol manager associated with this message and therefore account
336+
guard let accountProtocol = OTRProtocolManager.sharedInstance().protocol(for: account) as? OTRXMPPManager else {
337+
completion(true, 0.0)
338+
return
339+
}
340+
341+
//Ensure protocol is connected or if not and autologin then connnect
342+
if (accountProtocol.connectionStatus == .connected) {
343+
// Add the buddy to our roster
344+
let jid = XMPPJID(string: removeBuddyAction.buddyJid)
345+
accountProtocol.xmppRoster.removeUser(jid)
346+
completion(true, 0.0)
347+
} else if (account.autologin == true) {
348+
self.waitingForAccount(account.uniqueId, action: OutstandingActionInfo(action: removeBuddyAction, timer: nil, completion: completion))
349+
accountProtocol.connectUserInitiated(false)
350+
} else {
351+
// Retry later
352+
completion(false, self.accountRetryTimeout)
353+
}
251354
}
355+
252356

253357
//Mark: Callback for Account
254358

@@ -263,12 +367,15 @@ public class MessageQueueHandler:NSObject {
263367

264368
fileprivate func didConnectAccount(_ accountKey:String, accountCollection:String) {
265369

266-
guard let messageSet = self.popWaitingAccount(accountKey) else {
370+
guard let actionSet = self.popWaitingAccount(accountKey) else {
267371
return
268372
}
269373

270-
for messageInfo in messageSet {
271-
self.sendMessage(messageInfo)
374+
for actionInfo in actionSet {
375+
self.operationQueue.addOperation { [weak self] in
376+
guard let strongSelf = self else { return }
377+
strongSelf.handleNextItem(actionInfo.action, completion: actionInfo.completion)
378+
}
272379
}
273380
}
274381

@@ -277,7 +384,7 @@ public class MessageQueueHandler:NSObject {
277384
fileprivate func handleMessageStateDidChangeNotification(_ notification:Notification) {
278385
guard let buddy = notification.object as? OTRBuddy,
279386
let messageStateInt = (notification.userInfo?[OTRMessageStateKey] as? NSNumber)?.uintValue else {
280-
return
387+
return
281388
}
282389

283390
if messageStateInt == OTREncryptionMessageState.encrypted.rawValue {
@@ -355,37 +462,26 @@ extension MessageQueueHandler: OTRXMPPMessageStatusModuleDelegate {
355462
messageInfo.completion(true, 0.0)
356463
}
357464
}
358-
465+
359466
//MARK: YapTaskQueueHandler Protocol
360467
extension MessageQueueHandler: YapTaskQueueHandler {
361-
/** This method is called when an item is available to be exectued. Call completion once finished with the action item.
362-
363-
*/
468+
/** This method is called when an item is available to be exectued. Call completion once finished with the action item.
469+
470+
*/
364471

365472
public func handleNextItem(_ action:YapTaskQueueAction, completion:@escaping (_ success:Bool, _ retryTimeout:TimeInterval)->Void) {
366-
//Get the real message out of the database
367-
guard let messageSendingAction = action as? OTRYapMessageSendAction else {
368-
return
473+
switch action {
474+
case let sendMessageAction as OTRYapMessageSendAction:
475+
self.sendMessage(sendMessageAction, completion: completion)
476+
case let addBuddyAction as OTRYapAddBuddyAction:
477+
self.addBuddyToRoster(addBuddyAction, completion: completion)
478+
case let removeBuddyAction as OTRYapRemoveBuddyAction:
479+
self.removeBuddyFromRoster(removeBuddyAction, completion: completion)
480+
default: break
369481
}
370-
371-
let messageKey = messageSendingAction.messageKey
372-
let messageCollection = messageSendingAction.messageCollection
373-
var msg:OTROutgoingMessage? = nil
374-
self.databaseConnection.read { (transaction) in
375-
msg = self.fetchMessage(messageKey, collection: messageCollection, transaction: transaction)
376-
}
377-
378-
guard let message = msg else {
379-
// Somehow we have an action without a message. This is very strange. Do not like.
380-
// We tell the queue broker that we handle it successfully so it will be rmeoved and go on to the next action.
381-
completion(true, 0.0)
382-
return
383-
}
384-
385-
self.sendMessage(message, completion: completion)
386482
}
387483
}
388-
484+
389485
// Message sending logic
390486
extension MessageQueueHandler {
391487
typealias MessageQueueHandlerCompletion = (_ success: Bool, _ retryTimeout: TimeInterval) -> Void
@@ -460,6 +556,6 @@ extension MessageQueueHandler {
460556
messageInfo.completion(true, 0.0)
461557
}
462558
}
463-
})
559+
})
464560
}
465561
}

0 commit comments

Comments
 (0)