Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
/*
2
 * Copyright 2017 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 "FirebaseMessaging/Sources/FIRMessagingRmqManager.h"
18
 
19
#import <sqlite3.h>
20
 
21
#import "FirebaseMessaging/Sources/FIRMessagingConstants.h"
22
#import "FirebaseMessaging/Sources/FIRMessagingDefines.h"
23
#import "FirebaseMessaging/Sources/FIRMessagingLogger.h"
24
#import "FirebaseMessaging/Sources/FIRMessagingPersistentSyncMessage.h"
25
#import "FirebaseMessaging/Sources/FIRMessagingUtilities.h"
26
#import "FirebaseMessaging/Sources/NSError+FIRMessaging.h"
27
 
28
#ifndef _FIRMessagingRmqLogAndExit
29
#define _FIRMessagingRmqLogAndExit(stmt, return_value) \
30
  do {                                                 \
31
    [self logErrorAndFinalizeStatement:stmt];          \
32
    return return_value;                               \
33
  } while (0)
34
#endif
35
 
36
#ifndef FIRMessagingRmqLogAndReturn
37
#define FIRMessagingRmqLogAndReturn(stmt)     \
38
  do {                                        \
39
    [self logErrorAndFinalizeStatement:stmt]; \
40
    return;                                   \
41
  } while (0)
42
#endif
43
 
44
#ifndef FIRMessaging_MUST_NOT_BE_MAIN_THREAD
45
#define FIRMessaging_MUST_NOT_BE_MAIN_THREAD()                                        \
46
  do {                                                                                \
47
    NSAssert(![NSThread isMainThread], @"Must not be executing on the main thread."); \
48
  } while (0);
49
#endif
50
 
51
// table names
52
NSString *const kTableOutgoingRmqMessages = @"outgoingRmqMessages";
53
NSString *const kTableLastRmqId = @"lastrmqid";
54
NSString *const kOldTableS2DRmqIds = @"s2dRmqIds";
55
NSString *const kTableS2DRmqIds = @"s2dRmqIds_1";
56
 
57
// Used to prevent de-duping of sync messages received both via APNS and MCS.
58
NSString *const kTableSyncMessages = @"incomingSyncMessages";
59
 
60
static NSString *const kTablePrefix = @"";
61
 
62
// create tables
63
static NSString *const kCreateTableOutgoingRmqMessages = @"create TABLE IF NOT EXISTS %@%@ "
64
                                                         @"(_id INTEGER PRIMARY KEY, "
65
                                                         @"rmq_id INTEGER, "
66
                                                         @"type INTEGER, "
67
                                                         @"ts INTEGER, "
68
                                                         @"data BLOB)";
69
 
70
static NSString *const kCreateTableLastRmqId = @"create TABLE IF NOT EXISTS %@%@ "
71
                                               @"(_id INTEGER PRIMARY KEY, "
72
                                               @"rmq_id INTEGER)";
73
 
74
static NSString *const kCreateTableS2DRmqIds = @"create TABLE IF NOT EXISTS %@%@ "
75
                                               @"(_id INTEGER PRIMARY KEY, "
76
                                               @"rmq_id TEXT)";
77
 
78
static NSString *const kCreateTableSyncMessages = @"create TABLE IF NOT EXISTS %@%@ "
79
                                                  @"(_id INTEGER PRIMARY KEY, "
80
                                                  @"rmq_id TEXT, "
81
                                                  @"expiration_ts INTEGER, "
82
                                                  @"apns_recv INTEGER, "
83
                                                  @"mcs_recv INTEGER)";
84
 
85
static NSString *const kDropTableCommand = @"drop TABLE if exists %@%@";
86
 
87
// table infos
88
static NSString *const kRmqIdColumn = @"rmq_id";
89
static NSString *const kDataColumn = @"data";
90
static NSString *const kProtobufTagColumn = @"type";
91
static NSString *const kIdColumn = @"_id";
92
 
93
static NSString *const kOutgoingRmqMessagesColumns = @"rmq_id, type, data";
94
 
95
// Sync message columns
96
static NSString *const kSyncMessagesColumns = @"rmq_id, expiration_ts, apns_recv, mcs_recv";
97
// Message time expiration in seconds since 1970
98
static NSString *const kSyncMessageExpirationTimestampColumn = @"expiration_ts";
99
static NSString *const kSyncMessageAPNSReceivedColumn = @"apns_recv";
100
static NSString *const kSyncMessageMCSReceivedColumn = @"mcs_recv";
101
 
102
// Utility to create an NSString from a sqlite3 result code
103
NSString *_Nonnull FIRMessagingStringFromSQLiteResult(int result) {
104
#pragma clang diagnostic push
105
#pragma clang diagnostic ignored "-Wunguarded-availability"
106
  const char *errorStr = sqlite3_errstr(result);
107
#pragma clang diagnostic pop
108
  NSString *errorString = [NSString stringWithFormat:@"%d - %s", result, errorStr];
109
  return errorString;
110
}
111
 
112
@interface FIRMessagingRmqManager () {
113
  sqlite3 *_database;
114
  /// Serial queue for database read/write operations.
115
  dispatch_queue_t _databaseOperationQueue;
116
}
117
 
118
@property(nonatomic, readwrite, strong) NSString *databaseName;
119
// map the category of an outgoing message with the number of messages for that category
120
// should always have two keys -- the app, gcm
121
@property(nonatomic, readwrite, strong) NSMutableDictionary *outstandingMessages;
122
 
123
// Outgoing RMQ persistent id
124
@property(nonatomic, readwrite, assign) int64_t rmqId;
125
@end
126
 
127
@implementation FIRMessagingRmqManager
128
 
129
- (instancetype)initWithDatabaseName:(NSString *)databaseName {
130
  self = [super init];
131
  if (self) {
132
    _databaseOperationQueue =
133
        dispatch_queue_create("com.google.firebase.messaging.database.rmq", DISPATCH_QUEUE_SERIAL);
134
    _databaseName = [databaseName copy];
135
    [self openDatabase];
136
    _outstandingMessages = [NSMutableDictionary dictionaryWithCapacity:2];
137
    _rmqId = -1;
138
  }
139
  return self;
140
}
141
 
142
- (void)dealloc {
143
  sqlite3_close(_database);
144
}
145
 
146
#pragma mark - RMQ ID
147
 
148
- (void)loadRmqId {
149
  if (self.rmqId >= 0) {
150
    return;  // already done
151
  }
152
 
153
  [self loadInitialOutgoingPersistentId];
154
  if (self.outstandingMessages.count) {
155
    FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRmqManager000, @"Outstanding categories %ld",
156
                            _FIRMessaging_UL(self.outstandingMessages.count));
157
  }
158
}
159
 
160
/**
161
 * Initialize the 'initial RMQ':
162
 * - max ID of any message in the queue
163
 * - if the queue is empty, stored value in separate DB.
164
 *
165
 * Stream acks will remove from RMQ, when we remove the highest message we keep track
166
 * of its ID.
167
 */
