Skip to content

Commit

Permalink
Merge pull request #13 from launchdarkly/cm-event-capacity
Browse files Browse the repository at this point in the history
Event capacity
  • Loading branch information
jeff-byrnesinnovation committed Nov 19, 2015
2 parents 7a6a539 + 67bcc96 commit 112655b
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 97 deletions.
53 changes: 11 additions & 42 deletions Darkly/ClientManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ @implementation ClientManager

NSString *const kLDUserUpdatedNotification = @"Darkly.UserUpdatedNotification";

+(ClientManager *)sharedInstance
{
+(ClientManager *)sharedInstance {
static ClientManager *sharedApiManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Expand All @@ -32,10 +31,7 @@ +(ClientManager *)sharedInstance
return sharedApiManager;
}



- (void)startPolling
{
- (void)startPolling {
PollingManager *pollingMgr = [PollingManager sharedInstance];

LDClient *client = [LDClient sharedInstance];
Expand All @@ -49,8 +45,7 @@ - (void)startPolling
}


- (void)stopPolling
{
- (void)stopPolling {
DEBUG_LOGX(@"ClientManager stopPolling method called");
PollingManager *pollingMgr = [PollingManager sharedInstance];

Expand All @@ -60,8 +55,7 @@ - (void)stopPolling
[self flushEvents];
}

- (void)willEnterBackground
{
- (void)willEnterBackground {
DEBUG_LOGX(@"ClientManager entering background");
PollingManager *pollingMgr = [PollingManager sharedInstance];

Expand All @@ -71,16 +65,14 @@ - (void)willEnterBackground
[self flushEvents];
}

- (void)willEnterForeground
{
- (void)willEnterForeground {
DEBUG_LOGX(@"ClientManager entering foreground");
PollingManager *pollingMgr = [PollingManager sharedInstance];
[pollingMgr resumeConfigPolling];
[pollingMgr resumeEventPolling];
}

-(void)syncWithServerForEvents
{
-(void)syncWithServerForEvents {
if (!offlineEnabled) {
DEBUG_LOGX(@"ClientManager syncing events with server");

Expand All @@ -97,8 +89,7 @@ -(void)syncWithServerForEvents
}
}

-(void)syncWithServerForConfig
{
-(void)syncWithServerForConfig {
if (!offlineEnabled) {
DEBUG_LOGX(@"ClientManager syncing config with server");
LDClient *client = [LDClient sharedInstance];
Expand All @@ -117,39 +108,18 @@ -(void)syncWithServerForConfig
}
}

- (void)flushEvents
{
- (void)flushEvents {
[self syncWithServerForEvents];
}

- (void)processedEvents:(BOOL)success jsonEventArray:(NSData *)jsonEventArray eventInterval:(int)eventInterval
{
- (void)processedEvents:(BOOL)success jsonEventArray:(NSData *)jsonEventArray eventInterval:(int)eventInterval {
// If Success
if (success) {
DEBUG_LOGX(@"ClientManager processedEvents method called after receiving successful response from server");
// Audit cached events versus processed Events and only keep difference
NSArray *processedJsonArray = [NSJSONSerialization JSONObjectWithData:jsonEventArray options:NSJSONReadingMutableContainers error:nil];
if (processedJsonArray) {
BOOL hasMatchedEvents = NO;

// Loop through processedEvents
for (NSDictionary *processedEventDict in processedJsonArray) {
// Attempt to find match in currentEvents based on creationDate

Event *processedEvent = [MTLJSONAdapter modelOfClass:[Event class]
fromJSONDictionary:processedEventDict
error:nil];
NSManagedObject *matchedCurrentEvent = [[DataManager sharedManager] findEvent: [processedEvent creationDate]];
// If events match
if (matchedCurrentEvent) {
[[[DataManager sharedManager] managedObjectContext] deleteObject:matchedCurrentEvent];
hasMatchedEvents = YES;
}
}
// If number of managedObjects is greater than 0, then Save Context
if (hasMatchedEvents) {
[[DataManager sharedManager] saveContext];
}
[[DataManager sharedManager] deleteProcessedEvents: processedJsonArray];
}
} else {
DEBUG_LOGX(@"ClientManager processedEvents method called after receiving failure response from server");
Expand All @@ -159,8 +129,7 @@ - (void)processedEvents:(BOOL)success jsonEventArray:(NSData *)jsonEventArray ev
}
}

- (void)processedConfig:(BOOL)success jsonConfigDictionary:(NSDictionary *)jsonConfigDictionary configInterval:(int)configInterval
{
- (void)processedConfig:(BOOL)success jsonConfigDictionary:(NSDictionary *)jsonConfigDictionary configInterval:(int)configInterval {
if (success) {
DEBUG_LOGX(@"ClientManager processedConfig method called after receiving successful response from server");
// If Success
Expand Down
16 changes: 8 additions & 8 deletions Darkly/Models/DataManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ extern int const kUserCacheSize;
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

-(void) saveContext;
-(NSManagedObjectContext *) managedObjectContext;
@property (strong,atomic) NSNumber *eventCreatedCount;

+(DataManager *)sharedManager;

-(NSManagedObjectContext *) managedObjectContext;
-(NSManagedObject *)findEvent: (NSInteger) date;
-(NSData*) allEventsJsonData;
-(void) createFeatureEvent: (NSString *)featureKey keyValue:(BOOL)keyValue defaultKeyValue:(BOOL)defaultKeyValue;
-(void) createCustomEvent: (NSString *)eventKey
withCustomValuesDictionary: (NSDictionary *)customDict;
-(NSArray *)allEvents;
-(Config *) createConfigFromJsonDict: (NSDictionary *)jsonConfigDictionary;
-(UserEntity *)findUserEntityWithkey: (NSString *)key;
-(User *)findUserWithkey: (NSString *)key;
-(void)purgeOldUsers;
-(void)deleteOrphanedConfig;
-(void) createFeatureEvent: (NSString *)featureKey keyValue:(BOOL)keyValue defaultKeyValue:(BOOL)defaultKeyValue;
-(void) createCustomEvent: (NSString *)eventKey withCustomValuesDictionary: (NSDictionary *)customDict;
-(void) purgeOldUsers;
-(void) deleteOrphanedConfig;
-(void) saveUser: (User *) user;
-(void) saveContext;
-(void) deleteProcessedEvents: (NSArray *) processedJsonArray;

@end
142 changes: 95 additions & 47 deletions Darkly/Models/DataManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ @implementation DataManager
@synthesize managedObjectContext;
@synthesize managedObjectModel;
@synthesize persistentStoreCoordinator;
@synthesize eventCreatedCount;

+ (id)sharedManager {
static DataManager *sharedDataManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedDataManager = [[self alloc] init];
sharedDataManager.eventCreatedCount = [NSNumber numberWithInt: 0];
});
return sharedDataManager;
}

#pragma mark - users
-(void)purgeOldUsers {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"UserEntity"
Expand Down Expand Up @@ -70,27 +73,6 @@ -(void) saveUser: (User *) user {
[[DataManager sharedManager] saveContext];
}

-(void)deleteOrphanedConfig {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"ConfigEntity"
inManagedObjectContext:[self managedObjectContext]]];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"user.key = nil"];
request.predicate = predicate;

__block NSArray *configMoArray = nil;

[self.managedObjectContext performBlockAndWait:^{
NSError *error = nil;
configMoArray = [[self managedObjectContext] executeFetchRequest:request
error:&error];

for (int configMOIndex = 0; configMOIndex < configMoArray.count; configMOIndex++) {
DEBUG_LOG(@"Deleting orphaned config at index: %d", configMOIndex);
[self.managedObjectContext deleteObject: [configMoArray objectAtIndex:configMOIndex]];
}
}];
}

-(UserEntity *)findUserEntityWithkey:(NSString *)key {
DEBUG_LOG(@"Retrieving user with key: %@", key);
Expand Down Expand Up @@ -129,23 +111,27 @@ -(User *)findUserWithkey: (NSString *)key {
return nil;
}

-(void) createFeatureEvent: (NSString *)featureKey keyValue:(BOOL)keyValue defaultKeyValue:(BOOL)defaultKeyValue {
DEBUG_LOG(@"Creating event for feature:%@ with value:%d and defaultValue:%d", featureKey, keyValue, defaultKeyValue);
Event *featureEvent = [[Event alloc] featureEventWithKey: featureKey keyValue:keyValue defaultKeyValue:defaultKeyValue];
[MTLManagedObjectAdapter managedObjectFromModel:featureEvent
insertingIntoContext:[self managedObjectContext]
error:nil];
[self saveContext];
}

-(void) createCustomEvent: (NSString *)eventKey withCustomValuesDictionary: (NSDictionary *)customDict {
DEBUG_LOG(@"Creating event for custom key:%@ and value:%@", eventKey, customDict);
Event *customEvent = [[Event alloc] customEventWithKey: eventKey andDataDictionary: customDict];
#pragma mark - config
-(void)deleteOrphanedConfig {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"ConfigEntity"
inManagedObjectContext:[self managedObjectContext]]];

[MTLManagedObjectAdapter managedObjectFromModel:customEvent
insertingIntoContext:[self managedObjectContext]
error:nil];
[self saveContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"user.key = nil"];
request.predicate = predicate;

__block NSArray *configMoArray = nil;

[self.managedObjectContext performBlockAndWait:^{
NSError *error = nil;
configMoArray = [[self managedObjectContext] executeFetchRequest:request
error:&error];

for (int configMOIndex = 0; configMOIndex < configMoArray.count; configMOIndex++) {
DEBUG_LOG(@"Deleting orphaned config at index: %d", configMOIndex);
[self.managedObjectContext deleteObject: [configMoArray objectAtIndex:configMOIndex]];
}
}];
}

-(Config *) createConfigFromJsonDict: (NSDictionary *)jsonConfigDictionary {
Expand All @@ -160,6 +146,44 @@ -(Config *) createConfigFromJsonDict: (NSDictionary *)jsonConfigDictionary {

return config;
}
#pragma mark - events

-(void) createFeatureEvent: (NSString *)featureKey keyValue:(BOOL)keyValue defaultKeyValue:(BOOL)defaultKeyValue {

if(![self isAtEventCapacity]) {
DEBUG_LOG(@"Creating event for feature:%@ with value:%d and defaultValue:%d", featureKey, keyValue, defaultKeyValue);
Event *featureEvent = [[Event alloc] featureEventWithKey: featureKey keyValue:keyValue defaultKeyValue:defaultKeyValue];
[MTLManagedObjectAdapter managedObjectFromModel:featureEvent
insertingIntoContext:[self managedObjectContext]
error:nil];

int eventCreatedCountInt = [eventCreatedCount intValue];
eventCreatedCount = [NSNumber numberWithInt:eventCreatedCountInt + 1];
[self saveContext];
} else
DEBUG_LOG(@"Events have surpassed capacity. Discarding feature event %@", featureKey);
}

-(void) createCustomEvent: (NSString *)eventKey withCustomValuesDictionary: (NSDictionary *)customDict {
if(![self isAtEventCapacity]) {
DEBUG_LOG(@"Creating event for custom key:%@ and value:%@", eventKey, customDict);
Event *customEvent = [[Event alloc] customEventWithKey: eventKey andDataDictionary: customDict];

[MTLManagedObjectAdapter managedObjectFromModel:customEvent
insertingIntoContext:[self managedObjectContext]
error:nil];
int eventCreatedCountInt = [eventCreatedCount intValue];
eventCreatedCount = [NSNumber numberWithInt:eventCreatedCountInt + 1];
[self saveContext];
} else
DEBUG_LOG(@"Events have surpassed capacity. Discarding event %@ with dictionary %@", eventKey, customDict);
}

-(BOOL)isAtEventCapacity {
LDConfig *ldConfig = [[LDClient sharedInstance] ldConfig];

return ldConfig.capacity && eventCreatedCount >= ldConfig.capacity;
}

-(NSManagedObject *)findEvent: (NSInteger) date {
DEBUG_LOG(@"Retrieving event for date: %ld", (long)date);
Expand All @@ -183,6 +207,33 @@ -(NSManagedObject *)findEvent: (NSInteger) date {
return nil;
}

-(void) deleteProcessedEvents: (NSArray *) processedJsonArray {
__block BOOL hasMatchedEvents = NO;

[self.managedObjectContext performBlockAndWait:^{
// Loop through processedEvents
for (NSDictionary *processedEventDict in processedJsonArray) {
// Attempt to find match in currentEvents based on creationDate
Event *processedEvent = [MTLJSONAdapter modelOfClass:[Event class]
fromJSONDictionary:processedEventDict
error:nil];
NSManagedObject *matchedCurrentEvent = [[DataManager sharedManager] findEvent: [processedEvent creationDate]];
// If events match
if (matchedCurrentEvent) {
[[[DataManager sharedManager] managedObjectContext] deleteObject:matchedCurrentEvent];
hasMatchedEvents = YES;

int eventCreatedCountInt = [eventCreatedCount intValue];
eventCreatedCount = [NSNumber numberWithInt:eventCreatedCountInt - 1];
}
}
// If number of managedObjects is greater than 0, then Save Context
if (hasMatchedEvents) {
[[DataManager sharedManager] saveContext];
}
}];
}

-(NSArray *)allEvents {
DEBUG_LOGX(@"Retrieving all events");
NSFetchRequest *request = [[NSFetchRequest alloc] init];
Expand Down Expand Up @@ -299,15 +350,12 @@ - (NSManagedObjectContext *)managedObjectContext {
#pragma mark - Core Data Saving support

- (void)saveContext {
if ([self managedObjectContext] != nil)
[self saveInBackground];
}

-(void)saveInBackground {
[self.managedObjectContext performBlock:^{
NSError *error = nil;
if (![self.managedObjectContext save:&error])
NSLog(@"Error saving to child context %@, %@", error, [error userInfo]);
}];
if ([self managedObjectContext] != nil) {
[self.managedObjectContext performBlock:^{
NSError *error = nil;
if (![self.managedObjectContext save:&error])
NSLog(@"Error saving to child context %@, %@", error, [error userInfo]);
}];
}
}
@end
2 changes: 2 additions & 0 deletions DarklyTests/DarklyXCTestCase.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ -(void) deleteAllEvents {
[context deleteObject:event];
}
[dataManagerMock saveContext];

[dataManagerMock setEventCreatedCount: 0];
}
@end
21 changes: 21 additions & 0 deletions DarklyTests/Models/DataManagerTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,25 @@ -(void) testPurgeUsers {
userEntity = [[DataManager sharedManager] findUserEntityWithkey: userKey];
XCTAssertNil(userEntity);
}

-(void)testCreateEventAfterCapacityReached{
LDConfigBuilder *builder = [[LDConfigBuilder alloc] init];
builder = [builder withCapacity: 2];
builder = [builder withApiKey: @"AnApiKey"];
LDConfig *config = [builder build];

OCMStub([clientMock ldConfig]).andReturn(config);

[self.dataManagerMock createCustomEvent:@"aKey" withCustomValuesDictionary: @{@"carrot": @"cake"}];
[self.dataManagerMock createCustomEvent:@"aKey" withCustomValuesDictionary: @{@"carrot": @"cake"}];
[self.dataManagerMock createCustomEvent:@"aKey" withCustomValuesDictionary: @{@"carrot": @"cake"}];
[self.dataManagerMock createFeatureEvent: @"anotherKet" keyValue: YES defaultKeyValue: NO];

[self.dataManagerMock saveContext];

NSArray *allEvents = [self.dataManagerMock allEvents];

XCTAssertTrue(allEvents.count == 2);
}

@end

0 comments on commit 112655b

Please sign in to comment.