Merge branch 'hotfix/2.5.5'

This commit is contained in:
Andreas Linde 2012-12-06 18:48:01 +01:00
commit 90b2bfe61d
14 changed files with 219 additions and 42 deletions

View File

@ -36,7 +36,7 @@ typedef enum {
BITCrashManagerStatusAlwaysAsk = 1,
BITCrashManagerStatusAutoSend = 2
} BITCrashManagerStatus;
static NSString *kBITCrashManagerStatus = @"BITCrashManagerStatus";
extern NSString *const kBITCrashManagerStatus;
@protocol BITCrashManagerDelegate;

View File

@ -33,6 +33,7 @@
#import <UIKit/UIKit.h>
#import "HockeySDK.h"
#import "HockeySDKPrivate.h"
#import "BITHockeyHelper.h"
#import "BITCrashManagerPrivate.h"
#import "BITCrashReportTextFormatter.h"
@ -47,6 +48,8 @@
#define kBITCrashMetaUserEmail @"BITCrashMetaUserEmail"
#define kBITCrashMetaApplicationLog @"BITCrashMetaApplicationLog"
NSString *const kBITCrashManagerStatus = @"BITCrashManagerStatus";
@interface BITCrashManager ()
@ -64,7 +67,6 @@
@synthesize fileManager = _fileManager;
- (id)initWithAppIdentifier:(NSString *)appIdentifier {
if ((self = [super init])) {
_updateURL = BITHOCKEYSDK_URL;
@ -275,6 +277,15 @@
if (crashData == nil) {
BITHockeyLog(@"ERROR: Could not load crash report: %@", error);
} else {
// 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.applicationInfo respondsToSelector:@selector(applicationStartupTimestamp)]) {
if (report.systemInfo.timestamp && report.applicationInfo.applicationStartupTimestamp) {
_timeintervalCrashInLastSessionOccured = [report.systemInfo.timestamp timeIntervalSinceDate:report.applicationInfo.applicationStartupTimestamp];
}
}
[crashData writeToFile:[_crashesDir stringByAppendingPathComponent: cacheFilename] atomically:YES];
// write the meta file
@ -307,15 +318,6 @@
} 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.applicationInfo respondsToSelector:@selector(applicationStartupTimestamp)]) {
if (report.systemInfo.timestamp && report.applicationInfo.applicationStartupTimestamp) {
_timeintervalCrashInLastSessionOccured = [report.systemInfo.timestamp timeIntervalSinceDate:report.applicationInfo.applicationStartupTimestamp];
}
}
}
}
@ -365,8 +367,17 @@
if ([_crashFiles count] > 0) {
BITHockeyLog(@"INFO: %i pending crash reports found.", [_crashFiles count]);
return YES;
} else
} else {
if (_didCrashInLastSession) {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashManagerWillCancelSendingCrashReport:)]) {
[self.delegate crashManagerWillCancelSendingCrashReport:self];
}
_didCrashInLastSession = NO;
}
return NO;
}
}
@ -398,9 +409,7 @@
[self.delegate crashManagerWillShowSubmitCrashReportAlert:self];
}
NSString *appName = [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"];
if (!appName)
appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] ?: BITHockeyLocalizedString(@"HockeyAppNamePlaceholder");
NSString *appName = bit_appName(BITHockeyLocalizedString(@"HockeyAppNamePlaceholder"));
NSString *alertDescription = [NSString stringWithFormat:BITHockeyLocalizedString(@"CrashDataFoundAnonymousDescription"), appName];
// the crash report is not anynomous any more if username or useremail are not nil
@ -532,7 +541,8 @@
if ([report respondsToSelector:@selector(reportInfo)]) {
crashUUID = report.reportInfo.reportGUID ?: @"";
}
NSString *crashLogString = [BITCrashReportTextFormatter stringValueForCrashReport:report];
NSString *installString = bit_appAnonID() ?: @"";
NSString *crashLogString = [BITCrashReportTextFormatter stringValueForCrashReport:report crashReporterKey:installString];
if ([report.applicationInfo.applicationVersion compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) {
_crashIdenticalCurrentVersion = YES;
@ -569,7 +579,7 @@
description = [NSString stringWithFormat:@"%@", 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>",
[crashes appendFormat:@"<crash><applicationname>%s</applicationname><uuids>%@</uuids><bundleidentifier>%@</bundleidentifier><systemversion>%@</systemversion><platform>%@</platform><senderversion>%@</senderversion><version>%@</version><uuid>%@</uuid><log><![CDATA[%@]]></log><username>%@</username><contact>%@</contact><installstring>%@</installstring><description><![CDATA[%@]]></description></crash>",
[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"] UTF8String],
[self extractAppUUIDs:report],
report.applicationInfo.applicationIdentifier,
@ -581,6 +591,7 @@
[crashLogString stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,crashLogString.length)],
username,
useremail,
installString,
[description stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,description.length)]];

View File

@ -47,7 +47,7 @@
@interface BITCrashReportTextFormatter : NSObject {
}
+ (NSString *)stringValueForCrashReport:(PLCrashReport *)report;
+ (NSString *)stringValueForCrashReport:(PLCrashReport *)report crashReporterKey:(NSString *)crashReporterKey;
+ (NSArray *)arrayOfAppUUIDsForCrashReport:(PLCrashReport *)report;
@end

View File

@ -76,7 +76,7 @@ static NSInteger binaryImageSort(id binary1, id binary2, void *context) {
*
* @return Returns the formatted result on success, or nil if an error occurs.
*/
+ (NSString *)stringValueForCrashReport:(PLCrashReport *)report {
+ (NSString *)stringValueForCrashReport:(PLCrashReport *)report crashReporterKey:(NSString *)crashReporterKey {
NSMutableString* text = [NSMutableString string];
boolean_t lp64 = true; // quiesce GCC uninitialized value warning
@ -173,18 +173,22 @@ static NSInteger binaryImageSort(id binary1, id binary2, void *context) {
}
{
NSString *reportGUID = @"[TODO]";
NSString *reportGUID = @"TODO";
if ([report respondsToSelector:@selector(reportInfo)]) {
if (report.hasReportInfo && report.reportInfo.reportGUID != nil)
reportGUID = report.reportInfo.reportGUID;
}
NSString *reporterKey = @"TODO";
if (crashReporterKey && [crashReporterKey length] > 0)
reporterKey = crashReporterKey;
NSString *hardwareModel = @"???";
if (report.hasMachineInfo && report.machineInfo.modelName != nil)
hardwareModel = report.machineInfo.modelName;
[text appendFormat: @"Incident Identifier: %@\n", reportGUID];
[text appendFormat: @"CrashReporter Key: [TODO]\n"];
[text appendFormat: @"CrashReporter Key: %@\n", reporterKey];
[text appendFormat: @"Hardware Model: %@\n", hardwareModel];
}
@ -240,8 +244,14 @@ static NSInteger binaryImageSort(id binary1, id binary2, void *context) {
NSString *osBuild = @"???";
if (report.systemInfo.operatingSystemBuild != nil)
osBuild = report.systemInfo.operatingSystemBuild;
[text appendFormat: @"Date/Time: %@\n", report.systemInfo.timestamp];
NSLocale *enUSPOSIXLocale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease];
NSDateFormatter *rfc3339Formatter = [[[NSDateFormatter alloc] init] autorelease];
[rfc3339Formatter setLocale:enUSPOSIXLocale];
[rfc3339Formatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
[rfc3339Formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
[text appendFormat: @"Date/Time: %@\n", [rfc3339Formatter stringFromDate:report.systemInfo.timestamp]];
[text appendFormat: @"OS Version: %@ %@ (%@)\n", osName, report.systemInfo.operatingSystemVersion, osBuild];
[text appendFormat: @"Report Version: 104\n"];
}

View File

@ -33,6 +33,9 @@
NSString *bit_URLEncodedString(NSString *inputString);
NSString *bit_URLDecodedString(NSString *inputString);
NSComparisonResult bit_versionCompare(NSString *stringA, NSString *stringB);
NSString *bit_encodeAppIdentifier(NSString *inputString);
NSString *bit_appName(NSString *placeHolderString);
NSString *bit_appAnonID(void);
/* UIImage helpers */
UIImage *bit_roundedCornerImage(UIImage *inputImage, NSInteger cornerSize, NSInteger borderSize);

View File

@ -74,6 +74,44 @@ NSComparisonResult bit_versionCompare(NSString *stringA, NSString *stringB) {
return result;
}
NSString *bit_encodeAppIdentifier(NSString *inputString) {
return (inputString ? bit_URLEncodedString(inputString) : bit_URLEncodedString([[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]));
}
NSString *bit_appName(NSString *placeHolderString) {
NSString *appName = [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"];
if (!appName)
appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] ?: placeHolderString;
return appName;
}
NSString *bit_appAnonID(void) {
// try to new iOS6 identifierForAdvertising
Class advertisingClass = NSClassFromString(@"ASIdentifierManager");
if (advertisingClass) {
id adInstance = [advertisingClass performSelector:NSSelectorFromString(@"sharedManager")];
SEL adidSelector = NSSelectorFromString(@"advertisingIdentifier");
return [[adInstance performSelector:adidSelector] performSelector:NSSelectorFromString(@"UUIDString")];
}
// try to new iOS6 identifierForVendor, in case ASIdentifierManager is not linked
SEL vendoridSelector = NSSelectorFromString(@"identifierForVendor");
if ([[UIDevice currentDevice] respondsToSelector:vendoridSelector]) {
return [[[UIDevice currentDevice] performSelector:vendoridSelector] performSelector:NSSelectorFromString(@"UUIDString")];
}
// use app bundle path
NSArray *pathComponents = [[[NSBundle mainBundle] bundlePath] pathComponents];
if ([pathComponents count] > 1) {
return [pathComponents objectAtIndex:(pathComponents.count - 2)];
}
return nil;
}
#pragma mark UIImage private helpers

View File

@ -97,15 +97,11 @@
_appStoreEnvironment = NO;
_startManagerIsInvoked = NO;
// check if we are really not in an app store environment
if ([[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"]) {
_appStoreEnvironment = NO;
} else {
#if !TARGET_IPHONE_SIMULATOR
// check if we are really in an app store environment
if (![[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"]) {
_appStoreEnvironment = YES;
}
#if TARGET_IPHONE_SIMULATOR
_appStoreEnvironment = NO;
#endif
[self performSelector:@selector(validateStartManagerIsInvoked) withObject:nil afterDelay:0.0f];

View File

@ -483,7 +483,10 @@
if (NSClassFromString(@"TTNavigator")) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
parentViewController = [[NSClassFromString(@"TTNavigator") performSelector:(NSSelectorFromString(@"navigator"))] visibleViewController];
UIViewController *ttParentViewController = nil;
ttParentViewController = [[NSClassFromString(@"TTNavigator") performSelector:(NSSelectorFromString(@"navigator"))] visibleViewController];
if (ttParentViewController)
parentViewController = ttParentViewController;
#pragma clang diagnostic pop
}

View File

@ -52,8 +52,7 @@ typedef enum {
BITCrashAPIReceivedEmptyResponse,
BITCrashAPIErrorWithStatusCode
} BITCrashErrorReason;
static NSString *kBITCrashErrorDomain = @"BITCrashReporterErrorDomain";
extern NSString *const kBITCrashErrorDomain;
// Update App Versions
@ -66,7 +65,7 @@ typedef enum {
BITUpdateAPIClientAuthorizationMissingSecret,
BITUpdateAPIClientCannotCreateConnection
} BITUpdateErrorReason;
static NSString *kBITUpdateErrorDomain = @"BITUpdaterErrorDomain";
extern NSString *const kBITUpdateErrorDomain;
#endif

View File

@ -30,6 +30,8 @@
#import "HockeySDKPrivate.h"
#include <CommonCrypto/CommonDigest.h>
NSString *const kBITCrashErrorDomain = @"BITCrashReporterErrorDomain";
NSString *const kBITUpdateErrorDomain = @"BITUpdaterErrorDomain";
// Load the framework bundle.
NSBundle *BITHockeyBundle(void) {
@ -66,4 +68,4 @@ NSString *BITHockeyMD5(NSString *str) {
result[12], result[13],
result[14], result[15]
];
}
}

View File

@ -1,12 +1,12 @@
Pod::Spec.new do |s|
s.name = 'HockeySDK'
s.version = '2.5.3'
s.version = '2.5.5'
s.license = 'MIT'
s.platform = :ios, '4.0'
s.summary = 'Distribute beta apps and collect crash reports with HockeyApp.'
s.homepage = 'http://hockeyapp.net/'
s.author = { 'Andreas Linde' => 'mail@andreaslinde.de', 'Thomas Dohmke' => "thomas@dohmke.de" }
s.source = { :git => 'https://github.com/bitstadium/HockeySDK-iOS.git', :tag => '2.5.3' }
s.source = { :git => 'https://github.com/bitstadium/HockeySDK-iOS.git', :tag => '2.5.5' }
s.description = 'HockeyApp is a server to distribute beta apps and collect crash reports. ' \
'It improves the testing process dramatically and can be used for both beta ' \

View File

@ -1,3 +1,3 @@
BUILD_NUMBER = 9
VERSION_STRING = 2.5.4
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) BITHOCKEY_VERSION="@\"2.5.4\""
BUILD_NUMBER = 10
VERSION_STRING = 2.5.5
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) BITHOCKEY_VERSION="@\"2.5.5\""

View File

@ -1,3 +1,21 @@
### Version 2.5.5
- General:
- [BUGFIX] Fix some new compiler warnings
- Crash Reporting:
- [NEW] Add anonymous device ID to crash reports
- [BUGFIX] Move calculation of time interval between startup and crash further up in the code, so delegates can use this information e.g. to add it into a log file
- [BUGFIX] Call delegate also if a crash was detected but could not be read (if handling crashes on startup is implemented)
- [BUGFIX] Format timestamp in crash report to be always UTC in en_US locale
- [BUGFIX] Make sure crash reports incident identifier and key don't have special [] chars and some value
- Updating:
- [BUGFIX] Fix a problem showing the update UI animated if there TTNavigator class is present even though not being used
### Version 2.5.4
- General:

View File

@ -58,7 +58,7 @@ This example code is based on CocoaLumberjack logging into log files:
- (NSString *) getLogFilesContentWithMaxSize:(NSInteger)maxSize {
NSMutableString *description = [NSMutableString string];
NSArray *sortedLogFileInfos = [[[Logging fileLogger] logFileManager] sortedLogFileInfos];
NSArray *sortedLogFileInfos = [[_fileLogger logFileManager] sortedLogFileInfos];
NSUInteger count = [sortedLogFileInfos count];
// we start from the last one
@ -96,3 +96,100 @@ This example code is based on CocoaLumberjack logging into log files:
@end
## Advanced HowTo
If you want to restrict the log files to contain only the data from the last application run (from app start until it crashed, ignoring all suspend and resume actions), you follow these steps. (Thanks to Michael Tyson for this hint!)
4. Adjust CocoaLumberjack to initialize a new log file per application start
[_fileLogger performSelector:@selector(currentLogFileHandle)]; // init log file prior to rolling
[_fileLogger rollLogFile];
[_fileLogger performSelector:@selector(currentLogFileHandle)]; // re-init log file to apply roll
5. And when loading the prior (crashed) application run log file, stop the iteration over the log files before the most current one (which is the new session, the one after the crash):
// we start from the last (oldest) one, and stop just before the newest (which is the log for the session after the crash)
for (int index = count - 1; index > 0; index--) {
## Example
@interface BITAppDelegate () <BITCrashManagerDelegate> {}
@property (nonatomic) DDFileLogger *fileLogger;
@end
@implementation BITAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self.window makeKeyAndVisible];
// initialize before HockeySDK, so the delegate can access the file logger!
_fileLogger = [[DDFileLogger alloc] init];
_fileLogger.maximumFileSize = (1024 * 64); // 64 KByte
_fileLogger.logFileManager.maximumNumberOfLogFiles = 1;
[_fileLogger performSelector:@selector(currentLogFileHandle)]; // init log file prior to rolling
[_fileLogger rollLogFile];
[_fileLogger performSelector:@selector(currentLogFileHandle)]; // re-init log file to apply roll
[DDLog addLogger:_fileLogger];
[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"<>"
delegate:nil];
[[BITHockeyManager sharedHockeyManager] startManager];
// add Xcode console logger if not running in the App Store
if (![[BITHockeyManager sharedHockeyManager] isAppStoreEnvironment]) {
PSDDFormatter *psLogger = [[[PSDDFormatter alloc] init] autorelease];
[[DDTTYLogger sharedInstance] setLogFormatter:psLogger];
[DDLog addLogger:[DDTTYLogger sharedInstance]];
[DDLog addLogger:[DDNSLoggerLogger sharedInstance]];
}
return YES;
}
// get the log content with a maximum byte size
- (NSString *) getLogFilesContentWithMaxSize:(NSInteger)maxSize {
NSMutableString *description = [NSMutableString string];
NSArray *sortedLogFileInfos = [[_fileLogger logFileManager] sortedLogFileInfos];
NSUInteger count = [sortedLogFileInfos count];
// we start from the last (oldest) one, and stop just before the newest (which is the log for the session after the crash)
for (int index = count - 1; index > 0; index--) {
DDLogFileInfo *logFileInfo = [sortedLogFileInfos objectAtIndex:index];
NSData *logData = [[NSFileManager defaultManager] contentsAtPath:[logFileInfo filePath]];
if ([logData length] > 0) {
NSString *result = [[NSString alloc] initWithBytes:[logData bytes]
length:[logData length]
encoding: NSUTF8StringEncoding];
[description appendString:result];
[result release];
}
}
if ([description length] > maxSize) {
description = (NSMutableString *)[description substringWithRange:NSMakeRange([description length]-maxSize-1, maxSize)];
}
return description;
}
#pragma mark - BITCrashManagerDelegate
- (NSString *)applicationLogForCrashManager:(BITCrashManager *)crashManager {
NSString *description = [self getLogFilesContentWithMaxSize:5000]; // 5000 bytes should be enough!
if ([description length] == 0) {
return nil;
} else {
return description;
}
}
@end