michael@0: // Copyright (c) 2012, Google Inc. michael@0: // All rights reserved. michael@0: // michael@0: // Redistribution and use in source and binary forms, with or without michael@0: // modification, are permitted provided that the following conditions are michael@0: // met: michael@0: // michael@0: // * Redistributions of source code must retain the above copyright michael@0: // notice, this list of conditions and the following disclaimer. michael@0: // * Redistributions in binary form must reproduce the above michael@0: // copyright notice, this list of conditions and the following disclaimer michael@0: // in the documentation and/or other materials provided with the michael@0: // distribution. michael@0: // * Neither the name of Google Inc. nor the names of its michael@0: // contributors may be used to endorse or promote products derived from michael@0: // this software without specific prior written permission. michael@0: // michael@0: // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: #import "BreakpadController.h" michael@0: michael@0: #import michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: michael@0: #pragma mark - michael@0: #pragma mark Private Methods michael@0: michael@0: @interface BreakpadController () michael@0: michael@0: // Init the singleton instance. michael@0: - (id)initSingleton; michael@0: michael@0: // Load a crash report and send it to the server. michael@0: - (void)sendStoredCrashReports; michael@0: michael@0: // Returns when a report can be sent. |-1| means never, |0| means that a report michael@0: // can be sent immediately, a positive number is the number of seconds to wait michael@0: // before being allowed to upload a report. michael@0: - (int)sendDelay; michael@0: michael@0: // Notifies that a report will be sent, and update the last sending time michael@0: // accordingly. michael@0: - (void)reportWillBeSent; michael@0: michael@0: @end michael@0: michael@0: #pragma mark - michael@0: #pragma mark Anonymous namespace michael@0: michael@0: namespace { michael@0: michael@0: // The name of the user defaults key for the last submission to the crash michael@0: // server. michael@0: NSString* const kLastSubmission = @"com.google.Breakpad.LastSubmission"; michael@0: michael@0: // Returns a NSString describing the current platform. michael@0: NSString* GetPlatform() { michael@0: // Name of the system call for getting the platform. michael@0: static const char kHwMachineSysctlName[] = "hw.machine"; michael@0: michael@0: NSString* result = nil; michael@0: michael@0: size_t size = 0; michael@0: if (sysctlbyname(kHwMachineSysctlName, NULL, &size, NULL, 0) || size == 0) michael@0: return nil; michael@0: google_breakpad::scoped_array machine(new char[size]); michael@0: if (sysctlbyname(kHwMachineSysctlName, machine.get(), &size, NULL, 0) == 0) michael@0: result = [NSString stringWithUTF8String:machine.get()]; michael@0: return result; michael@0: } michael@0: michael@0: } // namespace michael@0: michael@0: #pragma mark - michael@0: #pragma mark BreakpadController Implementation michael@0: michael@0: @implementation BreakpadController michael@0: michael@0: + (BreakpadController*)sharedInstance { michael@0: @synchronized(self) { michael@0: static BreakpadController* sharedInstance_ = michael@0: [[BreakpadController alloc] initSingleton]; michael@0: return sharedInstance_; michael@0: } michael@0: } michael@0: michael@0: - (id)init { michael@0: return nil; michael@0: } michael@0: michael@0: - (id)initSingleton { michael@0: self = [super init]; michael@0: if (self) { michael@0: queue_ = dispatch_queue_create("com.google.BreakpadQueue", NULL); michael@0: configuration_ = [[[NSBundle mainBundle] infoDictionary] mutableCopy]; michael@0: enableUploads_ = NO; michael@0: started_ = NO; michael@0: NSString* uploadInterval = michael@0: [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL]; michael@0: [self setUploadInterval:[uploadInterval intValue]]; michael@0: } michael@0: return self; michael@0: } michael@0: michael@0: // Since this class is a singleton, this method is not expected to be called. michael@0: - (void)dealloc { michael@0: assert(!breakpadRef_); michael@0: dispatch_release(queue_); michael@0: [configuration_ release]; michael@0: [super dealloc]; michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: - (void)start:(BOOL)onCurrentThread { michael@0: if (started_) michael@0: return; michael@0: started_ = YES; michael@0: void(^startBlock)() = ^{ michael@0: assert(!breakpadRef_); michael@0: breakpadRef_ = BreakpadCreate(configuration_); michael@0: if (breakpadRef_) { michael@0: BreakpadAddUploadParameter(breakpadRef_, @"platform", GetPlatform()); michael@0: } michael@0: }; michael@0: if (onCurrentThread) michael@0: startBlock(); michael@0: else michael@0: dispatch_async(queue_, startBlock); michael@0: } michael@0: michael@0: - (void)stop { michael@0: if (!started_) michael@0: return; michael@0: started_ = NO; michael@0: dispatch_sync(queue_, ^{ michael@0: if (breakpadRef_) { michael@0: BreakpadRelease(breakpadRef_); michael@0: breakpadRef_ = NULL; michael@0: } michael@0: }); michael@0: } michael@0: michael@0: - (void)setUploadingEnabled:(BOOL)enabled { michael@0: NSAssert(started_, michael@0: @"The controller must be started before setUploadingEnabled is called"); michael@0: dispatch_async(queue_, ^{ michael@0: if (enabled == enableUploads_) michael@0: return; michael@0: if (enabled) { michael@0: // Set this before calling doSendStoredCrashReport, because that michael@0: // calls sendDelay, which in turn checks this flag. michael@0: enableUploads_ = YES; michael@0: [self sendStoredCrashReports]; michael@0: } else { michael@0: enableUploads_ = NO; michael@0: [NSObject cancelPreviousPerformRequestsWithTarget:self michael@0: selector:@selector(sendStoredCrashReports) michael@0: object:nil]; michael@0: } michael@0: }); michael@0: } michael@0: michael@0: - (void)updateConfiguration:(NSDictionary*)configuration { michael@0: NSAssert(!started_, michael@0: @"The controller must not be started when updateConfiguration is called"); michael@0: [configuration_ addEntriesFromDictionary:configuration]; michael@0: NSString* uploadInterval = michael@0: [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL]; michael@0: if (uploadInterval) michael@0: [self setUploadInterval:[uploadInterval intValue]]; michael@0: } michael@0: michael@0: - (void)setUploadingURL:(NSString*)url { michael@0: NSAssert(!started_, michael@0: @"The controller must not be started when setUploadingURL is called"); michael@0: [configuration_ setValue:url forKey:@BREAKPAD_URL]; michael@0: } michael@0: michael@0: - (void)setUploadInterval:(int)intervalInSeconds { michael@0: NSAssert(!started_, michael@0: @"The controller must not be started when setUploadInterval is called"); michael@0: [configuration_ removeObjectForKey:@BREAKPAD_REPORT_INTERVAL]; michael@0: uploadIntervalInSeconds_ = intervalInSeconds; michael@0: if (uploadIntervalInSeconds_ < 0) michael@0: uploadIntervalInSeconds_ = 0; michael@0: } michael@0: michael@0: - (void)addUploadParameter:(NSString*)value forKey:(NSString*)key { michael@0: NSAssert(started_, michael@0: @"The controller must be started before addUploadParameter is called"); michael@0: dispatch_async(queue_, ^{ michael@0: if (breakpadRef_) michael@0: BreakpadAddUploadParameter(breakpadRef_, key, value); michael@0: }); michael@0: } michael@0: michael@0: - (void)removeUploadParameterForKey:(NSString*)key { michael@0: NSAssert(started_, @"The controller must be started before " michael@0: "removeUploadParameterForKey is called"); michael@0: dispatch_async(queue_, ^{ michael@0: if (breakpadRef_) michael@0: BreakpadRemoveUploadParameter(breakpadRef_, key); michael@0: }); michael@0: } michael@0: michael@0: - (void)withBreakpadRef:(void(^)(BreakpadRef))callback { michael@0: NSAssert(started_, michael@0: @"The controller must be started before withBreakpadRef is called"); michael@0: dispatch_async(queue_, ^{ michael@0: callback(breakpadRef_); michael@0: }); michael@0: } michael@0: michael@0: - (void)hasReportToUpload:(void(^)(BOOL))callback { michael@0: NSAssert(started_, @"The controller must be started before " michael@0: "hasReportToUpload is called"); michael@0: dispatch_async(queue_, ^{ michael@0: callback(breakpadRef_ && BreakpadHasCrashReportToUpload(breakpadRef_)); michael@0: }); michael@0: } michael@0: michael@0: #pragma mark - michael@0: michael@0: - (int)sendDelay { michael@0: if (!breakpadRef_ || uploadIntervalInSeconds_ <= 0 || !enableUploads_) michael@0: return -1; michael@0: michael@0: // To prevent overloading the crash server, crashes are not sent than one michael@0: // report every |uploadIntervalInSeconds_|. A value in the user defaults is michael@0: // used to keep the time of the last upload. michael@0: NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; michael@0: NSNumber *lastTimeNum = [userDefaults objectForKey:kLastSubmission]; michael@0: NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0; michael@0: NSTimeInterval spanSeconds = CFAbsoluteTimeGetCurrent() - lastTime; michael@0: michael@0: if (spanSeconds >= uploadIntervalInSeconds_) michael@0: return 0; michael@0: return uploadIntervalInSeconds_ - static_cast(spanSeconds); michael@0: } michael@0: michael@0: - (void)reportWillBeSent { michael@0: NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; michael@0: [userDefaults setObject:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()] michael@0: forKey:kLastSubmission]; michael@0: [userDefaults synchronize]; michael@0: } michael@0: michael@0: - (void)sendStoredCrashReports { michael@0: dispatch_async(queue_, ^{ michael@0: if (!BreakpadHasCrashReportToUpload(breakpadRef_)) michael@0: return; michael@0: michael@0: int timeToWait = [self sendDelay]; michael@0: michael@0: // Unable to ever send report. michael@0: if (timeToWait == -1) michael@0: return; michael@0: michael@0: // A report can be sent now. michael@0: if (timeToWait == 0) { michael@0: [self reportWillBeSent]; michael@0: BreakpadUploadNextReport(breakpadRef_); michael@0: michael@0: // If more reports must be sent, make sure this method is called again. michael@0: if (BreakpadHasCrashReportToUpload(breakpadRef_)) michael@0: timeToWait = uploadIntervalInSeconds_; michael@0: } michael@0: michael@0: // A report must be sent later. michael@0: if (timeToWait > 0) michael@0: [self performSelector:@selector(sendStoredCrashReports) michael@0: withObject:nil michael@0: afterDelay:timeToWait]; michael@0: }); michael@0: } michael@0: michael@0: @end