Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/*
2
 * Copyright 2018 Google
3
 *
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
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
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.
15
 */
16
 
17
#import <TargetConditionals.h>
18
#if TARGET_OS_IOS || TARGET_OS_TV
19
 
20
#import <UIKit/UIKit.h>
21
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
22
 
23
#import "FirebaseInAppMessaging/Sources/FIRCore+InAppMessaging.h"
24
#import "FirebaseInAppMessaging/Sources/Private/Analytics/FIRIAMClearcutUploader.h"
25
#import "FirebaseInAppMessaging/Sources/Private/Util/FIRIAMTimeFetcher.h"
26
 
27
#import "FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutHttpRequestSender.h"
28
#import "FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutLogStorage.h"
29
 
30
// a macro for turning a millisecond value into seconds
31
#define MILLS_TO_SECONDS(x) (((long)x) / 1000)
32
 
33
@implementation FIRIAMClearcutStrategy
34
- (instancetype)initWithMinWaitTimeInMills:(NSInteger)minWaitTimeInMills
35
                        maxWaitTimeInMills:(NSInteger)maxWaitTimeInMills
36
                 failureBackoffTimeInMills:(NSInteger)failureBackoffTimeInMills
37
                             batchSendSize:(NSInteger)batchSendSize {
38
  if (self = [super init]) {
39
    _minimalWaitTimeInMills = minWaitTimeInMills;
40
    _maximumWaitTimeInMills = maxWaitTimeInMills;
41
    _failureBackoffTimeInMills = failureBackoffTimeInMills;
42
    _batchSendSize = batchSendSize;
43
  }
44
  return self;
45
}
46
 
47
- (NSString *)description {
48
  return [NSString stringWithFormat:@"min wait time in seconds:%ld;max wait time in seconds:%ld;"
49
                                     "failure backoff time in seconds:%ld;batch send size:%d",
50
                                    MILLS_TO_SECONDS(self.minimalWaitTimeInMills),
51
                                    MILLS_TO_SECONDS(self.maximumWaitTimeInMills),
52
                                    MILLS_TO_SECONDS(self.failureBackoffTimeInMills),
53
                                    (int)self.batchSendSize];
54
}
55
@end
56
 
57
@interface FIRIAMClearcutUploader () {
58
  dispatch_queue_t _queue;
59
  BOOL _nextSendScheduled;
60
}
61
 
62
@property(readwrite, nonatomic) FIRIAMClearcutHttpRequestSender *requestSender;
63
@property(nonatomic, assign) int64_t nextValidSendTimeInMills;
64
 
65
@property(nonatomic, readonly) id<FIRIAMTimeFetcher> timeFetcher;
66
@property(nonatomic, readonly) FIRIAMClearcutLogStorage *logStorage;
67
 
68
@property(nonatomic, readonly) FIRIAMClearcutStrategy *strategy;
69
@property(nonatomic, readonly) NSUserDefaults *userDefaults;
70
@end
71
 
72
static NSString *FIRIAM_UserDefaultsKeyForNextValidClearcutUploadTimeInMills =
73
    @"firebase-iam-next-clearcut-upload-timestamp-in-mills";
74
 
75
/**
76
 * The high level behavior in this implementation is like this
77
 *  1 New records always pushed into FIRIAMClearcutLogStorage first.
78
 *  2 Upload log records in batches.
79
 *  3 If prior upload was successful, next upload would wait for the time parsed out of the
80
 *      clearcut response body.
81
 *  4 If prior upload failed, next upload attempt would wait for failureBackoffTimeInMills defined
82
 *      in strategy
83
 *  5 When app
84
 */
85
 
86
@implementation FIRIAMClearcutUploader
87
 
88
- (instancetype)initWithRequestSender:(FIRIAMClearcutHttpRequestSender *)requestSender
89
                          timeFetcher:(id<FIRIAMTimeFetcher>)timeFetcher
90
                           logStorage:(FIRIAMClearcutLogStorage *)logStorage
91
                        usingStrategy:(FIRIAMClearcutStrategy *)strategy
92
                    usingUserDefaults:(nullable NSUserDefaults *)userDefaults {
93
  if (self = [super init]) {
94
    _nextSendScheduled = NO;
95
    _timeFetcher = timeFetcher;
96
    _requestSender = requestSender;
97
    _logStorage = logStorage;
98
    _strategy = strategy;
99
    _queue = dispatch_queue_create("com.google.firebase.inappmessaging.clearcut_upload", NULL);
100
    [[NSNotificationCenter defaultCenter] addObserver:self
101
                                             selector:@selector(scheduleNextSendFromForeground:)
102
                                                 name:UIApplicationWillEnterForegroundNotification
103
                                               object:nil];
104
#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
105
    if (@available(iOS 13.0, tvOS 13.0, *)) {
106
      [[NSNotificationCenter defaultCenter] addObserver:self
107
                                               selector:@selector(scheduleNextSendFromForeground:)
108
                                                   name:UISceneWillEnterForegroundNotification
109
                                                 object:nil];
110
    }
111
#endif  // defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
112
    _userDefaults = userDefaults ? userDefaults : [NSUserDefaults standardUserDefaults];
113
    // it would be 0 if it does not exist, which is equvilent to saying that
114
    // you can send now
115
    _nextValidSendTimeInMills = (int64_t)
116
        [_userDefaults doubleForKey:FIRIAM_UserDefaultsKeyForNextValidClearcutUploadTimeInMills];
117
 
118
    NSArray<FIRIAMClearcutLogRecord *> *availableLogs =
119
        [logStorage popStillValidRecordsForUpTo:strategy.batchSendSize];
120
    if (availableLogs.count) {
121
      [self scheduleNextSend];
122
    }
123
 
124
    FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260001",
125
                @"FIRIAMClearcutUploader created with strategy as %@", self.strategy);
126
  }
