Swiftgram/Classes/BITCrashManager.m
2012-08-07 16:14:24 +02:00

704 lines
28 KiB
Objective-C

/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Kent Sutherland
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde & Kent Sutherland.
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
#import <CrashReporter/CrashReporter.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import <UIKit/UIKit.h>
#import "HockeySDK.h"
#import "HockeySDKPrivate.h"
#import "BITCrashManagerPrivate.h"
#import "BITCrashReportTextFormatter.h"
#include <sys/sysctl.h>
// flags if the crashreporter should automatically send crashes without asking the user again
#define kBITCrashAutomaticallySendReports @"BITCrashAutomaticallySendReports"
// stores the set of crashreports that have been approved but aren't sent yet
#define kBITCrashApprovedReports @"HockeySDKCrashApprovedReports"
// keys for meta information associated to each crash
#define kBITCrashMetaUserName @"BITCrashMetaUserName"
#define kBITCrashMetaUserEmail @"BITCrashMetaUserEmail"
#define kBITCrashMetaApplicationLog @"BITCrashMetaApplicationLog"
@interface BITCrashManager ()
@property (nonatomic, retain) NSFileManager *fileManager;
@end
@implementation BITCrashManager
@synthesize delegate = _delegate;
@synthesize crashManagerStatus = _crashManagerStatus;
@synthesize showAlwaysButton = _showAlwaysButton;
@synthesize didCrashInLastSession = _didCrashInLastSession;
@synthesize timeintervalCrashInLastSessionOccured = _timeintervalCrashInLastSessionOccured;
@synthesize fileManager = _fileManager;
- (id)initWithAppIdentifier:(NSString *)appIdentifier {
if ((self = [super init])) {
BITHockeyLog(@"Initializing CrashReporter");
_updateURL = BITHOCKEYSDK_URL;
_appIdentifier = appIdentifier;
_delegate = nil;
_showAlwaysButton = NO;
_crashIdenticalCurrentVersion = YES;
_urlConnection = nil;
_responseData = nil;
_sendingInProgress = NO;
_didCrashInLastSession = NO;
_timeintervalCrashInLastSessionOccured = -1;
_approvedCrashReports = [[NSMutableDictionary alloc] init];
_fileManager = [[NSFileManager alloc] init];
_crashFiles = [[NSMutableArray alloc] init];
_crashManagerStatus = BITCrashManagerStatusAlwaysAsk;
NSString *testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kBITCrashManagerStatus];
if (testValue) {
_crashManagerStatus = [[NSUserDefaults standardUserDefaults] integerForKey:kBITCrashManagerStatus];
} else {
[[NSUserDefaults standardUserDefaults] setInteger:_crashManagerStatus forKey:kBITCrashManagerStatus];
}
// temporary directory for crashes grabbed from PLCrashReporter
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
_crashesDir = [[[paths objectAtIndex:0] stringByAppendingPathComponent:BITHOCKEY_IDENTIFIER] retain];
if (![self.fileManager fileExistsAtPath:_crashesDir]) {
NSDictionary *attributes = [NSDictionary dictionaryWithObject: [NSNumber numberWithUnsignedLong: 0755] forKey: NSFilePosixPermissions];
NSError *theError = NULL;
[self.fileManager createDirectoryAtPath:_crashesDir withIntermediateDirectories: YES attributes: attributes error: &theError];
}
_settingsFile = [[_crashesDir stringByAppendingPathComponent:BITHOCKEY_CRASH_SETTINGS] retain];
_analyzerInProgressFile = [[_crashesDir stringByAppendingPathComponent:BITHOCKEY_CRASH_ANALYZER] retain];
if ([_fileManager fileExistsAtPath:_analyzerInProgressFile]) {
NSError *error = nil;
[_fileManager removeItemAtPath:_analyzerInProgressFile error:&error];
}
// on the very first startup this will always be initialized, since the default value for _crashManagerStatus is BITCrashManagerStatusAlwaysAsk
// but we do it anyway, to be able to initialize PLCrashReporter as early as possible
if (_crashManagerStatus != BITCrashManagerStatusDisabled) {
PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
NSError *error = NULL;
// Check if we previously crashed
if ([crashReporter hasPendingCrashReport]) {
_didCrashInLastSession = YES;
[self handleCrashReport];
}
// Enable the Crash Reporter
if (![crashReporter enableCrashReporterAndReturnError: &error])
NSLog(@"WARNING: Could not enable crash reporter: %@", error);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startManager) name:BITHockeyNetworkDidBecomeReachableNotification object:nil];
}
if (!BITHockeyBundle()) {
NSLog(@"WARNING: %@ is missing, will send reports automatically!", BITHOCKEYSDK_BUNDLE);
}
}
return self;
}
- (void) dealloc {
_delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:BITHockeyNetworkDidBecomeReachableNotification object:nil];
[_updateURL release];
_updateURL = nil;
[_appIdentifier release];
_appIdentifier = nil;
[_urlConnection cancel];
[_urlConnection release];
_urlConnection = nil;
[_crashesDir release];
[_crashFiles release];
[_fileManager release];
_fileManager = nil;
[_approvedCrashReports release];
_approvedCrashReports = nil;
[_analyzerInProgressFile release];
_analyzerInProgressFile = nil;
[super dealloc];
}
- (void)setCrashManagerStatus:(BITCrashManagerStatus)crashManagerStatus {
_crashManagerStatus = crashManagerStatus;
[[NSUserDefaults standardUserDefaults] setInteger:crashManagerStatus forKey:kBITCrashManagerStatus];
}
#pragma mark - Private
- (void)saveSettings {
NSString *errorString = nil;
NSMutableDictionary *rootObj = [NSMutableDictionary dictionaryWithCapacity:2];
if (_approvedCrashReports && [_approvedCrashReports count] > 0)
[rootObj setObject:_approvedCrashReports forKey:kBITCrashApprovedReports];
NSData *plist = [NSPropertyListSerialization dataFromPropertyList:(id)rootObj
format:NSPropertyListBinaryFormat_v1_0
errorDescription:&errorString];
if (plist) {
[plist writeToFile:_settingsFile atomically:YES];
} else {
BITHockeyLog(@"ERROR: Writing settings. %@", errorString);
}
}
- (void)loadSettings {
NSString *errorString = nil;
NSPropertyListFormat format;
if (![_fileManager fileExistsAtPath:_settingsFile])
return;
NSData *plist = [NSData dataWithContentsOfFile:_settingsFile];
if (plist) {
NSDictionary *rootObj = (NSDictionary *)[NSPropertyListSerialization
propertyListFromData:plist
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format
errorDescription:&errorString];
if ([rootObj objectForKey:kBITCrashApprovedReports])
[_approvedCrashReports setDictionary:[rootObj objectForKey:kBITCrashApprovedReports]];
} else {
BITHockeyLog(@"ERROR: Reading settings. %@", errorString);
}
}
- (void)cleanCrashReports {
NSError *error = NULL;
for (NSUInteger i=0; i < [_crashFiles count]; i++) {
[_fileManager removeItemAtPath:[_crashFiles objectAtIndex:i] error:&error];
[_fileManager removeItemAtPath:[[_crashFiles objectAtIndex:i] stringByAppendingString:@".meta"] error:&error];
}
[_crashFiles removeAllObjects];
[_approvedCrashReports removeAllObjects];
[self saveSettings];
}
- (NSString *) extractAppUUIDs:(PLCrashReport *)report {
NSMutableString *uuidString = [NSMutableString string];
NSArray *uuidArray = [BITCrashReportTextFormatter arrayOfAppUUIDsForCrashReport:report];
for (NSDictionary *element in uuidArray) {
if ([element objectForKey:kBITBinaryImageKeyUUID] && [element objectForKey:kBITBinaryImageKeyArch] && [element objectForKey:kBITBinaryImageKeyUUID]) {
[uuidString appendFormat:@"<uuid type=\"%@\" arch=\"%@\">%@</uuid>",
[element objectForKey:kBITBinaryImageKeyType],
[element objectForKey:kBITBinaryImageKeyArch],
[element objectForKey:kBITBinaryImageKeyUUID]
];
}
}
return uuidString;
}
- (NSString *)getDevicePlatform {
size_t size = 0;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *answer = (char*)malloc(size);
sysctlbyname("hw.machine", answer, &size, NULL, 0);
NSString *platform = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding];
free(answer);
return platform;
}
#pragma mark - PLCrashReporter
// Called to handle a pending crash report.
- (void) handleCrashReport {
PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
NSError *error = NULL;
[self loadSettings];
// check if the next call ran successfully the last time
if (![_fileManager fileExistsAtPath:_analyzerInProgressFile]) {
// mark the start of the routine
[_fileManager createFileAtPath:_analyzerInProgressFile contents:nil attributes:nil];
[self saveSettings];
// Try loading the crash report
NSData *crashData = [[[NSData alloc] initWithData:[crashReporter loadPendingCrashReportDataAndReturnError: &error]] autorelease];
NSString *cacheFilename = [NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate]];
if (crashData == nil) {
BITHockeyLog(@"ERROR: Could not load crash report: %@", error);
} else {
[crashData writeToFile:[_crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES];
// write the meta file
NSMutableDictionary *metaDict = [NSMutableDictionary dictionaryWithCapacity:4];
NSString *username = @"";
NSString *useremail = @"";
NSString *applicationLog = @"";
NSString *errorString = nil;
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(userNameForCrashManager:)]) {
username = [self.delegate userNameForCrashManager:self] ?: @"";
}
[metaDict setObject:username forKey:kBITCrashMetaUserName];
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(userEmailForCrashManager:)]) {
useremail = [self.delegate userEmailForCrashManager:self] ?: @"";
}
[metaDict setObject:useremail forKey:kBITCrashMetaUserEmail];
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(applicationLogForCrashManager:)]) {
applicationLog = [self.delegate applicationLogForCrashManager:self] ?: @"";
}
[metaDict setObject:applicationLog forKey:kBITCrashMetaApplicationLog];
NSData *plist = [NSPropertyListSerialization dataFromPropertyList:(id)metaDict
format:NSPropertyListBinaryFormat_v1_0
errorDescription:&errorString];
if (plist) {
[plist writeToFile:[NSString stringWithFormat:@"%@.meta", [_crashesDir stringByAppendingPathComponent: cacheFilename]] atomically:YES];
} else {
BITHockeyLog(@"ERROR: Writing crash meta data failed. %@", error);
}
// get the startup timestamp from the crash report, and the file timestamp to calculate the timeinterval when the crash happened after startup
PLCrashReport *report = [[[PLCrashReport alloc] initWithData:crashData error:&error] autorelease];
if (report.systemInfo.timestamp && report.applicationInfo.applicationStartupTimestamp) {
_timeintervalCrashInLastSessionOccured = [report.systemInfo.timestamp timeIntervalSinceDate:report.applicationInfo.applicationStartupTimestamp];
}
}
}
// Purge the report
// mark the end of the routine
if ([_fileManager fileExistsAtPath:_analyzerInProgressFile]) {
[_fileManager removeItemAtPath:_analyzerInProgressFile error:&error];
}
[self saveSettings];
[crashReporter purgePendingCrashReport];
}
- (BOOL)hasNonApprovedCrashReports {
if (!_approvedCrashReports || [_approvedCrashReports count] == 0) return YES;
for (NSUInteger i=0; i < [_crashFiles count]; i++) {
NSString *filename = [_crashFiles objectAtIndex:i];
if (![_approvedCrashReports objectForKey:filename]) return YES;
}
return NO;
}
- (BOOL)hasPendingCrashReport {
if (_crashManagerStatus == BITCrashManagerStatusDisabled) return NO;
if ([self.fileManager fileExistsAtPath:_crashesDir]) {
NSString *file = nil;
NSError *error = NULL;
NSDirectoryEnumerator *dirEnum = [self.fileManager enumeratorAtPath: _crashesDir];
while ((file = [dirEnum nextObject])) {
NSDictionary *fileAttributes = [self.fileManager attributesOfItemAtPath:[_crashesDir stringByAppendingPathComponent:file] error:&error];
if ([[fileAttributes objectForKey:NSFileSize] intValue] > 0 &&
![file hasSuffix:@".analyzer"] &&
![file hasSuffix:@".plist"] &&
![file hasSuffix:@".meta"]) {
[_crashFiles addObject:[_crashesDir stringByAppendingPathComponent: file]];
}
}
}
if ([_crashFiles count] > 0) {
BITHockeyLog(@"%i pending crash reports found.", [_crashFiles count]);
return YES;
} else
return NO;
}
#pragma mark - Crash Report Processing
// begin the startup process
- (void)startManager {
if (_crashManagerStatus == BITCrashManagerStatusDisabled) return;
if (!_sendingInProgress && [self hasPendingCrashReport]) {
_sendingInProgress = YES;
if (!BITHockeyBundle()) {
[self sendCrashReports];
} else if (_crashManagerStatus != BITCrashManagerStatusAutoSend && [self hasNonApprovedCrashReports]) {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashManagerWillShowSubmitCrashReportAlert:)]) {
[self.delegate crashManagerWillShowSubmitCrashReportAlert:self];
}
NSString *appName = [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"];
if (!appName)
appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] ?: BITHockeyLocalizedString(@"HockeyAppNamePlaceholder");
NSString *alertDescription = [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashDataFoundAnonymousDescription"), appName];
// the crash report is not anynomous any more if username or useremail are not nil
NSString *username = nil;
NSString *useremail = nil;
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(userNameForCrashManager:)]) {
username = [self.delegate userNameForCrashManager:self];
}
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(userEmailForCrashManager:)]) {
useremail = [self.delegate userEmailForCrashManager:self];
}
if (username || useremail) {
alertDescription = [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashDataFoundDescription"), appName];
}
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:BITHockeyLocalizedString(@"CrashDataFoundTitle"), appName]
message:alertDescription
delegate:self
cancelButtonTitle:BITHockeyLocalizedString(@"CrashDontSendReport")
otherButtonTitles:BITHockeyLocalizedString(@"CrashSendReport"), nil];
if (self.shouldShowAlwaysButton) {
[alertView addButtonWithTitle:BITHockeyLocalizedString(@"CrashSendReportAlways")];
}
[alertView show];
[alertView release];
} else {
[self sendCrashReports];
}
}
}
- (void)sendCrashReports {
// send it to the next runloop
[self performSelector:@selector(performSendingCrashReports) withObject:nil afterDelay:1.0f];
}
- (void)performSendingCrashReports {
NSError *error = NULL;
NSMutableString *crashes = nil;
_crashIdenticalCurrentVersion = NO;
for (NSUInteger i=0; i < [_crashFiles count]; i++) {
NSString *filename = [_crashFiles objectAtIndex:i];
NSData *crashData = [NSData dataWithContentsOfFile:filename];
if ([crashData length] > 0) {
PLCrashReport *report = [[[PLCrashReport alloc] initWithData:crashData error:&error] autorelease];
if (report == nil) {
BITHockeyLog(@"Could not parse crash report");
// we cannot do anything with this report, so delete it
[_fileManager removeItemAtPath:filename error:&error];
[_fileManager removeItemAtPath:[NSString stringWithFormat:@"%@.meta", filename] error:&error];
continue;
}
NSString *crashUUID = report.reportInfo.reportGUID ?: @"";
NSString *crashLogString = [BITCrashReportTextFormatter stringValueForCrashReport:report];
if ([report.applicationInfo.applicationVersion compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) {
_crashIdenticalCurrentVersion = YES;
}
if (crashes == nil) {
crashes = [NSMutableString string];
}
NSString *username = @"";
NSString *useremail = @"";
NSString *applicationLog = @"";
NSString *description = @"";
NSString *errorString = nil;
NSPropertyListFormat format;
NSData *plist = [NSData dataWithContentsOfFile:[filename stringByAppendingString:@".meta"]];
if (plist) {
NSDictionary *metaDict = (NSDictionary *)[NSPropertyListSerialization
propertyListFromData:plist
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:&format
errorDescription:&errorString];
username = [metaDict objectForKey:kBITCrashMetaUserName] ?: @"";
useremail = [metaDict objectForKey:kBITCrashMetaUserEmail] ?: @"";
applicationLog = [metaDict objectForKey:kBITCrashMetaApplicationLog] ?: @"";
} else {
BITHockeyLog(@"ERROR: Reading crash meta data. %@", error);
}
if ([applicationLog length] > 0) {
description = [NSString stringWithFormat:@"Log:\n%@", applicationLog];
}
[crashes appendFormat:@"<crash><applicationname>%s</applicationname><uuids>%@</uuids><bundleidentifier>%@</bundleidentifier><systemversion>%@</systemversion><platform>%@</platform><senderversion>%@</senderversion><version>%@</version><uuid>%@</uuid><log><![CDATA[%@]]></log><userid>%@</userid><contact>%@</contact><description><![CDATA[%@]]></description></crash>",
[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"] UTF8String],
[self extractAppUUIDs:report],
report.applicationInfo.applicationIdentifier,
report.systemInfo.operatingSystemVersion,
[self getDevicePlatform],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
report.applicationInfo.applicationVersion,
crashUUID,
[crashLogString stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,crashLogString.length)],
username,
useremail,
[description stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,description.length)]];
// store this crash report as user approved, so if it fails it will retry automatically
[_approvedCrashReports setObject:[NSNumber numberWithBool:YES] forKey:filename];
} else {
// we cannot do anything with this report, so delete it
[_fileManager removeItemAtPath:filename error:&error];
[_fileManager removeItemAtPath:[NSString stringWithFormat:@"%@.meta", filename] error:&error];
}
}
[self saveSettings];
if (crashes != nil) {
BITHockeyLog(@"Sending crash reports:\n%@", crashes);
[self postXML:[NSString stringWithFormat:@"<crashes>%@</crashes>", crashes]];
}
}
#pragma mark - UIAlertView Delegate
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
switch (buttonIndex) {
case 0:
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashManagerWillCancelSendingCrashReport:)]) {
[self.delegate crashManagerWillCancelSendingCrashReport:self];
}
_sendingInProgress = NO;
[self cleanCrashReports];
break;
case 1:
[self sendCrashReports];
break;
case 2: {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kBITCrashAutomaticallySendReports];
[[NSUserDefaults standardUserDefaults] synchronize];
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashManagerWillSendCrashReportsAlways:)]) {
[self.delegate crashManagerWillSendCrashReportsAlways:self];
}
[self sendCrashReports];
break;
}
default:
_sendingInProgress = NO;
[self cleanCrashReports];
break;
}
}
#pragma mark - Networking
- (void)postXML:(NSString*)xml {
NSMutableURLRequest *request = nil;
NSString *boundary = @"----FOO";
request = [NSMutableURLRequest requestWithURL:
[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes?sdk=%@&sdk_version=%@&feedbackEnabled=no",
_updateURL,
[_appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
BITHOCKEY_NAME,
BITHOCKEY_VERSION
]
]];
[request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData];
[request setValue:@"Quincy/iOS" forHTTPHeaderField:@"User-Agent"];
[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
[request setTimeoutInterval: 15];
[request setHTTPMethod:@"POST"];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
[request setValue:contentType forHTTPHeaderField:@"Content-type"];
NSMutableData *postBody = [NSMutableData data];
[postBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[@"Content-Disposition: form-data; name=\"xml\"; filename=\"crash.xml\"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[[NSString stringWithFormat:@"Content-Type: text/xml\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[xml dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBody:postBody];
_statusCode = 200;
//Release when done in the delegate method
_responseData = [[NSMutableData alloc] init];
_urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (!_urlConnection) {
BITHockeyLog(@"Sending crash reports could not start!");
_sendingInProgress = NO;
} else {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashManagerWillSendCrashReport:)]) {
[self.delegate crashManagerWillSendCrashReport:self];
}
BITHockeyLog(@"Sending crash reports started.");
}
}
#pragma mark - NSURLConnection Delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
_statusCode = [(NSHTTPURLResponse *)response statusCode];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashManager:didFailWithError:)]) {
[self.delegate crashManager:self didFailWithError:error];
}
BITHockeyLog(@"ERROR: %@", [error localizedDescription]);
_sendingInProgress = NO;
[_responseData release];
_responseData = nil;
[_urlConnection release];
_urlConnection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSError *error = nil;
if (_statusCode >= 200 && _statusCode < 400 && _responseData != nil && [_responseData length] > 0) {
[self cleanCrashReports];
// HockeyApp uses PList XML format
NSMutableDictionary *response = [NSPropertyListSerialization propertyListFromData:_responseData
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:nil
errorDescription:NULL];
BITHockeyLog(@"Received API response: %@", response);
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashManagerDidFinishSendingCrashReport:)]) {
[self.delegate crashManagerDidFinishSendingCrashReport:self];
}
} else if (_statusCode == 400) {
[self cleanCrashReports];
error = [NSError errorWithDomain:kBITCrashErrorDomain
code:BITCrashAPIAppVersionRejected
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The server rejected receiving crash reports for this app version!", NSLocalizedDescriptionKey, nil]];
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashManager:didFailWithError:)]) {
[self.delegate crashManager:self didFailWithError:error];
}
BITHockeyLog(@"ERROR: %@", [error localizedDescription]);
} else {
if (_responseData == nil || [_responseData length] == 0) {
error = [NSError errorWithDomain:kBITCrashErrorDomain
code:BITCrashAPIReceivedEmptyResponse
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Sending failed with an empty response!", NSLocalizedDescriptionKey, nil]];
} else {
error = [NSError errorWithDomain:kBITCrashErrorDomain
code:BITCrashAPIErrorWithStatusCode
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Sending failed with status code: %i", _statusCode], NSLocalizedDescriptionKey, nil]];
}
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashManager:didFailWithError:)]) {
[self.delegate crashManager:self didFailWithError:error];
}
BITHockeyLog(@"ERROR: %@", [error localizedDescription]);
}
_sendingInProgress = NO;
[_responseData release];
_responseData = nil;
[_urlConnection release];
_urlConnection = nil;
}
@end