168
- (void)loadInitialOutgoingPersistentId {
169
  // we shouldn't always trust the lastRmqId stored in the LastRmqId table, because
170
  // we only save to the LastRmqId table once in a while (after getting the lastRmqId sent
171
  // by the server after reconnect, and after getting a rmq ack from the server). The
172
  // rmq message with the highest rmq id tells the real story, so check against that first.
173
 
174
  __block int64_t rmqId;
175
  dispatch_sync(_databaseOperationQueue, ^{
176
    rmqId = [self queryHighestRmqId];
177
  });
178
  if (rmqId == 0) {
179
    dispatch_sync(_databaseOperationQueue, ^{
180
      rmqId = [self queryLastRmqId];
181
    });
182
  }
183
  self.rmqId = rmqId + 1;
184
}
185
 
186
/**
187
 * This is called when we delete the largest outgoing message from queue.
188
 */
189
- (void)saveLastOutgoingRmqId:(int64_t)rmqID {
190
  dispatch_async(_databaseOperationQueue, ^{
191
    NSString *queryFormat = @"INSERT OR REPLACE INTO %@ (%@, %@) VALUES (?, ?)";
192
    NSString *query = [NSString stringWithFormat:queryFormat,
193
                                                 kTableLastRmqId,           // table
194
                                                 kIdColumn, kRmqIdColumn];  // columns
195
    sqlite3_stmt *statement;
196
    if (sqlite3_prepare_v2(self->_database, [query UTF8String], -1, &statement, NULL) !=
197
        SQLITE_OK) {
198
      FIRMessagingRmqLogAndReturn(statement);
199
    }
200
    if (sqlite3_bind_int(statement, 1, 1) != SQLITE_OK) {
201
      FIRMessagingRmqLogAndReturn(statement);
202
    }
203
    if (sqlite3_bind_int64(statement, 2, rmqID) != SQLITE_OK) {
204
      FIRMessagingRmqLogAndReturn(statement);
205
    }
206
    if (sqlite3_step(statement) != SQLITE_DONE) {
207
      FIRMessagingRmqLogAndReturn(statement);
208
    }
209
    sqlite3_finalize(statement);
210
  });
211
}
212
 
