Skip to content

Commit

Permalink
Release 16.12.6
Browse files Browse the repository at this point in the history
  • Loading branch information
rlepinski committed Jan 30, 2024
1 parent 32e2cba commit 7a0c4b7
Show file tree
Hide file tree
Showing 17 changed files with 436 additions and 111 deletions.
2 changes: 1 addition & 1 deletion Airship.podspec
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
AIRSHIP_VERSION="16.12.5"
AIRSHIP_VERSION="16.12.6"

Pod::Spec.new do |s|
s.version = AIRSHIP_VERSION
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ NS_ASSUME_NONNULL_BEGIN
*
* @param completionHandler A completion handler called with the result.
*/
- (void)getFrequencyChecker:(NSArray<NSString *> *)constraintIDs completionHandler:(void (^)(UAFrequencyChecker *))completionHandler;
- (void)getFrequencyChecker:(NSArray<NSString *> *)constraintIDs completionHandler:(void (^)(UAFrequencyChecker * _Nullable))completionHandler;

/**
* Updates the frequency constraints.
*
* @param constraints The constraints.
* @param completionHandler A completion handler called with the result.
*/
- (void)updateConstraints:(NSArray<UAFrequencyConstraint *> *)constraints;
- (void)updateConstraints:(NSArray<UAFrequencyConstraint *> *)constraints completionHandler:(void (^)(BOOL))completionHandler;

@end

Expand Down
203 changes: 130 additions & 73 deletions Airship/AirshipAutomation/Source/UAFrequencyLimitManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
#else
@import AirshipCore;
#endif

@interface UAFrequencyLimitManager ()
@property(nonatomic, strong) NSMutableDictionary<UAFrequencyConstraint *, NSMutableArray<UAOccurrence *> *> *occurrencesMap;
@property(nonatomic, strong) NSMutableDictionary<NSString *, UAFrequencyConstraint *> *constraintMap;
@property(nonatomic, strong) NSMutableDictionary<NSString *, NSMutableArray<UAOccurrence *> *> *occurrencesMap;

@property(nonatomic, strong) NSMutableArray<UAOccurrence *> *pendingOccurrences;
@property(nonatomic, strong) UAFrequencyLimitStore *frequencyLimitStore;
@property(nonatomic, strong) UADate *date;
Expand All @@ -34,6 +37,7 @@ - (instancetype)initWithDataStore:(UAFrequencyLimitStore *)dataStore date:(UADat
self.date = date;
self.pendingOccurrences = [NSMutableArray array];
self.occurrencesMap = [NSMutableDictionary dictionary];
self.constraintMap = [NSMutableDictionary dictionary];
self.dispatcher = dispatcher;
self.lock = [[NSObject alloc] init];
}
Expand All @@ -51,15 +55,59 @@ + (instancetype)managerWithConfig:(UARuntimeConfig *)config {
dispatcher:UADispatcher.serial];
}

