Skip to content

Commit 27847ee

Browse files
authored
Refactor event fileURLs to be derived (#5137)
* Refactor event fileURLs to be derived Apparently, since iOS 8, it's possible for the app's bundle to change directories as often as every launch. This is a mechanism that's hopefully slightly more robust against that, as data is supposed to carry over, the UUID component of the app's absolute directory might just change. * Convert a couple of tests, actually use a non-absolute URL in GDTCOREvent * Make sure to use the event fileURL property as the source of truth. Add some additional logs and improve the integration test * Fix KVC not working for the removed property
1 parent ab1a9c9 commit 27847ee

File tree

13 files changed

+157
-113
lines changed

13 files changed

+157
-113
lines changed

GoogleDataTransport/GDTCORLibrary/GDTCOREvent.m

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#import <GoogleDataTransport/GDTCORAssert.h>
2020
#import <GoogleDataTransport/GDTCORClock.h>
2121
#import <GoogleDataTransport/GDTCORConsoleLogger.h>
22+
#import <GoogleDataTransport/GDTCORPlatform.h>
2223

2324
#import "GDTCORLibrary/Private/GDTCORDataFuture.h"
2425
#import "GDTCORLibrary/Private/GDTCOREvent_Private.h"
@@ -47,7 +48,7 @@ - (instancetype)copy {
4748
copy.qosTier = _qosTier;
4849
copy.clockSnapshot = _clockSnapshot;
4950
copy.customPrioritizationParams = _customPrioritizationParams;
50-
copy->_fileURL = _fileURL;
51+
copy->_GDTFilePath = _GDTFilePath;
5152
GDTCORLogDebug("Copying event %@ to event %@", self, copy);
5253
return copy;
5354
}
@@ -57,7 +58,7 @@ - (NSUInteger)hash {
5758
NSUInteger mappingIDHash = [_mappingID hash];
5859
NSUInteger timeHash = [_clockSnapshot hash];
5960
NSInteger dataObjectHash = [_dataObject hash];
60-
NSUInteger fileURL = [_fileURL hash];
61+
NSUInteger fileURL = [_GDTFilePath hash];
6162

6263
return mappingIDHash ^ _target ^ _qosTier ^ timeHash ^ dataObjectHash ^ fileURL;
6364
}
@@ -66,6 +67,8 @@ - (BOOL)isEqual:(id)object {
6667
return [self hash] == [object hash];
6768
}
6869

70+
#pragma mark - Property overrides
71+
6972
- (void)setDataObject:(id<GDTCOREventDataObject>)dataObject {
7073
// If you're looking here because of a performance issue in -transportBytes slowing the assignment
7174
// of -dataObject, one way to address this is to add a queue to this class,
@@ -75,22 +78,31 @@ - (void)setDataObject:(id<GDTCOREventDataObject>)dataObject {
7578
}
7679
}
7780

78-
- (BOOL)writeToURL:(NSURL *)fileURL error:(NSError **)error {
81+
- (NSURL *)fileURL {
82+
if (!_GDTFilePath) {
83+
_GDTFilePath = [NSString stringWithFormat:@"event-%lu", (unsigned long)self.hash];
84+
}
85+
return [GDTCORRootDirectory() URLByAppendingPathComponent:_GDTFilePath];
86+
}
87+
88+
#pragma mark - Private methods
89+
90+
- (BOOL)writeToGDTPath:(NSString *)filePath error:(NSError **)error {
7991
NSData *dataTransportBytes = [_dataObject transportBytes];
8092
if (dataTransportBytes == nil) {
81-
_fileURL = nil;
93+
_GDTFilePath = nil;
8294
_dataObject = nil;
8395
return NO;
8496
}
97+
NSURL *fileURL = [GDTCORRootDirectory() URLByAppendingPathComponent:filePath];
8598
BOOL writingSuccess = [dataTransportBytes writeToURL:fileURL
8699
options:NSDataWritingAtomic
87100
error:error];
88101
if (!writingSuccess) {
89102
GDTCORLogError(GDTCORMCEFileWriteError, @"An event file could not be written: %@", fileURL);
90-
_fileURL = nil;
91103
return NO;
92104
}
93-
_fileURL = fileURL;
105+
_GDTFilePath = filePath;
94106
_dataObject = nil;
95107
return YES;
96108
}
@@ -112,6 +124,9 @@ - (BOOL)writeToURL:(NSURL *)fileURL error:(NSError **)error {
112124
/** NSCoding key for fileURL property. */
113125
static NSString *fileURLKey = @"_fileURL";
114126

127+
/** NSCoding key for GDTFilePath property. */
128+
static NSString *kGDTFilePathKey = @"_GDTFilePath";
129+
115130
/** NSCoding key for customPrioritizationParams property. */
116131
static NSString *customPrioritizationParams = @"_customPrioritizationParams";
117132

@@ -152,7 +167,12 @@ - (id)initWithCoder:(NSCoder *)aDecoder {
152167
if (self) {
153168
_qosTier = [aDecoder decodeIntegerForKey:qosTierKey];
154169
_clockSnapshot = [aDecoder decodeObjectOfClass:[GDTCORClock class] forKey:clockSnapshotKey];
155-
_fileURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:fileURLKey];
170+
NSURL *fileURL = [aDecoder decodeObjectOfClass:[NSURL class] forKey:fileURLKey];
171+
if (fileURL) {
172+
_GDTFilePath = [fileURL lastPathComponent];
173+
} else {
174+
_GDTFilePath = [aDecoder decodeObjectOfClass:[NSString class] forKey:kGDTFilePathKey];
175+
}
156176
_customPrioritizationParams = [aDecoder decodeObjectOfClass:[NSDictionary class]
157177
forKey:customPrioritizationParams];
158178
}
@@ -171,7 +191,11 @@ - (id)initWithCoderForStoredEventBackwardCompatibility:(NSCoder *)aDecoder
171191
forKey:kStoredEventQosTierKey] integerValue];
172192
_clockSnapshot = [aDecoder decodeObjectOfClass:[GDTCORClock class]
173193
forKey:kStoredEventClockSnapshotKey];
174-
_fileURL = fileURL;
194+
if (fileURL) {
195+
_GDTFilePath = [fileURL lastPathComponent];
196+
} else {
197+
_GDTFilePath = [aDecoder decodeObjectOfClass:[NSString class] forKey:kGDTFilePathKey];
198+
}
175199
_customPrioritizationParams =
176200
[aDecoder decodeObjectOfClass:[NSDictionary class]
177201
forKey:kStoredEventCustomPrioritizationParamsKey];
@@ -184,7 +208,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder {
184208
[aCoder encodeInteger:_target forKey:targetKey];
185209
[aCoder encodeInteger:_qosTier forKey:qosTierKey];
186210
[aCoder encodeObject:_clockSnapshot forKey:clockSnapshotKey];
187-
[aCoder encodeObject:_fileURL forKey:fileURLKey];
211+
[aCoder encodeObject:_GDTFilePath forKey:kGDTFilePathKey];
188212
[aCoder encodeObject:_customPrioritizationParams forKey:customPrioritizationParams];
189213
}
190214

GoogleDataTransport/GDTCORLibrary/GDTCORPlatform.m

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,20 @@
4040

4141
NSString *const kGDTCORApplicationWillTerminateNotification =
4242
@"GDTCORApplicationWillTerminateNotification";
43+
44+
NSURL *GDTCORRootDirectory(void) {
45+
static NSURL *GDTPath;
46+
static dispatch_once_t onceToken;
47+
dispatch_once(&onceToken, ^{
48+
NSString *cachePath =
49+
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
50+
GDTPath =
51+
[NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/google-sdks-events", cachePath]];
52+
GDTCORLogDebug("GDT's state will be saved to: %@", GDTPath);
53+
});
54+
return GDTPath;
55+
}
56+
4357
#if !TARGET_OS_WATCH
4458
BOOL GDTCORReachabilityFlagsContainWWAN(SCNetworkReachabilityFlags flags) {
4559
#if TARGET_OS_IOS
@@ -134,9 +148,18 @@ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage() {
134148
resultData = [NSKeyedArchiver archivedDataWithRootObject:obj
135149
requiringSecureCoding:YES
136150
error:error];
137-
BOOL result = [resultData writeToFile:archivePath atomically:YES];
138-
result = result; // To get rid of the warning.
139-
GDTCORLogDebug(@"Attempt to write archive. successful:%@ path:%@", result, archivePath);
151+
if (*error) {
152+
GDTCORLogDebug(@"Encoding an object failed: %@", *error);
153+
return nil;
154+
}
155+
if (archivePath) {
156+
BOOL result = [resultData writeToFile:archivePath options:NSDataWritingAtomic error:error];
157+
if (result == NO || *error) {
158+
GDTCORLogDebug(@"Attempt to write archive failed: URL:%@ error:%@", archivePath, *error);
159+
} else {
160+
GDTCORLogDebug(@"Writing archive succeeded: %@", archivePath);
161+
}
162+
}
140163
} else {
141164
#endif
142165
BOOL result = NO;
@@ -145,15 +168,23 @@ GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage() {
145168
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
146169
resultData = [NSKeyedArchiver archivedDataWithRootObject:obj];
147170
#pragma clang diagnostic pop
148-
result = [resultData writeToFile:archivePath atomically:YES];
171+
if (archivePath) {
172+
result = [resultData writeToFile:archivePath options:NSDataWritingAtomic error:error];
173+
if (result == NO || *error) {
174+
GDTCORLogDebug(@"Attempt to write archive failed: URL:%@ error:%@", archivePath, *error);
175+
} else {
176+
GDTCORLogDebug(@"Writing archive succeeded: %@", archivePath);
177+
}
178+
}
149179
} @catch (NSException *exception) {
150180
NSString *errorString =
151181
[NSString stringWithFormat:@"An exception was thrown during encoding: %@", exception];
152182
*error = [NSError errorWithDomain:NSCocoaErrorDomain
153183
code:-1
154184
userInfo:@{NSLocalizedFailureReasonErrorKey : errorString}];
155185
}
156-
GDTCORLogDebug(@"Attempt to write archive. successful:%@ path:%@", result, archivePath);
186+
GDTCORLogDebug(@"Attempt to write archive. successful:%@ URL:%@ error:%@",
187+
result ? @"YES" : @"NO", archivePath, *error);
157188
}
158189
return resultData;
159190
}

GoogleDataTransport/GDTCORLibrary/GDTCORStorage.m

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,13 @@
2727
#import "GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
2828
#import "GDTCORLibrary/Private/GDTCORUploadCoordinator.h"
2929

30-
/** Creates and/or returns a singleton NSString that is the shared storage path.
31-
*
32-
* @return The SDK event storage path.
33-
*/
34-
static NSString *GDTCORStoragePath() {
35-
static NSString *storagePath;
36-
static dispatch_once_t onceToken;
37-
dispatch_once(&onceToken, ^{
38-
NSString *cachePath =
39-
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
40-
storagePath = [NSString stringWithFormat:@"%@/google-sdks-events", cachePath];
41-
GDTCORLogDebug("Events will be saved to %@", storagePath);
42-
});
43-
return storagePath;
44-
}
45-
4630
@implementation GDTCORStorage
4731

4832
+ (NSString *)archivePath {
4933
static NSString *archivePath;
5034
static dispatch_once_t onceToken;
5135
dispatch_once(&onceToken, ^{
52-
archivePath = [GDTCORStoragePath() stringByAppendingPathComponent:@"GDTCORStorageArchive"];
36+
archivePath = [GDTCORRootDirectory() URLByAppendingPathComponent:@"GDTCORStorageArchive"].path;
5337
});
5438
return archivePath;
5539
}
@@ -169,10 +153,10 @@ - (void)removeEvents:(NSSet<GDTCOREvent *> *)events {
169153
/** Creates the storage directory if it does not exist. */
170154
- (void)createEventDirectoryIfNotExists {
171155
NSError *error;
172-
BOOL result = [[NSFileManager defaultManager] createDirectoryAtPath:GDTCORStoragePath()
173-
withIntermediateDirectories:YES
174-
attributes:0
175-
error:&error];
156+
BOOL result = [[NSFileManager defaultManager] createDirectoryAtURL:GDTCORRootDirectory()
157+
withIntermediateDirectories:YES
158+
attributes:0
159+
error:&error];
176160
if (!result || error) {
177161
GDTCORLogError(GDTCORMCEDirectoryCreationError, @"Error creating the directory: %@", error);
178162
}
@@ -190,16 +174,13 @@ - (void)createEventDirectoryIfNotExists {
190174
- (NSURL *)saveEventBytesToDisk:(GDTCOREvent *)event
191175
eventHash:(NSUInteger)eventHash
192176
error:(NSError **)error {
193-
NSString *storagePath = GDTCORStoragePath();
194177
NSString *eventFileName = [NSString stringWithFormat:@"event-%lu", (unsigned long)eventHash];
195-
NSURL *eventFilePath =
196-
[NSURL fileURLWithPath:[storagePath stringByAppendingPathComponent:eventFileName]];
197-
198-
GDTCORAssert(![[NSFileManager defaultManager] fileExistsAtPath:eventFilePath.path],
199-
@"An event shouldn't already exist at this path: %@", eventFilePath.path);
200-
201-
[event writeToURL:eventFilePath error:error];
202-
return eventFilePath;
178+
NSError *writingError;
179+
[event writeToGDTPath:eventFileName error:&writingError];
180+
if (writingError) {
181+
GDTCORLogDebug(@"There was an error saving an event to disk: %@", writingError);
182+
}
183+
return event.fileURL;
203184
}
204185

205186
/** Adds the event to internal tracking collections.
@@ -242,6 +223,8 @@ - (void)appWillBackground:(GDTCORApplication *)app {
242223
GDTCOREncodeArchive(self, [GDTCORStorage archivePath], &error);
243224
if (error) {
244225
GDTCORLogDebug(@"Serializing GDTCORStorage to an archive failed: %@", error);
226+
} else {
227+
GDTCORLogDebug(@"Serialized GDTCORStorage to %@", [GDTCORStorage archivePath]);
245228
}
246229

247230
// End the background task if it's still valid.
@@ -256,6 +239,8 @@ - (void)appWillTerminate:(GDTCORApplication *)application {
256239
GDTCOREncodeArchive(self, [GDTCORStorage archivePath], &error);
257240
if (error) {
258241
GDTCORLogDebug(@"Serializing GDTCORStorage to an archive failed: %@", error);
242+
} else {
243+
GDTCORLogDebug(@"Serialized GDTCORStorage to %@", [GDTCORStorage archivePath]);
259244
}
260245
});
261246
}

GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@ NS_ASSUME_NONNULL_BEGIN
2222

2323
@interface GDTCOREvent ()
2424

25+
/** The GDT relative file path of the event. */
26+
@property(nullable, nonatomic, readonly) NSString *GDTFilePath;
27+
2528
/** Writes [dataObject transportBytes] to the given URL, populates fileURL with the filename, then
2629
* nils the dataObject property. This method should not be called twice on the same event.
2730
*
28-
* @param fileURL The fileURL that dataObject will be written to.
31+
* @param filePath The GDTCORRootDirectory-relative path that dataObject will be written to.
2932
* @param error If populated, the error encountered during writing to disk.
3033
* @return YES if writing dataObject to disk was successful, NO otherwise.
3134
*/
32-
- (BOOL)writeToURL:(NSURL *)fileURL error:(NSError **)error;
35+
- (BOOL)writeToGDTPath:(NSString *)filePath error:(NSError **)error;
3336

3437
@end
3538

GoogleDataTransport/GDTCORLibrary/Public/GDTCORPlatform.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ typedef NS_ENUM(NSInteger, GDTCORNetworkMobileSubtype) {
6666
GDTCORNetworkMobileSubtypeLTE = 11,
6767
};
6868

69+
/** Returns a URL to the root directory under which all GDT-associated data must be saved.
70+
*
71+
* @return A URL to the root directory under which all GDT-associated data must be saved.
72+
*/
73+
NSURL *GDTCORRootDirectory(void);
74+
6975
#if !TARGET_OS_WATCH
7076
/** Compares flags with the WWAN reachability flag, if available, and returns YES if present.
7177
*

GoogleDataTransport/GDTCORTests/Integration/GDTCORIntegrationTest.m

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ - (void)testEndToEndEvent {
102102
[testServer registerTestPaths];
103103
[testServer start];
104104

105-
// Create eventgers.
105+
// Create events.
106106
self.transport1 = [[GDTCORTransport alloc] initWithMappingID:@"eventMap1"
107107
transformers:nil
108108
target:kGDTCORIntegrationTestTarget];
@@ -168,7 +168,8 @@ - (void)testEndToEndEvent {
168168

169169
/** Generates a bunch of random events. */
170170
- (void)generateEvents {
171-
for (int i = 0; i < arc4random_uniform(10) + 1; i++) {
171+
int limit = arc4random_uniform(10) + 1;
172+
for (int i = 0; i < limit; i++) {
172173
// Choose a random transport, and randomly choose if it's a telemetry event.
173174
GDTCORTransport *transport = arc4random_uniform(2) ? self.transport1 : self.transport2;
174175
BOOL isTelemetryEvent = arc4random_uniform(2);

GoogleDataTransport/GDTCORTests/Unit/GDTCOREventTest.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ - (void)testIsEqualAndHash {
9292
event1.qosTier = GDTCOREventQosDefault;
9393
event1.customPrioritizationParams = @{@"customParam1" : @"aValue1"};
9494
NSError *error1;
95-
[event1 writeToURL:[NSURL fileURLWithPath:@"/tmp/fake.txt"] error:&error1];
95+
[event1 writeToGDTPath:@"/tmp/fake.txt" error:&error1];
9696
XCTAssertNil(error1);
9797

9898
GDTCOREvent *event2 = [[GDTCOREvent alloc] initWithMappingID:@"1018" target:1];
@@ -104,7 +104,7 @@ - (void)testIsEqualAndHash {
104104
event2.qosTier = GDTCOREventQosDefault;
105105
event2.customPrioritizationParams = @{@"customParam1" : @"aValue1"};
106106
NSError *error2;
107-
[event2 writeToURL:[NSURL fileURLWithPath:@"/tmp/fake.txt"] error:&error2];
107+
[event2 writeToGDTPath:@"/tmp/fake.txt" error:&error2];
108108
XCTAssertNil(error2);
109109

110110
XCTAssertEqual([event1 hash], [event2 hash]);

GoogleDataTransport/GDTCORTests/Unit/GDTCORStorageTest.m

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -596,18 +596,11 @@ - (void)testMigrationFromOldVersion {
596596
NSData *v1ArchiveData = [[NSData alloc] initWithBase64EncodedString:base64EncodedArchive
597597
options:0];
598598
XCTAssertNotNil(v1ArchiveData);
599-
GDTCORStorage *archiveStorage;
600-
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
601-
NSError *error;
602-
XCTAssertNoThrow(archiveStorage =
603-
[NSKeyedUnarchiver unarchivedObjectOfClass:[GDTCORStorage class]
604-
fromData:v1ArchiveData
605-
error:&error]);
606-
} else {
607-
#if !TARGET_OS_MACCATALYST && !TARGET_OS_WATCH
608-
XCTAssertNoThrow(archiveStorage = [NSKeyedUnarchiver unarchiveObjectWithData:v1ArchiveData]);
609-
#endif
610-
}
599+
NSError *error;
600+
GDTCORStorage *archiveStorage =
601+
(GDTCORStorage *)GDTCORDecodeArchive([GDTCORStorage class], nil, v1ArchiveData, &error);
602+
XCTAssertNil(error);
603+
XCTAssertNotNil(archiveStorage);
611604
XCTAssertEqual(archiveStorage.targetToEventSet[@(kGDTCORTargetCCT)].count, 6);
612605
XCTAssertEqual(archiveStorage.targetToEventSet[@(kGDTCORTargetFLL)].count, 12);
613606
XCTAssertEqual(archiveStorage.storedEvents.count, 18);

GoogleDataTransport/GDTCORTests/Unit/GDTCORUploadPackageTest.m

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,23 +113,15 @@ - (void)testEncoding {
113113
NSMutableSet<GDTCOREvent *> *set = [GDTCOREventGenerator generate3Events];
114114
uploadPackage.events = set;
115115
uploadPackage.handler = self;
116-
GDTCORUploadPackage *recreatedPackage;
117116
NSError *error;
118-
119-
if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
120-
NSData *packageData = [NSKeyedArchiver archivedDataWithRootObject:uploadPackage
121-
requiringSecureCoding:YES
122-
error:&error];
123-
recreatedPackage = [NSKeyedUnarchiver unarchivedObjectOfClass:[GDTCORUploadPackage class]
124-
fromData:packageData
125-
error:&error];
126-
XCTAssertNil(error);
127-
} else {
128-
#if !TARGET_OS_MACCATALYST
129-
NSData *packageData = [NSKeyedArchiver archivedDataWithRootObject:uploadPackage];
130-
recreatedPackage = [NSKeyedUnarchiver unarchiveObjectWithData:packageData];
131-
#endif
132-
}
117+
NSData *packageData = GDTCOREncodeArchive(uploadPackage, nil, &error);
118+
XCTAssertNil(error);
119+
XCTAssertNotNil(packageData);
120+
error = nil;
121+
GDTCORUploadPackage *recreatedPackage = (GDTCORUploadPackage *)GDTCORDecodeArchive(
122+
[GDTCORUploadPackage class], nil, packageData, &error);
123+
XCTAssertNil(error);
124+
XCTAssertNotNil(recreatedPackage);
133125
XCTAssertEqualObjects(uploadPackage, recreatedPackage);
134126
}
135127

0 commit comments

Comments
 (0)