213
- (void)saveS2dMessageWithRmqId:(NSString *)rmqId {
214
  dispatch_async(_databaseOperationQueue, ^{
215
    NSString *insertFormat = @"INSERT INTO %@ (%@) VALUES (?)";
216
    NSString *insertSQL = [NSString stringWithFormat:insertFormat, kTableS2DRmqIds, kRmqIdColumn];
217
    sqlite3_stmt *insert_statement;
218
    if (sqlite3_prepare_v2(self->_database, [insertSQL UTF8String], -1, &insert_statement, NULL) !=
219
        SQLITE_OK) {
220
      FIRMessagingRmqLogAndReturn(insert_statement);
221
    }
222
    if (sqlite3_bind_text(insert_statement, 1, [rmqId UTF8String], (int)[rmqId length],
223
                          SQLITE_STATIC) != SQLITE_OK) {
224
      FIRMessagingRmqLogAndReturn(insert_statement);
225
    }
226
    if (sqlite3_step(insert_statement) != SQLITE_DONE) {
227
      FIRMessagingRmqLogAndReturn(insert_statement);
228
    }
229
    sqlite3_finalize(insert_statement);
230
  });
231
}
232
 
233
#pragma mark - Query
234
 
235
- (int64_t)queryHighestRmqId {
236
  NSString *queryFormat = @"SELECT %@ FROM %@ ORDER BY %@ DESC LIMIT %d";
237
  NSString *query = [NSString stringWithFormat:queryFormat,
238
                                               kRmqIdColumn,               // column
239
                                               kTableOutgoingRmqMessages,  // table
240
                                               kRmqIdColumn,               // order by column
241
                                               1];                         // limit
242
 
243
  sqlite3_stmt *statement;
244
  int64_t highestRmqId = 0;
245
  if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &statement, NULL) != SQLITE_OK) {
246
    _FIRMessagingRmqLogAndExit(statement, highestRmqId);
247
  }
248
  if (sqlite3_step(statement) == SQLITE_ROW) {
249
    highestRmqId = sqlite3_column_int64(statement, 0);
250
  }
251
  sqlite3_finalize(statement);
252
  return highestRmqId;
253
}
254
 
255
- (int64_t)queryLastRmqId {
256
  NSString *queryFormat = @"SELECT %@ FROM %@ ORDER BY %@ DESC LIMIT %d";
257
  NSString *query = [NSString stringWithFormat:queryFormat,
258
                                               kRmqIdColumn,     // column
259
                                               kTableLastRmqId,  // table
260
                                               kRmqIdColumn,     // order by column
261
                                               1];               // limit
262
 
263
  sqlite3_stmt *statement;
264
  int64_t lastRmqId = 0;
265
  if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &statement, NULL) != SQLITE_OK) {
266
    _FIRMessagingRmqLogAndExit(statement, lastRmqId);
267
  }
268
  if (sqlite3_step(statement) == SQLITE_ROW) {
269
    lastRmqId = sqlite3_column_int64(statement, 0);
270
  }