- (void)getFrequencyChecker:(NSArray<NSString *> *)constraintIDs completionHandler:(void (^)(UAFrequencyChecker *))completionHandler {
- (void)getFrequencyChecker:(NSArray<NSString *> *)constraintIDs
completionHandler:(void (^)(UAFrequencyChecker *))completionHandler {

if (!constraintIDs.count) {
UAFrequencyChecker *checker = [UAFrequencyChecker frequencyCheckerWithIsOverLimit:^{
return NO;
} checkAndIncrement:^{
return YES;
}];
completionHandler(checker);
return;
}

UA_WEAKIFY(self)
[self.dispatcher dispatchAsync:^{
UA_STRONGIFY(self)
completionHandler([self createFrequencyChecker:[self fetchConstraints:constraintIDs]]);

for (NSString *constraintID in constraintIDs) {
@synchronized (self.lock) {
if (self.constraintMap[constraintID]) {
// Already loaded
continue;
}
}

UAFrequencyConstraint *constraint = [[self.frequencyLimitStore getConstraints:@[constraintID]] firstObject];
NSMutableArray<UAOccurrence *> *occurrences = [[self.frequencyLimitStore getOccurrences:constraintID] mutableCopy];

if (!constraint) {
UA_LERR(@"Failed to get constraint %@", constraint);
completionHandler(nil);
return;
}

@synchronized (self.lock) {
self.constraintMap[constraintID] = constraint;
self.occurrencesMap[constraintID] = occurrences;
}
}

UAFrequencyChecker *checker = [UAFrequencyChecker frequencyCheckerWithIsOverLimit:^{
return [self isOverLimit:constraintIDs];
} checkAndIncrement:^{
return [self checkAndIncrement:constraintIDs];
}];

completionHandler(checker);
}];
}

- (void)updateConstraints:(NSArray<UAFrequencyConstraint *> *)constraints {
- (void)updateConstraints:(NSArray<UAFrequencyConstraint *> *)constraints
completionHandler:(void (^)(BOOL))completionHandler {

UA_WEAKIFY(self)
[self.dispatcher dispatchAsync:^{
UA_STRONGIFY(self)
Expand All @@ -74,35 +122,53 @@ - (void)updateConstraints:(NSArray<UAFrequencyConstraint *> *)constraints {
UAFrequencyConstraint *existing = constraintIDMap[constraint.identifier];
if (existing) {
constraintIDMap[constraint.identifier] = nil;

if (existing.range != constraint.range) {
if ([self deleteConstraint:existing]) {
[self saveConstraint:constraint];
if (![self.frequencyLimitStore deleteConstraint:existing]) {
completionHandler(NO);
return;
}

if (![self.frequencyLimitStore saveConstraint:constraint]) {
completionHandler(NO);
return;
}

@synchronized (self.lock) {
if (self.constraintMap[constraint.identifier]) {
self.constraintMap[constraint.identifier] = constraint;
self.occurrencesMap[constraint.identifier] = [NSMutableArray array];
}
}
} else {
[self saveConstraint:constraint];
if (![self.frequencyLimitStore saveConstraint:constraint]) {
completionHandler(NO);
return;
}

@synchronized (self.lock) {
if (self.constraintMap[constraint.identifier]) {
self.constraintMap[constraint.identifier] = constraint;
}
}
}
} else {
[self saveConstraint:constraint];
if (![self.frequencyLimitStore saveConstraint:constraint]) {
completionHandler(NO);
return;
}
}
}

[self.frequencyLimitStore deleteConstraints:constraintIDMap.allKeys];
}];
}

- (void)saveConstraint:(UAFrequencyConstraint *)constraint {
if (![self.frequencyLimitStore saveConstraint:constraint]) {
UA_LERR(@"Unable to save constraint: %@", constraint);
}
}

- (BOOL)deleteConstraint:(UAFrequencyConstraint *)constraint {
if (![self.frequencyLimitStore deleteConstraint:constraint]) {
UA_LERR(@"Unable to delete constraint: %@", constraint);
return NO;
}
@synchronized (self.lock) {
for (NSString *constraintID in constraintIDMap.allKeys) {
[self.constraintMap removeObjectForKey:constraintID];
[self.occurrencesMap removeObjectForKey:constraintID];
}
}

return YES;
completionHandler([self.frequencyLimitStore deleteConstraints:constraintIDMap.allKeys]);
}];
}

- (NSArray<UAFrequencyConstraint *> *)fetchConstraints:(NSArray<NSString *> *)constraintIDs {
Expand All @@ -123,75 +189,65 @@ - (BOOL)deleteConstraint:(UAFrequencyConstraint *)constraint {
return constraints;
}

- (UAFrequencyChecker *)createFrequencyChecker:(NSArray<UAFrequencyConstraint *> *)constraints {
UA_WEAKIFY(self)
return [UAFrequencyChecker frequencyCheckerWithIsOverLimit:^{
UA_STRONGIFY(self)
return [self isOverLimit:constraints];
} checkAndIncrement:^{
UA_STRONGIFY(self)
return [self checkAndIncrement:constraints];
}];
}

- (BOOL)isOverLimit:(NSArray<UAFrequencyConstraint *> *)constraints {
- (BOOL)isOverLimit:(NSArray<NSString *> *)constraintIDs {
@synchronized(self.lock) {
for (UAFrequencyConstraint *constraint in constraints) {
if ([self isConstraintOverLimit:constraint]) {
return YES;
}
}

return NO;
}
}
for (NSString *constraintID in constraintIDs) {
NSArray<UAOccurrence *> *occurrences = self.occurrencesMap[constraintID];
UAFrequencyConstraint *constraint = self.constraintMap[constraintID];

- (BOOL)checkAndIncrement:(NSArray<UAFrequencyConstraint *> *)constraints {
@synchronized(self.lock) {
if ([self isOverLimit:constraints]) {
return NO;
}
if (!occurrences || !constraint) {
// Can happen if constraint is removed mid check
continue;
}

[self recordOccurrence:[constraints valueForKey:@"identifier"]];
if (occurrences.count < constraint.count) {
continue;
}

return YES;
}
NSArray *sorted = [occurrences sortedArrayUsingComparator:^NSComparisonResult(UAOccurrence *obj1, UAOccurrence *obj2) {
return [obj1.timestamp compare:obj2.timestamp];
}];

return NO;
}
NSDate *timeStamp = ((UAOccurrence *)sorted[occurrences.count - constraint.count]).timestamp;
NSTimeInterval timeSinceOccurrece = [self.date.now timeIntervalSinceDate:timeStamp];
if (timeSinceOccurrece <= constraint.range) {
return YES;
}
}

- (BOOL)isConstraintOverLimit:(UAFrequencyConstraint *)constraint {
NSArray<UAOccurrence *> *occurrences = self.occurrencesMap[constraint];
if (!occurrences || occurrences.count < constraint.count) {
return NO;
}

NSDate *timeStamp = ((UAOccurrence *)occurrences[occurrences.count - constraint.count]).timestamp;
NSTimeInterval timeSinceOccurrece = [self.date.now timeIntervalSinceDate:timeStamp];
return timeSinceOccurrece <= constraint.range;
}

- (void)recordOccurrence:(NSArray<NSString *>*)constraintIDs {
- (BOOL)checkAndIncrement:(NSArray<NSString *> *)constraintIDs {
if (!constraintIDs.count) {
return;
return YES;
}

NSDate *date = self.date.now;
for (NSString *identifier in constraintIDs) {
UAOccurrence *occurrence = [UAOccurrence occurrenceWithParentConstraintID:identifier timestamp:date];

[self.pendingOccurrences addObject:occurrence];
@synchronized(self.lock) {
if ([self isOverLimit:constraintIDs]) {
return NO;
}

// Update any currently active constraints
for (UAFrequencyConstraint *constraint in self.occurrencesMap) {
if ([identifier isEqualToString:constraint.identifier]) {
NSMutableArray<UAOccurrence *> *occurrences = self.occurrencesMap[constraint];
[occurrences addObject:occurrence];
for (NSString *constraintID in constraintIDs) {
if (!self.constraintMap[constraintID]) {
// Can happen if constraint is removed mid check
continue;
}

UAOccurrence *occurrence = [UAOccurrence occurrenceWithParentConstraintID:constraintID timestamp:date];
[self.pendingOccurrences addObject:occurrence];
[self.occurrencesMap[constraintID] addObject:occurrence];
}

[self writePendingOccurrences];

return YES;
}

[self writePendingOccurrences];
return NO;
}

- (void)writePendingOccurrences {
Expand All @@ -204,6 +260,7 @@ - (void)writePendingOccurrences {

if (![self.frequencyLimitStore saveOccurrences:occurrences]) {
UA_LERR(@"Unable to save occurrences: %@", occurrences);
[self.pendingOccurrences addObjectsFromArray:occurrences];
}
}];
}
Expand Down
20 changes: 18 additions & 2 deletions Airship/AirshipAutomation/Source/UAFrequencyLimitStore.m
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,18 @@ - (BOOL)saveConstraint:(UAFrequencyConstraint *)constraint {
success = [UACoreData safeSave:context];
}];

if (!success) {
UA_LERR(@"Unable to delete constraint: %@", constraint);
}
return success;
}

- (BOOL)deleteConstraints:(NSArray<NSString *> *)constraintIDs {
if (!constraintIDs.count) {
return YES;
}

__block BOOL success;

[self.coreData safePerformBlockAndWait:^(BOOL isSafe, NSManagedObjectContext *context) {
if (!isSafe) {
success = NO;
Expand All @@ -116,6 +122,10 @@ - (BOOL)deleteConstraints:(NSArray<NSString *> *)constraintIDs {
}
}];

if (!success) {
UA_LERR(@"Unable to delete constraints: %@", constraintIDs);
}

return success;
}

Expand All @@ -127,6 +137,10 @@ - (BOOL)deleteConstraint:(UAFrequencyConstraint *)constraint {
__block NSArray<UAOccurrence *> *occurrences;

[self.coreData safePerformBlockAndWait:^(BOOL isSafe, NSManagedObjectContext *context) {
if (!isSafe) {
occurrences = @[];
return;
}
occurrences = [self occurrencesFromData:[self getOccurrencesData:constraintID context:context]];
}];

Expand All @@ -144,7 +158,9 @@ - (BOOL)saveOccurrences:(NSArray<UAOccurrence *> *)occurrences {

for (UAOccurrence *occurrence in occurrences) {
UAFrequencyConstraintData *constraintData = [self getConstraintsDataForIDs:@[occurrence.parentConstraintID] context:context].firstObject;
[self addDataForOccurrence:occurrence constraintData:constraintData context:context];
if (constraintData) {
[self addDataForOccurrence:occurrence constraintData:constraintData context:context];
}
}

success = [UACoreData safeSave:context];
Expand Down
12 changes: 10 additions & 2 deletions Airship/AirshipAutomation/Source/UAInAppAutomation.m
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,14 @@ - (void)prepareSchedule:(UASchedule *)schedule

UARetriable *checkFrequencyLimits = [UARetriable retriableWithRunBlock:^(UARetriableCompletionHandler retriableHandler) {
[self.frequencyLimitManager getFrequencyChecker:schedule.frequencyConstraintIDs completionHandler:^(UAFrequencyChecker *c) {
if (!c) {
[self.remoteDataClient notifyOnUpdate:^{
completionHandler(UAAutomationSchedulePrepareResultInvalidate);
}];
retriableHandler(UARetriableResultCancel, 0);
return;
}

checker = c;
if (checker.isOverLimit) {
// If we're over the limit, skip the rest of the prepare steps and invalidate the pipeline
Expand Down Expand Up @@ -637,8 +645,8 @@ - (void)onComponentEnableChange {
[self updateEnginePauseState];
}

- (void)updateConstraints:(NSArray<UAFrequencyConstraint *> *)constraints {
[self.frequencyLimitManager updateConstraints:constraints];
- (void)updateConstraints:(NSArray<UAFrequencyConstraint *> *)constraints completionHandler:(nonnull void (^)(BOOL))completionHandler {
[self.frequencyLimitManager updateConstraints:constraints completionHandler:completionHandler];
}

- (void)setPaused:(BOOL)paused {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ NS_ASSUME_NONNULL_BEGIN
* Called with updated constraints.
* @param constraints The updated constraints.
*/
- (void)updateConstraints:(NSArray<UAFrequencyConstraint *> *)constraints;
- (void)updateConstraints:(NSArray<UAFrequencyConstraint *> *)constraints completionHandler:(void (^)(BOOL))completionHandler;

@end

Expand Down
Loading

0 comments on commit 7a0c4b7

Please sign in to comment.