mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
712 lines
28 KiB
Objective-C
712 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 crashlog analyzer is started. since this may theoretically crash we need to track it
|
|
#define kBITCrashAnalyzerStarted @"HockeySDKCrashAnalyzerStarted"
|
|
|
|
// stores the set of crashreports that have been approved but aren't sent yet
|
|
#define kBITCrashApprovedReports @"HockeySDKCrashApprovedReports"
|
|
|
|
// stores the user name entered in the UI
|
|
#define kBITCrashUserName @"HockeySDKCrashUserName"
|
|
|
|
// stores the user email address entered in the UI
|
|
#define kBITCrashUserEmail @"HockeySDKCrashUserEmail"
|
|
|
|
|
|
@interface BITCrashManager ()
|
|
|
|
@property (nonatomic, retain) NSFileManager *fileManager;
|
|
|
|
@end
|
|
|
|
@implementation BITCrashManager
|
|
|
|
@synthesize delegate = _delegate;
|
|
@synthesize showAlwaysButton = _showAlwaysButton;
|
|
@synthesize feedbackActivated = _feedbackActivated;
|
|
@synthesize autoSubmitCrashReport = _autoSubmitCrashReport;
|
|
@synthesize didCrashInLastSession = _didCrashInLastSession;
|
|
@synthesize timeintervalCrashInLastSessionOccured = _timeintervalCrashInLastSessionOccured;
|
|
|
|
@synthesize fileManager = _fileManager;
|
|
|
|
|
|
- (id)initWithAppIdentifier:(NSString *)appIdentifier {
|
|
if ((self = [super init])) {
|
|
BITHockeyLog(@"Initializing CrashReporter");
|
|
|
|
_appIdentifier = appIdentifier;
|
|
|
|
_delegate = nil;
|
|
_userName = nil;
|
|
_userEmail = nil;
|
|
_feedbackActivated = NO;
|
|
_showAlwaysButton = NO;
|
|
_autoSubmitCrashReport = NO;
|
|
|
|
_serverResult = BITCrashStatusUnknown;
|
|
_crashIdenticalCurrentVersion = YES;
|
|
_crashData = nil;
|
|
_urlConnection = nil;
|
|
_responseData = nil;
|
|
_sendingInProgress = NO;
|
|
_didCrashInLastSession = NO;
|
|
_timeintervalCrashInLastSessionOccured = -1;
|
|
_fileManager = [[NSFileManager alloc] init];
|
|
|
|
NSString *testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kBITCrashAnalyzerStarted];
|
|
if (testValue) {
|
|
_analyzerStarted = [[NSUserDefaults standardUserDefaults] integerForKey:kBITCrashAnalyzerStarted];
|
|
} else {
|
|
_analyzerStarted = 0;
|
|
}
|
|
|
|
testValue = nil;
|
|
testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kBITCrashActivated];
|
|
if (testValue) {
|
|
_crashReportActivated = [[NSUserDefaults standardUserDefaults] boolForKey:kBITCrashActivated];
|
|
} else {
|
|
_crashReportActivated = YES;
|
|
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:YES] forKey:kBITCrashActivated];
|
|
}
|
|
|
|
if (_crashReportActivated) {
|
|
_crashFiles = [[NSMutableArray alloc] init];
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
|
_crashesDir = [[NSString stringWithFormat:@"%@", [[paths objectAtIndex:0] stringByAppendingPathComponent:@"/crashes/"]] 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];
|
|
}
|
|
|
|
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: %@.bundle is missing, will send reports automatically!", BITHOCKEYSDK_BUNDLE);
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
|
|
- (void) dealloc {
|
|
self.delegate = nil;
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:BITHockeyNetworkDidBecomeReachableNotification object:nil];
|
|
|
|
[_appIdentifier release];
|
|
_appIdentifier = nil;
|
|
|
|
[_urlConnection cancel];
|
|
[_urlConnection release];
|
|
_urlConnection = nil;
|
|
|
|
[_crashData release];
|
|
|
|
[_crashesDir release];
|
|
[_crashFiles release];
|
|
|
|
[_fileManager release];
|
|
_fileManager = nil;
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
|
|
#pragma mark - private methods
|
|
|
|
- (BOOL)autoSendCrashReports {
|
|
BOOL result = NO;
|
|
|
|
if (!self.autoSubmitCrashReport) {
|
|
if (self.isShowingAlwaysButton && [[NSUserDefaults standardUserDefaults] boolForKey: kBITCrashAutomaticallySendReports]) {
|
|
result = YES;
|
|
}
|
|
} else {
|
|
result = YES;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// begin the startup process
|
|
- (void)startManager {
|
|
if (!_sendingInProgress && [self hasPendingCrashReport]) {
|
|
_sendingInProgress = YES;
|
|
if (!BITHockeyBundle()) {
|
|
NSLog(@"WARNING: HockeySDKResource.bundle is missing, sending reports automatically!");
|
|
[self sendCrashReports];
|
|
} else if (![self autoSendCrashReports] && [self hasNonApprovedCrashReports]) {
|
|
|
|
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReporterWillShowSubmitCrashReportAlert:)]) {
|
|
[self.delegate crashReporterWillShowSubmitCrashReportAlert:self];
|
|
}
|
|
|
|
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
|
|
NSString *alertDescription = [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashDataFoundAnonymousDescription"), appName];
|
|
|
|
// the crash report is not anynomous any more
|
|
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 isShowingAlwaysButton]) {
|
|
[alertView addButtonWithTitle:BITHockeyLocalizedString(@"CrashSendReportAlways")];
|
|
}
|
|
|
|
[alertView setTag: BITCrashAlertTypeSend];
|
|
[alertView show];
|
|
[alertView release];
|
|
} else {
|
|
[self sendCrashReports];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark - PLCrashReporter
|
|
|
|
//
|
|
// Called to handle a pending crash report.
|
|
//
|
|
- (void) handleCrashReport {
|
|
PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
|
|
NSError *error = NULL;
|
|
|
|
// check if the next call ran successfully the last time
|
|
if (_analyzerStarted == 0) {
|
|
// mark the start of the routine
|
|
_analyzerStarted = 1;
|
|
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:_analyzerStarted] forKey:kBITCrashAnalyzerStarted];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
|
|
// Try loading the crash report
|
|
_crashData = [[NSData alloc] initWithData:[crashReporter loadPendingCrashReportDataAndReturnError: &error]];
|
|
|
|
NSString *cacheFilename = [NSString stringWithFormat: @"%.0f", [NSDate timeIntervalSinceReferenceDate]];
|
|
|
|
if (_crashData == nil) {
|
|
NSLog(@"Could not load crash report: %@", error);
|
|
} else {
|
|
[_crashData writeToFile:[_crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES];
|
|
|
|
// 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
|
|
_analyzerStarted = 0;
|
|
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:_analyzerStarted] forKey:kBITCrashAnalyzerStarted];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
|
|
[crashReporter purgePendingCrashReport];
|
|
return;
|
|
}
|
|
|
|
- (BOOL)hasNonApprovedCrashReports {
|
|
NSDictionary *approvedCrashReports = [[NSUserDefaults standardUserDefaults] dictionaryForKey: kBITCrashApprovedReports];
|
|
|
|
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 (_crashReportActivated) {
|
|
if ([_crashFiles count] == 0 && [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) {
|
|
[_crashFiles addObject:file];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ([_crashFiles count] > 0) {
|
|
BITHockeyLog(@"Pending crash reports found.");
|
|
return YES;
|
|
} else
|
|
return NO;
|
|
} else
|
|
return NO;
|
|
}
|
|
|
|
|
|
- (void) showCrashStatusMessage {
|
|
UIAlertView *alertView = nil;
|
|
|
|
if (_serverResult >= BITCrashStatusAssigned &&
|
|
_crashIdenticalCurrentVersion &&
|
|
BITHockeyBundle()) {
|
|
// show some feedback to the user about the crash status
|
|
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
|
|
switch (_serverResult) {
|
|
case BITCrashStatusAssigned:
|
|
alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashResponseTitle"), appName ]
|
|
message: [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashResponseNextRelease"), appName]
|
|
delegate: self
|
|
cancelButtonTitle: BITHockeyLocalizedString(@"HockeyOK")
|
|
otherButtonTitles: nil];
|
|
break;
|
|
case BITCrashStatusSubmitted:
|
|
alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashResponseTitle"), appName ]
|
|
message: [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashResponseWaitingApple"), appName]
|
|
delegate: self
|
|
cancelButtonTitle: BITHockeyLocalizedString(@"HockeyOK")
|
|
otherButtonTitles: nil];
|
|
break;
|
|
case BITCrashStatusAvailable:
|
|
alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashResponseTitle"), appName ]
|
|
message: [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashResponseAvailable"), appName]
|
|
delegate: self
|
|
cancelButtonTitle: BITHockeyLocalizedString(@"HockeyOK")
|
|
otherButtonTitles: nil];
|
|
break;
|
|
default:
|
|
alertView = nil;
|
|
break;
|
|
}
|
|
|
|
if (alertView) {
|
|
[alertView setTag: BITCrashAlertTypeFeedback];
|
|
[alertView show];
|
|
[alertView release];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark UIAlertView Delegate
|
|
|
|
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
|
|
if ([alertView tag] == BITCrashAlertTypeSend) {
|
|
switch (buttonIndex) {
|
|
case 0:
|
|
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReporterWillCancelSendingCrashReport:)]) {
|
|
[self.delegate crashReporterWillCancelSendingCrashReport: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(crashReporterWillSendCrashReportsAlways:)]) {
|
|
[self.delegate crashReporterWillSendCrashReportsAlways:self];
|
|
}
|
|
|
|
[self sendCrashReports];
|
|
break;
|
|
}
|
|
default:
|
|
_sendingInProgress = NO;
|
|
[self cleanCrashReports];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#pragma mark - Private
|
|
|
|
|
|
- (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;
|
|
}
|
|
|
|
|
|
- (void)performSendingCrashReports {
|
|
NSMutableDictionary *approvedCrashReports = [NSMutableDictionary dictionaryWithDictionary:[[NSUserDefaults standardUserDefaults] dictionaryForKey: kBITCrashApprovedReports]];
|
|
|
|
NSError *error = NULL;
|
|
|
|
NSString *username = _userName ?: @"";
|
|
NSString *email = _userEmail ?: @"";
|
|
NSString *applicationLog = @"";
|
|
|
|
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(applicationLogForCrashReporter:)]) {
|
|
applicationLog = [self.delegate applicationLogForCrashReporter:self] ?: @"";
|
|
}
|
|
|
|
NSMutableString *crashes = nil;
|
|
_crashIdenticalCurrentVersion = NO;
|
|
|
|
for (NSUInteger i=0; i < [_crashFiles count]; i++) {
|
|
NSString *filename = [_crashesDir stringByAppendingPathComponent:[_crashFiles objectAtIndex:i]];
|
|
NSData *crashData = [NSData dataWithContentsOfFile:filename];
|
|
|
|
if ([crashData length] > 0) {
|
|
PLCrashReport *report = [[[PLCrashReport alloc] initWithData:crashData error:&error] autorelease];
|
|
|
|
if (report == nil) {
|
|
NSLog(@"Could not parse crash report");
|
|
// we cannot do anything with this report, so delete it
|
|
[self.fileManager removeItemAtPath: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];
|
|
}
|
|
|
|
[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,
|
|
email,
|
|
[applicationLog stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,applicationLog.length)]];
|
|
|
|
|
|
// store this crash report as user approved, so if it fails it will retry automatically
|
|
[approvedCrashReports setObject:[NSNumber numberWithBool:YES] forKey:[_crashFiles objectAtIndex:i]];
|
|
} else {
|
|
// we cannot do anything with this report, so delete it
|
|
[self.fileManager removeItemAtPath:filename error:&error];
|
|
}
|
|
}
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:approvedCrashReports forKey:kBITCrashApprovedReports];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
|
|
if (crashes != nil) {
|
|
BITHockeyLog(@"Sending crash reports:\n%@", crashes);
|
|
[self postXML:[NSString stringWithFormat:@"<crashes>%@</crashes>", crashes]
|
|
toURL:[NSURL URLWithString:BITHOCKEYSDK_URL]];
|
|
|
|
}
|
|
}
|
|
|
|
- (void)cleanCrashReports {
|
|
NSError *error = NULL;
|
|
|
|
for (NSUInteger i=0; i < [_crashFiles count]; i++) {
|
|
[self.fileManager removeItemAtPath:[_crashesDir stringByAppendingPathComponent:[_crashFiles objectAtIndex:i]] error:&error];
|
|
}
|
|
[_crashFiles removeAllObjects];
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:nil forKey:kBITCrashApprovedReports];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
}
|
|
|
|
- (void)sendCrashReports {
|
|
// send it to the next runloop
|
|
[self performSelector:@selector(performSendingCrashReports) withObject:nil afterDelay:0.0f];
|
|
}
|
|
|
|
- (void)checkForFeedbackStatus {
|
|
NSMutableURLRequest *request = nil;
|
|
|
|
request = [NSMutableURLRequest requestWithURL:
|
|
[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes/%@",
|
|
BITHOCKEYSDK_URL,
|
|
[_appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
|
|
_feedbackRequestID
|
|
]
|
|
]];
|
|
|
|
[request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData];
|
|
[request setValue:@"Quincy/iOS" forHTTPHeaderField:@"User-Agent"];
|
|
[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
|
|
[request setTimeoutInterval: 15];
|
|
[request setHTTPMethod:@"GET"];
|
|
|
|
_serverResult = BITCrashStatusUnknown;
|
|
_statusCode = 200;
|
|
|
|
// Release when done in the delegate method
|
|
_responseData = [[NSMutableData alloc] init];
|
|
|
|
_urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
|
|
|
|
if (!_urlConnection) {
|
|
BITHockeyLog(@"Requesting feedback status could not start!");
|
|
} else {
|
|
BITHockeyLog(@"Requesting feedback status.");
|
|
}
|
|
}
|
|
|
|
- (void)postXML:(NSString*)xml toURL:(NSURL*)url {
|
|
NSMutableURLRequest *request = nil;
|
|
NSString *boundary = @"----FOO";
|
|
|
|
NSString *feedbackEnabled = @"&feedbackEnabled=no";
|
|
|
|
if ([self isFeedbackActivated]) {
|
|
feedbackEnabled = @"&feedbackEnabled=yes";
|
|
}
|
|
|
|
request = [NSMutableURLRequest requestWithURL:
|
|
[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes?sdk=%@&sdk_version=%@%@",
|
|
BITHOCKEYSDK_URL,
|
|
[_appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
|
|
BITHOCKEYSDK_NAME,
|
|
BITHOCKEYSDK_VERSION,
|
|
feedbackEnabled
|
|
]
|
|
]];
|
|
|
|
[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];
|
|
|
|
_serverResult = BITCrashStatusUnknown;
|
|
_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(crashReporterWillSendCrashReport:)]) {
|
|
[self.delegate crashReporterWillSendCrashReport: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(crashReporter:didFailWithError:)]) {
|
|
[self.delegate crashReporter: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];
|
|
|
|
_feedbackRequestID = nil;
|
|
// HockeyApp uses PList XML format
|
|
NSMutableDictionary *response = [NSPropertyListSerialization propertyListFromData:_responseData
|
|
mutabilityOption:NSPropertyListMutableContainersAndLeaves
|
|
format:nil
|
|
errorDescription:NULL];
|
|
BITHockeyLog(@"Received API response: %@", response);
|
|
|
|
_serverResult = (BITCrashStatus)[[response objectForKey:@"status"] intValue];
|
|
if ([response objectForKey:@"id"]) {
|
|
_feedbackRequestID = [[NSString alloc] initWithString:[response objectForKey:@"id"]];
|
|
_feedbackDelayInterval = [[response objectForKey:@"delay"] floatValue];
|
|
if (_feedbackDelayInterval > 0)
|
|
_feedbackDelayInterval *= 0.01;
|
|
}
|
|
|
|
if ([self isFeedbackActivated]) {
|
|
// only proceed if the server did not report any problem
|
|
if (_serverResult == BITCrashStatusQueued) {
|
|
// the report is still in the queue
|
|
if (_feedbackRequestID) {
|
|
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(checkForFeedbackStatus) object:nil];
|
|
[self performSelector:@selector(checkForFeedbackStatus) withObject:nil afterDelay:_feedbackDelayInterval];
|
|
}
|
|
} else {
|
|
[self showCrashStatusMessage];
|
|
}
|
|
|
|
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReporterDidFinishSendingCrashReport:)]) {
|
|
[self.delegate crashReporterDidFinishSendingCrashReport: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(crashReporter:didFailWithError:)]) {
|
|
[self.delegate crashReporter: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(crashReporter:didFailWithError:)]) {
|
|
[self.delegate crashReporter:self didFailWithError:error];
|
|
}
|
|
|
|
BITHockeyLog(@"ERROR: %@", [error localizedDescription]);
|
|
}
|
|
|
|
_sendingInProgress = NO;
|
|
|
|
[_responseData release];
|
|
_responseData = nil;
|
|
[_urlConnection release];
|
|
_urlConnection = nil;
|
|
}
|
|
|
|
|
|
@end
|