271
  sqlite3_finalize(statement);
272
  return lastRmqId;
273
}
274
 
275
#pragma mark - Sync Messages
276
 
277
- (FIRMessagingPersistentSyncMessage *)querySyncMessageWithRmqID:(NSString *)rmqID {
278
  __block FIRMessagingPersistentSyncMessage *persistentMessage;
279
  dispatch_sync(_databaseOperationQueue, ^{
280
    NSString *queryFormat = @"SELECT %@ FROM %@ WHERE %@ = '%@'";
281
    NSString *query =
282
        [NSString stringWithFormat:queryFormat,
283
                                   kSyncMessagesColumns,  // SELECT (rmq_id, expiration_ts,
284
                                                          // apns_recv, mcs_recv)
285
                                   kTableSyncMessages,    // FROM sync_rmq
286
                                   kRmqIdColumn,          // WHERE rmq_id
287
                                   rmqID];
288
 
289
    sqlite3_stmt *stmt;
290
    if (sqlite3_prepare_v2(self->_database, [query UTF8String], -1, &stmt, NULL) != SQLITE_OK) {
291
      [self logError];
292
      sqlite3_finalize(stmt);
293
      return;
294
    }
295
 
296
    const int rmqIDColumn = 0;
297
    const int expirationTimestampColumn = 1;
298
    const int apnsReceivedColumn = 2;
299
    const int mcsReceivedColumn = 3;
300
 
301
    int count = 0;
302
 
303
    while (sqlite3_step(stmt) == SQLITE_ROW) {
304
      NSString *rmqID =
305
          [NSString stringWithUTF8String:(char *)sqlite3_column_text(stmt, rmqIDColumn)];
306
      int64_t expirationTimestamp = sqlite3_column_int64(stmt, expirationTimestampColumn);
307
      BOOL apnsReceived = sqlite3_column_int(stmt, apnsReceivedColumn);
308
      BOOL mcsReceived = sqlite3_column_int(stmt, mcsReceivedColumn);
309
 
310
      // create a new persistent message
311
      persistentMessage =
312
          [[FIRMessagingPersistentSyncMessage alloc] initWithRMQID:rmqID
313
                                                    expirationTime:expirationTimestamp];
314
      persistentMessage.apnsReceived = apnsReceived;
315
      persistentMessage.mcsReceived = mcsReceived;
316
 
317
      count++;
318
    }
319
    sqlite3_finalize(stmt);
320
  });
321
 
322
  return persistentMessage;
323
}
324
 
325
- (void)deleteExpiredOrFinishedSyncMessages {
326
  dispatch_async(_databaseOperationQueue, ^{
327
    int64_t now = FIRMessagingCurrentTimestampInSeconds();
328
    NSString *deleteSQL = @"DELETE FROM %@ "
329
                          @"WHERE %@ < %lld OR "   // expirationTime < now
330
                          @"(%@ = 1 AND %@ = 1)";  // apns_received = 1 AND mcs_received = 1
331
    NSString *query = [NSString
332
        stringWithFormat:deleteSQL, kTableSyncMessages, kSyncMessageExpirationTimestampColumn, now,
333
                         kSyncMessageAPNSReceivedColumn, kSyncMessageMCSReceivedColumn];
334
    sqlite3_stmt *stmt;
335
    if (sqlite3_prepare_v2(self->_database, [query UTF8String], -1, &stmt, NULL) != SQLITE_OK) {
336
      FIRMessagingRmqLogAndReturn(stmt);
337
    }
338
 
339
    if (sqlite3_step(stmt) != SQLITE_DONE) {
340
      FIRMessagingRmqLogAndReturn(stmt);
341
    }
342
 
343
    sqlite3_finalize(stmt);
344
    int deleteCount = sqlite3_changes(self->_database);
345
    if (deleteCount > 0) {
346
      FIRMessagingLoggerDebug(kFIRMessagingMessageCodeSyncMessageManager001,
347
                              @"Successfully deleted %d sync messages from store", deleteCount);
348
    }
349
  });
350
}
351
 