127
  return self;
128
}
129
 
130
- (void)dealloc {
131
  [[NSNotificationCenter defaultCenter] removeObserver:self];
132
}
133
 
134
- (void)scheduleNextSendFromForeground:(NSNotification *)notification {
135
  FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260010",
136
              @"App foregrounded, FIRIAMClearcutUploader will seed next send");
137
  [self scheduleNextSend];
138
}
139
 
140
- (void)addNewLogRecord:(FIRIAMClearcutLogRecord *)record {
141
  FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260002",
142
              @"New log record sent to clearcut uploader");
143
 
144
  [self.logStorage pushRecords:@[ record ]];
145
  [self scheduleNextSend];
146
}
147
 
148
- (void)attemptUploading {
149
  NSArray<FIRIAMClearcutLogRecord *> *availableLogs =
150
      [self.logStorage popStillValidRecordsForUpTo:self.strategy.batchSendSize];
151
 
152
  if (availableLogs.count > 0) {
153
    FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260011", @"Deliver %d clearcut records",
154
                (int)availableLogs.count);
155
    [self.requestSender
156
        sendClearcutHttpRequestForLogs:availableLogs
157
                        withCompletion:^(BOOL success, BOOL shouldRetryLogs,
158
                                         int64_t waitTimeInMills) {
159
                          if (success) {
160
                            FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260003",
161
                                        @"Delivering %d clearcut records was successful",
162
                                        (int)availableLogs.count);
163
                            // make sure the effective wait time is between two bounds
164
                            // defined in strategy
165
                            waitTimeInMills =
166
                                MAX(self.strategy.minimalWaitTimeInMills, waitTimeInMills);
167
 
168
                            waitTimeInMills =
169
                                MIN(waitTimeInMills, self.strategy.maximumWaitTimeInMills);
170
                          } else {
171
                            // failed to deliver
172
                            FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260004",
173
                                        @"Failed to attempt the delivery of %d clearcut "
174
                                        @"records and should-retry for them is %@",
175
                                        (int)availableLogs.count, shouldRetryLogs ? @"YES" : @"NO");
176
                            if (shouldRetryLogs) {
177
                              /**
178
                               * Note that there is a chance that the app crashes before we can
179
                               * call pushRecords: on the logStorage below which means we lost
180
                               * these log records permanently. This is a trade-off between handling
181
                               * duplicate records on server side vs taking the risk of lossing
182
                               * data. This implementation picks the latter.
183
                               */
184
                              FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260007",
185
                                          @"Push failed log records back to storage");
186
                              [self.logStorage pushRecords:availableLogs];
187
                            }
188
 
189
                            waitTimeInMills = (int64_t)self.strategy.failureBackoffTimeInMills;
190
                          }
191
 
192
                          FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260005",
193
                                      @"Wait for at least %ld seconds before next upload attempt",
194
                                      MILLS_TO_SECONDS(waitTimeInMills));
195
 
196
                          self.nextValidSendTimeInMills =
197
                              (int64_t)[self.timeFetcher currentTimestampInSeconds] * 1000 +
198
                              waitTimeInMills;
199
 
200
                          // persisted so that it can be recovered next time the app runs
201
                          [self.userDefaults
202
                              setDouble:(double)self.nextValidSendTimeInMills
203
                                 forKey:
204
                                     FIRIAM_UserDefaultsKeyForNextValidClearcutUploadTimeInMills];
205
 
206
                          @synchronized(self) {
207
                            self->_nextSendScheduled = NO;
208
                          }
209
                          [self scheduleNextSend];
210
                        }];
211
 
212
  } else {
213
    FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260007", @"No clearcut records to be uploaded");
214
    @synchronized(self) {
215
      _nextSendScheduled = NO;
216
    }
217
  }
218
}
219
 
220
- (void)scheduleNextSend {
221
  @synchronized(self) {
222
    if (_nextSendScheduled) {
223
      return;
224
    }
225
  }
226
 
227
  int64_t delayTimeInMills =
228
      self.nextValidSendTimeInMills - (int64_t)[self.timeFetcher currentTimestampInSeconds] * 1000;
229
 
230
  if (delayTimeInMills <= 0) {
231
    delayTimeInMills = 0;  // no need to delay since we can send now
232
  }
233
 
234
  FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM260006",
235
              @"Next upload attempt scheduled in %d seconds", (int)delayTimeInMills / 1000);
236
 
237
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayTimeInMills * (int64_t)NSEC_PER_MSEC),
238
                 _queue, ^{
239
                   [self attemptUploading];
240
                 });
241
  @synchronized(self) {
242
    _nextSendScheduled = YES;
243
  }
244
}
245
 
246
@end
247
 
248
#endif  // TARGET_OS_IOS || TARGET_OS_TV