GDTCORFlatFileStorage.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  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. #import "GDTCORLibrary/Private/GDTCORFlatFileStorage.h"
  17. #import <GoogleDataTransport/GDTCORAssert.h>
  18. #import <GoogleDataTransport/GDTCORConsoleLogger.h>
  19. #import <GoogleDataTransport/GDTCOREvent.h>
  20. #import <GoogleDataTransport/GDTCORLifecycle.h>
  21. #import <GoogleDataTransport/GDTCORPrioritizer.h>
  22. #import "GDTCORLibrary/Private/GDTCOREvent_Private.h"
  23. #import "GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
  24. #import "GDTCORLibrary/Private/GDTCORUploadCoordinator.h"
  25. @implementation GDTCORFlatFileStorage
  26. + (void)load {
  27. [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetCCT];
  28. [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetFLL];
  29. [[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetCSH];
  30. // Sets a global translation mapping to decode GDTCORStoredEvent objects encoded as instances of
  31. // GDTCOREvent instead. Then we do the same thing with GDTCORStorage. This must be done in load
  32. // because there are no direct references to this class and the NSCoding methods won't be called
  33. // unless the class name is mapped early.
  34. [NSKeyedUnarchiver setClass:[GDTCOREvent class] forClassName:@"GDTCORStoredEvent"];
  35. [NSKeyedUnarchiver setClass:[GDTCORFlatFileStorage class] forClassName:@"GDTCORStorage"];
  36. }
  37. + (NSString *)archivePath {
  38. static NSString *archivePath;
  39. static dispatch_once_t onceToken;
  40. dispatch_once(&onceToken, ^{
  41. archivePath =
  42. [GDTCORRootDirectory() URLByAppendingPathComponent:@"GDTCORFlatFileStorageArchive"].path;
  43. });
  44. return archivePath;
  45. }
  46. + (NSString *)libraryDataPath {
  47. static NSString *libraryDataPath;
  48. static dispatch_once_t onceToken;
  49. dispatch_once(&onceToken, ^{
  50. libraryDataPath =
  51. [GDTCORRootDirectory() URLByAppendingPathComponent:NSStringFromClass([self class])
  52. isDirectory:YES]
  53. .path;
  54. libraryDataPath = [libraryDataPath stringByAppendingPathComponent:@"gdt_library_data"];
  55. if (![[NSFileManager defaultManager] fileExistsAtPath:libraryDataPath isDirectory:NULL]) {
  56. NSError *error;
  57. [[NSFileManager defaultManager] createDirectoryAtPath:libraryDataPath
  58. withIntermediateDirectories:YES
  59. attributes:0
  60. error:&error];
  61. GDTCORAssert(error == nil, @"Creating the library data path failed: %@", error);
  62. }
  63. });
  64. return libraryDataPath;
  65. }
  66. + (instancetype)sharedInstance {
  67. static GDTCORFlatFileStorage *sharedStorage;
  68. static dispatch_once_t onceToken;
  69. dispatch_once(&onceToken, ^{
  70. sharedStorage = [[GDTCORFlatFileStorage alloc] init];
  71. });
  72. return sharedStorage;
  73. }
  74. - (instancetype)init {
  75. self = [super init];
  76. if (self) {
  77. _storageQueue =
  78. dispatch_queue_create("com.google.GDTCORFlatFileStorage", DISPATCH_QUEUE_SERIAL);
  79. _targetToEventSet = [[NSMutableDictionary alloc] init];
  80. _storedEvents = [[NSMutableDictionary alloc] init];
  81. _uploadCoordinator = [GDTCORUploadCoordinator sharedInstance];
  82. }
  83. return self;
  84. }
  85. - (void)storeEvent:(GDTCOREvent *)event
  86. onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion {
  87. GDTCORLogDebug(@"Saving event: %@", event);
  88. if (event == nil) {
  89. GDTCORLogDebug(@"%@", @"The event was nil, so it was not saved.");
  90. return;
  91. }
  92. BOOL hadOriginalCompletion = completion != nil;
  93. if (!completion) {
  94. completion = ^(BOOL wasWritten, NSError *_Nullable error) {
  95. GDTCORLogDebug(@"event %@ stored. success:%@ error:%@", event, wasWritten ? @"YES" : @"NO",
  96. error);
  97. };
  98. }
  99. __block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
  100. bgID = [[GDTCORApplication sharedApplication]
  101. beginBackgroundTaskWithName:@"GDTStorage"
  102. expirationHandler:^{
  103. // End the background task if it's still valid.
  104. [[GDTCORApplication sharedApplication] endBackgroundTask:bgID];
  105. bgID = GDTCORBackgroundIdentifierInvalid;
  106. }];
  107. dispatch_async(_storageQueue, ^{
  108. // Check that a backend implementation is available for this target.
  109. NSInteger target = event.target;
  110. // Check that a prioritizer is available for this target.
  111. id<GDTCORPrioritizer> prioritizer =
  112. [GDTCORRegistrar sharedInstance].targetToPrioritizer[@(target)];
  113. GDTCORAssert(prioritizer, @"There's no prioritizer registered for the given target. Are you "
  114. @"sure you've added the support library for the backend you need?");
  115. // Write the transport bytes to disk, get a filename.
  116. GDTCORAssert([event.dataObject transportBytes],
  117. @"The event should have been serialized to bytes");
  118. NSError *error = nil;
  119. NSURL *eventFile = [self saveEventBytesToDisk:event eventHash:event.hash error:&error];
  120. if (!eventFile || error) {
  121. GDTCORLogError(GDTCORMCEFileWriteError, @"Event failed to save to disk: %@", error);
  122. completion(NO, error);
  123. return;
  124. } else {
  125. GDTCORLogDebug(@"Event saved to disk: %@", eventFile);
  126. completion(YES, error);
  127. }
  128. // Add event to tracking collections.
  129. [self addEventToTrackingCollections:event];
  130. // Have the prioritizer prioritize the event and save state if there was an onComplete block.
  131. [prioritizer prioritizeEvent:event];
  132. if (hadOriginalCompletion && [prioritizer respondsToSelector:@selector(saveState)]) {
  133. [prioritizer saveState];
  134. GDTCORLogDebug(@"Prioritizer %@ has saved state due to an event's onComplete block.",
  135. prioritizer);
  136. }
  137. // Check the QoS, if it's high priority, notify the target that it has a high priority event.
  138. if (event.qosTier == GDTCOREventQoSFast) {
  139. [self.uploadCoordinator forceUploadForTarget:target];
  140. }
  141. // Write state to disk if there was an onComplete block or if we're in the background.
  142. if (hadOriginalCompletion || [[GDTCORApplication sharedApplication] isRunningInBackground]) {
  143. if (hadOriginalCompletion) {
  144. GDTCORLogDebug(@"%@",
  145. @"Saving flat file storage state because a completion block was passed.");
  146. } else {
  147. GDTCORLogDebug(
  148. @"%@", @"Saving flat file storage state because the app is running in the background");
  149. }
  150. NSError *error;
  151. GDTCOREncodeArchive(self, [GDTCORFlatFileStorage archivePath], &error);
  152. if (error) {
  153. GDTCORLogDebug(@"Serializing GDTCORFlatFileStorage to an archive failed: %@", error);
  154. }
  155. }
  156. // Cancel or end the associated background task if it's still valid.
  157. [[GDTCORApplication sharedApplication] endBackgroundTask:bgID];
  158. bgID = GDTCORBackgroundIdentifierInvalid;
  159. GDTCORLogDebug(@"Event %@ is stored. There are %ld events stored on disk", event,
  160. (unsigned long)self->_storedEvents.count);
  161. });
  162. }
  163. - (void)removeEvents:(NSSet<NSNumber *> *)eventIDs {
  164. NSSet<NSNumber *> *eventsToRemove = [eventIDs copy];
  165. dispatch_async(_storageQueue, ^{
  166. for (NSNumber *eventID in eventsToRemove) {
  167. // Remove from disk, first and foremost.
  168. GDTCOREvent *event = self->_storedEvents[eventID];
  169. if (event) {
  170. NSError *error;
  171. if (event.fileURL) {
  172. NSURL *fileURL = event.fileURL;
  173. BOOL result = [[NSFileManager defaultManager] removeItemAtPath:fileURL.path error:&error];
  174. if (!result || error) {
  175. GDTCORLogWarning(GDTCORMCWFileReadError,
  176. @"There was an error removing an event file: %@", error);
  177. } else {
  178. GDTCORLogDebug(@"Removed event from disk: %@", fileURL);
  179. }
  180. }
  181. // Remove from the tracking collections.
  182. [self.storedEvents removeObjectForKey:event.eventID];
  183. [self.targetToEventSet[@(event.target)] removeObject:event];
  184. }
  185. }
  186. });
  187. }
  188. #pragma mark - GDTCORStorageProtocol
  189. - (void)libraryDataForKey:(nonnull NSString *)key
  190. onComplete:
  191. (nonnull void (^)(NSData *_Nullable, NSError *_Nullable error))onComplete {
  192. dispatch_async(_storageQueue, ^{
  193. NSString *dataPath = [[[self class] libraryDataPath] stringByAppendingPathComponent:key];
  194. NSError *error;
  195. NSData *data = [NSData dataWithContentsOfFile:dataPath options:0 error:&error];
  196. if (onComplete) {
  197. onComplete(data, error);
  198. }
  199. });
  200. }
  201. - (void)storeLibraryData:(NSData *)data
  202. forKey:(nonnull NSString *)key
  203. onComplete:(nonnull void (^)(NSError *_Nullable error))onComplete {
  204. if (!data || data.length <= 0) {
  205. if (onComplete) {
  206. onComplete([NSError errorWithDomain:NSInternalInconsistencyException code:-1 userInfo:nil]);
  207. }
  208. return;
  209. }
  210. dispatch_async(_storageQueue, ^{
  211. NSError *error;
  212. NSString *dataPath = [[[self class] libraryDataPath] stringByAppendingPathComponent:key];
  213. [data writeToFile:dataPath options:NSDataWritingAtomic error:&error];
  214. if (onComplete) {
  215. onComplete(error);
  216. }
  217. });
  218. }
  219. - (void)removeLibraryDataForKey:(nonnull NSString *)key
  220. onComplete:(nonnull void (^)(NSError *_Nullable error))onComplete {
  221. dispatch_async(_storageQueue, ^{
  222. NSError *error;
  223. NSString *dataPath = [[[self class] libraryDataPath] stringByAppendingPathComponent:key];
  224. if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath]) {
  225. [[NSFileManager defaultManager] removeItemAtPath:dataPath error:&error];
  226. if (onComplete) {
  227. onComplete(error);
  228. }
  229. }
  230. });
  231. }
  232. #pragma mark - Private helper methods
  233. /** Saves the event's dataObject to a file using NSData mechanisms.
  234. *
  235. * @note This method should only be called from a method within a block on _storageQueue to maintain
  236. * thread safety.
  237. *
  238. * @param event The event.
  239. * @param eventHash The hash value of the event.
  240. * @return The filename
  241. */
  242. - (NSURL *)saveEventBytesToDisk:(GDTCOREvent *)event
  243. eventHash:(NSUInteger)eventHash
  244. error:(NSError **)error {
  245. NSString *eventFileName = [NSString stringWithFormat:@"event-%lu", (unsigned long)eventHash];
  246. NSError *writingError;
  247. [event writeToGDTPath:eventFileName error:&writingError];
  248. if (writingError) {
  249. GDTCORLogDebug(@"There was an error saving an event to disk: %@", writingError);
  250. }
  251. return event.fileURL;
  252. }
  253. /** Adds the event to internal tracking collections.
  254. *
  255. * @note This method should only be called from a method within a block on _storageQueue to maintain
  256. * thread safety.
  257. *
  258. * @param event The event to track.
  259. */
  260. - (void)addEventToTrackingCollections:(GDTCOREvent *)event {
  261. _storedEvents[event.eventID] = event;
  262. NSNumber *target = @(event.target);
  263. NSMutableSet<GDTCOREvent *> *events = self.targetToEventSet[target];
  264. events = events ? events : [[NSMutableSet alloc] init];
  265. [events addObject:event];
  266. _targetToEventSet[target] = events;
  267. }
  268. #pragma mark - GDTCORLifecycleProtocol
  269. - (void)appWillForeground:(GDTCORApplication *)app {
  270. dispatch_async(_storageQueue, ^{
  271. NSError *error;
  272. GDTCORDecodeArchive([GDTCORFlatFileStorage class], [GDTCORFlatFileStorage archivePath], nil,
  273. &error);
  274. if (error) {
  275. GDTCORLogDebug(@"Deserializing GDTCORFlatFileStorage from an archive failed: %@", error);
  276. }
  277. });
  278. }
  279. - (void)appWillBackground:(GDTCORApplication *)app {
  280. dispatch_async(_storageQueue, ^{
  281. // Immediately request a background task to run until the end of the current queue of work, and
  282. // cancel it once the work is done.
  283. __block GDTCORBackgroundIdentifier bgID =
  284. [app beginBackgroundTaskWithName:@"GDTStorage"
  285. expirationHandler:^{
  286. [app endBackgroundTask:bgID];
  287. bgID = GDTCORBackgroundIdentifierInvalid;
  288. }];
  289. NSError *error;
  290. GDTCOREncodeArchive(self, [GDTCORFlatFileStorage archivePath], &error);
  291. if (error) {
  292. GDTCORLogDebug(@"Serializing GDTCORFlatFileStorage to an archive failed: %@", error);
  293. } else {
  294. GDTCORLogDebug(@"Serialized GDTCORFlatFileStorage to %@",
  295. [GDTCORFlatFileStorage archivePath]);
  296. }
  297. // End the background task if it's still valid.
  298. [app endBackgroundTask:bgID];
  299. bgID = GDTCORBackgroundIdentifierInvalid;
  300. });
  301. }
  302. - (void)appWillTerminate:(GDTCORApplication *)application {
  303. dispatch_sync(_storageQueue, ^{
  304. NSError *error;
  305. GDTCOREncodeArchive(self, [GDTCORFlatFileStorage archivePath], &error);
  306. if (error) {
  307. GDTCORLogDebug(@"Serializing GDTCORFlatFileStorage to an archive failed: %@", error);
  308. } else {
  309. GDTCORLogDebug(@"Serialized GDTCORFlatFileStorage to %@",
  310. [GDTCORFlatFileStorage archivePath]);
  311. }
  312. });
  313. }
  314. #pragma mark - NSSecureCoding
  315. /** The NSKeyedCoder key for the storedEvents property. */
  316. static NSString *const kGDTCORFlatFileStorageStoredEventsKey = @"GDTCORStorageStoredEventsKey";
  317. /** The NSKeyedCoder key for the targetToEventSet property. */
  318. static NSString *const kGDTCORFlatFileStorageTargetToEventSetKey =
  319. @"GDTCORStorageTargetToEventSetKey";
  320. /** The NSKeyedCoder key for the uploadCoordinator property. */
  321. static NSString *const kGDTCORFlatFileStorageUploadCoordinatorKey =
  322. @"GDTCORStorageUploadCoordinatorKey";
  323. + (BOOL)supportsSecureCoding {
  324. return YES;
  325. }
  326. - (instancetype)initWithCoder:(NSCoder *)aDecoder {
  327. // Create the singleton and populate its ivars.
  328. GDTCORFlatFileStorage *sharedInstance = [self.class sharedInstance];
  329. NSSet *classes = [NSSet setWithObjects:[NSMutableOrderedSet class], [NSMutableDictionary class],
  330. [GDTCOREvent class], nil];
  331. id storedEvents = [aDecoder decodeObjectOfClasses:classes
  332. forKey:kGDTCORFlatFileStorageStoredEventsKey];
  333. NSMutableDictionary<NSNumber *, GDTCOREvent *> *events = [[NSMutableDictionary alloc] init];
  334. if ([storedEvents isKindOfClass:[NSMutableOrderedSet class]]) {
  335. [(NSMutableOrderedSet *)storedEvents
  336. enumerateObjectsUsingBlock:^(GDTCOREvent *_Nonnull obj, NSUInteger idx,
  337. BOOL *_Nonnull stop) {
  338. events[obj.eventID] = obj;
  339. }];
  340. } else if ([storedEvents isKindOfClass:[NSMutableDictionary class]]) {
  341. events = (NSMutableDictionary *)storedEvents;
  342. }
  343. sharedInstance->_storedEvents = events;
  344. classes = [NSSet
  345. setWithObjects:[NSMutableDictionary class], [NSMutableSet class], [GDTCOREvent class], nil];
  346. sharedInstance->_targetToEventSet =
  347. [aDecoder decodeObjectOfClasses:classes forKey:kGDTCORFlatFileStorageTargetToEventSetKey];
  348. sharedInstance->_uploadCoordinator =
  349. [aDecoder decodeObjectOfClass:[GDTCORUploadCoordinator class]
  350. forKey:kGDTCORFlatFileStorageUploadCoordinatorKey];
  351. return sharedInstance;
  352. }
  353. - (void)encodeWithCoder:(NSCoder *)aCoder {
  354. GDTCORFlatFileStorage *sharedInstance = [self.class sharedInstance];
  355. NSMutableDictionary<NSNumber *, GDTCOREvent *> *storedEvents = sharedInstance->_storedEvents;
  356. if (storedEvents) {
  357. [aCoder encodeObject:storedEvents forKey:kGDTCORFlatFileStorageStoredEventsKey];
  358. }
  359. NSMutableDictionary<NSNumber *, NSMutableSet<GDTCOREvent *> *> *targetToEventSet =
  360. sharedInstance->_targetToEventSet;
  361. if (targetToEventSet) {
  362. [aCoder encodeObject:targetToEventSet forKey:kGDTCORFlatFileStorageTargetToEventSetKey];
  363. }
  364. GDTCORUploadCoordinator *uploadCoordinator = sharedInstance->_uploadCoordinator;
  365. if (uploadCoordinator) {
  366. [aCoder encodeObject:uploadCoordinator forKey:kGDTCORFlatFileStorageUploadCoordinatorKey];
  367. }
  368. }
  369. @end