352
- (void)saveSyncMessageWithRmqID:(NSString *)rmqID expirationTime:(int64_t)expirationTime {
353
  BOOL apnsReceived = YES;
354
  BOOL mcsReceived = NO;
355
  dispatch_async(_databaseOperationQueue, ^{
356
    NSString *insertFormat = @"INSERT INTO %@ (%@, %@, %@, %@) VALUES (?, ?, ?, ?)";
357
    NSString *insertSQL =
358
        [NSString stringWithFormat:insertFormat,
359
                                   kTableSyncMessages,                     // Table name
360
                                   kRmqIdColumn,                           // rmq_id
361
                                   kSyncMessageExpirationTimestampColumn,  // expiration_ts
362
                                   kSyncMessageAPNSReceivedColumn,         // apns_recv
363
                                   kSyncMessageMCSReceivedColumn /* mcs_recv */];
364
 
365
    sqlite3_stmt *stmt;
366
 
367
    if (sqlite3_prepare_v2(self->_database, [insertSQL UTF8String], -1, &stmt, NULL) != SQLITE_OK) {
368
      FIRMessagingRmqLogAndReturn(stmt);
369
    }
370
 
371
    if (sqlite3_bind_text(stmt, 1, [rmqID UTF8String], (int)[rmqID length], NULL) != SQLITE_OK) {
372
      FIRMessagingRmqLogAndReturn(stmt);
373
    }
374
 
375
    if (sqlite3_bind_int64(stmt, 2, expirationTime) != SQLITE_OK) {
376
      FIRMessagingRmqLogAndReturn(stmt);
377
    }
378
 
379
    if (sqlite3_bind_int(stmt, 3, apnsReceived ? 1 : 0) != SQLITE_OK) {
380
      FIRMessagingRmqLogAndReturn(stmt);
381
    }
382
 
383
    if (sqlite3_bind_int(stmt, 4, mcsReceived ? 1 : 0) != SQLITE_OK) {
384
      FIRMessagingRmqLogAndReturn(stmt);
385
    }
386
 
387
    if (sqlite3_step(stmt) != SQLITE_DONE) {
388
      FIRMessagingRmqLogAndReturn(stmt);
389
    }
390
    sqlite3_finalize(stmt);
391
    FIRMessagingLoggerInfo(kFIRMessagingMessageCodeSyncMessageManager004,
392
                           @"Added sync message to cache: %@", rmqID);
393
  });
394
}
395
 
396
- (void)updateSyncMessageViaAPNSWithRmqID:(NSString *)rmqID {
397
  dispatch_async(_databaseOperationQueue, ^{
398
    if (![self updateSyncMessageWithRmqID:rmqID column:kSyncMessageAPNSReceivedColumn value:YES]) {
399
      FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager005,
400
                              @"Failed to update APNS state for sync message %@", rmqID);
401
    }
402
  });
403
}
404
 
405
- (BOOL)updateSyncMessageWithRmqID:(NSString *)rmqID column:(NSString *)column value:(BOOL)value {
406
  FIRMessaging_MUST_NOT_BE_MAIN_THREAD();
407
  NSString *queryFormat = @"UPDATE %@ "     // Table name
408
                          @"SET %@ = %d "   // column=value
409
                          @"WHERE %@ = ?";  // condition
410
  NSString *query = [NSString
411
      stringWithFormat:queryFormat, kTableSyncMessages, column, value ? 1 : 0, kRmqIdColumn];
412
  sqlite3_stmt *stmt;
413
 
414
  if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &stmt, NULL) != SQLITE_OK) {
415
    _FIRMessagingRmqLogAndExit(stmt, NO);
416
  }
417
 
418
  if (sqlite3_bind_text(stmt, 1, [rmqID UTF8String], (int)[rmqID length], NULL) != SQLITE_OK) {
419
    _FIRMessagingRmqLogAndExit(stmt, NO);
420
  }
421
 
422
  if (sqlite3_step(stmt) != SQLITE_DONE) {
423
    _FIRMessagingRmqLogAndExit(stmt, NO);
424
  }
