added iOS source code
[wl-app.git] / iOS / Pods / FirebaseMessaging / Firebase / Messaging / FIRMessagingPendingTopicsList.m
diff --git a/iOS/Pods/FirebaseMessaging/Firebase/Messaging/FIRMessagingPendingTopicsList.m b/iOS/Pods/FirebaseMessaging/Firebase/Messaging/FIRMessagingPendingTopicsList.m
new file mode 100644 (file)
index 0000000..b10b552
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRMessagingPendingTopicsList.h"
+
+#import "FIRMessaging_Private.h"
+#import "FIRMessagingLogger.h"
+#import "FIRMessagingPubSub.h"
+
+#import "FIRMessagingDefines.h"
+
+NSString *const kPendingTopicBatchActionKey = @"action";
+NSString *const kPendingTopicBatchTopicsKey = @"topics";
+
+NSString *const kPendingBatchesEncodingKey = @"batches";
+NSString *const kPendingTopicsTimestampEncodingKey = @"ts";
+
+#pragma mark - FIRMessagingTopicBatch
+
+@interface FIRMessagingTopicBatch ()
+
+@property(nonatomic, strong, nonnull) NSMutableDictionary
+    <NSString *, NSMutableArray <FIRMessagingTopicOperationCompletion> *> *topicHandlers;
+
+@end
+
+@implementation FIRMessagingTopicBatch
+
+- (instancetype)initWithAction:(FIRMessagingTopicAction)action {
+  if (self = [super init]) {
+    _action = action;
+    _topics = [NSMutableSet set];
+    _topicHandlers = [NSMutableDictionary dictionary];
+  }
+  return self;
+}
+
+#pragma mark NSCoding
+
+- (void)encodeWithCoder:(NSCoder *)aCoder {
+  [aCoder encodeInteger:self.action forKey:kPendingTopicBatchActionKey];
+  [aCoder encodeObject:self.topics forKey:kPendingTopicBatchTopicsKey];
+}
+
+- (instancetype)initWithCoder:(NSCoder *)aDecoder {
+
+  // Ensure that our integer -> enum casting is safe
+  NSInteger actionRawValue = [aDecoder decodeIntegerForKey:kPendingTopicBatchActionKey];
+  FIRMessagingTopicAction action = FIRMessagingTopicActionSubscribe;
+  if (actionRawValue == FIRMessagingTopicActionUnsubscribe) {
+    action = FIRMessagingTopicActionUnsubscribe;
+  }
+
+  if (self = [self initWithAction:action]) {
+    NSSet *topics = [aDecoder decodeObjectForKey:kPendingTopicBatchTopicsKey];
+    if ([topics isKindOfClass:[NSSet class]]) {
+      _topics = [topics mutableCopy];
+    }
+    _topicHandlers = [NSMutableDictionary dictionary];
+  }
+  return self;
+}
+
+@end
+
+#pragma mark - FIRMessagingPendingTopicsList
+
+@interface FIRMessagingPendingTopicsList ()
+
+@property(nonatomic, readwrite, strong) NSDate *archiveDate;
+@property(nonatomic, strong) NSMutableArray <FIRMessagingTopicBatch *> *topicBatches;
+
+@property(nonatomic, strong) FIRMessagingTopicBatch *currentBatch;
+@property(nonatomic, strong) NSMutableSet <NSString *> *topicsInFlight;
+
+@end
+
+@implementation FIRMessagingPendingTopicsList
+
+- (instancetype)init {
+  if (self = [super init]) {
+    _topicBatches = [NSMutableArray array];
+    _topicsInFlight = [NSMutableSet set];
+  }
+  return self;
+}
+
++ (void)pruneTopicBatches:(NSMutableArray <FIRMessagingTopicBatch *> *)topicBatches {
+  // For now, just remove empty batches. In the future we can use this to make the subscriptions
+  // more efficient, by actually pruning topic actions that cancel each other out, for example.
+  for (NSInteger i = topicBatches.count-1; i >= 0; i--) {
+    FIRMessagingTopicBatch *batch = topicBatches[i];
+    if (batch.topics.count == 0) {
+      [topicBatches removeObjectAtIndex:i];
+    }
+  }
+}
+
+#pragma mark NSCoding
+
+- (void)encodeWithCoder:(NSCoder *)aCoder {
+  [aCoder encodeObject:[NSDate date] forKey:kPendingTopicsTimestampEncodingKey];
+  [aCoder encodeObject:self.topicBatches forKey:kPendingBatchesEncodingKey];
+}
+
+- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
+
+  if (self = [self init]) {
+    _archiveDate = [aDecoder decodeObjectForKey:kPendingTopicsTimestampEncodingKey];
+    NSArray *archivedBatches = [aDecoder decodeObjectForKey:kPendingBatchesEncodingKey];
+    if (archivedBatches) {
+      _topicBatches = [archivedBatches mutableCopy];
+      [FIRMessagingPendingTopicsList pruneTopicBatches:_topicBatches];
+    }
+    _topicsInFlight = [NSMutableSet set];
+  }
+  return self;
+}
+
+#pragma mark Getters
+
+- (NSUInteger)numberOfBatches {
+  return self.topicBatches.count;
+}
+
+#pragma mark Adding/Removing topics
+
+- (void)addOperationForTopic:(NSString *)topic
+                  withAction:(FIRMessagingTopicAction)action
+                  completion:(nullable FIRMessagingTopicOperationCompletion)completion {
+
+  FIRMessagingTopicBatch *lastBatch = nil;
+  @synchronized (self) {
+    lastBatch = self.topicBatches.lastObject;
+    if (!lastBatch || lastBatch.action != action) {
+      // There either was no last batch, or our last batch's action was not the same, so we have to
+      // create a new batch
+      lastBatch = [[FIRMessagingTopicBatch alloc] initWithAction:action];
+      [self.topicBatches addObject:lastBatch];
+    }
+    BOOL topicExistedBefore = ([lastBatch.topics member:topic] != nil);
+    if (!topicExistedBefore) {
+      [lastBatch.topics addObject:topic];
+      [self.delegate pendingTopicsListDidUpdate:self];
+    }
+    // Add the completion handler to the batch
+    if (completion) {
+      NSMutableArray *handlers = lastBatch.topicHandlers[topic];
+      if (!handlers) {
+        handlers = [[NSMutableArray alloc] init];
+      }
+      [handlers addObject:completion];
+      lastBatch.topicHandlers[topic] = handlers;
+    }
+    if (!self.currentBatch) {
+      self.currentBatch = lastBatch;
+    }
+    // This may have been the first topic added, or was added to an ongoing batch
+    if (self.currentBatch == lastBatch && !topicExistedBefore) {
+      // Add this topic to our ongoing operations
+      FIRMessaging_WEAKIFY(self);
+      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
+        FIRMessaging_STRONGIFY(self);
+        [self resumeOperationsIfNeeded];
+      });
+    }
+  }
+}
+
+- (void)resumeOperationsIfNeeded {
+  @synchronized (self) {
+    // If current batch is not set, set it now
+    if (!self.currentBatch) {
+      self.currentBatch = self.topicBatches.firstObject;
+    }
+    if (self.currentBatch.topics.count == 0) {
+      return;
+    }
+    if (!self.delegate) {
+      FIRMessagingLoggerError(kFIRMessagingMessageCodePendingTopicsList000,
+                              @"Attempted to update pending topics without a delegate");
+      return;
+    }
+    if (![self.delegate pendingTopicsListCanRequestTopicUpdates:self]) {
+      return;
+    }
+    for (NSString *topic in self.currentBatch.topics) {
+      if ([self.topicsInFlight member:topic]) {
+        // This topic is already active, so skip
+        continue;
+      }
+      [self beginUpdateForCurrentBatchTopic:topic];
+    }
+  }
+}
+
+- (BOOL)subscriptionErrorIsRecoverable:(NSError *)error {
+  return [error.domain isEqualToString:NSURLErrorDomain];
+}
+
+- (void)beginUpdateForCurrentBatchTopic:(NSString *)topic {
+
+  @synchronized (self) {
+    [self.topicsInFlight addObject:topic];
+  }
+  FIRMessaging_WEAKIFY(self);
+  [self.delegate
+            pendingTopicsList:self
+      requestedUpdateForTopic:topic
+                       action:self.currentBatch.action
+                   completion:^(NSError *error) {
+                     dispatch_async(
+                         dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
+                           FIRMessaging_STRONGIFY(self);
+                           @synchronized(self) {
+                             [self.topicsInFlight removeObject:topic];
+
+                             BOOL recoverableError = [self subscriptionErrorIsRecoverable:error];
+                             if (!error || !recoverableError) {
+                               // Notify our handlers and remove the topic from our batch
+                               NSMutableArray *handlers = self.currentBatch.topicHandlers[topic];
+                               if (handlers.count) {
+                                 dispatch_async(dispatch_get_main_queue(), ^{
+                                   for (FIRMessagingTopicOperationCompletion handler in handlers) {
+                                     handler(error);
+                                   }
+                                   [handlers removeAllObjects];
+                                 });
+                               }
+                               [self.currentBatch.topics removeObject:topic];
+                               [self.currentBatch.topicHandlers removeObjectForKey:topic];
+                               if (self.currentBatch.topics.count == 0) {
+                                 // All topic updates successfully finished in this batch, move on
+                                 // to the next batch
+                                 [self.topicBatches removeObject:self.currentBatch];
+                                 self.currentBatch = nil;
+                               }
+                               [self.delegate pendingTopicsListDidUpdate:self];
+                               FIRMessaging_WEAKIFY(self);
+                               dispatch_async(
+                                   dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
+                                   ^{
+                                     FIRMessaging_STRONGIFY(self);
+                                     [self resumeOperationsIfNeeded];
+                                   });
+                             }
+                           }
+                         });
+                   }];
+}
+
+@end