2 * Copyright 2017 Google
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #import "FIRMessagingDelayedMessageQueue.h"
19 #import "Protos/GtalkCore.pbobjc.h"
21 #import "FIRMessagingDefines.h"
22 #import "FIRMessagingRmqManager.h"
23 #import "FIRMessagingUtilities.h"
25 static const int kMaxQueuedMessageCount = 10;
27 @interface FIRMessagingDelayedMessageQueue ()
29 @property(nonatomic, readonly, weak) id<FIRMessagingRmqScanner> rmqScanner;
30 @property(nonatomic, readonly, copy) FIRMessagingSendDelayedMessagesHandler sendDelayedMessagesHandler;
32 @property(nonatomic, readwrite, assign) int persistedMessageCount;
33 // the scheduled timeout or -1 if not set
34 @property(nonatomic, readwrite, assign) int64_t scheduledTimeoutMilliseconds;
35 // The time of the last scan of the message DB,
36 // used to avoid retrieving messages more than once.
37 @property(nonatomic, readwrite, assign) int64_t lastDBScanTimestampSeconds;
39 @property(nonatomic, readwrite, strong) NSMutableArray *messages;
40 @property(nonatomic, readwrite, strong) NSTimer *sendTimer;
44 @implementation FIRMessagingDelayedMessageQueue
46 - (instancetype)init {
47 FIRMessagingInvalidateInitializer();
50 - (instancetype)initWithRmqScanner:(id<FIRMessagingRmqScanner>)rmqScanner
51 sendDelayedMessagesHandler:(FIRMessagingSendDelayedMessagesHandler)sendDelayedMessagesHandler {
52 _FIRMessagingDevAssert(sendDelayedMessagesHandler, @"Invalid nil callback for delayed messages");
55 _rmqScanner = rmqScanner;
56 _sendDelayedMessagesHandler = sendDelayedMessagesHandler;
57 _messages = [NSMutableArray arrayWithCapacity:10];
58 _scheduledTimeoutMilliseconds = -1;
63 - (BOOL)queueMessage:(GtalkDataMessageStanza *)message {
64 if (self.messages.count >= kMaxQueuedMessageCount) {
67 if (message.ttl == 0) {
68 // ttl=0 messages aren't persisted, add it to memory
69 [self.messages addObject:message];
71 self.persistedMessageCount++;
73 int64_t timeoutMillis = [self calculateTimeoutInMillisWithDelayInSeconds:message.maxDelay];
74 if (![self isTimeoutScheduled] || timeoutMillis < self.scheduledTimeoutMilliseconds) {
75 [self scheduleTimeoutInMillis:timeoutMillis];
80 - (NSArray *)removeDelayedMessages {
82 if ([self messageCount] == 0) {
86 NSMutableArray *delayedMessages = [NSMutableArray array];
87 // add the ttl=0 messages
88 if (self.messages.count) {
89 [delayedMessages addObjectsFromArray:delayedMessages];
90 [self.messages removeAllObjects];
93 // add persistent messages
94 if (self.persistedMessageCount > 0) {
95 FIRMessaging_WEAKIFY(self);
96 [self.rmqScanner scanWithRmqMessageHandler:nil
97 dataMessageHandler:^(int64_t rmqId, GtalkDataMessageStanza *stanza) {
98 FIRMessaging_STRONGIFY(self);
99 if ([stanza hasMaxDelay] &&
100 [stanza sent] >= self.lastDBScanTimestampSeconds) {
101 [delayedMessages addObject:stanza];
104 self.lastDBScanTimestampSeconds = FIRMessagingCurrentTimestampInSeconds();
105 self.persistedMessageCount = 0;
107 return delayedMessages;
110 - (void)sendMessages {
111 if (self.sendDelayedMessagesHandler) {
112 self.sendDelayedMessagesHandler([self removeDelayedMessages]);
116 #pragma mark - Private
118 - (NSInteger)messageCount {
119 return self.messages.count + self.persistedMessageCount;
122 - (BOOL)isTimeoutScheduled {
123 return self.scheduledTimeoutMilliseconds > 0;
126 - (int64_t)calculateTimeoutInMillisWithDelayInSeconds:(int)delay {
127 return FIRMessagingCurrentTimestampInMilliseconds() + delay * 1000.0;
130 - (void)scheduleTimeoutInMillis:(int64_t)time {
131 [self cancelTimeout];
132 self.scheduledTimeoutMilliseconds = time;
133 double delay = (time - FIRMessagingCurrentTimestampInMilliseconds()) / 1000.0;
134 [self performSelector:@selector(sendMessages) withObject:self afterDelay:delay];
137 - (void)cancelTimeout {
138 if ([self isTimeoutScheduled]) {
139 [NSObject cancelPreviousPerformRequestsWithTarget:self
140 selector:@selector(sendMessages)
142 self.scheduledTimeoutMilliseconds = -1;