425
 
426
  sqlite3_finalize(stmt);
427
  return YES;
428
}
429
 
430
#pragma mark - Database
431
 
432
- (NSString *)pathForDatabase {
433
  return [[self class] pathForDatabaseWithName:_databaseName];
434
}
435
 
436
+ (NSString *)pathForDatabaseWithName:(NSString *)databaseName {
437
  NSString *dbNameWithExtension = [NSString stringWithFormat:@"%@.sqlite", databaseName];
438
  NSArray *paths =
439
      NSSearchPathForDirectoriesInDomains(FIRMessagingSupportedDirectory(), NSUserDomainMask, YES);
440
  NSArray *components = @[ paths.lastObject, kFIRMessagingSubDirectoryName, dbNameWithExtension ];
441
  return [NSString pathWithComponents:components];
442
}
443
 
444
- (void)createTableWithName:(NSString *)tableName command:(NSString *)command {
445
  FIRMessaging_MUST_NOT_BE_MAIN_THREAD();
446
  char *error = NULL;
447
  NSString *createDatabase = [NSString stringWithFormat:command, kTablePrefix, tableName];
448
  if (sqlite3_exec(self->_database, [createDatabase UTF8String], NULL, NULL, &error) != SQLITE_OK) {
449
    // remove db before failing
450
    [self removeDatabase];
451
    NSString *sqlError;
452
    if (error != NULL) {
453
      sqlError = [NSString stringWithCString:error encoding:NSUTF8StringEncoding];
454
      sqlite3_free(error);
455
    } else {
456
      sqlError = @"(null)";
457
    }
458
    NSString *errorMessage =
459
        [NSString stringWithFormat:@"Couldn't create table: %@ with command: %@ error: %@",
460
                                   kCreateTableOutgoingRmqMessages, createDatabase, sqlError];
461
    FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStoreErrorCreatingTable, @"%@",
462
                            errorMessage);
463
    NSAssert(NO, errorMessage);
464
  }
465
}
466
 
467
- (void)dropTableWithName:(NSString *)tableName {
468
  FIRMessaging_MUST_NOT_BE_MAIN_THREAD();
469
  char *error;
470
  NSString *dropTableSQL = [NSString stringWithFormat:kDropTableCommand, kTablePrefix, tableName];
471
  if (sqlite3_exec(self->_database, [dropTableSQL UTF8String], NULL, NULL, &error) != SQLITE_OK) {
472
    FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStore002,
473
                            @"Failed to remove table %@", tableName);
474
  }
475
}
476
 
477
- (void)removeDatabase {
478
  // Ensure database is removed in a sync queue as this sometimes makes test have race conditions.
479
  dispatch_async(_databaseOperationQueue, ^{
480
    NSString *path = [self pathForDatabase];
481
    [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
482
  });
483
}
484
 
485
- (void)openDatabase {
486
  dispatch_async(_databaseOperationQueue, ^{
487
    NSFileManager *fileManager = [NSFileManager defaultManager];
488
    NSString *path = [self pathForDatabase];
489
 
490
    BOOL didOpenDatabase = YES;
491
    if (![fileManager fileExistsAtPath:path]) {
492
      // We've to separate between different versions here because of backwards compatbility issues.
493
      int result = sqlite3_open_v2(
494
          [path UTF8String], &self -> _database,
495
          SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FILEPROTECTION_NONE, NULL);
496
      if (result != SQLITE_OK) {
497
        NSString *errorString = FIRMessagingStringFromSQLiteResult(result);
498
        NSString *errorMessage = [NSString
499
            stringWithFormat:@"Could not open existing RMQ database at path %@, error: %@", path,
500
                             errorString];
501
        FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStoreErrorOpeningDatabase,
502
                                @"%@", errorMessage);
503
        NSAssert(NO, errorMessage);
504
        return;
505
      }
506
      [self createTableWithName:kTableOutgoingRmqMessages command:kCreateTableOutgoingRmqMessages];
507
 
508
      [self createTableWithName:kTableLastRmqId command:kCreateTableLastRmqId];
509
      [self createTableWithName:kTableS2DRmqIds command:kCreateTableS2DRmqIds];
510
    } else {
511
      // Calling sqlite3_open should create the database, since the file doesn't exist.
512
      int result = sqlite3_open_v2(
513
          [path UTF8String], &self -> _database,
514
          SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FILEPROTECTION_NONE, NULL);
515
      if (result != SQLITE_OK) {
516
        NSString *errorString = FIRMessagingStringFromSQLiteResult(result);
517
        NSString *errorMessage =
518
            [NSString stringWithFormat:@"Could not create RMQ database at path %@, error: %@", path,
519
                                       errorString];
520
        FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStoreErrorCreatingDatabase,
521
                                @"%@", errorMessage);
522
        NSAssert(NO, errorMessage);
523
        didOpenDatabase = NO;
524
      } else {
525
        [self updateDBWithStringRmqID];
526
      }
527
    }
528
 
529
    if (didOpenDatabase) {
530
      [self createTableWithName:kTableSyncMessages command:kCreateTableSyncMessages];
531
    }
532
  });
