/* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "GDTCORLibrary/Public/GDTCORUploadPackage.h" #import #import #import "GDTCORLibrary/Private/GDTCORStorage_Private.h" #import "GDTCORLibrary/Private/GDTCORUploadCoordinator.h" #import "GDTCORLibrary/Private/GDTCORUploadPackage_Private.h" @implementation GDTCORUploadPackage { /** If YES, the package's -completeDelivery method has been called. */ BOOL _isDelivered; /** If YES, is being handled by the handler. */ BOOL _isHandled; /** A timer that will regularly check to see whether this package has expired or not. */ NSTimer *_expirationTimer; } - (instancetype)initWithTarget:(GDTCORTarget)target { self = [super init]; if (self) { _target = target; _storage = [GDTCORStorage sharedInstance]; _deliverByTime = [GDTCORClock clockSnapshotInTheFuture:180000]; _handler = [GDTCORUploadCoordinator sharedInstance]; _expirationTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(checkIfPackageIsExpired:) userInfo:nil repeats:YES]; } GDTCORLogDebug("Upload package created %@", self); return self; } - (instancetype)copy { GDTCORUploadPackage *newPackage = [[GDTCORUploadPackage alloc] initWithTarget:_target]; newPackage->_events = [_events copy]; GDTCORLogDebug("Copying UploadPackage %@ to %@", self, newPackage); return newPackage; } - (NSUInteger)hash { return [_events hash]; } - (BOOL)isEqual:(id)object { return [self hash] == [object hash]; } - (void)dealloc { [_expirationTimer invalidate]; } - (void)setStorage:(GDTCORStorage *)storage { if (storage != _storage) { _storage = storage; } } - (void)completeDelivery { if (_isDelivered) { GDTCORLogError(GDTCORMCEDeliverTwice, @"%@", @"It's an API violation to call -completeDelivery twice."); } _isDelivered = YES; if (!_isHandled && _handler && [_handler respondsToSelector:@selector(packageDelivered:successful:)]) { [_expirationTimer invalidate]; _isHandled = YES; [_handler packageDelivered:[self copy] successful:YES]; } GDTCORLogDebug("Upload package delivered: %@", self); } - (void)retryDeliveryInTheFuture { if (!_isHandled && _handler && [_handler respondsToSelector:@selector(packageDelivered:successful:)]) { [_expirationTimer invalidate]; _isHandled = YES; [_handler packageDelivered:[self copy] successful:NO]; } GDTCORLogDebug("Upload package will retry in the future: %@", self); } - (void)checkIfPackageIsExpired:(NSTimer *)timer { if ([[GDTCORClock snapshot] isAfter:_deliverByTime]) { if (_handler && [_handler respondsToSelector:@selector(packageExpired:)]) { _isHandled = YES; [_expirationTimer invalidate]; GDTCORLogDebug("Upload package expired: %@", self); [_handler packageExpired:self]; } } } #pragma mark - NSSecureCoding /** The keyed archiver key for the events property. */ static NSString *const kEventsKey = @"GDTCORUploadPackageEventsKey"; /** The keyed archiver key for the _isHandled property. */ static NSString *const kDeliverByTimeKey = @"GDTCORUploadPackageDeliveryByTimeKey"; /** The keyed archiver key for the _isHandled ivar. */ static NSString *const kIsHandledKey = @"GDTCORUploadPackageIsHandledKey"; /** The keyed archiver key for the handler property. */ static NSString *const kHandlerKey = @"GDTCORUploadPackageHandlerKey"; /** The keyed archiver key for the target property. */ static NSString *const kTargetKey = @"GDTCORUploadPackageTargetKey"; + (BOOL)supportsSecureCoding { return YES; } - (void)encodeWithCoder:(nonnull NSCoder *)aCoder { [aCoder encodeObject:_events forKey:kEventsKey]; [aCoder encodeObject:_deliverByTime forKey:kDeliverByTimeKey]; [aCoder encodeBool:_isHandled forKey:kIsHandledKey]; [aCoder encodeObject:_handler forKey:kHandlerKey]; [aCoder encodeInteger:_target forKey:kTargetKey]; } - (nullable instancetype)initWithCoder:(nonnull NSCoder *)aDecoder { // Sets a global translation mapping to decode GDTCORStoredEvent objects encoded as instances of // GDTCOREvent instead. [NSKeyedUnarchiver setClass:[GDTCOREvent class] forClassName:@"GDTCORStoredEvent"]; GDTCORTarget target = [aDecoder decodeIntegerForKey:kTargetKey]; self = [self initWithTarget:target]; if (self) { NSSet *classes = [NSSet setWithObjects:[NSSet class], [GDTCOREvent class], nil]; _events = [aDecoder decodeObjectOfClasses:classes forKey:kEventsKey]; _deliverByTime = [aDecoder decodeObjectOfClass:[GDTCORClock class] forKey:kDeliverByTimeKey]; _isHandled = [aDecoder decodeBoolForKey:kIsHandledKey]; // _handler isn't technically NSSecureCoding, because we don't know the class of this object. // but it gets decoded anyway. } return self; } @end