Skip to content

Commit 641d853

Browse files
authored
Expand the storage protocol and provide an event iterator (#5742)
* Expand the storage protocol and provide an event iterator * Remove unused variable * Remove unused variable * Make implicit self retain explicit * Just check for NSFileTypeRegular instead And fix a comment * Missed a damned implicit self retain * Implement some tests for the new API * Fix yet another implicit self retain
1 parent dbe9007 commit 641d853

File tree

7 files changed

+294
-3
lines changed

7 files changed

+294
-3
lines changed

GoogleDataTransport/GDTCORLibrary/GDTCORFlatFileStorage.m

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@
2020
#import <GoogleDataTransport/GDTCORConsoleLogger.h>
2121
#import <GoogleDataTransport/GDTCOREvent.h>
2222
#import <GoogleDataTransport/GDTCORLifecycle.h>
23+
#import <GoogleDataTransport/GDTCORPlatform.h>
2324
#import <GoogleDataTransport/GDTCORPrioritizer.h>
2425
#import <GoogleDataTransport/GDTCORStorageEventSelector.h>
2526

2627
#import "GDTCORLibrary/Private/GDTCOREvent_Private.h"
28+
#import "GDTCORLibrary/Private/GDTCORFlatFileStorageIterator.h"
2729
#import "GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
2830
#import "GDTCORLibrary/Private/GDTCORUploadCoordinator.h"
2931

@@ -323,7 +325,7 @@ - (void)libraryDataForKey:(nonnull NSString *)key
323325

324326
- (void)storeLibraryData:(NSData *)data
325327
forKey:(nonnull NSString *)key
326-
onComplete:(nonnull void (^)(NSError *_Nullable error))onComplete {
328+
onComplete:(nullable void (^)(NSError *_Nullable error))onComplete {
327329
if (!data || data.length <= 0) {
328330
if (onComplete) {
329331
onComplete([NSError errorWithDomain:NSInternalInconsistencyException code:-1 userInfo:nil]);
@@ -354,6 +356,76 @@ - (void)removeLibraryDataForKey:(nonnull NSString *)key
354356
});
355357
}
356358

359+
- (BOOL)hasEventsForTarget:(GDTCORTarget)target {
360+
NSString *searchPath = [GDTCORFlatFileStorage pathForTarget:target qosTier:nil mappingID:nil];
361+
NSFileManager *fm = [NSFileManager defaultManager];
362+
NSDirectoryEnumerator *enumerator = [fm enumeratorAtPath:searchPath];
363+
while ([enumerator nextObject]) {
364+
if ([enumerator.fileAttributes[NSFileType] isEqual:NSFileTypeRegular]) {
365+
return YES;
366+
}
367+
}
368+
return NO;
369+
}
370+
371+
- (nullable id<GDTCORStorageEventIterator>)iteratorWithSelector:
372+
(nonnull GDTCORStorageEventSelector *)eventSelector {
373+
__block GDTCORFlatFileStorageIterator *iterator;
374+
dispatch_sync(_storageQueue, ^{
375+
NSMutableArray<NSString *> *filePaths;
376+
NSArray<NSString *> *searchPaths =
377+
[GDTCORFlatFileStorage searchPathsWithEventSelector:eventSelector];
378+
for (NSString *searchPath in searchPaths) {
379+
NSDirectoryEnumerator *enumerator =
380+
[[NSFileManager defaultManager] enumeratorAtPath:searchPath];
381+
NSString *nextFile;
382+
while ((nextFile = [enumerator nextObject])) {
383+
NSFileAttributeType fileType = enumerator.fileAttributes[NSFileType];
384+
if ([fileType isEqual:NSFileTypeRegular]) {
385+
[filePaths addObject:nextFile];
386+
}
387+
}
388+
iterator = [[GDTCORFlatFileStorageIterator alloc] initWithTarget:eventSelector.selectedTarget
389+
queue:self->_storageQueue];
390+
iterator.eventFiles = filePaths;
391+
}
392+
});
393+
return iterator;
394+
}
395+
396+
- (void)purgeEventsFromBefore:(GDTCORClock *)beforeSnapshot
397+
onComplete:(void (^)(NSError *_Nullable error))onComplete {
398+
// TODO(mikehaney24): Figure out how we're going to deal with this.
399+
}
400+
401+
- (void)storageSizeWithCallback:(void (^)(uint64_t storageSize))onComplete {
402+
dispatch_async(_storageQueue, ^{
403+
unsigned long long totalBytes = 0;
404+
NSString *eventDataPath = [GDTCORFlatFileStorage baseEventStoragePath];
405+
NSString *libraryDataPath = [GDTCORFlatFileStorage libraryDataStoragePath];
406+
NSDirectoryEnumerator *enumerator =
407+
[[NSFileManager defaultManager] enumeratorAtPath:eventDataPath];
408+
while ([enumerator nextObject]) {
409+
NSFileAttributeType fileType = enumerator.fileAttributes[NSFileType];
410+
if ([fileType isEqual:NSFileTypeRegular]) {
411+
NSNumber *fileSize = enumerator.fileAttributes[NSFileSize];
412+
totalBytes += fileSize.unsignedLongLongValue;
413+
}
414+
}
415+
enumerator = [[NSFileManager defaultManager] enumeratorAtPath:libraryDataPath];
416+
while ([enumerator nextObject]) {
417+
NSFileAttributeType fileType = enumerator.fileAttributes[NSFileType];
418+
if ([fileType isEqual:NSFileTypeRegular]) {
419+
NSNumber *fileSize = enumerator.fileAttributes[NSFileSize];
420+
totalBytes += fileSize.unsignedLongLongValue;
421+
}
422+
}
423+
if (onComplete) {
424+
onComplete(totalBytes);
425+
}
426+
});
427+
}
428+
357429
#pragma mark - Private helper methods
358430

359431
/** Saves the event's dataObject to a file using NSData mechanisms.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2020 Google LLC
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 "GDTCORLibrary/Private/GDTCORFlatFileStorageIterator.h"
18+
19+
#import <GoogleDataTransport/GDTCORConsoleLogger.h>
20+
#import <GoogleDataTransport/GDTCOREvent.h>
21+
22+
@implementation GDTCORFlatFileStorageIterator {
23+
/** The current eventFiles array index, incremented in -nextEvent. */
24+
NSInteger _currentIndex;
25+
}
26+
27+
- (instancetype)initWithTarget:(GDTCORTarget)target queue:(dispatch_queue_t)queue {
28+
self = [super init];
29+
if (self) {
30+
_queue = queue;
31+
_target = target;
32+
}
33+
return self;
34+
}
35+
36+
- (nullable GDTCOREvent *)nextEvent {
37+
if (_currentIndex == -1) {
38+
return nil;
39+
}
40+
if (!_eventFiles) {
41+
GDTCORLogDebug(@"%@", @"eventFiles property not set, so -nextEvent will be nil.");
42+
return nil;
43+
}
44+
dispatch_queue_t queue = _queue;
45+
if (!queue) {
46+
GDTCORLogDebug(@"%@", @"iterator queue was nil, so -nextEvent will be nil.");
47+
return nil;
48+
}
49+
__block GDTCOREvent *nextEvent;
50+
dispatch_sync(queue, ^{
51+
NSData *data = [NSData dataWithContentsOfFile:self->_eventFiles[self->_currentIndex]];
52+
if (data) {
53+
NSError *error;
54+
nextEvent = (GDTCOREvent *)GDTCORDecodeArchive([GDTCOREvent class], nil, data, &error);
55+
if (error || nextEvent == nil) {
56+
GDTCORLogDebug(@"Unarchiving an event failed: %@", error);
57+
nextEvent = nil;
58+
self->_currentIndex = -1;
59+
return;
60+
}
61+
}
62+
});
63+
return nextEvent;
64+
}
65+
66+
@end
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2020 Google LLC
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 <Foundation/Foundation.h>
18+
19+
#import <GoogleDataTransport/GDTCORStorageProtocol.h>
20+
21+
/** The flat file storage iterator. */
22+
@interface GDTCORFlatFileStorageIterator : NSObject <GDTCORStorageEventIterator>
23+
24+
/** The queue on which the iterator will run. */
25+
@property(nonatomic, readonly) dispatch_queue_t queue;
26+
27+
/** The target the iterator will work on. */
28+
@property(nonatomic, readonly) GDTCORTarget target;
29+
30+
/** The list of event files to iterate over. This should be set before calling -nextEvent. */
31+
@property(nonatomic) NSArray<NSString *> *eventFiles;
32+
33+
@end

GoogleDataTransport/GDTCORLibrary/Public/GDTCORStorageProtocol.h

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,35 @@
1717
#import <Foundation/Foundation.h>
1818

1919
#import <GoogleDataTransport/GDTCORLifecycle.h>
20+
#import <GoogleDataTransport/GDTCORStorageEventSelector.h>
2021
#import <GoogleDataTransport/GDTCORTargets.h>
2122

2223
@class GDTCOREvent;
24+
@class GDTCORClock;
2325

2426
NS_ASSUME_NONNULL_BEGIN
2527

28+
/** Defines an iterator API for processing or fetching events. */
29+
@protocol GDTCORStorageEventIterator <NSObject>
30+
31+
@required
32+
33+
/** Initializes an iterator instance with the given target and queue.
34+
*
35+
* @param target The target for which the events are stored.
36+
* @param queue The queue on which the iterator should run.
37+
* @return An iterator instance.
38+
*/
39+
- (instancetype)initWithTarget:(GDTCORTarget)target queue:(dispatch_queue_t)queue;
40+
41+
/** Returns the next event or nil if there are no more events to iterate over.
42+
*
43+
* @return A GDTCOREvent instance or nil if the iterator has iterated through all the events.
44+
*/
45+
- (nullable GDTCOREvent *)nextEvent;
46+
47+
@end
48+
2649
/** Defines the interface a storage subsystem is expected to implement. */
2750
@protocol GDTCORStorageProtocol <NSObject, GDTCORLifecycleProtocol>
2851

@@ -47,7 +70,7 @@ NS_ASSUME_NONNULL_BEGIN
4770
*/
4871
- (void)storeLibraryData:(NSData *)data
4972
forKey:(NSString *)key
50-
onComplete:(void (^)(NSError *_Nullable error))onComplete;
73+
onComplete:(nullable void (^)(NSError *_Nullable error))onComplete;
5174

5275
/** Retrieves the stored data for the given key.
5376
*
@@ -65,6 +88,34 @@ NS_ASSUME_NONNULL_BEGIN
6588
- (void)removeLibraryDataForKey:(NSString *)key
6689
onComplete:(void (^)(NSError *_Nullable error))onComplete;
6790

91+
/** Returns YES if some events have been stored for the given target, NO otherwise.
92+
*
93+
* @return YES if the storage contains events for the given target, NO otherwise.
94+
*/
95+
- (BOOL)hasEventsForTarget:(GDTCORTarget)target;
96+
97+
/** Returns an iterator object that can be used to iterate over events that match the eventSelector.
98+
*
99+
* @param eventSelector The event selector to match events with.
100+
* @return An iterator instance.
101+
*/
102+
- (nullable id<GDTCORStorageEventIterator>)iteratorWithSelector:
103+
(GDTCORStorageEventSelector *)eventSelector;
104+
105+
/** Removes events from before the given time.
106+
*
107+
* @param beforeSnapshot The time at which all events prior should be deleted.
108+
* @param onComplete The callback that will be invoked when deleting events is complete.
109+
*/
110+
- (void)purgeEventsFromBefore:(GDTCORClock *)beforeSnapshot
111+
onComplete:(void (^)(NSError *_Nullable error))onComplete;
112+
113+
/** Calculates and returns the total disk size that this storage consumes.
114+
*
115+
* @param onComplete The callback that will be invoked once storage size calculation is complete.
116+
*/
117+
- (void)storageSizeWithCallback:(void (^)(uint64_t storageSize))onComplete;
118+
68119
@end
69120

70121
/** Retrieves the storage instance for the given target.

GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORFlatFileStorage+Testing.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ - (void)reset {
3131
[[NSFileManager defaultManager] removeItemAtPath:[GDTCORFlatFileStorage libraryDataStoragePath]
3232
error:nil];
3333
});
34+
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
35+
[[GDTCORFlatFileStorage sharedInstance] storageSizeWithCallback:^(uint64_t storageSize) {
36+
NSAssert(storageSize == 0, @"Storage should contain nothing after a reset");
37+
dispatch_semaphore_signal(sema);
38+
}];
39+
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
3440
}
3541

3642
@end

GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.m

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ - (void)libraryDataForKey:(nonnull NSString *)key
3838

3939
- (void)storeLibraryData:(NSData *)data
4040
forKey:(nonnull NSString *)key
41-
onComplete:(nonnull void (^)(NSError *_Nullable error))onComplete {
41+
onComplete:(nullable void (^)(NSError *_Nullable error))onComplete {
4242
if (onComplete) {
4343
onComplete(nil);
4444
}
@@ -51,4 +51,20 @@ - (void)removeLibraryDataForKey:(nonnull NSString *)key
5151
}
5252
}
5353

54+
- (BOOL)hasEventsForTarget:(GDTCORTarget)target {
55+
return NO;
56+
}
57+
58+
- (nullable id<GDTCORStorageEventIterator>)iteratorWithSelector:
59+
(nonnull GDTCORStorageEventSelector *)eventSelector {
60+
return nil;
61+
}
62+
63+
- (void)purgeEventsFromBefore:(GDTCORClock *)beforeSnapshot
64+
onComplete:(void (^)(NSError *_Nullable error))onComplete {
65+
}
66+
67+
- (void)storageSizeWithCallback:(void (^)(uint64_t storageSize))onComplete {
68+
}
69+
5470
@end

GoogleDataTransport/GDTCORTests/Unit/GDTCORFlatFileStorageTest.m

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,53 @@ - (void)testSearchPathsWithEventSelector {
575575
// TODO(mikehaney24): Implement when events are actually being stored.
576576
}
577577

578+
/** Tests hasEventsForTarget: returns YES when events are stored and NO otherwise. */
579+
- (void)testHasEventsForTarget {
580+
XCTAssertFalse([[GDTCORFlatFileStorage sharedInstance] hasEventsForTarget:kGDTCORTargetTest]);
581+
582+
// TODO(mikehaney24): Store events and test for YES.
583+
}
584+
585+
/** Tests -iteratorWithSelector produces a usable iterator. */
586+
- (void)testIteratorWithSelector {
587+
GDTCORStorageEventSelector *eventSelector =
588+
[[GDTCORStorageEventSelector alloc] initWithTarget:kGDTCORTargetTest
589+
eventIDEqualTo:nil
590+
mappingIDEqualTo:nil
591+
qosTiers:nil];
592+
id<GDTCORStorageEventIterator> iter =
593+
[[GDTCORFlatFileStorage sharedInstance] iteratorWithSelector:eventSelector];
594+
XCTAssertNil([iter nextEvent]);
595+
596+
// TODO(mikehaney24): Store events and test.
597+
}
598+
599+
/** Tests -purgeEventsFromBefore:onComplete: removes prior to an arbitrary time. */
600+
- (void)testPurgeEvents {
601+
// TODO(mikehaney24): Complete when purge events is implemented.
602+
}
603+
604+
/** Tests that the size of the storage is returned accurately. */
605+
- (void)testStorageSizeWithCallback {
606+
XCTestExpectation *expectation = [self expectationWithDescription:@"storageSize complete"];
607+
[[GDTCORFlatFileStorage sharedInstance] storageSizeWithCallback:^(uint64_t storageSize) {
608+
XCTAssertEqual(storageSize, 0);
609+
[expectation fulfill];
610+
}];
611+
[self waitForExpectations:@[ expectation ] timeout:10.0];
612+
613+
expectation = [self expectationWithDescription:@"storageSize complete"];
614+
NSData *data = [@"this is a test" dataUsingEncoding:NSUTF8StringEncoding];
615+
[[GDTCORFlatFileStorage sharedInstance] storeLibraryData:data forKey:@"testKey" onComplete:nil];
616+
[[GDTCORFlatFileStorage sharedInstance] storageSizeWithCallback:^(uint64_t storageSize) {
617+
XCTAssertEqual(storageSize, data.length);
618+
[expectation fulfill];
619+
}];
620+
[self waitForExpectations:@[ expectation ] timeout:10.0];
621+
622+
// TODO(mikehaney24): Complete this test when events are actually being stored.
623+
}
624+
578625
/** Tests migration from v1 of the storage format to v2. */
579626
- (void)testMigrationFromOldVersion {
580627
static NSString *base64EncodedArchive =

0 commit comments

Comments
 (0)