533
}
534
 
535
- (void)updateDBWithStringRmqID {
536
  dispatch_async(_databaseOperationQueue, ^{
537
    [self createTableWithName:kTableS2DRmqIds command:kCreateTableS2DRmqIds];
538
    [self dropTableWithName:kOldTableS2DRmqIds];
539
  });
540
}
541
 
542
#pragma mark - Private
543
 
544
- (BOOL)saveMessageWithRmqId:(int64_t)rmqId tag:(int8_t)tag data:(NSData *)data {
545
  FIRMessaging_MUST_NOT_BE_MAIN_THREAD();
546
  NSString *insertFormat = @"INSERT INTO %@ (%@, %@, %@) VALUES (?, ?, ?)";
547
  NSString *insertSQL =
548
      [NSString stringWithFormat:insertFormat,
549
                                 kTableOutgoingRmqMessages,  // table
550
                                 kRmqIdColumn, kProtobufTagColumn, kDataColumn /* columns */];
551
  sqlite3_stmt *insert_statement;
552
  if (sqlite3_prepare_v2(self->_database, [insertSQL UTF8String], -1, &insert_statement, NULL) !=
553
      SQLITE_OK) {
554
    _FIRMessagingRmqLogAndExit(insert_statement, NO);
555
  }
556
  if (sqlite3_bind_int64(insert_statement, 1, rmqId) != SQLITE_OK) {
557
    _FIRMessagingRmqLogAndExit(insert_statement, NO);
558
  }
559
  if (sqlite3_bind_int(insert_statement, 2, tag) != SQLITE_OK) {
560
    _FIRMessagingRmqLogAndExit(insert_statement, NO);
561
  }
562
  if (sqlite3_bind_blob(insert_statement, 3, [data bytes], (int)[data length], NULL) != SQLITE_OK) {
563
    _FIRMessagingRmqLogAndExit(insert_statement, NO);
564
  }
565
  if (sqlite3_step(insert_statement) != SQLITE_DONE) {
566
    _FIRMessagingRmqLogAndExit(insert_statement, NO);
567
  }
568
 
569
  sqlite3_finalize(insert_statement);
570
 
571
  return YES;
572
}
573
 
