1- //
1+ //
22// MessageQueueHandler.swift
33// ChatSecure
44//
88
99import Foundation
1010import 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
1336private 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
2245extension 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
360467extension 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
390486extension 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