574
- (void)deleteMessagesFromTable:(NSString *)tableName withRmqIds:(NSArray *)rmqIds {
575
  dispatch_async(_databaseOperationQueue, ^{
576
    BOOL isRmqIDString = NO;
577
    // RmqID is a string only for outgoing messages
578
    if ([tableName isEqualToString:kTableS2DRmqIds] ||
579
        [tableName isEqualToString:kTableSyncMessages]) {
580
      isRmqIDString = YES;
581
    }
582
 
583
    NSMutableString *delete =
584
        [NSMutableString stringWithFormat:@"DELETE FROM %@ WHERE ", tableName];
585
 
586
    NSString *toDeleteArgument = [NSString stringWithFormat:@"%@ = ? OR ", kRmqIdColumn];
587
 
588
    int toDelete = (int)[rmqIds count];
589
    if (toDelete == 0) {
590
      return;
591
    }
592
    int maxBatchSize = 100;
593
    int start = 0;
594
    int deleteCount = 0;
595
    while (start < toDelete) {
596
      // construct the WHERE argument
597
      int end = MIN(start + maxBatchSize, toDelete);
598
      NSMutableString *whereArgument = [NSMutableString string];
599
      for (int i = start; i < end; i++) {
600
        [whereArgument appendString:toDeleteArgument];
601
      }
602
      // remove the last * OR * from argument
603
      NSRange range = NSMakeRange([whereArgument length] - 4, 4);
604
      [whereArgument deleteCharactersInRange:range];
605
      NSString *deleteQuery = [NSString stringWithFormat:@"%@ %@", delete, whereArgument];
606
 
607
      // sqlite update
608
      sqlite3_stmt *delete_statement;
609
      if (sqlite3_prepare_v2(self->_database, [deleteQuery UTF8String], -1, &delete_statement,
610
                             NULL) != SQLITE_OK) {
611
        FIRMessagingRmqLogAndReturn(delete_statement);
612
      }
613
 
614
      // bind values
615
      int rmqIndex = 0;
616
      int placeholderIndex = 1;          // placeholders in sqlite3 start with 1
617
      for (NSString *rmqId in rmqIds) {  // objectAtIndex: is O(n) -- would make it slow
618
        if (rmqIndex < start) {
619
          rmqIndex++;
620
          continue;
621
        } else if (rmqIndex >= end) {
622
          break;
623
        } else {
624
          if (isRmqIDString) {
625
            if (sqlite3_bind_text(delete_statement, placeholderIndex, [rmqId UTF8String],
626
                                  (int)[rmqId length], SQLITE_STATIC) != SQLITE_OK) {
627
              FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRmq2PersistentStore003,
628
                                      @"Failed to bind rmqID %@", rmqId);
629
              FIRMessagingLoggerError(kFIRMessagingMessageCodeSyncMessageManager007,
630
                                      @"Failed to delete sync message %@", rmqId);
631
              continue;
632
            }
633
          } else {
634
            int64_t rmqIdValue = [rmqId longLongValue];
635
            sqlite3_bind_int64(delete_statement, placeholderIndex, rmqIdValue);
636
          }
637
          placeholderIndex++;
638
        }
639
        rmqIndex++;
640
        FIRMessagingLoggerInfo(kFIRMessagingMessageCodeSyncMessageManager008,
641
                               @"Successfully deleted sync message from cache %@", rmqId);
642
      }
643
      if (sqlite3_step(delete_statement) != SQLITE_DONE) {
644
        FIRMessagingRmqLogAndReturn(delete_statement);
645
      }
646
      sqlite3_finalize(delete_statement);
647
      deleteCount += sqlite3_changes(self->_database);
648
      start = end;
649
    }
650
 
651
    // if we are here all of our sqlite queries should have succeeded
652
    FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRmq2PersistentStore004,
653
                            @"Trying to delete %d s2D ID's, successfully deleted %d", toDelete,
654
                            deleteCount);
655
  });
656
}
657
 
658
- (int64_t)nextRmqId {
659
  return ++self.rmqId;
660
}
661
 
662
- (NSString *)lastErrorMessage {
663
  return [NSString stringWithFormat:@"%s", sqlite3_errmsg(_database)];
664
}
665
 
666
- (int)lastErrorCode {
667
  return sqlite3_errcode(_database);
668
}
669
 
670
- (void)logError {
671
  FIRMessagingLoggerError(kFIRMessagingMessageCodeRmq2PersistentStore006,
672
                          @"Error: code (%d) message: %@", [self lastErrorCode],
673
                          [self lastErrorMessage]);
674
}
675
 
676
- (void)logErrorAndFinalizeStatement:(sqlite3_stmt *)stmt {
677
  [self logError];
678
  sqlite3_finalize(stmt);
679
}
680
 
681
- (dispatch_queue_t)databaseOperationQueue {
682
  return _databaseOperationQueue;
683
}
684
 
685
@end