Merge branch 'feature/refactor' into develop

This commit is contained in:
Andreas Linde 2012-08-06 19:38:15 +02:00
commit 929b942fde
135 changed files with 7806 additions and 5058 deletions

2
.gitignore vendored
View file

@ -18,3 +18,5 @@ profile
*.swp
*~.nib
profile
documentation/

View file

@ -0,0 +1,53 @@
/*
* Author: Peter Steinberger
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde, Peter Steinberger.
* 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 <Foundation/Foundation.h>
@interface BITAppVersionMetaInfo : NSObject {
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *version;
@property (nonatomic, copy) NSString *shortVersion;
@property (nonatomic, copy) NSString *notes;
@property (nonatomic, copy) NSDate *date;
@property (nonatomic, copy) NSNumber *size;
@property (nonatomic, copy) NSNumber *mandatory;
- (NSString *)nameAndVersionString;
- (NSString *)versionString;
- (NSString *)dateString;
- (NSString *)sizeInMB;
- (NSString *)notesOrEmptyString;
- (void)setDateWithTimestamp:(NSTimeInterval)timestamp;
- (BOOL)isValid;
- (BOOL)isEqualToAppVersionMetaInfo:(BITAppVersionMetaInfo *)anAppVersionMetaInfo;
+ (BITAppVersionMetaInfo *)appVersionMetaInfoFromDict:(NSDictionary *)dict;
@end

View file

@ -0,0 +1,186 @@
/*
* Author: Peter Steinberger
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde, Peter Steinberger.
* 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 "BITAppVersionMetaInfo.h"
#import "HockeySDKPrivate.h"
@implementation BITAppVersionMetaInfo
@synthesize name = _name;
@synthesize version = _version;
@synthesize shortVersion = _shortVersion;
@synthesize notes = _notes;
@synthesize date = _date;
@synthesize size = _size;
@synthesize mandatory = _mandatory;
#pragma mark - Static
+ (BITAppVersionMetaInfo *)appVersionMetaInfoFromDict:(NSDictionary *)dict {
BITAppVersionMetaInfo *appVersionMetaInfo = [[[[self class] alloc] init] autorelease];
if ([dict isKindOfClass:[NSDictionary class]]) {
appVersionMetaInfo.name = [dict objectForKey:@"title"];
appVersionMetaInfo.version = [dict objectForKey:@"version"];
appVersionMetaInfo.shortVersion = [dict objectForKey:@"shortversion"];
[appVersionMetaInfo setDateWithTimestamp:[[dict objectForKey:@"timestamp"] doubleValue]];
appVersionMetaInfo.size = [dict objectForKey:@"appsize"];
appVersionMetaInfo.notes = [dict objectForKey:@"notes"];
appVersionMetaInfo.mandatory = [dict objectForKey:@"mandatory"];
}
return appVersionMetaInfo;
}
#pragma mark - NSObject
- (void)dealloc {
[_name release];
[_version release];
[_shortVersion release];
[_notes release];
[_date release];
[_size release];
[_mandatory release];
[super dealloc];
}
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [self isEqualToAppVersionMetaInfo:other];
}
- (BOOL)isEqualToAppVersionMetaInfo:(BITAppVersionMetaInfo *)anAppVersionMetaInfo {
if (self == anAppVersionMetaInfo)
return YES;
if (self.name != anAppVersionMetaInfo.name && ![self.name isEqualToString:anAppVersionMetaInfo.name])
return NO;
if (self.version != anAppVersionMetaInfo.version && ![self.version isEqualToString:anAppVersionMetaInfo.version])
return NO;
if (self.shortVersion != anAppVersionMetaInfo.shortVersion && ![self.shortVersion isEqualToString:anAppVersionMetaInfo.shortVersion])
return NO;
if (self.notes != anAppVersionMetaInfo.notes && ![self.notes isEqualToString:anAppVersionMetaInfo.notes])
return NO;
if (self.date != anAppVersionMetaInfo.date && ![self.date isEqualToDate:anAppVersionMetaInfo.date])
return NO;
if (self.size != anAppVersionMetaInfo.size && ![self.size isEqualToNumber:anAppVersionMetaInfo.size])
return NO;
if (self.mandatory != anAppVersionMetaInfo.mandatory && ![self.mandatory isEqualToNumber:anAppVersionMetaInfo.mandatory])
return NO;
return YES;
}
#pragma mark - NSCoder
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeObject:self.version forKey:@"version"];
[encoder encodeObject:self.shortVersion forKey:@"shortVersion"];
[encoder encodeObject:self.notes forKey:@"notes"];
[encoder encodeObject:self.date forKey:@"date"];
[encoder encodeObject:self.size forKey:@"size"];
[encoder encodeObject:self.mandatory forKey:@"mandatory"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) {
self.name = [decoder decodeObjectForKey:@"name"];
self.version = [decoder decodeObjectForKey:@"version"];
self.shortVersion = [decoder decodeObjectForKey:@"shortVersion"];
self.notes = [decoder decodeObjectForKey:@"notes"];
self.date = [decoder decodeObjectForKey:@"date"];
self.size = [decoder decodeObjectForKey:@"size"];
self.mandatory = [decoder decodeObjectForKey:@"mandatory"];
}
return self;
}
#pragma mark - Properties
- (NSString *)nameAndVersionString {
NSString *appNameAndVersion = [NSString stringWithFormat:@"%@ %@", self.name, [self versionString]];
return appNameAndVersion;
}
- (NSString *)versionString {
NSString *shortString = ([self.shortVersion respondsToSelector:@selector(length)] && [self.shortVersion length]) ? [NSString stringWithFormat:@"%@", self.shortVersion] : @"";
NSString *versionString = [shortString length] ? [NSString stringWithFormat:@" (%@)", self.version] : self.version;
return [NSString stringWithFormat:@"%@ %@%@", BITHockeyLocalizedString(@"UpdateVersion"), shortString, versionString];
}
- (NSString *)dateString {
NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setDateStyle:NSDateFormatterMediumStyle];
return [formatter stringFromDate:self.date];
}
- (NSString *)sizeInMB {
if ([_size isKindOfClass: [NSNumber class]] && [_size doubleValue] > 0) {
double appSizeInMB = [_size doubleValue]/(1024*1024);
NSString *appSizeString = [NSString stringWithFormat:@"%.1f MB", appSizeInMB];
return appSizeString;
}
return @"0 MB";
}
- (void)setDateWithTimestamp:(NSTimeInterval)timestamp {
if (timestamp) {
NSDate *appDate = [NSDate dateWithTimeIntervalSince1970:timestamp];
self.date = appDate;
} else {
self.date = nil;
}
}
- (NSString *)notesOrEmptyString {
if (self.notes) {
return self.notes;
}else {
return [NSString string];
}
}
// a valid app needs at least following properties: name, version, date
- (BOOL)isValid {
BOOL valid = [self.name length] && [self.version length] && self.date;
return valid;
}
@end

187
Classes/BITCrashManager.h Normal file
View file

@ -0,0 +1,187 @@
/*
* 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 <Foundation/Foundation.h>
// hockey crash manager status
typedef enum {
BITCrashManagerStatusDisabled = 0,
BITCrashManagerStatusAlwaysAsk = 1,
BITCrashManagerStatusAutoSend = 2
} BITCrashManagerStatus;
static NSString *kBITCrashManagerStatus = @"BITCrashManagerStatus";
@protocol BITCrashManagerDelegate;
/**
The crash reporting module.
This is the HockeySDK module for handling crash reports, including when distributed via the App Store.
As a foundation it is using the open source, reliable and async-safe crash reporting framework
[PLCrashReporter](https://code.google.com/p/plcrashreporter/).
This module works as a wrapper around the underlying crash reporting framework and provides functionality to
detect new crashes, queues them if networking is not available, present a user interface to approve sending
the reports to the HockeyApp servers and more.
It also provides options to add additional meta information to each crash report, like `userName`, `userEmail`,
additional textual log information via `BITCrashManagerDelegate` protocol and a way to detect startup crashes so
you can adjust your startup process to get these crash reports too and delay your app initialization.
Crashes are send the next time the app starts. If `autoSubmitCrashReport` is enabled, crashes will be send
without any user interaction, otherwise an alert will appear allowing the users to decide wether they want
to send the report or not. This module is not sending the reports right when the crash happens deliberately,
because if is not safe to implement such a mechanism while being async-safe (any Objective-C code is _NOT_
async-safe!) and not causing more danger like a deadlock of the device, than helping. We found that users
do start the app again because most don't know what happened, and you will get by far most of the reports.
Sending the reports on startup is done asynchronously (non-blocking). This is the only safe way to ensure
that the app won't be possibly killed by the iOS watchdog process, because startup could take too long
and the app could not react to any user input when network conditions are bad or connectivity might be
very slow.
More background information on this topic can be found in the following blog post by Landon Fuller, the
developer of [PLCrashReporter](https://code.google.com/p/plcrashreporter/), about writing reliable and
safe crash reporting: [Reliable Crash Reporting](http://goo.gl/WvTBR)
*/
@interface BITCrashManager : NSObject {
@private
NSString *_appIdentifier;
NSMutableDictionary *_approvedCrashReports;
NSMutableArray *_crashFiles;
NSString *_crashesDir;
NSString *_settingsFile;
NSString *_analyzerInProgressFile;
NSFileManager *_fileManager;
BOOL _crashIdenticalCurrentVersion;
NSMutableData *_responseData;
NSInteger _statusCode;
NSURLConnection *_urlConnection;
BOOL _sendingInProgress;
}
///-----------------------------------------------------------------------------
/// @name Delegate
///-----------------------------------------------------------------------------
/**
Sets the optional `BITCrashManagerDelegate` delegate.
*/
@property (nonatomic, assign) id <BITCrashManagerDelegate> delegate;
///-----------------------------------------------------------------------------
/// @name Configuration
///-----------------------------------------------------------------------------
/** Set the default status of the Crash Manager
Defines if the crash reporting feature should be disabled, ask the user before
sending each crash report or send crash reportings automatically without
asking.. This must be assigned one of the following:
- `BITCrashManagerStatusDisabled`: Crash reporting is disabled
- `BITCrashManagerStatusAlwaysAsk`: User is asked each time before sending
- `BITCrashManagerStatusAutoSend`: Each crash report is send automatically
The default value is `BITCrashManagerStatusAlwaysAsk`. You can allow the user
to switch from `BITCrashManagerStatusAlwaysAsk` to
`BITCrashManagerStatusAutoSend` by setting `showAlwaysButton`
to _YES_.
The current value is always stored in User Defaults with the key
"BITCrashManagerStatus".
If you intend to implement a user setting to let them enable or disable
crash reporting, this delegate should be used to return that value. You also
have to make sure the new value is stored in the UserDefaults with the key
"BITCrashManagerStatus".
@see showAlwaysButton
*/
@property (nonatomic, assign) BITCrashManagerStatus crashManagerStatus;
/**
Flag that determines if an "Always" option should be shown
If enabled the crash reporting alert will also present an "Always" option, so
the user doesn't have to approve every single crash over and over again.
If `autoSubmitCrashReport` is enabled, this property has no effect, since no
alert will be presented.
@warning This will cause the dialog not to show the alert description text landscape mode!
@see crashManagerStatus
*/
@property (nonatomic, assign, getter=shouldShowAlwaysButton) BOOL showAlwaysButton;
///-----------------------------------------------------------------------------
/// @name Crash Meta Information
///-----------------------------------------------------------------------------
/**
Indicates if the app crash in the previous session
Use this on startup, to check if the app starts the first time after it crashed
previously. You can use this also to disable specific events, like asking
the user to rate your app.
*/
@property (nonatomic, readonly) BOOL didCrashInLastSession;
/**
Provides the time between startup and crash in seconds
Use this in together with `didCrashInLastSession` to detect if the app crashed very
early after startup. This can be used to delay app initialization until the crash
report has been sent to the server or if you want to do any other actions like
cleaning up some cache data etc.
The `BITCrashManagerDelegate` protocol provides some delegates to inform if sending
a crash report was finished successfully, ended in error or was cancelled by the user.
*Default*: _-1_
@see didCrashInLastSession
@see BITCrashManagerDelegate
*/
@property (nonatomic, readonly) NSTimeInterval timeintervalCrashInLastSessionOccured;
@end

701
Classes/BITCrashManager.m Normal file
View file

@ -0,0 +1,701 @@
/*
* 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] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
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

View file

@ -0,0 +1,128 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* 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 <Foundation/Foundation.h>
/**
The `BITCrashManagerDelegate` formal protocol defines methods further configuring
the behaviour of `BITCrashManager`.
*/
@protocol BITCrashManagerDelegate <NSObject>
@optional
///-----------------------------------------------------------------------------
/// @name Additional meta data
///-----------------------------------------------------------------------------
/** Return any log string based data the crash report being processed should contain
@param crashManager The `BITCrashManager` instance invoking this delegate
@see userNameForCrashManager:
@see userEmailForCrashManager:
*/
-(NSString *)applicationLogForCrashManager:(BITCrashManager *)crashManager;
/** Return the user name or userid that should be send along each crash report
@param crashManager The `BITCrashManager` instance invoking this delegate
@see applicationLogForCrashManager:
@see userEmailForCrashManager:
@warning When returning a non nil value, crash reports are not anonymous any
more and the alerts will not show the "anonymous" word!
*/
-(NSString *)userNameForCrashManager:(BITCrashManager *)crashManager;
/** Return the users email address that should be send along each crash report
@param crashManager The `BITCrashManager` instance invoking this delegate
@see applicationLogForCrashManager:
@see userNameForCrashManager:
@warning When returning a non nil value, crash reports are not anonymous any
more and the alerts will not show the "anonymous" word!
*/
-(NSString *)userEmailForCrashManager:(BITCrashManager *)crashManager;
///-----------------------------------------------------------------------------
/// @name Alert
///-----------------------------------------------------------------------------
/** Invoked before the user is asked to send a crash report, so you can do additional actions.
E.g. to make sure not to ask the user for an app rating :)
@param crashManager The `BITCrashManager` instance invoking this delegate
*/
-(void)crashManagerWillShowSubmitCrashReportAlert:(BITCrashManager *)crashManager;
/** Invoked after the user did choose _NOT_ to send a crash in the alert
@param crashManager The `BITCrashManager` instance invoking this delegate
*/
-(void)crashManagerWillCancelSendingCrashReport:(BITCrashManager *)crashManager;
/** Invoked after the user did choose to send crashes always in the alert
@param crashManager The `BITCrashManager` instance invoking this delegate
*/
-(void)crashManagerWillSendCrashReportsAlways:(BITCrashManager *)crashManager;
///-----------------------------------------------------------------------------
/// @name Networking
///-----------------------------------------------------------------------------
/** Invoked right before sending crash reports will start
@param crashManager The `BITCrashManager` instance invoking this delegate
*/
- (void)crashManagerWillSendCrashReport:(BITCrashManager *)crashManager;
/** Invoked after sending crash reports failed
@param crashManager The `BITCrashManager` instance invoking this delegate
@param error The error returned from the NSURLConnection call or `kBITCrashErrorDomain`
with reason of type `BITCrashErrorReason`.
*/
- (void)crashManager:(BITCrashManager *)crashManager didFailWithError:(NSError *)error;
/** Invoked after sending crash reports succeeded
@param crashManager The `BITCrashManager` instance invoking this delegate
*/
- (void)crashManagerDidFinishSendingCrashReport:(BITCrashManager *)crashManager;
@end

View file

@ -0,0 +1,44 @@
/*
* 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 <Foundation/Foundation.h>
@interface BITCrashManager () {
}
// set the server URL
@property (nonatomic, retain) NSString *updateURL;
- (id)initWithAppIdentifier:(NSString *)appIdentifier;
- (void)startManager;
@end

View file

@ -0,0 +1,53 @@
/*
* Authors:
* Landon Fuller <landonf@plausiblelabs.com>
* Damian Morris <damian@moso.com.au>
* Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2008-2012 Plausible Labs Cooperative, Inc.
* Copyright (c) 2010 MOSO Corporation, Pty Ltd.
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* 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 <Foundation/Foundation.h>
#import <CrashReporter/PLCrashReport.h>
// Dictionary keys for array elements returned by arrayOfAppUUIDsForCrashReport:
#ifndef kBITBinaryImageKeyUUID
#define kBITBinaryImageKeyUUID @"uuid"
#define kBITBinaryImageKeyArch @"arch"
#define kBITBinaryImageKeyType @"type"
#endif
@interface BITCrashReportTextFormatter : NSObject {
}
+ (NSString *)stringValueForCrashReport:(PLCrashReport *)report;
+ (NSArray *)arrayOfAppUUIDsForCrashReport:(PLCrashReport *)report;
@end

View file

@ -0,0 +1,604 @@
/*
* Authors:
* Landon Fuller <landonf@plausiblelabs.com>
* Damian Morris <damian@moso.com.au>
* Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2008-2012 Plausible Labs Cooperative, Inc.
* Copyright (c) 2010 MOSO Corporation, Pty Ltd.
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* 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 "BITCrashReportTextFormatter.h"
@interface BITCrashReportTextFormatter (PrivateAPI)
NSInteger binaryImageSort(id binary1, id binary2, void *context);
+ (NSString *)formatStackFrame:(PLCrashReportStackFrameInfo *)frameInfo
frameIndex:(NSUInteger)frameIndex
report:(PLCrashReport *)report;
@end
/**
* Formats PLCrashReport data as human-readable text.
*/
@implementation BITCrashReportTextFormatter
/**
* Formats the provided @a report as human-readable text in the given @a textFormat, and return
* the formatted result as a string.
*
* @param report The report to format.
*
* @return Returns the formatted result on success, or nil if an error occurs.
*/
+ (NSString *)stringValueForCrashReport:(PLCrashReport *)report {
NSMutableString* text = [NSMutableString string];
boolean_t lp64 = true; // quiesce GCC uninitialized value warning
/* Header */
/* Map to apple style OS nane */
NSString *osName;
switch (report.systemInfo.operatingSystem) {
case PLCrashReportOperatingSystemMacOSX:
osName = @"Mac OS X";
break;
case PLCrashReportOperatingSystemiPhoneOS:
osName = @"iPhone OS";
break;
case PLCrashReportOperatingSystemiPhoneSimulator:
osName = @"Mac OS X";
break;
default:
osName = [NSString stringWithFormat: @"Unknown (%d)", report.systemInfo.operatingSystem];
break;
}
/* Map to Apple-style code type, and mark whether architecture is LP64 (64-bit) */
NSString *codeType = nil;
{
/* Attempt to derive the code type from the binary images */
for (PLCrashReportBinaryImageInfo *image in report.images) {
/* Skip images with no specified type */
if (image.codeType == nil)
continue;
/* Skip unknown encodings */
if (image.codeType.typeEncoding != PLCrashReportProcessorTypeEncodingMach)
continue;
switch (image.codeType.type) {
case CPU_TYPE_ARM:
codeType = @"ARM";
lp64 = false;
break;
case CPU_TYPE_X86:
codeType = @"X86";
lp64 = false;
break;
case CPU_TYPE_X86_64:
codeType = @"X86-64";
lp64 = true;
break;
case CPU_TYPE_POWERPC:
codeType = @"PPC";
lp64 = false;
break;
default:
// Do nothing, handled below.
break;
}
/* Stop immediately if code type was discovered */
if (codeType != nil)
break;
}
/* If we were unable to determine the code type, fall back on the legacy architecture value. */
if (codeType == nil) {
switch (report.systemInfo.architecture) {
case PLCrashReportArchitectureARMv6:
case PLCrashReportArchitectureARMv7:
codeType = @"ARM";
lp64 = false;
break;
case PLCrashReportArchitectureX86_32:
codeType = @"X86";
lp64 = false;
break;
case PLCrashReportArchitectureX86_64:
codeType = @"X86-64";
lp64 = true;
break;
case PLCrashReportArchitecturePPC:
codeType = @"PPC";
lp64 = false;
break;
default:
codeType = [NSString stringWithFormat: @"Unknown (%d)", report.systemInfo.architecture];
lp64 = true;
break;
}
}
}
{
NSString *reportGUID = @"[TODO]";
if (report.hasReportInfo && report.reportInfo.reportGUID != nil)
reportGUID = report.reportInfo.reportGUID;
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: @"Hardware Model: %@\n", hardwareModel];
}
/* Application and process info */
{
NSString *unknownString = @"???";
NSString *processName = unknownString;
NSString *processId = unknownString;
NSString *processPath = unknownString;
NSString *parentProcessName = unknownString;
NSString *parentProcessId = unknownString;
/* Process information was not available in earlier crash report versions */
if (report.hasProcessInfo) {
/* Process Name */
if (report.processInfo.processName != nil)
processName = report.processInfo.processName;
/* PID */
processId = [[NSNumber numberWithUnsignedInteger: report.processInfo.processID] stringValue];
/* Process Path */
if (report.processInfo.processPath != nil) {
processPath = report.processInfo.processPath;
/* Remove username from the path */
processPath = [processPath stringByAbbreviatingWithTildeInPath];
if ([[processPath substringToIndex:1] isEqualToString:@"~"])
processPath = [NSString stringWithFormat:@"/Users/USER%@", [processPath substringFromIndex:1]];
}
/* Parent Process Name */
if (report.processInfo.parentProcessName != nil)
parentProcessName = report.processInfo.parentProcessName;
/* Parent Process ID */
parentProcessId = [[NSNumber numberWithUnsignedInteger: report.processInfo.parentProcessID] stringValue];
}
[text appendFormat: @"Process: %@ [%@]\n", processName, processId];
[text appendFormat: @"Path: %@\n", processPath];
[text appendFormat: @"Identifier: %@\n", report.applicationInfo.applicationIdentifier];
[text appendFormat: @"Version: %@\n", report.applicationInfo.applicationVersion];
[text appendFormat: @"Code Type: %@\n", codeType];
[text appendFormat: @"Parent Process: %@ [%@]\n", parentProcessName, parentProcessId];
}
[text appendString: @"\n"];
/* System info */
{
NSString *osBuild = @"???";
if (report.systemInfo.operatingSystemBuild != nil)
osBuild = report.systemInfo.operatingSystemBuild;
[text appendFormat: @"Date/Time: %@\n", report.systemInfo.timestamp];
[text appendFormat: @"OS Version: %@ %@ (%@)\n", osName, report.systemInfo.operatingSystemVersion, osBuild];
[text appendFormat: @"Report Version: 104\n"];
}
[text appendString: @"\n"];
/* Exception code */
[text appendFormat: @"Exception Type: %@\n", report.signalInfo.name];
[text appendFormat: @"Exception Codes: %@ at 0x%" PRIx64 "\n", report.signalInfo.code, report.signalInfo.address];
for (PLCrashReportThreadInfo *thread in report.threads) {
if (thread.crashed) {
[text appendFormat: @"Crashed Thread: %ld\n", (long) thread.threadNumber];
break;
}
}
[text appendString: @"\n"];
/* Uncaught Exception */
if (report.hasExceptionInfo) {
[text appendFormat: @"Application Specific Information:\n"];
[text appendFormat: @"*** Terminating app due to uncaught exception '%@', reason: '%@'\n",
report.exceptionInfo.exceptionName, report.exceptionInfo.exceptionReason];
[text appendString: @"\n"];
}
/* If an exception stack trace is available, output a pseudo-thread to provide the frame info */
if (report.exceptionInfo != nil && report.exceptionInfo.stackFrames != nil && [report.exceptionInfo.stackFrames count] > 0) {
PLCrashReportExceptionInfo *exception = report.exceptionInfo;
/* Write out the frames */
NSUInteger numberBlankStackFrames = 0;
for (NSUInteger frame_idx = 0; frame_idx < [exception.stackFrames count]; frame_idx++) {
PLCrashReportStackFrameInfo *frameInfo = [exception.stackFrames objectAtIndex: frame_idx];
NSString *formattedStackFrame = [self formatStackFrame: frameInfo frameIndex: frame_idx - numberBlankStackFrames report: report];
if (formattedStackFrame) {
if (frame_idx - numberBlankStackFrames == 0) {
/* Create the pseudo-thread header. We use the named thread format to mark this thread */
[text appendString: @"Last Exception Backtrace:\n"];
}
[text appendString: formattedStackFrame];
} else {
numberBlankStackFrames++;
}
}
[text appendString: @"\n\n"];
}
/* Threads */
PLCrashReportThreadInfo *crashed_thread = nil;
NSInteger maxThreadNum = 0;
for (PLCrashReportThreadInfo *thread in report.threads) {
/* Write out the frames */
NSUInteger numberBlankStackFrames = 0;
for (NSUInteger frame_idx = 0; frame_idx < [thread.stackFrames count]; frame_idx++) {
PLCrashReportStackFrameInfo *frameInfo = [thread.stackFrames objectAtIndex: frame_idx];
NSString *formattedStackFrame = [self formatStackFrame: frameInfo frameIndex: frame_idx report: report];
if (formattedStackFrame) {
if (frame_idx - numberBlankStackFrames == 0) {
/* Create the thread header. */
if (thread.crashed) {
[text appendFormat: @"Thread %ld Crashed:\n", (long) thread.threadNumber];
crashed_thread = thread;
} else {
[text appendFormat: @"Thread %ld:\n", (long) thread.threadNumber];
}
}
[text appendString: formattedStackFrame];
} else {
numberBlankStackFrames++;
}
}
[text appendString: @"\n"];
/* Track the highest thread number */
maxThreadNum = MAX(maxThreadNum, thread.threadNumber);
}
/* Registers */
if (crashed_thread != nil) {
[text appendFormat: @"Thread %ld crashed with %@ Thread State:\n", (long) crashed_thread.threadNumber, codeType];
int regColumn = 0;
for (PLCrashReportRegisterInfo *reg in crashed_thread.registers) {
NSString *reg_fmt;
/* Use 32-bit or 64-bit fixed width format for the register values */
if (lp64)
reg_fmt = @"%6s: 0x%016" PRIx64 " ";
else
reg_fmt = @"%6s: 0x%08" PRIx64 " ";
/* Remap register names to match Apple's crash reports */
NSString *regName = reg.registerName;
if (report.machineInfo != nil && report.machineInfo.processorInfo.typeEncoding == PLCrashReportProcessorTypeEncodingMach) {
PLCrashReportProcessorInfo *pinfo = report.machineInfo.processorInfo;
cpu_type_t arch_type = pinfo.type & ~CPU_ARCH_MASK;
/* Apple uses 'ip' rather than 'r12' on ARM */
if (arch_type == CPU_TYPE_ARM && [regName isEqual: @"r12"]) {
regName = @"ip";
}
}
[text appendFormat: reg_fmt, [regName UTF8String], reg.registerValue];
regColumn++;
if (regColumn == 4) {
[text appendString: @"\n"];
regColumn = 0;
}
}
if (regColumn != 0)
[text appendString: @"\n"];
[text appendString: @"\n"];
}
/* Images. The iPhone crash report format sorts these in ascending order, by the base address */
[text appendString: @"Binary Images:\n"];
for (PLCrashReportBinaryImageInfo *imageInfo in [report.images sortedArrayUsingFunction: binaryImageSort context: nil]) {
NSString *uuid;
/* Fetch the UUID if it exists */
if (imageInfo.hasImageUUID)
uuid = imageInfo.imageUUID;
else
uuid = @"???";
/* Determine the architecture string */
NSString *archName = @"???";
if (imageInfo.codeType != nil && imageInfo.codeType.typeEncoding == PLCrashReportProcessorTypeEncodingMach) {
switch (imageInfo.codeType.type) {
case CPU_TYPE_ARM:
/* Apple includes subtype for ARM binaries. */
switch (imageInfo.codeType.subtype) {
case CPU_SUBTYPE_ARM_V6:
archName = @"armv6";
break;
case CPU_SUBTYPE_ARM_V7:
archName = @"armv7";
break;
default:
archName = @"arm-unknown";
break;
}
break;
case CPU_TYPE_X86:
archName = @"i386";
break;
case CPU_TYPE_X86_64:
archName = @"x86_64";
break;
case CPU_TYPE_POWERPC:
archName = @"powerpc";
break;
default:
// Use the default archName value (initialized above).
break;
}
}
/* Determine if this is the main executable */
NSString *binaryDesignator = @" ";
if ([imageInfo.imageName isEqual: report.processInfo.processPath])
binaryDesignator = @"+";
/* base_address - terminating_address [designator]file_name arch <uuid> file_path */
NSString *fmt = nil;
if (lp64) {
fmt = @"%18#" PRIx64 " - %18#" PRIx64 " %@%@ %@ <%@> %@\n";
} else {
fmt = @"%10#" PRIx64 " - %10#" PRIx64 " %@%@ %@ <%@> %@\n";
}
/* Remove username from the image path */
NSString *imageName = [imageInfo.imageName stringByAbbreviatingWithTildeInPath];
if ([[imageName substringToIndex:1] isEqualToString:@"~"])
imageName = [NSString stringWithFormat:@"/Users/USER%@", [imageName substringFromIndex:1]];
[text appendFormat: fmt,
imageInfo.imageBaseAddress,
imageInfo.imageBaseAddress + (MAX(1, imageInfo.imageSize) - 1), // The Apple format uses an inclusive range
binaryDesignator,
[imageInfo.imageName lastPathComponent],
archName,
uuid,
imageName];
}
return text;
}
/**
* Returns an array of app UUIDs and their architecture
* As a dictionary for each element
*
* @param report The report to format.
*
* @return Returns the formatted result on success, or nil if an error occurs.
*/
+ (NSArray *)arrayOfAppUUIDsForCrashReport:(PLCrashReport *)report {
NSMutableArray* appUUIDs = [NSMutableArray array];
/* Images. The iPhone crash report format sorts these in ascending order, by the base address */
for (PLCrashReportBinaryImageInfo *imageInfo in [report.images sortedArrayUsingFunction: binaryImageSort context: nil]) {
NSString *uuid;
/* Fetch the UUID if it exists */
if (imageInfo.hasImageUUID)
uuid = imageInfo.imageUUID;
else
uuid = @"???";
/* Determine the architecture string */
NSString *archName = @"???";
if (imageInfo.codeType != nil && imageInfo.codeType.typeEncoding == PLCrashReportProcessorTypeEncodingMach) {
switch (imageInfo.codeType.type) {
case CPU_TYPE_ARM:
/* Apple includes subtype for ARM binaries. */
switch (imageInfo.codeType.subtype) {
case CPU_SUBTYPE_ARM_V6:
archName = @"armv6";
break;
case CPU_SUBTYPE_ARM_V7:
archName = @"armv7";
break;
default:
archName = @"arm-unknown";
break;
}
break;
case CPU_TYPE_X86:
archName = @"i386";
break;
case CPU_TYPE_X86_64:
archName = @"x86_64";
break;
case CPU_TYPE_POWERPC:
archName = @"powerpc";
break;
default:
// Use the default archName value (initialized above).
break;
}
}
/* Determine if this is the app executable or app specific framework */
NSString *imagePath = [imageInfo.imageName stringByStandardizingPath];
NSString *appBundleContentsPath = [[report.processInfo.processPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
NSString *imageType = @"";
if ([imageInfo.imageName isEqual: report.processInfo.processPath]) {
imageType = @"app";
} else {
imageType = @"framework";
}
if ([imagePath isEqual: report.processInfo.processPath] || [imagePath hasPrefix:appBundleContentsPath]) {
[appUUIDs addObject:[NSDictionary dictionaryWithObjectsAndKeys:uuid, kBITBinaryImageKeyUUID, archName, kBITBinaryImageKeyArch, imageType, kBITBinaryImageKeyType, nil]];
}
}
return appUUIDs;
}
@end
@implementation BITCrashReportTextFormatter (PrivateAPI)
/**
* Format a stack frame for display in a thread backtrace.
*
* @param frameInfo The stack frame to format
* @param frameIndex The frame's index
* @param report The report from which this frame was acquired.
*
* @return Returns a formatted frame line.
*/
+ (NSString *)formatStackFrame: (PLCrashReportStackFrameInfo *) frameInfo
frameIndex: (NSUInteger) frameIndex
report: (PLCrashReport *) report
{
/* Base image address containing instrumention pointer, offset of the IP from that base
* address, and the associated image name */
uint64_t baseAddress = 0x0;
uint64_t pcOffset = 0x0;
NSString *imageName = @"\?\?\?";
NSString *symbol = nil;
PLCrashReportBinaryImageInfo *imageInfo = [report imageForAddress: frameInfo.instructionPointer];
if (imageInfo != nil) {
imageName = [imageInfo.imageName lastPathComponent];
baseAddress = imageInfo.imageBaseAddress;
pcOffset = frameInfo.instructionPointer - imageInfo.imageBaseAddress;
NSString *imagePath = [imageInfo.imageName stringByStandardizingPath];
NSString *appBundleContentsPath = [[report.processInfo.processPath stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
if (![imagePath isEqual: report.processInfo.processPath] && ![imagePath hasPrefix:appBundleContentsPath]) {
symbol = frameInfo.symbolName;
pcOffset = frameInfo.instructionPointer - frameInfo.symbolStart;
}
}
/* The frame has nothing useful, so return nil so it can be filtered out */
if (frameInfo.instructionPointer == 0 && baseAddress == 0 && pcOffset == 0) {
return nil;
}
/* Make sure UTF8/16 characters are handled correctly */
NSInteger offset = 0;
NSInteger index = 0;
for (index = 0; index < [imageName length]; index++) {
NSRange range = [imageName rangeOfComposedCharacterSequenceAtIndex:index];
if (range.length > 1) {
offset += range.length - 1;
index += range.length - 1;
}
if (index > 32) {
imageName = [NSString stringWithFormat:@"%@...", [imageName substringToIndex:index - 1]];
index += 3;
break;
}
}
if (index-offset < 36) {
imageName = [imageName stringByPaddingToLength:36+offset withString:@" " startingAtIndex:0];
}
if (symbol) {
return [NSString stringWithFormat: @"%-4ld%@0x%08" PRIx64 " %@ + %" PRId64 "\n",
(long) frameIndex,
imageName,
frameInfo.instructionPointer,
symbol,
pcOffset];
} else {
return [NSString stringWithFormat: @"%-4ld%@0x%08" PRIx64 " 0x%" PRIx64 " + %" PRId64 "\n",
(long) frameIndex,
imageName,
frameInfo.instructionPointer,
baseAddress,
pcOffset];
}
}
/**
* Sort PLCrashReportBinaryImageInfo instances by their starting address.
*/
NSInteger binaryImageSort(id binary1, id binary2, void *context) {
uint64_t addr1 = [binary1 imageBaseAddress];
uint64_t addr2 = [binary2 imageBaseAddress];
if (addr1 < addr2)
return NSOrderedAscending;
else if (addr1 > addr2)
return NSOrderedDescending;
else
return NSOrderedSame;
}
@end

240
Classes/BITHockeyManager.h Normal file
View file

@ -0,0 +1,240 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Kent Sutherland
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* 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 "BITUpdateManager.h"
@protocol BITHockeyManagerDelegate;
@class BITCrashManager;
@class BITUpdateManager;
/**
The HockeySDK manager.
This is the principal SDK class. It represents the entry point for the HockeySDK. The main promises of the class are initializing the SDK modules, providing access to global properties and to all modules. Initialization is divided into several distinct phases:
1. Setup the [HockeyApp](http://hockeyapp.net/) app identifier and the optional delegate: This is the least required information on setting up the SDK and using it. It does some simple validation of the app identifier and checks if the app is running from the App Store or not. If the [Atlassian JMC framework](http://www.atlassian.com/jmc/) is found, it will disable its Crash Reporting module and configure it with the Jira configuration data from [HockeyApp](http://hockeyapp.net/).
2. Provides access to the SDK modules `BITCrashManager` and `BITUpdateManager`. This way all modules can be further configured to personal needs, if the defaults don't fit the requirements.
3. Start up all modules.
Example:
[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"<AppIdentifierFromHockeyApp>" delegate:nil];
[[BITHockeyManager sharedHockeyManager] startManager];
@warning When also using the SDK for updating app versions (AdHoc or Enterprise) and collecting
beta usage analytics, you also have to to set `[BITUpdateManager delegate]` and
implement `[BITUpdateManagerDelegate customDeviceIdentifierForUpdateManager:]`!
Example implementation if your Xcode configuration for the App Store is called "AppStore":
- (NSString *)customDeviceIdentifierForUpdateManager:(BITUpdateManager *)updateManager {
#ifndef (CONFIGURATION_AppStore)
if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)])
return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)];
#endif
return nil;
}
[[BITHockeyManager sharedHockeyManager].updateManager setDelegate:self];
*/
@interface BITHockeyManager : NSObject {
@private
id<BITHockeyManagerDelegate> delegate;
NSString *_appIdentifier;
BOOL _validAppIdentifier;
BOOL _startManagerIsInvoked;
}
#pragma mark - Public Methods
///-----------------------------------------------------------------------------
/// @name Initializion
///-----------------------------------------------------------------------------
/**
Returns a shared BITHockeyManager object
@return A singleton BITHockeyManager instance ready use
*/
+ (BITHockeyManager *)sharedHockeyManager;
/**
Initializes the manager with a particular app identifier and delegate
Initialize the manager with a HockeyApp app identifier and assign the class that
implements the optional BITHockeyManagerDelegate protocol.
[[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"<AppIdentifierFromHockeyApp>" delegate:nil];
@see configureWithBetaIdentifier:liveIdentifier:delegate:
@see startManager
@param appIdentifier The app identifier that should be used.
@param delegate `nil` or the class implementing the option BITHockeyManagerDelegate protocol
*/
- (void)configureWithIdentifier:(NSString *)appIdentifier delegate:(id<BITHockeyManagerDelegate>)delegate;
/**
Initializes the manager with an app identifier for beta, one for live usage and delegate
Initialize the manager with different HockeyApp app identifiers for beta and live usage.
All modules will automatically detect if the app is running in the App Store and use
the live app identifier for that. In all other cases it will use the beta app identifier.
[[BITHockeyManager sharedHockeyManager] configureWithBetaIdentifier:@"<AppIdentifierForBetaAppFromHockeyApp>"
liveIdentifier:@"<AppIdentifierForLiveAppFromHockeyApp>"
delegate:nil];
@see configureWithIdentifier:delegate:
@see startManager
@param betaIdentifier The app identifier for the _non_ app store (beta) configurations
@param liveIdentifier The app identifier for the app store configurations.
@param delegate `nil` or the implementing the optional BITHockeyManagerDelegate protocol
*/
- (void)configureWithBetaIdentifier:(NSString *)betaIdentifier liveIdentifier:(NSString *)liveIdentifier delegate:(id<BITHockeyManagerDelegate>)delegate;
/**
Starts the manager and runs all modules
Call this after configuring the manager and setting up all modules.
@see configureWithIdentifier:delegate:
@see configureWithBetaIdentifier:liveIdentifier:delegate:
*/
- (void)startManager;
#pragma mark - Public Properties
///-----------------------------------------------------------------------------
/// @name Modules
///-----------------------------------------------------------------------------
/**
Defines the server URL to send data to or request data from
By default this is set to the HockeyApp servers and there rarely should be a
need to modify that.
*/
@property (nonatomic, retain) NSString *updateURL;
/**
Reference to the initialized BITCrashManager module
@see configureWithIdentifier:delegate:
@see configureWithBetaIdentifier:liveIdentifier:delegate:
@see startManager
@see disableCrashManager
@return The BITCrashManager instance initialized by BITHockeyManager
*/
@property (nonatomic, retain, readonly) BITCrashManager *crashManager;
/**
Flag the determines wether the Crash Manager should be disabled
If this flag is enabled, then crash reporting is disabled and no crashes will
be send.
Please note that the Crash Manager will be initialized anyway!
*Default*: _NO_
@see crashManager
*/
@property (nonatomic, getter = isCrashManagerDisabled) BOOL disableCrashManager;
/**
Reference to the initialized BITUpdateManager module
@see configureWithIdentifier:delegate:
@see configureWithBetaIdentifier:liveIdentifier:delegate:
@see startManager
@see disableUpdateManager
@return The BITCrashManager instance initialized by BITUpdateManager
*/
@property (nonatomic, retain, readonly) BITUpdateManager *updateManager;
/**
Flag the determines wether the Update Manager should be disabled
If this flag is enabled, then checking for updates and submitting beta usage
analytics will be turned off!
Please note that the Update Manager will be initialized anyway!
*Default*: _NO_
@see updateManager
*/
@property (nonatomic, getter = isUpdateManagerDisabled) BOOL disableUpdateManager;
///-----------------------------------------------------------------------------
/// @name Environment
///-----------------------------------------------------------------------------
/**
Flag that determines whether the application is installed and running
from an App Store installation.
@return YES if the app is installed and running from the App Store
@return NO if the app is installed via debug, ad-hoc or enterprise distribution
*/
@property (nonatomic, readonly, getter=isAppStoreEnvironment) BOOL appStoreEnvironment;
///-----------------------------------------------------------------------------
/// @name Debug Logging
///-----------------------------------------------------------------------------
/**
Flag that determines whether additional logging output should be generated
by the manager and all modules.
This is ignored if the app is running in the App Store and reverts to the
default value in that case.
*Default*: _NO_
*/
@property (nonatomic, assign, getter=isDebugLogEnabled) BOOL debugLogEnabled;
@end

328
Classes/BITHockeyManager.m Normal file
View file

@ -0,0 +1,328 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Kent Sutherland
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* 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 "HockeySDK.h"
#import "HockeySDKPrivate.h"
#import "BITCrashManagerPrivate.h"
#import "BITUpdateManagerPrivate.h"
@interface BITHockeyManager ()
- (BOOL)shouldUseLiveIdenfitier;
- (void)configureJMC;
@end
@implementation BITHockeyManager
@synthesize crashManager = _crashManager;
@synthesize updateManager = _updateManager;
@synthesize appStoreEnvironment = _appStoreEnvironment;
#pragma mark - Public Class Methods
+ (BITHockeyManager *)sharedHockeyManager {
static BITHockeyManager *sharedInstance = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
sharedInstance = [BITHockeyManager alloc];
sharedInstance = [sharedInstance init];
});
return sharedInstance;
}
- (id) init {
if ((self = [super init])) {
_updateURL = nil;
_disableCrashManager = NO;
_disableUpdateManager = NO;
_appStoreEnvironment = NO;
_startManagerIsInvoked = NO;
[self performSelector:@selector(validateStartManagerIsInvoked) withObject:nil afterDelay:0.0f];
}
return self;
}
- (void)dealloc {
[_appIdentifier release], _appIdentifier = nil;
[_crashManager release], _crashManager = nil;
[_updateManager release], _updateManager = nil;
delegate = nil;
[super dealloc];
}
#pragma mark - Public Instance Methods (Configuration)
- (void)configureWithIdentifier:(NSString *)newAppIdentifier delegate:(id)newDelegate {
delegate = newDelegate;
[_appIdentifier release];
_appIdentifier = [newAppIdentifier copy];
[self initializeModules];
}
- (void)configureWithBetaIdentifier:(NSString *)betaIdentifier liveIdentifier:(NSString *)liveIdentifier delegate:(id)newDelegate {
delegate = newDelegate;
[_appIdentifier release];
if ([self shouldUseLiveIdenfitier]) {
_appIdentifier = [liveIdentifier copy];
}
else {
_appIdentifier = [betaIdentifier copy];
}
[self initializeModules];
}
- (void)startManager {
if (!_validAppIdentifier) return;
BITHockeyLog(@"Starting HockeyManager");
_startManagerIsInvoked = YES;
// start CrashManager
if (![self isCrashManagerDisabled]) {
BITHockeyLog(@"Start crashManager");
if (_updateURL) {
[_crashManager setUpdateURL:_updateURL];
}
[_crashManager performSelector:@selector(startManager) withObject:nil afterDelay:0.5f];
}
// Setup UpdateManager
if (![self isUpdateManagerDisabled]) {
BITHockeyLog(@"Start UpdateManager");
if (_updateURL) {
[_updateManager setUpdateURL:_updateURL];
}
[_updateManager performSelector:@selector(startManager) withObject:nil afterDelay:0.5f];
}
}
- (void)validateStartManagerIsInvoked {
if (_validAppIdentifier && !_appStoreEnvironment) {
if (!_startManagerIsInvoked) {
NSLog(@"ERROR: You did not call [[BITHockeyManager sharedHockeyManager] startManager] to startup the HockeySDK! Please do so after setting up all properties. The SDK is NOT running.");
}
}
}
- (void)setUpdateURL:(NSString *)anUpdateURL {
// ensure url ends with a trailing slash
if (![anUpdateURL hasSuffix:@"/"]) {
anUpdateURL = [NSString stringWithFormat:@"%@/", anUpdateURL];
}
if (_updateURL != anUpdateURL) {
[_updateURL release];
_updateURL = [anUpdateURL copy];
}
}
#pragma mark - Private Instance Methods
- (BOOL)shouldUseLiveIdenfitier {
BOOL delegateResult = NO;
if ([delegate respondsToSelector:@selector(shouldUseLiveIdenfitier)]) {
delegateResult = [(NSObject <BITHockeyManagerDelegate>*)delegate shouldUseLiveIdenfitier];
}
return (delegateResult) || (_appStoreEnvironment);
}
- (void)initializeModules {
NSCharacterSet *hexSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789abcdef"];
NSCharacterSet *inStringSet = [NSCharacterSet characterSetWithCharactersInString:_appIdentifier];
_validAppIdentifier = ([_appIdentifier length] == 32) && ([hexSet isSupersetOfSet:inStringSet]);
// check if we are really not in an app store environment
if ([[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"]) {
_appStoreEnvironment = NO;
} else {
_appStoreEnvironment = YES;
}
#if TARGET_IPHONE_SIMULATOR
_appStoreEnvironment = NO;
#endif
_startManagerIsInvoked = NO;
if (_validAppIdentifier) {
BITHockeyLog(@"Setup CrashManager");
_crashManager = [[BITCrashManager alloc] initWithAppIdentifier:_appIdentifier];
BITHockeyLog(@"Setup UpdateManager");
_updateManager = [[BITUpdateManager alloc] initWithAppIdentifier:_appIdentifier isAppStoreEnvironemt:_appStoreEnvironment];
// Only if JMC is part of the project
if ([[self class] isJMCPresent]) {
BITHockeyLog(@"Setup JMC");
[_updateManager setCheckForTracker:YES];
[_updateManager addObserver:self forKeyPath:@"trackerConfig" options:0 context:nil];
[[self class] disableJMCCrashReporter];
[self performSelector:@selector(configureJMC) withObject:nil afterDelay:0];
}
} else {
NSLog(@"ERROR: The app identifier is invalid! Please use the HockeyApp app identifier you find on the apps website on HockeyApp! The SDK is disabled!");
}
}
#pragma mark - JMC
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (id)jmcInstance {
id jmcClass = NSClassFromString(@"JMC");
if ((jmcClass) && ([jmcClass respondsToSelector:@selector(sharedInstance)])) {
return [jmcClass performSelector:@selector(sharedInstance)];
}
#ifdef JMC_LEGACY
else if ((jmcClass) && ([jmcClass respondsToSelector:@selector(instance)])) {
return [jmcClass performSelector:@selector(instance)]; // legacy pre (JMC 1.0.11) support
}
#endif
return nil;
}
#pragma clang diagnostic pop
+ (BOOL)isJMCActive {
id jmcInstance = [self jmcInstance];
return (jmcInstance) && ([jmcInstance performSelector:@selector(url)]);
}
+ (BOOL)isJMCPresent {
return [self jmcInstance] != nil;
}
#pragma mark - Private Class Methods
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (void)disableJMCCrashReporter {
id jmcInstance = [self jmcInstance];
id jmcOptions = [jmcInstance performSelector:@selector(options)];
SEL crashReporterSelector = @selector(setCrashReportingEnabled:);
BOOL value = NO;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jmcOptions methodSignatureForSelector:crashReporterSelector]];
invocation.target = jmcOptions;
invocation.selector = crashReporterSelector;
[invocation setArgument:&value atIndex:2];
[invocation invoke];
}
#pragma clang diagnostic pop
+ (BOOL)checkJMCConfiguration:(NSDictionary *)configuration {
return (([configuration isKindOfClass:[NSDictionary class]]) &&
([[configuration valueForKey:@"enabled"] boolValue]) &&
([[configuration valueForKey:@"url"] length] > 0) &&
([[configuration valueForKey:@"key"] length] > 0) &&
([[configuration valueForKey:@"project"] length] > 0));
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (void)applyJMCConfiguration:(NSDictionary *)configuration {
id jmcInstance = [self jmcInstance];
SEL configureSelector = @selector(configureJiraConnect:projectKey:apiKey:);
NSString *url = [configuration valueForKey:@"url"];
NSString *project = [configuration valueForKey:@"project"];
NSString *key = [configuration valueForKey:@"key"];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jmcInstance methodSignatureForSelector:configureSelector]];
invocation.target = jmcInstance;
invocation.selector = configureSelector;
[invocation setArgument:&url atIndex:2];
[invocation setArgument:&project atIndex:3];
[invocation setArgument:&key atIndex:4];
[invocation invoke];
if ([jmcInstance respondsToSelector:@selector(ping)]) {
[jmcInstance performSelector:@selector(ping)];
}
}
#pragma clang diagnostic pop
- (void)configureJMC {
// Return if JMC is already configured
if ([[self class] isJMCActive]) {
return;
}
// Configure JMC from user defaults
NSDictionary *configurations = [[NSUserDefaults standardUserDefaults] valueForKey:@"BITTrackerConfigurations"];
NSDictionary *configuration = [configurations valueForKey:_appIdentifier];
if ([[self class] checkJMCConfiguration:configuration]) {
[[self class] applyJMCConfiguration:configuration];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (([object trackerConfig]) && ([[object trackerConfig] isKindOfClass:[NSDictionary class]])) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *trackerConfig = [[defaults valueForKey:@"BITTrackerConfigurations"] mutableCopy];
if (!trackerConfig) {
trackerConfig = [[NSMutableDictionary dictionaryWithCapacity:1] retain];
}
[trackerConfig setValue:[object trackerConfig] forKey:_appIdentifier];
[defaults setValue:trackerConfig forKey:@"BITTrackerConfigurations"];
[trackerConfig release];
[defaults synchronize];
[self configureJMC];
}
}
@end

View file

@ -0,0 +1,58 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* 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 <Foundation/Foundation.h>
/**
The `BITHockeyManagerDelegate` formal protocol defines methods further configuring
the behaviour of `BITHockeyManager`.
*/
@protocol BITHockeyManagerDelegate <NSObject>
@optional
/**
Implement to force the usage of the live identifier
This is useful if you are e.g. distributing an enterprise app inside your company
and want to use the `liveIdentifier` for that even though it is not running from
the App Store.
Example:
- (BOOL)shouldUseLiveIdenfitier {
#ifdef (CONFIGURATION_Release)
return YES;
#endif
return NO;
}
*/
- (BOOL)shouldUseLiveIdenfitier;
@end

296
Classes/BITUpdateManager.h Normal file
View file

@ -0,0 +1,296 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Peter Steinberger
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde.
* 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 <UIKit/UIKit.h>
typedef enum {
BITUpdateComparisonResultDifferent,
BITUpdateComparisonResultGreater
} BITUpdateComparisonResult;
typedef enum {
BITUpdateAuthorizationDenied,
BITUpdateAuthorizationAllowed,
BITUpdateAuthorizationPending
} BITUpdateAuthorizationState;
typedef enum {
BITUpdateCheckStartup = 0,
BITUpdateCheckDaily = 1,
BITUpdateCheckManually = 2
} BITUpdateSetting;
@protocol BITUpdateManagerDelegate;
@class BITAppVersionMetaInfo;
@class BITUpdateViewController;
/**
The update manager module.
This is the HockeySDK module for handling app updates when using Ad-Hoc or Enterprise provisioning profiles.
This modul handles version updates, presents update and version information in a App Store like user interface,
collects usage information and provides additional authorization options when using Ad-Hoc provisioning profiles.
To use this module, it is important to implement set the `delegate` property and implement
`[BITUpdateManagerDelegate customDeviceIdentifierForUpdateManager:]`.
Example implementation if your Xcode configuration for the App Store is called "AppStore":
- (NSString *)customDeviceIdentifierForUpdateManager:(BITUpdateManager *)updateManager {
#ifndef (CONFIGURATION_AppStore)
if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)])
return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)];
#endif
return nil;
}
[[BITHockeyManager sharedHockeyManager].updateManager setDelegate:self];
*/
@interface BITUpdateManager : NSObject <UIAlertViewDelegate> {
@private
NSString *_appIdentifier;
NSString *_currentAppVersion;
UINavigationController *_navController;
BITUpdateViewController *_currentHockeyViewController;
BOOL _dataFound;
BOOL _showFeedback;
BOOL _updateAlertShowing;
BOOL _lastCheckFailed;
BOOL _sendUsageData;
BOOL _isAppStoreEnvironment;
NSString *_uuid;
}
///-----------------------------------------------------------------------------
/// @name Delegate
///-----------------------------------------------------------------------------
/**
Sets the `BITUpdateManagerDelegate` delegate.
When using `BITUpdateManager` to distribute updates of your beta or enterprise
application, it is _REQUIRED_ to set this delegate and implement
`[BITUpdateManagerDelegate customDeviceIdentifierForUpdateManager:]`!
*/
@property (nonatomic, assign) id <BITUpdateManagerDelegate> delegate;
///-----------------------------------------------------------------------------
/// @name Configuration
///-----------------------------------------------------------------------------
/**
The type of version comparisson.
Defines when a version is defined as being newer than the currently installed
version. This must be assigned one of the following:
When running the app from the App Store, this setting is ignored.
- `BITUpdateComparisonResultDifferent`: Version is different
- `BITUpdateComparisonResultGreater`: Version is greater
**Default**: BITUpdateComparisonResultGreater
*/
@property (nonatomic, assign) BITUpdateComparisonResult compareVersionType;
///-----------------------------------------------------------------------------
/// @name Update Checking
///-----------------------------------------------------------------------------
// see HockeyUpdateSetting-enum. Will be saved in user defaults.
// default value: HockeyUpdateCheckStartup
/**
When to check for new updates.
Defines when a the SDK should check if there is a new update available on the
server. This must be assigned one of the following:
- `BITUpdateCheckStartup`: On every startup or or when the app comes to the foreground
- `BITUpdateCheckDaily`: Once a day
- `BITUpdateCheckManually`: Manually
When running the app from the App Store, this setting is ignored.
**Default**: BITUpdateCheckStartup
@warning When setting this to `BITUpdateCheckManually` you need to either
invoke the update checking process yourself with `checkForUpdate` somehow, e.g. by
proving an update check button for the user or integrating the Update View into your
user interface.
@see checkForUpdateOnLaunch
@see checkForUpdate
*/
@property (nonatomic, assign) BITUpdateSetting updateSetting;
/**
Flag that determines whether the automatic update checks should be done.
If this is enabled the update checks will be performed automatically depending on the
`updateSetting` property. If this is disabled the `updateSetting` property will have
no effect, and checking for updates is totally up to be done by yourself.
When running the app from the App Store, this setting is ignored.
*Default*: _YES_
@warning When setting this to `NO` you need to invoke update checks yourself!
@see updateSetting
@see checkForUpdate
*/
@property (nonatomic, assign, getter=isCheckForUpdateOnLaunch) BOOL checkForUpdateOnLaunch;
// manually start an update check
/**
Check for an update
Call this to trigger a check if there is a new update available on the HockeyApp servers.
When running the app from the App Store, this setting is ignored.
@see updateSetting
@see checkForUpdateOnLaunch
*/
- (void)checkForUpdate;
///-----------------------------------------------------------------------------
/// @name Update Notification
///-----------------------------------------------------------------------------
/**
Flag that determines if updates alert should be repeatedly shown
If enabled the update alert shows on every startup and whenever the app becomes active,
until the update is installed.
If disabled the update alert is only shown once ever and it is up to you to provide an
alternate way for the user to navigate to the update UI or update in another way.
When running the app from the App Store, this setting is ignored.
*Default*: _YES_
*/
@property (nonatomic, assign) BOOL alwaysShowUpdateReminder;
/**
Flag that determines if the update alert should show an direct install option
If enabled the update alert shows an additional option which allows to invoke the update
installation process directly, instead of viewing the update UI first.
By default the alert only shows a `Show` and `Ignore` option.
When running the app from the App Store, this setting is ignored.
*Default*: _NO_
*/
@property (nonatomic, assign, getter=isShowingDirectInstallOption) BOOL showDirectInstallOption;
///-----------------------------------------------------------------------------
/// @name Authorization
///-----------------------------------------------------------------------------
/**
Flag that determines if each update should be authenticated
If enabled each update will be authenticated on startup against the HockeyApp servers.
The process will basically validate if the current device is part of the provisioning
profile on the server. If not, it will present a blocking view on top of the apps UI
so that no interaction is possible.
When running the app from the App Store, this setting is ignored.
*Default*: _NO_
@see authenticationSecret
@warning This only works when using Ad-Hoc provisioning profiles!
*/
@property (nonatomic, assign, getter=isRequireAuthorization) BOOL requireAuthorization;
/**
The authentication token from HockeyApp.
Set the token to the `Secret ID` which HockeyApp provides for every app.
When running the app from the App Store, this setting is ignored.
@see requireAuthorization
*/
@property (nonatomic, retain) NSString *authenticationSecret;
///-----------------------------------------------------------------------------
/// @name User Interface
///-----------------------------------------------------------------------------
/**
The UIBarStyle of the update user interface navigation bar.
*/
@property (nonatomic, assign) UIBarStyle barStyle;
/**
The UIModalPresentationStyle for showing the update user interface when invoked
with the update alert.
*/
@property (nonatomic, assign) UIModalPresentationStyle modalPresentationStyle;
/**
Present the modal update user interface.
*/
- (void)showUpdateView;
/**
Create an update view
@param modal Return a view ready for modal presentation with integrated navigation bar
@return BITUpdateViewController The update user interface view controller,
e.g. to push it onto a navigation stack.
*/
- (BITUpdateViewController *)hockeyViewController:(BOOL)modal;
@end

1077
Classes/BITUpdateManager.m Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,105 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* 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 <Foundation/Foundation.h>
/**
The `BITUpdateManagerDelegate` formal protocol defines methods further configuring
the behaviour of `BITUpdateManager`.
*/
@protocol BITUpdateManagerDelegate <NSObject>
///-----------------------------------------------------------------------------
/// @name Configuration
///-----------------------------------------------------------------------------
/**
Return the unique device identifier
Return the device UDID which is required for beta testing, should return nil for app store configuration!
Example implementation if your Xcode configuration for the App Store is called "AppStore":
- (NSString *)customDeviceIdentifierForUpdateManager:(BITUpdateManager *)updateManager {
#ifndef (CONFIGURATION_AppStore)
if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)])
return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)];
#endif
return nil;
}
@param updateManager The `BITUpdateManager` instance invoking this delegate
*/
- (NSString *)customDeviceIdentifierForUpdateManager:(BITUpdateManager *)updateManager;
@optional
///-----------------------------------------------------------------------------
/// @name Privacy
///-----------------------------------------------------------------------------
/** Return NO if usage data should not be send
The update module send usage data by default, if the application is _NOT_
running in an App Store version. Implement this delegate and
return NO if you want to disable this.
If you intend to implement a user setting to let them enable or disable
sending usage data, this delegate should be used to return that value.
Usage data contains the following information:
- App Version
- iOS Version
- Device type
- Language
- Installation timestamp
- Usage time
@param updateManager The `BITUpdateManager` instance invoking this delegate
@warning When setting this to `NO`, you will _NOT_ know if this user is actually testing!
*/
- (BOOL)updateManagerShouldSendUsageData:(BITUpdateManager *)updateManager;
///-----------------------------------------------------------------------------
/// @name Update View Presentation Helper
///-----------------------------------------------------------------------------
/**
Provide a parent view controller for the update user interface
If you don't have a `rootViewController` set on your `UIWindow` and the SDK cannot
automatically find the current top most `UIViewController`, you can provide the
`UIViewController` that should be used to present the update user interface modal.
@param updateManager The `BITUpdateManager` instance invoking this delegate
*/
- (UIViewController *)viewControllerForUpdateManager:(BITUpdateManager *)updateManager;
@end

View file

@ -0,0 +1,96 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Peter Steinberger
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde.
* 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 <UIKit/UIKit.h>
@interface BITUpdateManager () {
}
// set the server URL
@property (nonatomic, retain) NSString *updateURL;
// is an update available?
@property (nonatomic, assign, getter=isUpdateAvailable) BOOL updateAvailable;
// are we currently checking for updates?
@property (nonatomic, assign, getter=isCheckInProgress) BOOL checkInProgress;
@property (nonatomic, retain) NSMutableData *receivedData;
@property (nonatomic, copy) NSDate *lastCheck;
// get array of all available versions
@property (nonatomic, copy) NSArray *appVersions;
@property (nonatomic, retain) NSURLConnection *urlConnection;
@property (nonatomic, copy) NSDate *usageStartTimestamp;
@property (nonatomic, retain) UIView *authorizeView;
// if YES, the API will return an existing JMC config
// if NO, the API will return only version information
@property (nonatomic, assign) BOOL checkForTracker;
// Contains the tracker config if received from server
@property (nonatomic, retain) NSDictionary *trackerConfig;
- (id)initWithAppIdentifier:(NSString *)appIdentifier isAppStoreEnvironemt:(BOOL)isAppStoreEnvironment;
- (void)startManager;
// checks for update, informs the user (error, no update found, etc)
- (void)checkForUpdateShowFeedback:(BOOL)feedback;
// initiates app-download call. displays an system UIAlertView
- (BOOL)initiateAppDownload;
// checks wether this app version is authorized
- (BOOL)appVersionIsAuthorized;
// start checking for an authorization key
- (void)checkForAuthorization;
// get/set current active hockey view controller
@property (nonatomic, retain) BITUpdateViewController *currentHockeyViewController;
// convenience method to get current running version string
- (NSString *)currentAppVersion;
// get newest app version
- (BITAppVersionMetaInfo *)newestAppVersion;
// check if there is any newer version mandatory
- (BOOL)hasNewerMandatoryVersion;
@end

View file

@ -0,0 +1,52 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Peter Steinberger
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde, Peter Steinberger.
* 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 <UIKit/UIKit.h>
@class PSStoreButton;
@class PSAppStoreHeader;
@interface BITUpdateViewController : UITableViewController {
@private
BOOL _kvoRegistered;
BOOL _showAllVersions;
UIStatusBarStyle _statusBarStyle;
PSAppStoreHeader *_appStoreHeader;
PSStoreButton *_appStoreButton;
id _popOverController;
NSMutableArray *_cells;
BOOL _isAppStoreEnvironment;
}
@end

View file

@ -1,133 +1,104 @@
//
// BWHockeyViewController.m
//
// Created by Andreas Linde on 8/17/10.
// Copyright 2010-2011 Andreas Linde, Peter Steinberger. 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.
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Peter Steinberger
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde, Peter Steinberger.
* 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 <QuartzCore/QuartzCore.h>
#import "NSString+HockeyAdditions.h"
#import "BWHockeyViewController.h"
#import "BWHockeyManager.h"
#import "BWGlobal.h"
#import "UIImage+HockeyAdditions.h"
#import "NSString+BITHockeyAdditions.h"
#import "BITAppVersionMetaInfo.h"
#import "UIImage+BITHockeyAdditions.h"
#import "PSAppStoreHeader.h"
#import "PSWebTableViewCell.h"
#import "BWHockeySettingsViewController.h"
#import "PSStoreButton.h"
#define BW_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1]
#import "HockeySDK.h"
#import "HockeySDKPrivate.h"
#import "BITUpdateManagerPrivate.h"
#import "BITUpdateViewControllerPrivate.h"
#define BIT_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1]
#define kWebCellIdentifier @"PSWebTableViewCell"
#define kAppStoreViewHeight 90
@interface BWHockeyViewController ()
// updates the whole view
- (void)showPreviousVersionAction;
- (void)redrawTableView;
@property (nonatomic, assign) AppStoreButtonState appStoreButtonState;
- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState animated:(BOOL)animated;
@end
@implementation BITUpdateViewController
@synthesize appStoreButtonState = _appStoreButtonState;
@synthesize modal = _modal;
@synthesize modalAnimated = _modalAnimated;
@implementation BWHockeyViewController
#pragma mark - Private
@synthesize appStoreButtonState = appStoreButtonState_;
@synthesize hockeyManager = hockeyManager_;
@synthesize modal = modal_;
@synthesize modalAnimated = modalAnimated_;
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark private
- (void)restoreStoreButtonStateAnimated_:(BOOL)animated {
if ([self.hockeyManager isAppStoreEnvironment]) {
- (void)restoreStoreButtonStateAnimated:(BOOL)animated {
if (_isAppStoreEnvironment) {
[self setAppStoreButtonState:AppStoreButtonStateOffline animated:animated];
} else if ([self.hockeyManager isUpdateAvailable]) {
} else if ([_updateManager isUpdateAvailable]) {
[self setAppStoreButtonState:AppStoreButtonStateUpdate animated:animated];
} else {
[self setAppStoreButtonState:AppStoreButtonStateCheck animated:animated];
}
}
- (void)updateAppStoreHeader_ {
BWApp *app = self.hockeyManager.app;
appStoreHeader_.headerLabel = app.name;
appStoreHeader_.middleHeaderLabel = [app versionString];
- (void)updateAppStoreHeader {
BITAppVersionMetaInfo *appVersion = _updateManager.newestAppVersion;
_appStoreHeader.headerLabel = appVersion.name;
_appStoreHeader.middleHeaderLabel = [appVersion versionString];
NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setDateStyle:NSDateFormatterMediumStyle];
NSMutableString *subHeaderString = [NSMutableString string];
if (app.date) {
[subHeaderString appendString:[formatter stringFromDate:app.date]];
if (appVersion.date) {
[subHeaderString appendString:[formatter stringFromDate:appVersion.date]];
}
if (app.size) {
if (appVersion.size) {
if ([subHeaderString length]) {
[subHeaderString appendString:@" - "];
}
[subHeaderString appendString:app.sizeInMB];
[subHeaderString appendString:appVersion.sizeInMB];
}
appStoreHeader_.subHeaderLabel = subHeaderString;
_appStoreHeader.subHeaderLabel = subHeaderString;
}
- (void)appDidBecomeActive_ {
- (void)appDidBecomeActive {
if (self.appStoreButtonState == AppStoreButtonStateInstalling) {
[self setAppStoreButtonState:AppStoreButtonStateUpdate animated:YES];
} else if (![self.hockeyManager isCheckInProgress]) {
[self restoreStoreButtonStateAnimated_:YES];
} else if (![_updateManager isCheckInProgress]) {
[self restoreStoreButtonStateAnimated:YES];
}
}
- (void)openSettings:(id)sender {
BWHockeySettingsViewController *settings = [[[BWHockeySettingsViewController alloc] init] autorelease];
Class popoverControllerClass = NSClassFromString(@"UIPopoverController");
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && popoverControllerClass) {
if (popOverController_ == nil) {
popOverController_ = [[popoverControllerClass alloc] initWithContentViewController:settings];
}
if ([popOverController_ contentViewController].view.window) {
[popOverController_ dismissPopoverAnimated:YES];
}else {
[popOverController_ setPopoverContentSize: CGSizeMake(320, 440)];
[popOverController_ presentPopoverFromBarButtonItem:self.navigationItem.rightBarButtonItem
permittedArrowDirections:UIPopoverArrowDirectionUp animated:YES];
}
} else {
BW_IF_3_2_OR_GREATER(
settings.modalTransitionStyle = UIModalTransitionStylePartialCurl;
[self presentModalViewController:settings animated:YES];
)
BW_IF_PRE_3_2(
UINavigationController *navController = [[[UINavigationController alloc] initWithRootViewController:settings] autorelease];
navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:navController animated:YES];
)
}
}
- (UIImage *)addGlossToImage_:(UIImage *)image {
BW_IF_IOS4_OR_GREATER(UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);)
BW_IF_PRE_IOS4(UIGraphicsBeginImageContext(image.size);)
- (UIImage *)addGlossToImage:(UIImage *)image {
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);
[image drawAtPoint:CGPointZero];
UIImage *iconGradient = [UIImage bw_imageNamed:@"IconGradient.png" bundle:kHockeyBundleName];
UIImage *iconGradient = [UIImage bit_imageNamed:@"IconGradient.png" bundle:BITHOCKEYSDK_BUNDLE];
[iconGradient drawInRect:CGRectMake(0, 0, image.size.width, image.size.height) blendMode:kCGBlendModeNormal alpha:0.5];
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
@ -161,64 +132,62 @@
}
- (void)changePreviousVersionButtonBackground:(id)sender {
[(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)];
[(UIButton *)sender setBackgroundColor:BIT_RGBCOLOR(183,183,183)];
}
- (void)changePreviousVersionButtonBackgroundHighlighted:(id)sender {
[(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)];
[(UIButton *)sender setBackgroundColor:BIT_RGBCOLOR(183,183,183)];
}
- (void)showHidePreviousVersionsButton {
BOOL multipleVersionButtonNeeded = [self.hockeyManager.apps count] > 1 && !showAllVersions_;
BOOL multipleVersionButtonNeeded = [_updateManager.appVersions count] > 1 && !_showAllVersions;
if(multipleVersionButtonNeeded) {
// align at the bottom if tableview is small
UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kMinPreviousVersionButtonHeight)];
footerView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
footerView.backgroundColor = BW_RGBCOLOR(200, 202, 204);
footerView.backgroundColor = BIT_RGBCOLOR(200, 202, 204);
UIButton *footerButton = [UIButton buttonWithType:UIButtonTypeCustom];
BW_IF_IOS4_OR_GREATER(
//footerButton.layer.shadowOffset = CGSizeMake(-2, 2);
footerButton.layer.shadowColor = [[UIColor blackColor] CGColor];
footerButton.layer.shadowRadius = 2.0f;
)
//footerButton.layer.shadowOffset = CGSizeMake(-2, 2);
footerButton.layer.shadowColor = [[UIColor blackColor] CGColor];
footerButton.layer.shadowRadius = 2.0f;
footerButton.titleLabel.font = [UIFont boldSystemFontOfSize:14];
[footerButton setTitle:BWHockeyLocalize(@"HockeyShowPreviousVersions") forState:UIControlStateNormal];
[footerButton setTitle:BITHockeyLocalizedString(@"UpdateShowPreviousVersions") forState:UIControlStateNormal];
[footerButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[footerButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[footerButton setBackgroundImage:[UIImage bw_imageNamed:@"buttonHighlight.png" bundle:kHockeyBundleName] forState:UIControlStateHighlighted];
[footerButton setBackgroundImage:[UIImage bit_imageNamed:@"buttonHighlight.png" bundle:BITHOCKEYSDK_BUNDLE] forState:UIControlStateHighlighted];
footerButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
[footerButton addTarget:self action:@selector(showPreviousVersionAction) forControlEvents:UIControlEventTouchUpInside];
footerButton.frame = CGRectMake(0, kMinPreviousVersionButtonHeight-44, self.view.frame.size.width, 44);
footerButton.backgroundColor = BW_RGBCOLOR(183,183,183);
footerButton.backgroundColor = BIT_RGBCOLOR(183,183,183);
[footerView addSubview:footerButton];
self.tableView.tableFooterView = footerView;
[self realignPreviousVersionButton];
[footerView release];
} else {
self.tableView.tableFooterView = nil;
self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204);
self.tableView.backgroundColor = BIT_RGBCOLOR(200, 202, 204);
}
}
- (void)configureWebCell:(PSWebTableViewCell *)cell forApp_:(BWApp *)app {
- (void)configureWebCell:(PSWebTableViewCell *)cell forAppVersion:(BITAppVersionMetaInfo *)appVersion {
// create web view for a version
NSString *installed = @"";
if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) {
installed = [NSString stringWithFormat:@"<span style=\"float:%@;text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\"><b>%@</b></span>", [app isEqual:self.hockeyManager.app] ? @"left" : @"right", BWHockeyLocalize(@"HockeyInstalled")];
if ([appVersion.version isEqualToString:[_updateManager currentAppVersion]]) {
installed = [NSString stringWithFormat:@"<span style=\"float:%@;text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\"><b>%@</b></span>", [appVersion isEqual:_updateManager.newestAppVersion] ? @"left" : @"right", BITHockeyLocalizedString(@"UpdateInstalled")];
}
if ([app isEqual:self.hockeyManager.app]) {
if ([app.notes length] > 0) {
if ([appVersion isEqual:_updateManager.newestAppVersion]) {
if ([appVersion.notes length] > 0) {
installed = [NSString stringWithFormat:@"<p>&nbsp;%@</p>", installed];
cell.webViewContent = [NSString stringWithFormat:@"%@%@", installed, app.notes];
cell.webViewContent = [NSString stringWithFormat:@"%@%@", installed, appVersion.notes];
} else {
cell.webViewContent = [NSString stringWithFormat:@"<div style=\"min-height:200px;vertical-align:middle;text-align:center;text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\">%@</div>", BWHockeyLocalize(@"HockeyNoReleaseNotesAvailable")];
cell.webViewContent = [NSString stringWithFormat:@"<div style=\"min-height:200px;vertical-align:middle;text-align:center;text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\">%@</div>", BITHockeyLocalizedString(@"UpdateNoReleaseNotesAvailable")];
}
} else {
cell.webViewContent = [NSString stringWithFormat:@"<p><b style=\"text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\">%@</b>%@<br/><small>%@</small></p><p>%@</p>", [app versionString], installed, [app dateString], [app notesOrEmptyString]];
cell.webViewContent = [NSString stringWithFormat:@"<p><b style=\"text-shadow:rgba(255,255,255,0.6) 1px 1px 0px;\">%@</b>%@<br/><small>%@</small></p><p>%@</p>", [appVersion versionString], installed, [appVersion dateString], [appVersion notesOrEmptyString]];
}
cell.cellBackgroundColor = BW_RGBCOLOR(200, 202, 204);
cell.cellBackgroundColor = BIT_RGBCOLOR(200, 202, 204);
[cell addWebView];
// hack
@ -227,47 +196,39 @@
[cell addObserver:self forKeyPath:@"webViewSize" options:0 context:nil];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject
- (id)init:(BWHockeyManager *)newHockeyManager modal:(BOOL)newModal {
#pragma mark - Init
- (id)init:(BITUpdateManager *)newUpdateManager modal:(BOOL)newModal {
if ((self = [super initWithStyle:UITableViewStylePlain])) {
self.hockeyManager = newHockeyManager;
self.updateManager = newUpdateManager;
self.modal = newModal;
self.modalAnimated = YES;
self.title = BWHockeyLocalize(@"HockeyUpdateScreenTitle");
self.title = BITHockeyLocalizedString(@"UpdateScreenTitle");
if ([self.hockeyManager shouldShowUserSettings]) {
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithImage:[UIImage bw_imageNamed:@"gear.png" bundle:kHockeyBundleName]
style:UIBarButtonItemStyleBordered
target:self
action:@selector(openSettings:)] autorelease];
}
cells_ = [[NSMutableArray alloc] initWithCapacity:5];
popOverController_ = nil;
_isAppStoreEnvironment = [BITHockeyManager sharedHockeyManager].isAppStoreEnvironment;
_cells = [[NSMutableArray alloc] initWithCapacity:5];
_popOverController = nil;
}
return self;
}
- (id)init {
return [self init:[BWHockeyManager sharedHockeyManager] modal:NO];
return [self init:[BITHockeyManager sharedHockeyManager].updateManager modal:NO];
}
- (void)dealloc {
[self viewDidUnload];
for (UITableViewCell *cell in cells_) {
for (UITableViewCell *cell in _cells) {
[cell removeObserver:self forKeyPath:@"webViewSize"];
}
[cells_ release];
[_cells release];
[super dealloc];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark View lifecycle
#pragma mark - View lifecycle
- (void)onAction:(id)sender {
if (self.modal) {
@ -275,7 +236,10 @@
SEL presentingViewControllerSelector = NSSelectorFromString(@"presentingViewController");
UIViewController *presentingViewController = nil;
if ([self respondsToSelector:presentingViewControllerSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
presentingViewController = [self performSelector:presentingViewControllerSelector];
#pragma clang diagnostic pop
}
else {
presentingViewController = [self parentViewController];
@ -293,7 +257,7 @@
[self.navigationController popViewControllerAnimated:YES];
}
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_];
[[UIApplication sharedApplication] setStatusBarStyle:_statusBarStyle];
}
- (CAGradientLayer *)backgroundLayer {
@ -324,25 +288,25 @@
// add notifications only to loaded view
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[dnc addObserver:self selector:@selector(appDidBecomeActive_) name:UIApplicationDidBecomeActiveNotification object:nil];
[dnc addObserver:self selector:@selector(appDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
// hook into manager with kvo!
[self.hockeyManager addObserver:self forKeyPath:@"checkInProgress" options:0 context:nil];
[self.hockeyManager addObserver:self forKeyPath:@"isUpdateURLOffline" options:0 context:nil];
[self.hockeyManager addObserver:self forKeyPath:@"updateAvailable" options:0 context:nil];
[self.hockeyManager addObserver:self forKeyPath:@"apps" options:0 context:nil];
kvoRegistered_ = YES;
[_updateManager addObserver:self forKeyPath:@"checkInProgress" options:0 context:nil];
[_updateManager addObserver:self forKeyPath:@"isUpdateURLOffline" options:0 context:nil];
[_updateManager addObserver:self forKeyPath:@"updateAvailable" options:0 context:nil];
[_updateManager addObserver:self forKeyPath:@"apps" options:0 context:nil];
_kvoRegistered = YES;
self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204);
self.tableView.backgroundColor = BIT_RGBCOLOR(200, 202, 204);
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
UIView *topView = [[[UIView alloc] initWithFrame:CGRectMake(0, -(600-kAppStoreViewHeight), self.view.frame.size.width, 600)] autorelease];
topView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
topView.backgroundColor = BW_RGBCOLOR(140, 141, 142);
topView.backgroundColor = BIT_RGBCOLOR(140, 141, 142);
[self.tableView addSubview:topView];
appStoreHeader_ = [[PSAppStoreHeader alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kAppStoreViewHeight)];
[self updateAppStoreHeader_];
_appStoreHeader = [[PSAppStoreHeader alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kAppStoreViewHeight)];
[self updateAppStoreHeader];
NSString *iconString = nil;
NSArray *icons = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFiles"];
@ -362,7 +326,7 @@
if (icons) {
BOOL useHighResIcon = NO;
BW_IF_IOS4_OR_GREATER(if ([UIScreen mainScreen].scale == 2.0f) useHighResIcon = YES;)
if ([UIScreen mainScreen].scale == 2.0f) useHighResIcon = YES;
for(NSString *icon in icons) {
iconString = icon;
@ -386,12 +350,12 @@
}
if (addGloss) {
appStoreHeader_.iconImage = [self addGlossToImage_:[UIImage imageNamed:iconString]];
_appStoreHeader.iconImage = [self addGlossToImage:[UIImage imageNamed:iconString]];
} else {
appStoreHeader_.iconImage = [UIImage imageNamed:iconString];
_appStoreHeader.iconImage = [UIImage imageNamed:iconString];
}
self.tableView.tableHeaderView = appStoreHeader_;
self.tableView.tableHeaderView = _appStoreHeader;
if (self.modal) {
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
@ -406,47 +370,47 @@
storeButton.buttonData = [PSStoreButtonData dataWithLabel:@"" colors:[PSStoreButton appStoreGrayColor] enabled:NO];
self.appStoreButtonState = AppStoreButtonStateCheck;
[storeButton alignToSuperview];
appStoreButton_ = [storeButton retain];
_appStoreButton = [storeButton retain];
}
- (void)viewWillAppear:(BOOL)animated {
if ([self.hockeyManager isAppStoreEnvironment])
if (_isAppStoreEnvironment)
self.appStoreButtonState = AppStoreButtonStateOffline;
self.hockeyManager.currentHockeyViewController = self;
_updateManager.currentHockeyViewController = self;
[super viewWillAppear:animated];
statusBarStyle_ = [[UIApplication sharedApplication] statusBarStyle];
_statusBarStyle = [[UIApplication sharedApplication] statusBarStyle];
[[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque];
[self redrawTableView];
}
- (void)viewWillDisappear:(BOOL)animated {
self.hockeyManager.currentHockeyViewController = nil;
_updateManager.currentHockeyViewController = nil;
//if the popover is still visible, dismiss it
[popOverController_ dismissPopoverAnimated:YES];
[_popOverController dismissPopoverAnimated:YES];
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_];
[[UIApplication sharedApplication] setStatusBarStyle:_statusBarStyle];
}
- (void)redrawTableView {
[self restoreStoreButtonStateAnimated_:NO];
[self updateAppStoreHeader_];
[self restoreStoreButtonStateAnimated:NO];
[self updateAppStoreHeader];
// clean up and remove any pending overservers
for (UITableViewCell *cell in cells_) {
for (UITableViewCell *cell in _cells) {
[cell removeObserver:self forKeyPath:@"webViewSize"];
}
[cells_ removeAllObjects];
[_cells removeAllObjects];
int i = 0;
BOOL breakAfterThisApp = NO;
for (BWApp *app in self.hockeyManager.apps) {
BOOL breakAfterThisAppVersion = NO;
for (BITAppVersionMetaInfo *appVersion in _updateManager.appVersions) {
i++;
// only show the newer version of the app by default, if we don't show all versions
if (!showAllVersions_) {
if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) {
if (!_showAllVersions) {
if ([appVersion.version isEqualToString:[_updateManager currentAppVersion]]) {
if (i == 1) {
breakAfterThisApp = YES;
breakAfterThisAppVersion = YES;
} else {
break;
}
@ -454,10 +418,10 @@
}
PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease];
[self configureWebCell:cell forApp_:app];
[cells_ addObject:cell];
[self configureWebCell:cell forAppVersion:appVersion];
[_cells addObject:cell];
if (breakAfterThisApp) break;
if (breakAfterThisAppVersion) break;
}
[self.tableView reloadData];
@ -465,14 +429,14 @@
}
- (void)showPreviousVersionAction {
showAllVersions_ = YES;
_showAllVersions = YES;
BOOL showAllPending = NO;
for (BWApp *app in self.hockeyManager.apps) {
for (BITAppVersionMetaInfo *appVersion in _updateManager.appVersions) {
if (!showAllPending) {
if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) {
if ([appVersion.version isEqualToString:[_updateManager currentAppVersion]]) {
showAllPending = YES;
if (app == self.hockeyManager.app) {
if (appVersion == _updateManager.newestAppVersion) {
continue; // skip this version already if it the latest version is the installed one
}
} else {
@ -481,33 +445,32 @@
}
PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease];
[self configureWebCell:cell forApp_:app];
[cells_ addObject:cell];
[self configureWebCell:cell forAppVersion:appVersion];
[_cells addObject:cell];
}
[self.tableView reloadData];
[self showHidePreviousVersionsButton];
}
- (void)viewDidUnload {
[appStoreHeader_ release]; appStoreHeader_ = nil;
[popOverController_ release], popOverController_ = nil;
[_appStoreHeader release]; _appStoreHeader = nil;
[_popOverController release], _popOverController = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
// test if KVO's are registered. if class is destroyed before it was shown(viewDidLoad) no KVOs are registered.
if (kvoRegistered_) {
[self.hockeyManager removeObserver:self forKeyPath:@"checkInProgress"];
[self.hockeyManager removeObserver:self forKeyPath:@"isUpdateURLOffline"];
[self.hockeyManager removeObserver:self forKeyPath:@"updateAvailable"];
[self.hockeyManager removeObserver:self forKeyPath:@"apps"];
kvoRegistered_ = NO;
if (_kvoRegistered) {
[_updateManager removeObserver:self forKeyPath:@"checkInProgress"];
[_updateManager removeObserver:self forKeyPath:@"isUpdateURLOffline"];
[_updateManager removeObserver:self forKeyPath:@"updateAvailable"];
[_updateManager removeObserver:self forKeyPath:@"apps"];
_kvoRegistered = NO;
}
[super viewDidUnload];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Table view data source
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
@ -516,31 +479,30 @@
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat rowHeight = 0;
if ([cells_ count] > (NSUInteger)indexPath.row) {
PSWebTableViewCell *cell = [cells_ objectAtIndex:indexPath.row];
if ([_cells count] > (NSUInteger)indexPath.row) {
PSWebTableViewCell *cell = [_cells objectAtIndex:indexPath.row];
rowHeight = cell.webViewSize.height;
}
if ([self.hockeyManager.apps count] > 1 && !showAllVersions_) {
self.tableView.backgroundColor = BW_RGBCOLOR(183, 183, 183);
if ([_updateManager.appVersions count] > 1 && !_showAllVersions) {
self.tableView.backgroundColor = BIT_RGBCOLOR(183, 183, 183);
}
if (rowHeight == 0) {
rowHeight = indexPath.row == 0 ? 250 : 44; // fill screen on startup
self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204);
self.tableView.backgroundColor = BIT_RGBCOLOR(200, 202, 204);
}
return rowHeight;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger cellCount = [cells_ count];
NSInteger cellCount = [_cells count];
return cellCount;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark KVO
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// only make changes if we are visible
@ -549,15 +511,15 @@
[self.tableView reloadData];
[self realignPreviousVersionButton];
} else if ([keyPath isEqualToString:@"checkInProgress"]) {
if (self.hockeyManager.isCheckInProgress) {
if (_updateManager.isCheckInProgress) {
[self setAppStoreButtonState:AppStoreButtonStateSearching animated:YES];
}else {
[self restoreStoreButtonStateAnimated_:YES];
[self restoreStoreButtonStateAnimated:YES];
}
} else if ([keyPath isEqualToString:@"isUpdateURLOffline"]) {
[self restoreStoreButtonStateAnimated_:YES];
[self restoreStoreButtonStateAnimated:YES];
} else if ([keyPath isEqualToString:@"updateAvailable"]) {
[self restoreStoreButtonStateAnimated_:YES];
[self restoreStoreButtonStateAnimated:YES];
} else if ([keyPath isEqualToString:@"apps"]) {
[self redrawTableView];
}
@ -566,18 +528,16 @@
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([cells_ count] > (NSUInteger)indexPath.row) {
return [cells_ objectAtIndex:indexPath.row];
if ([_cells count] > (NSUInteger)indexPath.row) {
return [_cells objectAtIndex:indexPath.row];
} else {
BWHockeyLog(@"Warning: cells_ and indexPath do not match? forgot calling redrawTableView?");
BITHockeyLog(@"Warning: cells_ and indexPath do not match? forgot calling redrawTableView?");
}
return nil;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Rotation
#pragma mark - Rotation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
BOOL shouldAutorotate;
@ -595,35 +555,34 @@
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
// update all cells
[cells_ makeObjectsPerformSelector:@selector(addWebView)];
[_cells makeObjectsPerformSelector:@selector(addWebView)];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark PSAppStoreHeaderDelegate
#pragma mark - PSAppStoreHeaderDelegate
- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState {
[self setAppStoreButtonState:anAppStoreButtonState animated:NO];
}
- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState animated:(BOOL)animated {
appStoreButtonState_ = anAppStoreButtonState;
_appStoreButtonState = anAppStoreButtonState;
switch (anAppStoreButtonState) {
case AppStoreButtonStateOffline:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonOffline") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
[_appStoreButton setButtonData:[PSStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"UpdateButtonOffline") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
break;
case AppStoreButtonStateCheck:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonCheck") colors:[PSStoreButton appStoreGreenColor] enabled:YES] animated:animated];
[_appStoreButton setButtonData:[PSStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"UpdateButtonCheck") colors:[PSStoreButton appStoreGreenColor] enabled:YES] animated:animated];
break;
case AppStoreButtonStateSearching:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonSearching") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
[_appStoreButton setButtonData:[PSStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"UpdateButtonSearching") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
break;
case AppStoreButtonStateUpdate:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonUpdate") colors:[PSStoreButton appStoreBlueColor] enabled:YES] animated:animated];
[_appStoreButton setButtonData:[PSStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"UpdateButtonUpdate") colors:[PSStoreButton appStoreBlueColor] enabled:YES] animated:animated];
break;
case AppStoreButtonStateInstalling:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonInstalling") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
[_appStoreButton setButtonData:[PSStoreButtonData dataWithLabel:BITHockeyLocalizedString(@"UpdateButtonInstalling") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
break;
default:
break;
@ -631,12 +590,12 @@
}
- (void)storeButtonFired:(PSStoreButton *)button {
switch (appStoreButtonState_) {
switch (_appStoreButtonState) {
case AppStoreButtonStateCheck:
[self.hockeyManager checkForUpdateShowFeedback:YES];
[_updateManager checkForUpdateShowFeedback:YES];
break;
case AppStoreButtonStateUpdate:
if ([self.hockeyManager initiateAppDownload]) {
if ([_updateManager initiateAppDownload]) {
[self setAppStoreButtonState:AppStoreButtonStateInstalling animated:YES];
};
break;

View file

@ -0,0 +1,59 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Peter Steinberger
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde, Peter Steinberger.
* 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 <UIKit/UIKit.h>
#import "PSStoreButton.h"
typedef enum {
AppStoreButtonStateOffline,
AppStoreButtonStateCheck,
AppStoreButtonStateSearching,
AppStoreButtonStateUpdate,
AppStoreButtonStateInstalling
} AppStoreButtonState;
@class BITUpdateManager;
@protocol PSStoreButtonDelegate;
@interface BITUpdateViewController() <PSStoreButtonDelegate> {
}
@property (nonatomic, retain) BITUpdateManager *updateManager;
@property (nonatomic, readwrite) BOOL modal;
@property (nonatomic, readwrite) BOOL modalAnimated;
@property (nonatomic, assign) AppStoreButtonState appStoreButtonState;
- (id)init:(BITUpdateManager *)newUpdateManager modal:(BOOL)newModal;
- (id)init;
@end

View file

@ -1,56 +0,0 @@
//
// BWApp.h
// HockeyDemo
//
// Created by Peter Steinberger on 04.02.11.
// Copyright 2011 Buzzworks. 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 <Foundation/Foundation.h>
@interface BWApp : NSObject {
NSString *name_;
NSString *version_;
NSString *shortVersion_;
NSString *notes_;
NSDate *date_;
NSNumber *size_;
NSNumber *mandatory_;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *version;
@property (nonatomic, copy) NSString *shortVersion;
@property (nonatomic, copy) NSString *notes;
@property (nonatomic, copy) NSDate *date;
@property (nonatomic, copy) NSNumber *size;
@property (nonatomic, copy) NSNumber *mandatory;
- (NSString *)nameAndVersionString;
- (NSString *)versionString;
- (NSString *)dateString;
- (NSString *)sizeInMB;
- (NSString *)notesOrEmptyString;
- (void)setDateWithTimestamp:(NSTimeInterval)timestamp;
- (BOOL)isValid;
- (BOOL)isEqualToBWApp:(BWApp *)anApp;
+ (BWApp *)appFromDict:(NSDictionary *)dict;
@end

View file

@ -1,186 +0,0 @@
//
// BWApp.m
// HockeyDemo
//
// Created by Peter Steinberger on 04.02.11.
// Copyright 2011 Buzzworks. 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 "BWApp.h"
#import "BWGlobal.h"
@implementation BWApp
@synthesize name = name_;
@synthesize version = version_;
@synthesize shortVersion = shortVersion_;
@synthesize notes = notes_;
@synthesize date = date_;
@synthesize size = size_;
@synthesize mandatory = mandatory_;
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark static
+ (BWApp *)appFromDict:(NSDictionary *)dict {
BWApp *app = [[[[self class] alloc] init] autorelease];
// NSParameterAssert([dict isKindOfClass:[NSDictionary class]]);
if ([dict isKindOfClass:[NSDictionary class]]) {
app.name = [dict objectForKey:@"title"];
app.version = [dict objectForKey:@"version"];
app.shortVersion = [dict objectForKey:@"shortversion"];
[app setDateWithTimestamp:[[dict objectForKey:@"timestamp"] doubleValue]];
app.size = [dict objectForKey:@"appsize"];
app.notes = [dict objectForKey:@"notes"];
app.mandatory = [dict objectForKey:@"mandatory"];
}
return app;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject
- (void)dealloc {
[name_ release];
[version_ release];
[shortVersion_ release];
[notes_ release];
[date_ release];
[size_ release];
[mandatory_ release];
[super dealloc];
}
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [self isEqualToBWApp:other];
}
- (BOOL)isEqualToBWApp:(BWApp *)anApp {
if (self == anApp)
return YES;
if (self.name != anApp.name && ![self.name isEqualToString:anApp.name])
return NO;
if (self.version != anApp.version && ![self.version isEqualToString:anApp.version])
return NO;
if (self.shortVersion != anApp.shortVersion && ![self.shortVersion isEqualToString:anApp.shortVersion])
return NO;
if (self.notes != anApp.notes && ![self.notes isEqualToString:anApp.notes])
return NO;
if (self.date != anApp.date && ![self.date isEqualToDate:anApp.date])
return NO;
if (self.size != anApp.size && ![self.size isEqualToNumber:anApp.size])
return NO;
if (self.mandatory != anApp.mandatory && ![self.mandatory isEqualToNumber:anApp.mandatory])
return NO;
return YES;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSCoder
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeObject:self.version forKey:@"version"];
[encoder encodeObject:self.shortVersion forKey:@"shortVersion"];
[encoder encodeObject:self.notes forKey:@"notes"];
[encoder encodeObject:self.date forKey:@"date"];
[encoder encodeObject:self.size forKey:@"size"];
[encoder encodeObject:self.mandatory forKey:@"mandatory"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) {
self.name = [decoder decodeObjectForKey:@"name"];
self.version = [decoder decodeObjectForKey:@"version"];
self.shortVersion = [decoder decodeObjectForKey:@"shortVersion"];
self.notes = [decoder decodeObjectForKey:@"notes"];
self.date = [decoder decodeObjectForKey:@"date"];
self.size = [decoder decodeObjectForKey:@"size"];
self.mandatory = [decoder decodeObjectForKey:@"mandatory"];
}
return self;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Properties
- (NSString *)nameAndVersionString {
NSString *appNameAndVersion = [NSString stringWithFormat:@"%@ %@", self.name, [self versionString]];
return appNameAndVersion;
}
- (NSString *)versionString {
NSString *shortString = ([self.shortVersion respondsToSelector:@selector(length)] && [self.shortVersion length]) ? [NSString stringWithFormat:@"%@", self.shortVersion] : @"";
NSString *versionString = [shortString length] ? [NSString stringWithFormat:@" (%@)", self.version] : self.version;
return [NSString stringWithFormat:@"%@ %@%@", BWHockeyLocalize(@"HockeyVersion"), shortString, versionString];
}
- (NSString *)dateString {
NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setDateStyle:NSDateFormatterMediumStyle];
return [formatter stringFromDate:self.date];
}
- (NSString *)sizeInMB {
if ([size_ isKindOfClass: [NSNumber class]] && [size_ doubleValue] > 0) {
double appSizeInMB = [size_ doubleValue]/(1024*1024);
NSString *appSizeString = [NSString stringWithFormat:@"%.1f MB", appSizeInMB];
return appSizeString;
}
return @"0 MB";
}
- (void)setDateWithTimestamp:(NSTimeInterval)timestamp {
if (timestamp) {
NSDate *appDate = [NSDate dateWithTimeIntervalSince1970:timestamp];
self.date = appDate;
} else {
self.date = nil;
}
}
- (NSString *)notesOrEmptyString {
if (self.notes) {
return self.notes;
}else {
return [NSString string];
}
}
// a valid app needs at least following properties: name, version, date
- (BOOL)isValid {
BOOL valid = [self.name length] && [self.version length] && self.date;
return valid;
}
@end

View file

@ -1,127 +0,0 @@
//
// BWGlobal.h
//
// Created by Andreas Linde on 08/17/10.
// Copyright 2010-2011 Andreas Linde, Peter Steinberger. 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 "BWHockeyManager.h"
#import "BWApp.h"
#define SDK_NAME @"HockeySDK"
#define SDK_VERSION @"2.2.6"
#ifndef HOCKEY_BLOCK_UDID
#define HOCKEY_BLOCK_UDID 1
#endif
// uncomment this line to enable NSLog-debugging output
//#define kHockeyDebugEnabled
#define kArrayOfLastHockeyCheck @"ArrayOfLastHockeyCheck"
#define kDateOfLastHockeyCheck @"DateOfLastHockeyCheck"
#define kDateOfVersionInstallation @"DateOfVersionInstallation"
#define kUsageTimeOfCurrentVersion @"UsageTimeOfCurrentVersion"
#define kUsageTimeForVersionString @"kUsageTimeForVersionString"
#define kHockeyAutoUpdateSetting @"HockeyAutoUpdateSetting"
#define kHockeyAllowUserSetting @"HockeyAllowUserSetting"
#define kHockeyAllowUsageSetting @"HockeyAllowUsageSetting"
#define kHockeyAutoUpdateSetting @"HockeyAutoUpdateSetting"
#define kHockeyAuthorizedVersion @"HockeyAuthorizedVersion"
#define kHockeyAuthorizedToken @"HockeyAuthorizedToken"
#define kHockeyBundleName @"Hockey.bundle"
// Notification message which HockeyManager is listening to, to retry requesting updated from the server
#define BWHockeyNetworkBecomeReachable @"NetworkDidBecomeReachable"
#define BWHockeyLog(fmt, ...) do { if([BWHockeyManager sharedHockeyManager].isLoggingEnabled) { NSLog((@"[HockeyLib] %s/%d " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); }} while(0)
NSBundle *hockeyBundle(void);
NSString *BWmd5(NSString *str);
NSString *BWHockeyLocalize(NSString *stringToken);
// compatibility helper
#ifdef HOCKEYLIB_STATIC_LIBRARY
// If HockeyLib is built as a static library and linked into the project
// we can't use this project's deployment target to statically decide if
// native JSON is available
#define BW_NATIVE_JSON_AVAILABLE 0
#else
#define BW_NATIVE_JSON_AVAILABLE __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000
#endif
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_2
#define kCFCoreFoundationVersionNumber_iPhoneOS_3_2 478.61
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 32000
#define BW_IF_3_2_OR_GREATER(...) \
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_3_2) \
{ \
__VA_ARGS__ \
}
#else
#define BW_IF_3_2_OR_GREATER(...)
#endif
#define BW_IF_PRE_3_2(...) \
if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_2) \
{ \
__VA_ARGS__ \
}
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_4_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_4_0 550.32
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
#define BW_IF_IOS4_OR_GREATER(...) \
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \
{ \
__VA_ARGS__ \
}
#else
#define BW_IF_IOS4_OR_GREATER(...)
#endif
#define BW_IF_PRE_IOS4(...) \
if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_4_0) \
{ \
__VA_ARGS__ \
}
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_5_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_5_0 674.0
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000
#define BW_IF_IOS5_OR_GREATER(...) \
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_5_0) \
{ \
__VA_ARGS__ \
}
#else
#define BW_IF_IOS5_OR_GREATER(...)
#endif
#define BW_IF_PRE_IOS5(...) \
if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_5_0) \
{ \
__VA_ARGS__ \
}

View file

@ -1,61 +0,0 @@
//
// BWGlobal.h
//
// Created by Andreas Linde on 08/17/10.
// Copyright 2010-2011 Andreas Linde, Peter Steinberger. 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 "BWGlobal.h"
#include <CommonCrypto/CommonDigest.h>
NSBundle *hockeyBundle(void) {
static NSBundle* bundle = nil;
if (!bundle) {
NSString* path = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:kHockeyBundleName];
bundle = [[NSBundle bundleWithPath:path] retain];
}
return bundle;
}
NSString *BWmd5(NSString *str) {
const char *cStr = [str UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5( cStr, strlen(cStr), result );
return [NSString
stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1],
result[2], result[3],
result[4], result[5],
result[6], result[7],
result[8], result[9],
result[10], result[11],
result[12], result[13],
result[14], result[15]
];
}
NSString *BWHockeyLocalize(NSString *stringToken) {
if (hockeyBundle()) {
return NSLocalizedStringFromTableInBundle(stringToken, @"Hockey", hockeyBundle(), @"");
} else {
return stringToken;
}
}

View file

@ -1,266 +0,0 @@
//
// BWHockeyManager.h
//
// Created by Andreas Linde on 8/17/10.
// Copyright 2010-2011 Andreas Linde. 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 <UIKit/UIKit.h>
#import "BWHockeyViewController.h"
#import "BWGlobal.h"
typedef enum {
HockeyComparisonResultDifferent,
HockeyComparisonResultGreater
} HockeyComparisonResult;
typedef enum {
HockeyAuthorizationDenied,
HockeyAuthorizationAllowed,
HockeyAuthorizationPending
} HockeyAuthorizationState;
typedef enum {
HockeyUpdateCheckStartup = 0,
HockeyUpdateCheckDaily = 1,
HockeyUpdateCheckManually = 2
} HockeyUpdateSetting;
@protocol BWHockeyManagerDelegate;
@class BWApp;
@interface BWHockeyManager : NSObject <UIAlertViewDelegate> {
id <BWHockeyManagerDelegate> delegate_;
NSArray *apps_;
NSString *updateURL_;
NSString *appIdentifier_;
NSString *currentAppVersion_;
UINavigationController *navController_;
BWHockeyViewController *currentHockeyViewController_;
UIView *authorizeView_;
NSMutableData *receivedData_;
BOOL loggingEnabled_;
BOOL checkInProgress_;
BOOL dataFound;
BOOL updateAvailable_;
BOOL showFeedback_;
BOOL updateURLOffline_;
BOOL updateAlertShowing_;
BOOL lastCheckFailed_;
BOOL isAppStoreEnvironment_;
NSURLConnection *urlConnection_;
NSDate *lastCheck_;
NSDate *usageStartTimestamp_;
BOOL sendUserData_;
BOOL sendUsageTime_;
BOOL allowUserToDisableSendData_;
BOOL userAllowsSendUserData_;
BOOL userAllowsSendUsageTime_;
BOOL showUpdateReminder_;
BOOL checkForUpdateOnLaunch_;
HockeyComparisonResult compareVersionType_;
HockeyUpdateSetting updateSetting_;
BOOL showUserSettings_;
BOOL showDirectInstallOption_;
BOOL requireAuthorization_;
NSString *authenticationSecret_;
BOOL checkForTracker_;
NSDictionary *trackerConfig_;
UIBarStyle barStyle_;
UIModalPresentationStyle modalPresentationStyle_;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// this is a singleton
+ (BWHockeyManager *)sharedHockeyManager;
// update url needs to be set
@property (nonatomic, retain) NSString *updateURL;
// private app identifier (optional)
@property (nonatomic, retain) NSString *appIdentifier;
// delegate is optional
@property (nonatomic, assign) id <BWHockeyManagerDelegate> delegate;
// hockey secret is required if authentication is used
@property (nonatomic, retain) NSString *authenticationSecret;
///////////////////////////////////////////////////////////////////////////////////////////////////
// Setting Properties
// if YES, states will be logged using NSLog. Only enable this for debugging!
// if NO, nothing will be logged. (default)
@property (nonatomic, assign, getter=isLoggingEnabled) BOOL loggingEnabled;
// if YES, the current user data is send: device type, iOS version, app version, UDID (default)
// if NO, no such data is send to the server
@property (nonatomic, assign, getter=shouldSendUserData) BOOL sendUserData;
// if YES, the the users usage time of the app to the service, only in 1 minute granularity! (default)
// if NO, no such data is send to the server
@property (nonatomic, assign, getter=shouldSendUsageTime) BOOL sendUsageTime;
// if YES, the user agrees to send the usage data, user can change it if the developer shows the settings (default)
// if NO, the user overwrites the developer setting and no such data is sent
@property (nonatomic, assign, getter=isAllowUserToDisableSendData) BOOL allowUserToDisableSendData;
// if YES, the user allowed to send user data (default)
// if NO, the user denied to send user data
@property (nonatomic, assign, getter=doesUserAllowsSendUserData) BOOL userAllowsSendUserData;
// if YES, the user allowed to send usage data (default)
// if NO, the user denied to send usage data
@property (nonatomic, assign, getter=doesUserAllowsSendUsageTime) BOOL userAllowsSendUsageTime;
// if YES, the new version alert will be displayed always if the current version is outdated (default)
// if NO, the alert will be displayed only once for each new update
@property (nonatomic, assign) BOOL alwaysShowUpdateReminder;
// if YES, the user can change the HockeyUpdateSetting value (default)
// if NO, the user can not change it, and the default or developer defined value will be used
@property (nonatomic, assign, getter=shouldShowUserSettings) BOOL showUserSettings;
// set bar style of navigation controller
@property (nonatomic, assign) UIBarStyle barStyle;
// set modal presentation style of update view
@property (nonatomic, assign) UIModalPresentationStyle modalPresentationStyle;
// if YES, then an update check will be performed after the application becomes active (default)
// if NO, then the update check will not happen unless invoked explicitly
@property (nonatomic, assign, getter=isCheckForUpdateOnLaunch) BOOL checkForUpdateOnLaunch;
// if YES, the alert notifying about an new update also shows a button to install the update directly
// if NO, the alert notifying about an new update only shows ignore and show update button
@property (nonatomic, assign, getter=isShowingDirectInstallOption) BOOL showDirectInstallOption;
// if YES, each app version needs to be authorized by the server to run on this device
// if NO, each app version does not need to be authorized (default)
@property (nonatomic, assign, getter=isRequireAuthorization) BOOL requireAuthorization;
// HockeyComparisonResultDifferent: alerts if the version on the server is different (default)
// HockeyComparisonResultGreater: alerts if the version on the server is greater
@property (nonatomic, assign) HockeyComparisonResult compareVersionType;
// see HockeyUpdateSetting-enum. Will be saved in user defaults.
// default value: HockeyUpdateCheckStartup
@property (nonatomic, assign) HockeyUpdateSetting updateSetting;
///////////////////////////////////////////////////////////////////////////////////////////////////
// Private Properties
// if YES, the API will return an existing JMC config
// if NO, the API will return only version information
@property (nonatomic, assign) BOOL checkForTracker;
// Contains the tracker config if received from server
@property (nonatomic, retain, readonly) NSDictionary *trackerConfig;
// if YES the app is installed from the app store
// if NO the app is installed via ad-hoc or enterprise distribution
@property (nonatomic, readonly) BOOL isAppStoreEnvironment;
// is an update available?
- (BOOL)isUpdateAvailable;
// are we currently checking for updates?
- (BOOL)isCheckInProgress;
// open update info view
- (void)showUpdateView;
// manually start an update check
- (void)checkForUpdate;
// checks for update, informs the user (error, no update found, etc)
- (void)checkForUpdateShowFeedback:(BOOL)feedback;
// initiates app-download call. displays an system UIAlertView
- (BOOL)initiateAppDownload;
// checks wether this app version is authorized
- (BOOL)appVersionIsAuthorized;
// start checking for an authorization key
- (void)checkForAuthorization;
// convenience methode to create hockey view controller
- (BWHockeyViewController *)hockeyViewController:(BOOL)modal;
// get/set current active hockey view controller
@property (nonatomic, retain) BWHockeyViewController *currentHockeyViewController;
// convenience method to get current running version string
- (NSString *)currentAppVersion;
// get newest app or array of all available versions
- (BWApp *)app;
- (NSArray *)apps;
// check if there is any newer version mandatory
- (BOOL)hasNewerMandatoryVersion;
@end
///////////////////////////////////////////////////////////////////////////////////////////////////
@protocol BWHockeyManagerDelegate <NSObject>
/*
Return the device UDID which is required for beta testing, should return nil for app store configuration!
Example implementation if your configuration for the App Store is called "AppStore":
#ifndef (CONFIGURATION_AppStore)
if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)])
return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)];
#endif
return nil;
*/
- (NSString *)customDeviceIdentifier;
@optional
// Invoked when the internet connection is started, to let the app enable the activity indicator
- (void)connectionOpened;
// Invoked when the internet connection is closed, to let the app disable the activity indicator
- (void)connectionClosed;
// optional parent view controller for the update screen when invoked via the alert view, default is the root UIWindow instance
- (UIViewController *)viewControllerForHockeyController:(BWHockeyManager *)hockeyController;
@end

File diff suppressed because it is too large Load diff

View file

@ -1,22 +0,0 @@
//
// BWHockeySettingsViewController.h
// HockeyDemo
//
// Created by Andreas Linde on 3/8/11.
// Copyright 2011 Andreas Linde. All rights reserved.
//
#import <UIKit/UIKit.h>
@class BWHockeyManager;
@interface BWHockeySettingsViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
BWHockeyManager *hockeyManager_;
}
@property (nonatomic, retain) BWHockeyManager *hockeyManager;
- (id)init:(BWHockeyManager *)newHockeyManager;
- (id)init;
@end

View file

@ -1,294 +0,0 @@
//
// BWHockeySettingsViewController.m
// HockeyDemo
//
// Created by Andreas Linde on 3/8/11.
// Copyright 2011 Andreas Linde. All rights reserved.
//
#import "BWHockeySettingsViewController.h"
#import "BWHockeyManager.h"
#import "BWGlobal.h"
#define BW_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1]
@implementation BWHockeySettingsViewController
@synthesize hockeyManager = hockeyManager_;
- (void)dismissSettings {
[self.navigationController dismissModalViewControllerAnimated:YES];
}
#pragma mark -
#pragma mark Initialization
- (id)init:(BWHockeyManager *)newHockeyManager {
if ((self = [super init])) {
self.hockeyManager = newHockeyManager;
self.title = BWHockeyLocalize(@"HockeySettingsTitle");
CGRect frame = self.view.frame;
frame.origin = CGPointZero;
UITableView *tableView_ = [[[UITableView alloc] initWithFrame:CGRectMake(0, self.view.frame.size.height - 260, self.view.frame.size.width, 260) style:UITableViewStyleGrouped] autorelease];
tableView_.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleWidth;
BW_IF_3_2_OR_GREATER(
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
self.view.backgroundColor = BW_RGBCOLOR(200, 202, 204);
tableView_.backgroundColor = BW_RGBCOLOR(200, 202, 204);
} else {
tableView_.frame = frame;
tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
}
)
BW_IF_PRE_3_2(
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(dismissSettings)] autorelease];
tableView_.frame = frame;
tableView_.autoresizingMask = tableView_.autoresizingMask | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
)
tableView_.delegate = self;
tableView_.dataSource = self;
tableView_.clipsToBounds = NO;
[self.view addSubview:tableView_];
}
return self;
}
- (id)init {
return [self init:[BWHockeyManager sharedHockeyManager]];
}
#pragma mark -
#pragma mark Table view data source
- (int)numberOfSections {
int numberOfSections = 1;
if ([self.hockeyManager isAllowUserToDisableSendData]) {
if ([self.hockeyManager shouldSendUserData]) numberOfSections++;
if ([self.hockeyManager shouldSendUsageTime]) numberOfSections++;
}
return numberOfSections;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (section == [self numberOfSections] - 1) {
return BWHockeyLocalize(@"HockeySectionCheckTitle");
} else {
return nil;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
if (section < [self numberOfSections] - 1) {
return 66;
} else return 0;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
if ([self numberOfSections] > 1 && section < [self numberOfSections] - 1) {
UILabel *footer = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, 285, 66)] autorelease];
footer.backgroundColor = [UIColor clearColor];
footer.numberOfLines = 3;
footer.textAlignment = UITextAlignmentCenter;
footer.adjustsFontSizeToFitWidth = YES;
footer.textColor = [UIColor grayColor];
footer.font = [UIFont systemFontOfSize:13];
if (section == 0 && [self.hockeyManager isAllowUserToDisableSendData] && [self.hockeyManager shouldSendUserData]) {
footer.text = BWHockeyLocalize(@"HockeySettingsUserDataDescription");
} else if ([self.hockeyManager isAllowUserToDisableSendData] && section < [self numberOfSections]) {
footer.text = BWHockeyLocalize(@"HockeySettingsUsageDataDescription");
}
UIView* view = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 285, footer.frame.size.height + 6 + 11)] autorelease];
[view setBackgroundColor:[UIColor clearColor]];
CGRect frame = footer.frame;
frame.origin.y = 8;
frame.origin.x = 16;
frame.size.width = 285;
footer.frame = frame;
[view addSubview:footer];
[view sizeToFit];
return view;
}
return nil;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return [self numberOfSections];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
if (section == [self numberOfSections] - 1)
return 3;
else
return 1;
}
- (void)sendUserData:(UISwitch *)switcher {
[self.hockeyManager setUserAllowsSendUserData:switcher.on];
}
- (void)sendUsageData:(UISwitch *)switcher {
[self.hockeyManager setUserAllowsSendUsageTime:switcher.on];
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CheckmarkCellIdentifier = @"CheckmarkCell";
static NSString *SwitchCellIdentifier = @"SwitchCell";
NSString *requiredIdentifier = nil;
UITableViewCellStyle cellStyle = UITableViewCellStyleSubtitle;
if ((NSInteger)indexPath.section == [self numberOfSections] - 1) {
cellStyle = UITableViewCellStyleDefault;
requiredIdentifier = CheckmarkCellIdentifier;
} else {
cellStyle = UITableViewCellStyleValue1;
requiredIdentifier = SwitchCellIdentifier;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:requiredIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:cellStyle reuseIdentifier:requiredIdentifier] autorelease];
}
cell.accessoryType = UITableViewCellAccessoryNone;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
// Configure the cell...
if ((NSInteger)indexPath.section == [self numberOfSections] - 1) {
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
// update check selection
HockeyUpdateSetting hockeyAutoUpdateSetting = [[BWHockeyManager sharedHockeyManager] updateSetting];
if (indexPath.row == 0) {
// on startup
cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckStartup");
if (hockeyAutoUpdateSetting == HockeyUpdateCheckStartup) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
} else if (indexPath.row == 1) {
// daily
cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckDaily");
if (hockeyAutoUpdateSetting == HockeyUpdateCheckDaily) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
} else {
// manually
cell.textLabel.text = BWHockeyLocalize(@"HockeySectionCheckManually");
if (hockeyAutoUpdateSetting == HockeyUpdateCheckManually) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
}
} else {
UISwitch *toggleSwitch = [[[UISwitch alloc] initWithFrame:CGRectZero] autorelease];
if (indexPath.section == 0 && [self.hockeyManager shouldSendUserData] && [self.hockeyManager isAllowUserToDisableSendData]) {
// send user data
cell.textLabel.text = BWHockeyLocalize(@"HockeySettingsUserData");
[toggleSwitch addTarget:self action:@selector(sendUserData:)
forControlEvents:UIControlEventValueChanged];
[toggleSwitch setOn:[self.hockeyManager doesUserAllowsSendUserData]];
} else if ([self.hockeyManager shouldSendUsageTime] && [self.hockeyManager isAllowUserToDisableSendData]) {
// send usage time
cell.textLabel.text = BWHockeyLocalize(@"HockeySettingsUsageData");
[toggleSwitch addTarget:self action:@selector(sendUsageData:)
forControlEvents:UIControlEventValueChanged];
[toggleSwitch setOn:[self.hockeyManager doesUserAllowsSendUsageTime]];
}
cell.accessoryView = toggleSwitch;
}
return cell;
}
#pragma mark -
#pragma mark Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// update check interval selection
if (indexPath.row == 0) {
// on startup
[BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckStartup;
} else if (indexPath.row == 1) {
// daily
[BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckDaily;
} else {
// manually
[BWHockeyManager sharedHockeyManager].updateSetting = HockeyUpdateCheckManually;
}
[tableView reloadData];
}
#pragma mark -
#pragma mark Memory management
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Relinquish ownership any cached data, images, etc. that aren't in use.
}
- (void)viewDidUnload {
// Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
// For example: self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Rotation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
BOOL shouldAutorotate;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationPortrait);
} else {
shouldAutorotate = YES;
}
return shouldAutorotate;
}
@end

View file

@ -1,68 +0,0 @@
//
// BWHockeyViewController.h
//
// Created by Andreas Linde on 8/17/10.
// Copyright 2010-2011 Andreas Linde. 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 <UIKit/UIKit.h>
#import "PSStoreButton.h"
@class PSAppStoreHeader;
typedef enum {
AppStoreButtonStateOffline,
AppStoreButtonStateCheck,
AppStoreButtonStateSearching,
AppStoreButtonStateUpdate,
AppStoreButtonStateInstalling
} AppStoreButtonState;
@class BWHockeyManager;
@interface BWHockeyViewController : UITableViewController <PSStoreButtonDelegate> {
BWHockeyManager *hockeyManager_;
NSDictionary *cellLayout;
BOOL modal_;
BOOL modalAnimated_;
BOOL kvoRegistered_;
BOOL showAllVersions_;
UIStatusBarStyle statusBarStyle_;
PSAppStoreHeader *appStoreHeader_;
PSStoreButton *appStoreButton_;
id popOverController_;
AppStoreButtonState appStoreButtonState_;
NSMutableArray *cells_;
}
@property (nonatomic, retain) BWHockeyManager *hockeyManager;
@property (nonatomic, readwrite) BOOL modal;
@property (nonatomic, readwrite) BOOL modalAnimated;
- (id)init:(BWHockeyManager *)newHockeyManager modal:(BOOL)newModal;
- (id)init;
@end

View file

@ -1,224 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Kent Sutherland
*
* 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 <Foundation/Foundation.h>
#define BWQuincyLog(fmt, ...) do { if([BWQuincyManager sharedQuincyManager].isLoggingEnabled) { NSLog((@"[QuincyLib] %s/%d " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); }} while(0)
#define kQuincyBundleName @"Quincy.bundle"
NSBundle *quincyBundle(void);
NSString *BWQuincyLocalize(NSString *stringToken);
//#define BWQuincyLocalize(StringToken) NSLocalizedStringFromTableInBundle(StringToken, @"Quincy", quincyBundle(), @"")
// flags if the crashlog analyzer is started. since this may theoretically crash we need to track it
#define kQuincyKitAnalyzerStarted @"QuincyKitAnalyzerStarted"
// flags if the QuincyKit is activated at all
#define kQuincyKitActivated @"QuincyKitActivated"
// flags if the crashreporter should automatically send crashes without asking the user again
#define kAutomaticallySendCrashReports @"AutomaticallySendCrashReports"
// stores the set of crashreports that have been approved but aren't sent yet
#define kApprovedCrashReports @"ApprovedCrashReports"
// Notification message which QuincyManager is listening to, to retry sending pending crash reports to the server
#define BWQuincyNetworkBecomeReachable @"NetworkDidBecomeReachable"
typedef enum QuincyKitAlertType {
QuincyKitAlertTypeSend = 0,
QuincyKitAlertTypeFeedback = 1,
} CrashAlertType;
typedef enum CrashReportStatus {
// The status of the crash is queued, need to check later (HockeyApp)
CrashReportStatusQueued = -80,
// This app version is set to discontinued, no new crash reports accepted by the server
CrashReportStatusFailureVersionDiscontinued = -30,
// XML: Sender version string contains not allowed characters, only alphanumberical including space and . are allowed
CrashReportStatusFailureXMLSenderVersionNotAllowed = -21,
// XML: Version string contains not allowed characters, only alphanumberical including space and . are allowed
CrashReportStatusFailureXMLVersionNotAllowed = -20,
// SQL for adding a symoblicate todo entry in the database failed
CrashReportStatusFailureSQLAddSymbolicateTodo = -18,
// SQL for adding crash log in the database failed
CrashReportStatusFailureSQLAddCrashlog = -17,
// SQL for adding a new version in the database failed
CrashReportStatusFailureSQLAddVersion = -16,
// SQL for checking if the version is already added in the database failed
CrashReportStatusFailureSQLCheckVersionExists = -15,
// SQL for creating a new pattern for this bug and set amount of occurrances to 1 in the database failed
CrashReportStatusFailureSQLAddPattern = -14,
// SQL for checking the status of the bugfix version in the database failed
CrashReportStatusFailureSQLCheckBugfixStatus = -13,
// SQL for updating the occurances of this pattern in the database failed
CrashReportStatusFailureSQLUpdatePatternOccurances = -12,
// SQL for getting all the known bug patterns for the current app version in the database failed
CrashReportStatusFailureSQLFindKnownPatterns = -11,
// SQL for finding the bundle identifier in the database failed
CrashReportStatusFailureSQLSearchAppName = -10,
// the post request didn't contain valid data
CrashReportStatusFailureInvalidPostData = -3,
// incoming data may not be added, because e.g. bundle identifier wasn't found
CrashReportStatusFailureInvalidIncomingData = -2,
// database cannot be accessed, check hostname, username, password and database name settings in config.php
CrashReportStatusFailureDatabaseNotAvailable = -1,
CrashReportStatusUnknown = 0,
CrashReportStatusAssigned = 1,
CrashReportStatusSubmitted = 2,
CrashReportStatusAvailable = 3,
} CrashReportStatus;
// This protocol is used to send the image updates
@protocol BWQuincyManagerDelegate <NSObject>
@optional
// Return the userid the crashreport should contain, empty by default
-(NSString *) crashReportUserID;
// Return the contact value (e.g. email) the crashreport should contain, empty by default
-(NSString *) crashReportContact;
// Return the description the crashreport should contain, empty by default. The string will automatically be wrapped into <[DATA[ ]]>, so make sure you don't do that in your string.
-(NSString *) crashReportDescription;
// Invoked when the internet connection is started, to let the app enable the activity indicator
-(void) connectionOpened;
// Invoked when the internet connection is closed, to let the app disable the activity indicator
-(void) connectionClosed;
// Invoked before the user is asked to send a crash report, so you can do additional actions. E.g. to make sure not to ask the user for an app rating :)
-(void) willShowSubmitCrashReportAlert;
// Invoked after the user did choose to send crashes always in the alert
-(void) userDidChooseSendAlways;
@end
@interface BWQuincyManager : NSObject <NSXMLParserDelegate> {
NSString *_submissionURL;
id <BWQuincyManagerDelegate> _delegate;
BOOL _loggingEnabled;
BOOL _showAlwaysButton;
BOOL _feedbackActivated;
BOOL _autoSubmitCrashReport;
BOOL _didCrashInLastSession;
NSString *_appIdentifier;
NSString *_feedbackRequestID;
float _feedbackDelayInterval;
NSMutableString *_contentOfProperty;
CrashReportStatus _serverResult;
int _analyzerStarted;
NSString *_crashesDir;
NSFileManager *_fileManager;
BOOL _crashIdenticalCurrentVersion;
BOOL _crashReportActivated;
NSMutableArray *_crashFiles;
NSMutableData *_responseData;
NSInteger _statusCode;
NSURLConnection *_urlConnection;
NSData *_crashData;
NSString *_languageStyle;
BOOL _sendingInProgress;
}
+ (BWQuincyManager *)sharedQuincyManager;
// submission URL defines where to send the crash reports to (required)
@property (nonatomic, retain) NSString *submissionURL;
// delegate is optional
@property (nonatomic, assign) id <BWQuincyManagerDelegate> delegate;
///////////////////////////////////////////////////////////////////////////////////////////////////
// settings
// if YES, states will be logged using NSLog. Only enable this for debugging!
// if NO, nothing will be logged. (default)
@property (nonatomic, assign, getter=isLoggingEnabled) BOOL loggingEnabled;
// nil, using the default localization files (Default)
// set to another string which will be appended to the Quincy localization file name, "Alternate" is another provided text set
@property (nonatomic, retain) NSString *languageStyle;
// if YES, the user will get the option to choose "Always" for sending crash reports. This will cause the dialog not to show the alert description text landscape mode! (default)
// if NO, the dialog will not show a "Always" button
@property (nonatomic, assign, getter=isShowingAlwaysButton) BOOL showAlwaysButton;
// if YES, the user will be presented with a status of the crash, if known
// if NO, the user will not see any feedback information (default)
@property (nonatomic, assign, getter=isFeedbackActivated) BOOL feedbackActivated;
// if YES, the crash report will be submitted without asking the user
// if NO, the user will be asked if the crash report can be submitted (default)
@property (nonatomic, assign, getter=isAutoSubmitCrashReport) BOOL autoSubmitCrashReport;
// will return if the last session crashed, to e.g. make sure a "rate my app" alert will not show up
@property (nonatomic, readonly) BOOL didCrashInLastSession;
// If you want to use HockeyApp instead of your own server, this is required
@property (nonatomic, retain) NSString *appIdentifier;
@end

View file

@ -1,788 +0,0 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
* Kent Sutherland
*
* 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 "BWGlobal.h"
#import "BWQuincyManager.h"
#include <sys/sysctl.h>
#include <inttypes.h> //needed for PRIx64 macro
NSBundle *quincyBundle(void) {
static NSBundle* bundle = nil;
if (!bundle) {
NSString* path = [[[NSBundle mainBundle] resourcePath]
stringByAppendingPathComponent:kQuincyBundleName];
bundle = [[NSBundle bundleWithPath:path] retain];
}
return bundle;
}
NSString *BWQuincyLocalize(NSString *stringToken) {
if ([BWQuincyManager sharedQuincyManager].languageStyle == nil)
return NSLocalizedStringFromTableInBundle(stringToken, @"Quincy", quincyBundle(), @"");
else {
NSString *alternate = [NSString stringWithFormat:@"Quincy%@", [BWQuincyManager sharedQuincyManager].languageStyle];
return NSLocalizedStringFromTableInBundle(stringToken, alternate, quincyBundle(), @"");
}
}
@interface BWQuincyManager ()
- (void)startManager;
- (void)showCrashStatusMessage;
- (void)handleCrashReport;
- (void)_cleanCrashReports;
- (void)_checkForFeedbackStatus;
- (void)_performSendingCrashReports;
- (void)_sendCrashReports;
- (void)_postXML:(NSString*)xml toURL:(NSURL*)url;
- (NSString *)_getDevicePlatform;
- (BOOL)hasNonApprovedCrashReports;
- (BOOL)hasPendingCrashReport;
@property (nonatomic, retain) NSFileManager *fileManager;
@end
@implementation BWQuincyManager
@synthesize delegate = _delegate;
@synthesize submissionURL = _submissionURL;
@synthesize showAlwaysButton = _showAlwaysButton;
@synthesize feedbackActivated = _feedbackActivated;
@synthesize autoSubmitCrashReport = _autoSubmitCrashReport;
@synthesize languageStyle = _languageStyle;
@synthesize didCrashInLastSession = _didCrashInLastSession;
@synthesize loggingEnabled = _loggingEnabled;
@synthesize appIdentifier = _appIdentifier;
@synthesize fileManager = _fileManager;
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000
+(BWQuincyManager *)sharedQuincyManager {
static BWQuincyManager *sharedInstance = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
sharedInstance = [BWQuincyManager alloc];
sharedInstance = [sharedInstance init];
});
return sharedInstance;
}
#else
+ (BWQuincyManager *)sharedQuincyManager {
static BWQuincyManager *quincyManager = nil;
if (quincyManager == nil) {
quincyManager = [[BWQuincyManager alloc] init];
}
return quincyManager;
}
#endif
- (id) init {
if ((self = [super init])) {
_serverResult = CrashReportStatusUnknown;
_crashIdenticalCurrentVersion = YES;
_crashData = nil;
_urlConnection = nil;
_submissionURL = nil;
_responseData = nil;
_appIdentifier = nil;
_sendingInProgress = NO;
_languageStyle = nil;
_didCrashInLastSession = NO;
_loggingEnabled = NO;
_fileManager = [[NSFileManager alloc] init];
self.delegate = nil;
self.feedbackActivated = NO;
self.showAlwaysButton = NO;
self.autoSubmitCrashReport = NO;
NSString *testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kQuincyKitAnalyzerStarted];
if (testValue) {
_analyzerStarted = [[NSUserDefaults standardUserDefaults] integerForKey:kQuincyKitAnalyzerStarted];
} else {
_analyzerStarted = 0;
}
testValue = nil;
testValue = [[NSUserDefaults standardUserDefaults] stringForKey:kQuincyKitActivated];
if (testValue) {
_crashReportActivated = [[NSUserDefaults standardUserDefaults] boolForKey:kQuincyKitActivated];
} else {
_crashReportActivated = YES;
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:YES] forKey:kQuincyKitActivated];
}
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:BWQuincyNetworkBecomeReachable object:nil];
}
if (!quincyBundle()) {
NSLog(@"WARNING: Quincy.bundle is missing, will send reports automatically!");
}
}
return self;
}
- (void) dealloc {
self.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self name:BWQuincyNetworkBecomeReachable object:nil];
[_languageStyle release];
[_submissionURL release];
_submissionURL = 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 -
#pragma mark setter
- (void)setSubmissionURL:(NSString *)anSubmissionURL {
if (_submissionURL != anSubmissionURL) {
[_submissionURL release];
_submissionURL = [anSubmissionURL copy];
}
[self performSelector:@selector(startManager) withObject:nil afterDelay:1.0f];
}
- (void)setAppIdentifier:(NSString *)anAppIdentifier {
if (_appIdentifier != anAppIdentifier) {
[_appIdentifier release];
_appIdentifier = [anAppIdentifier copy];
}
[self setSubmissionURL:@"https://rink.hockeyapp.net/"];
}
#pragma mark -
#pragma mark private methods
- (BOOL)autoSendCrashReports {
BOOL result = NO;
if (!self.autoSubmitCrashReport) {
if (self.isShowingAlwaysButton && [[NSUserDefaults standardUserDefaults] boolForKey: kAutomaticallySendCrashReports]) {
result = YES;
}
} else {
result = YES;
}
return result;
}
// begin the startup process
- (void)startManager {
if (!_sendingInProgress && [self hasPendingCrashReport]) {
_sendingInProgress = YES;
if (!quincyBundle()) {
NSLog(@"WARNING: Quincy.bundle is missing, sending reports automatically!");
[self _sendCrashReports];
} else if (![self autoSendCrashReports] && [self hasNonApprovedCrashReports]) {
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(willShowSubmitCrashReportAlert)]) {
[self.delegate willShowSubmitCrashReportAlert];
}
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundTitle"), appName]
message:[NSString stringWithFormat:BWQuincyLocalize(@"CrashDataFoundDescription"), appName]
delegate:self
cancelButtonTitle:BWQuincyLocalize(@"CrashDontSendReport")
otherButtonTitles:BWQuincyLocalize(@"CrashSendReport"), nil];
if ([self isShowingAlwaysButton]) {
[alertView addButtonWithTitle:BWQuincyLocalize(@"CrashSendReportAlways")];
}
[alertView setTag: QuincyKitAlertTypeSend];
[alertView show];
[alertView release];
} else {
[self _sendCrashReports];
}
}
}
- (BOOL)hasNonApprovedCrashReports {
NSDictionary *approvedCrashReports = [[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports];
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) {
BWQuincyLog(@"Pending crash reports found.");
return YES;
} else
return NO;
} else
return NO;
}
- (void) showCrashStatusMessage {
UIAlertView *alertView = nil;
if (_serverResult >= CrashReportStatusAssigned &&
_crashIdenticalCurrentVersion &&
quincyBundle()) {
// show some feedback to the user about the crash status
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
switch (_serverResult) {
case CrashReportStatusAssigned:
alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ]
message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseNextRelease"), appName]
delegate: self
cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK")
otherButtonTitles: nil];
break;
case CrashReportStatusSubmitted:
alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ]
message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseWaitingApple"), appName]
delegate: self
cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK")
otherButtonTitles: nil];
break;
case CrashReportStatusAvailable:
alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ]
message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseAvailable"), appName]
delegate: self
cancelButtonTitle: BWQuincyLocalize(@"CrashResponseTitleOK")
otherButtonTitles: nil];
break;
default:
alertView = nil;
break;
}
if (alertView) {
[alertView setTag: QuincyKitAlertTypeFeedback];
[alertView show];
[alertView release];
}
}
}
#pragma mark -
#pragma mark UIAlertView Delegate
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if ([alertView tag] == QuincyKitAlertTypeSend) {
switch (buttonIndex) {
case 0:
_sendingInProgress = NO;
[self _cleanCrashReports];
break;
case 1:
[self _sendCrashReports];
break;
case 2: {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kAutomaticallySendCrashReports];
[[NSUserDefaults standardUserDefaults] synchronize];
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(userDidChooseSendAlways)]) {
[self.delegate userDidChooseSendAlways];
}
[self _sendCrashReports];
break;
}
default:
_sendingInProgress = NO;
[self _cleanCrashReports];
break;
}
}
}
#pragma mark -
#pragma mark NSXMLParser Delegate
#pragma mark NSXMLParser
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
if (qName) {
elementName = qName;
}
if ([elementName isEqualToString:@"result"]) {
_contentOfProperty = [NSMutableString string];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if (qName) {
elementName = qName;
}
// open source implementation
if ([elementName isEqualToString: @"result"]) {
if ([_contentOfProperty intValue] > _serverResult) {
_serverResult = (CrashReportStatus)[_contentOfProperty intValue];
} else {
CrashReportStatus errorcode = (CrashReportStatus)[_contentOfProperty intValue];
NSLog(@"CrashReporter ended in error code: %i", errorcode);
}
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (_contentOfProperty) {
// If the current element is one whose content we care about, append 'string'
// to the property that holds the content of the current element.
if (string != nil) {
[_contentOfProperty appendString:string];
}
}
}
#pragma mark -
#pragma mark Private
- (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: kApprovedCrashReports]];
NSError *error = NULL;
NSString *userid = @"";
NSString *contact = @"";
NSString *description = @"";
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReportUserID)]) {
userid = [self.delegate crashReportUserID] ?: @"";
}
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReportContact)]) {
contact = [self.delegate crashReportContact] ?: @"";
}
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(crashReportDescription)]) {
description = [self.delegate crashReportDescription] ?: @"";
}
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");
continue;
}
NSString *crashLogString = [PLCrashReportTextFormatter stringValueForCrashReport:report withTextFormat:PLCrashReportTextFormatiOS];
if ([report.applicationInfo.applicationVersion compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) {
_crashIdenticalCurrentVersion = YES;
}
if (crashes == nil) {
crashes = [NSMutableString string];
}
[crashes appendFormat:@"<crash><applicationname>%s</applicationname><bundleidentifier>%@</bundleidentifier><systemversion>%@</systemversion><platform>%@</platform><senderversion>%@</senderversion><version>%@</version><log><![CDATA[%@]]></log><userid>%@</userid><contact>%@</contact><description><![CDATA[%@]]></description></crash>",
[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"] UTF8String],
report.applicationInfo.applicationIdentifier,
report.systemInfo.operatingSystemVersion,
[self _getDevicePlatform],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
report.applicationInfo.applicationVersion,
[crashLogString stringByReplacingOccurrencesOfString:@"]]>" withString:@"]]" @"]]><![CDATA[" @">" options:NSLiteralSearch range:NSMakeRange(0,crashLogString.length)],
userid,
contact,
[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:[_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:kApprovedCrashReports];
[[NSUserDefaults standardUserDefaults] synchronize];
if (crashes != nil) {
BWQuincyLog(@"Sending crash reports:\n%@", crashes);
[self _postXML:[NSString stringWithFormat:@"<crashes>%@</crashes>", crashes]
toURL:[NSURL URLWithString:self.submissionURL]];
}
}
- (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:kApprovedCrashReports];
[[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/%@",
self.submissionURL,
[self.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 = CrashReportStatusUnknown;
_statusCode = 200;
// Release when done in the delegate method
_responseData = [[NSMutableData alloc] init];
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionOpened)]) {
[self.delegate connectionOpened];
}
_urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
BWQuincyLog(@"Requesting feedback status.");
}
- (void)_postXML:(NSString*)xml toURL:(NSURL*)url {
NSMutableURLRequest *request = nil;
NSString *boundary = @"----FOO";
if (self.appIdentifier) {
request = [NSMutableURLRequest requestWithURL:
[NSURL URLWithString:[NSString stringWithFormat:@"%@api/2/apps/%@/crashes?sdk=%@&sdk_version=%@",
self.submissionURL,
[self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding],
SDK_NAME,
SDK_VERSION
]
]];
} else {
request = [NSMutableURLRequest requestWithURL:url];
}
[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]];
if (self.appIdentifier) {
[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]];
} else {
[postBody appendData:[@"Content-Disposition: form-data; name=\"xmlstring\"\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 = CrashReportStatusUnknown;
_statusCode = 200;
//Release when done in the delegate method
_responseData = [[NSMutableData alloc] init];
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionOpened)]) {
[self.delegate connectionOpened];
}
_urlConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (!_urlConnection) {
BWQuincyLog(@"Sending crash reports could not start!");
_sendingInProgress = NO;
} else {
BWQuincyLog(@"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(connectionClosed)]) {
[self.delegate connectionClosed];
}
BWQuincyLog(@"ERROR: %@", [error localizedDescription]);
_sendingInProgress = NO;
[_responseData release];
_responseData = nil;
[_urlConnection release];
_urlConnection = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (_statusCode >= 200 && _statusCode < 400 && _responseData != nil && [_responseData length] > 0) {
[self _cleanCrashReports];
_feedbackRequestID = nil;
if (self.appIdentifier) {
// HockeyApp uses PList XML format
NSMutableDictionary *response = [NSPropertyListSerialization propertyListFromData:_responseData
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:nil
errorDescription:NULL];
BWQuincyLog(@"Received API response: %@", response);
_serverResult = (CrashReportStatus)[[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;
}
} else {
BWQuincyLog(@"Received API response: %@", [[[NSString alloc] initWithBytes:[_responseData bytes] length:[_responseData length] encoding: NSUTF8StringEncoding] autorelease]);
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:_responseData];
// Set self as the delegate of the parser so that it will receive the parser delegate methods callbacks.
[parser setDelegate:self];
// Depending on the XML document you're parsing, you may want to enable these features of NSXMLParser.
[parser setShouldProcessNamespaces:NO];
[parser setShouldReportNamespacePrefixes:NO];
[parser setShouldResolveExternalEntities:NO];
[parser parse];
[parser release];
}
if ([self isFeedbackActivated]) {
// only proceed if the server did not report any problem
if ((self.appIdentifier) && (_serverResult == CrashReportStatusQueued)) {
// 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];
}
}
} else if (_statusCode == 400 && self.appIdentifier) {
[self _cleanCrashReports];
BWQuincyLog(@"ERROR: The server rejected receiving crash reports for this app version!");
} else {
if (_responseData == nil || [_responseData length] == 0) {
BWQuincyLog(@"ERROR: Sending failed with an empty response!");
} else {
BWQuincyLog(@"ERROR: Sending failed with status code: %i", _statusCode);
}
}
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionClosed)]) {
[self.delegate connectionClosed];
}
_sendingInProgress = NO;
[_responseData release];
_responseData = nil;
[_urlConnection release];
_urlConnection = nil;
}
#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:kQuincyKitAnalyzerStarted];
[[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];
}
}
// Purge the report
// mark the end of the routine
_analyzerStarted = 0;
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:_analyzerStarted] forKey:kQuincyKitAnalyzerStarted];
[[NSUserDefaults standardUserDefaults] synchronize];
[crashReporter purgePendingCrashReport];
return;
}
@end

View file

@ -1,25 +0,0 @@
//
// CNSFixCategoryBug.h
// HockeySDK
//
// Created by Andreas Linde on 12/7/11.
// Copyright (c) 2011 Andreas Linde. All rights reserved.
//
#ifndef HockeySDK_CNSFixCategoryBug_h
#define HockeySDK_CNSFixCategoryBug_h
/**
Add this macro before each category implementation, so we don't have to use
-all_load or -force_load to load object files from static libraries that only contain
categories and no classes.
See http://developer.apple.com/library/mac/#qa/qa2006/qa1490.html for more info.
Shamelessly borrowed from Three20
*/
#define CNS_FIX_CATEGORY_BUG(name) @interface CNS_FIX_CATEGORY_BUG##name @end \
@implementation CNS_FIX_CATEGORY_BUG##name @end
#endif

View file

@ -1,261 +0,0 @@
// Copyright 2011 Codenauts UG (haftungsbeschränkt). All rights reserved.
// See LICENSE.txt for author information.
//
// 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 "BWHockeyManager.h"
#pragma mark - Delegate
@protocol CNSHockeyManagerDelegate <NSObject>
/*
Return the device UDID which is required for beta testing, should return nil for app store configuration!
Example implementation if your configuration for the App Store is called "AppStore":
#ifndef (CONFIGURATION_AppStore)
if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)])
return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)];
#endif
return nil;
*/
- (NSString *)customDeviceIdentifier;
@optional
// Invoked when the manager is configured
//
// Implement to force the usage of the live identifier, e.g. for enterprise apps
// which are distributed inside your company
- (BOOL)shouldUseLiveIdenfitier;
// Invoked when the internet connection is started
//
// Implement to let the delegate enable the activity indicator
- (void)connectionOpened;
// Invoked when the internet connection is closed
//
// Implement to let the delegate disable the activity indicator
- (void)connectionClosed;
// Invoked via the alert view to define the presenting view controller
//
// Default is the root view controller of the main window instance
- (UIViewController *)viewControllerForHockeyController:(BWHockeyManager *)hockeyController;
// Invoked before a crash report will be sent
//
// Return a userid or similar which the crashreport should contain
//
// Maximum length: 255 chars
//
// Default: empty
- (NSString *)crashReportUserID;
// Invoked before a crash report will be sent
//
// Return contact data, e.g. an email address, for the crash report
// Maximum length: 255 chars
//
// Default: empty
-(NSString *)crashReportContact;
// Invoked before a crash report will be sent
//
// Return a the description for the crashreport should contain; the string
// will automatically be wrapped into <[DATA[ ]]>, so make sure you don't do
// that in your string.
//
// Default: empty
-(NSString *)crashReportDescription;
// Invoked before the user is asked to send a crash report
//
// Implement to do additional actions, e.g. to make sure to not to ask the
// user for an app rating :)
- (void)willShowSubmitCrashReportAlert;
// Invoked after the user did choose to send crashes always in the alert
-(void) userDidChooseSendAlways;
@end
@interface CNSHockeyManager : NSObject {
@private
id<CNSHockeyManagerDelegate> delegate;
NSString *appIdentifier;
}
#pragma mark - Public Properties
// Custom language style; set to a string which will be appended to
// to the localization file name; the Hockey SDK includes an alternative
// file, to use this, set to @"Alternate"
//
// Default: nil
@property (nonatomic, retain) NSString *languageStyle;
// Enable debug logging; ONLY ENABLE THIS FOR DEBUGGING!
//
// Default: NO
@property (nonatomic, assign, getter=isLoggingEnabled) BOOL loggingEnabled;
// Show button "Always" in crash alert; this will cause the dialog not to
// show the alert description text landscape mode! (default)
//
// Default: NO
@property (nonatomic, assign, getter=isShowingAlwaysButton) BOOL showAlwaysButton;
// Show feedback from server with status of the crash; if you set a crash
// to resolved on HockeyApp and assign a fixed version, this version will
// be reported to the user
//
// Default: NO
@property (nonatomic, assign, getter=isFeedbackActivated) BOOL feedbackActivated;
// Submit crash report without asking the user
//
// Default: NO
@property (nonatomic, assign, getter=isAutoSubmitCrashReport) BOOL autoSubmitCrashReport;
// Send user data to HockeyApp when checking for a new version; works only
// for beta apps and should not be activated for live apps. User data includes
// the device type, OS version, app version and device UDID.
//
// Default: YES
@property (nonatomic, assign, getter=shouldSendUserData) BOOL sendUserData;
// Send usage time to HockeyApp when checking for a new version; works only
// for beta apps and should not be activated for live apps.
//
// Default: YES
@property (nonatomic, assign, getter=shouldSendUsageTime) BOOL sendUsageTime;
// Enable to allow the user to change the settings from the update view
//
// Default: YES
@property (nonatomic, assign, getter=shouldShowUserSettings) BOOL showUserSettings;
// Set bar style of navigation controller
//
// Default: UIBarStyleDefault
@property (nonatomic, assign) UIBarStyle barStyle;
// Set modal presentation style of update view
//
// Default: UIModalPresentationStyleFormSheet
@property (nonatomic, assign) UIModalPresentationStyle modalPresentationStyle;
// Allow the user to disable the sending of user data; this settings should
// only be set if showUserSettings is enabled.
//
// Default: YES
@property (nonatomic, assign, getter=isUserAllowedToDisableSendData) BOOL allowUserToDisableSendData;
// Enable to show the new version alert every time the app becomes active and
// the current version is outdated
//
// Default: YES
@property (nonatomic, assign) BOOL alwaysShowUpdateReminder;
// Enable to check for a new version every time the app becomes active; if
// disabled you need to trigger the update mechanism manually
//
// Default: YES
@property (nonatomic, assign, getter=shouldCheckForUpdateOnLaunch) BOOL checkForUpdateOnLaunch;
// Show a button "Install" in the update alert to let the user directly start
// the update; note that the user will not see the release notes.
//
// Default: NO
@property (nonatomic, assign, getter=isShowingDirectInstallOption) BOOL showDirectInstallOption;
// Enable to check on the server that the app is authorized to run on this
// device; the check is based on the UDID and the authentication secret which
// is shown on the app page on HockeyApp
//
// Default: NO
@property (nonatomic, assign, getter=shouldRequireAuthorization) BOOL requireAuthorization;
// Set to the authentication secret which is shown on the app page on HockeyApp;
// must be set if requireAuthorization is enabled; leave empty otherwise
//
// Default: nil
@property (nonatomic, retain) NSString *authenticationSecret;
// Define how the client determines if a new version is available.
//
// Values:
// HockeyComparisonResultDifferent - the version on the server is different
// HockeyComparisonResultGreate - the version on the server is greater
//
// Default: HockeyComparisonResultGreater
@property (nonatomic, assign) HockeyComparisonResult compareVersionType;
// if YES the app is installed from the app store
// if NO the app is installed via ad-hoc or enterprise distribution
@property (nonatomic, readonly) BOOL isAppStoreEnvironment;
#pragma mark - Public Methods
// Returns the shared manager object
+ (CNSHockeyManager *)sharedHockeyManager;
// Configure HockeyApp with a single app identifier and delegate; use this
// only for debug or beta versions of your app!
- (void)configureWithIdentifier:(NSString *)appIdentifier delegate:(id<CNSHockeyManagerDelegate>)delegate;
// Configure HockeyApp with different app identifiers for beta and live versions
// of the app; the update alert will only be shown for the beta identifier
- (void)configureWithBetaIdentifier:(NSString *)betaIdentifier liveIdentifier:(NSString *)liveIdentifier delegate:(id<CNSHockeyManagerDelegate>)delegate;
// Returns true if the app crashes in the last session
- (BOOL)didCrashInLastSession;
// Returns true if an update is available
- (BOOL)isUpdateAvailable;
// Returns true if update check is running
- (BOOL)isCheckInProgress;
// Show update info view
- (void)showUpdateView;
// Manually start an update check
- (void)checkForUpdate;
// Checks for update and informs the user if an update was found or an
// error occurred
- (void)checkForUpdateShowFeedback:(BOOL)feedback;
// Initiates app-download call; this displays an alert view of the OS
- (BOOL)initiateAppDownload;
// Returns true if this app version was authorized with the server
- (BOOL)appVersionIsAuthorized;
// Manually check if the device is authorized to run this version
- (void)checkForAuthorization;
// Return a new instance of BWHockeyViewController
- (BWHockeyViewController *)hockeyViewController:(BOOL)modal;
@end

View file

@ -1,427 +0,0 @@
// Copyright 2011 Codenauts UG (haftungsbeschränkt). All rights reserved.
// See LICENSE.txt for author information.
//
// 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 "CNSHockeyManager.h"
#import "BWQuincyManager.h"
#import "BWHockeyManager.h"
@interface CNSHockeyManager ()
- (BOOL)shouldUseLiveIdenfitier;
- (void)configureJMC;
- (void)configureHockeyManager;
- (void)configureQuincyManager;
@end
@implementation CNSHockeyManager
#pragma mark - Public Class Methods
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000
+ (CNSHockeyManager *)sharedHockeyManager {
static CNSHockeyManager *sharedInstance = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
sharedInstance = [CNSHockeyManager alloc];
sharedInstance = [sharedInstance init];
});
return sharedInstance;
}
#else
+ (CNSHockeyManager *)sharedHockeyManager {
static CNSHockeyManager *hockeyManager = nil;
if (hockeyManager == nil) {
hockeyManager = [[CNSHockeyManager alloc] init];
}
return hockeyManager;
}
#endif
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (id)jmcInstance {
id jmcClass = NSClassFromString(@"JMC");
if ((jmcClass) && ([jmcClass respondsToSelector:@selector(sharedInstance)])) {
return [jmcClass performSelector:@selector(sharedInstance)];
}
#ifdef JMC_LEGACY
else if ((jmcClass) && ([jmcClass respondsToSelector:@selector(instance)])) {
return [jmcClass performSelector:@selector(instance)]; // legacy pre (JMC 1.0.11) support
}
#endif
return nil;
}
#pragma clang diagnostic pop
+ (BOOL)isJMCActive {
id jmcInstance = [self jmcInstance];
return (jmcInstance) && ([jmcInstance performSelector:@selector(url)]);
}
+ (BOOL)isJMCPresent {
return [self jmcInstance] != nil;
}
#pragma mark - Private Class Methods
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (void)disableJMCCrashReporter {
id jmcInstance = [self jmcInstance];
id jmcOptions = [jmcInstance performSelector:@selector(options)];
SEL crashReporterSelector = @selector(setCrashReportingEnabled:);
BOOL value = NO;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jmcOptions methodSignatureForSelector:crashReporterSelector]];
invocation.target = jmcOptions;
invocation.selector = crashReporterSelector;
[invocation setArgument:&value atIndex:2];
[invocation invoke];
}
#pragma clang diagnostic pop
+ (BOOL)checkJMCConfiguration:(NSDictionary *)configuration {
return (([configuration isKindOfClass:[NSDictionary class]]) &&
([[configuration valueForKey:@"enabled"] boolValue]) &&
([[configuration valueForKey:@"url"] length] > 0) &&
([[configuration valueForKey:@"key"] length] > 0) &&
([[configuration valueForKey:@"project"] length] > 0));
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
+ (void)applyJMCConfiguration:(NSDictionary *)configuration {
id jmcInstance = [self jmcInstance];
SEL configureSelector = @selector(configureJiraConnect:projectKey:apiKey:);
NSString *url = [configuration valueForKey:@"url"];
NSString *project = [configuration valueForKey:@"project"];
NSString *key = [configuration valueForKey:@"key"];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jmcInstance methodSignatureForSelector:configureSelector]];
invocation.target = jmcInstance;
invocation.selector = configureSelector;
[invocation setArgument:&url atIndex:2];
[invocation setArgument:&project atIndex:3];
[invocation setArgument:&key atIndex:4];
[invocation invoke];
if ([jmcInstance respondsToSelector:@selector(ping)]) {
[jmcInstance performSelector:@selector(ping)];
}
}
#pragma clang diagnostic pop
#pragma mark - Public Instance Methods (Configuration)
- (void)configureWithIdentifier:(NSString *)newAppIdentifier delegate:(id)newDelegate {
delegate = newDelegate;
[appIdentifier release];
appIdentifier = [newAppIdentifier copy];
[self configureQuincyManager];
[self configureHockeyManager];
}
- (void)configureWithBetaIdentifier:(NSString *)betaIdentifier liveIdentifier:(NSString *)liveIdentifier delegate:(id)newDelegate {
delegate = newDelegate;
[appIdentifier release];
if ([self shouldUseLiveIdenfitier]) {
appIdentifier = [liveIdentifier copy];
}
else {
appIdentifier = [betaIdentifier copy];
}
if (appIdentifier) {
[self configureQuincyManager];
[self configureHockeyManager];
}
}
- (BOOL)isLoggingEnabled {
return [[BWHockeyManager sharedHockeyManager] isLoggingEnabled];
return [[BWQuincyManager sharedQuincyManager] isLoggingEnabled];
}
- (void)setLoggingEnabled:(BOOL)loggingEnabled {
return [[BWHockeyManager sharedHockeyManager] setLoggingEnabled:loggingEnabled];
return [[BWQuincyManager sharedQuincyManager] setLoggingEnabled:loggingEnabled];
}
#pragma mark - Public Instance Methods (Crash Reporting)
- (NSString *)languageStyle {
return [[BWQuincyManager sharedQuincyManager] languageStyle];
}
- (void)setLanguageStyle:(NSString *)languageStyle {
[[BWQuincyManager sharedQuincyManager] setLanguageStyle:languageStyle];
}
- (BOOL)isShowingAlwaysButton {
return [[BWQuincyManager sharedQuincyManager] isShowingAlwaysButton];
}
- (void)setShowAlwaysButton:(BOOL)showAlwaysButton {
[[BWQuincyManager sharedQuincyManager] setShowAlwaysButton:showAlwaysButton];
}
- (BOOL)isFeedbackActivated {
return [[BWQuincyManager sharedQuincyManager] isFeedbackActivated];
}
- (void)setFeedbackActivated:(BOOL)setFeedbackActivated {
[[BWQuincyManager sharedQuincyManager] setFeedbackActivated:setFeedbackActivated];
}
- (BOOL)isAutoSubmitCrashReport {
return [[BWQuincyManager sharedQuincyManager] isAutoSubmitCrashReport];
}
- (void)setAutoSubmitCrashReport:(BOOL)autoSubmitCrashReport {
[[BWQuincyManager sharedQuincyManager] setAutoSubmitCrashReport:autoSubmitCrashReport];
}
- (BOOL)didCrashInLastSession {
return [[BWQuincyManager sharedQuincyManager] didCrashInLastSession];
}
#pragma mark - Public Instance Methods (Distribution)
- (BOOL)shouldSendUserData {
return [[BWHockeyManager sharedHockeyManager] shouldSendUserData];
}
- (void)setSendUserData:(BOOL)sendUserData {
[[BWHockeyManager sharedHockeyManager] setSendUserData:sendUserData];
}
- (BOOL)shouldSendUsageTime {
return [[BWHockeyManager sharedHockeyManager] shouldSendUsageTime];
}
- (void)setSendUsageTime:(BOOL)sendUsageTime {
[[BWHockeyManager sharedHockeyManager] setSendUsageTime:sendUsageTime];
}
- (BOOL)shouldShowUserSettings {
return [[BWHockeyManager sharedHockeyManager] shouldShowUserSettings];
}
- (void)setShowUserSettings:(BOOL)showUserSettings {
[[BWHockeyManager sharedHockeyManager] setShowUserSettings:showUserSettings];
}
- (UIBarStyle)barStyle {
return [[BWHockeyManager sharedHockeyManager] barStyle];
}
- (void)setBarStyle:(UIBarStyle)barStyle {
[[BWHockeyManager sharedHockeyManager] setBarStyle:barStyle];
}
- (UIModalPresentationStyle)modalPresentationStyle {
return [[BWHockeyManager sharedHockeyManager] modalPresentationStyle];
}
- (void)setModalPresentationStyle:(UIModalPresentationStyle)modalPresentationStyle {
[[BWHockeyManager sharedHockeyManager] setModalPresentationStyle:modalPresentationStyle];
}
- (BOOL)isUserAllowedToDisableSendData {
return [[BWHockeyManager sharedHockeyManager] isAllowUserToDisableSendData];
}
- (void)setAllowUserToDisableSendData:(BOOL)allowUserToDisableSendData {
[[BWHockeyManager sharedHockeyManager] setAllowUserToDisableSendData:allowUserToDisableSendData];
}
- (BOOL)alwaysShowUpdateReminder {
return [[BWHockeyManager sharedHockeyManager] alwaysShowUpdateReminder];
}
- (void)setAlwaysShowUpdateReminder:(BOOL)alwaysShowUpdateReminder {
[[BWHockeyManager sharedHockeyManager] setAlwaysShowUpdateReminder:alwaysShowUpdateReminder];
}
- (BOOL)shouldCheckForUpdateOnLaunch {
return [[BWHockeyManager sharedHockeyManager] isCheckForUpdateOnLaunch];
}
- (void)setCheckForUpdateOnLaunch:(BOOL)checkForUpdateOnLaunch {
[[BWHockeyManager sharedHockeyManager] setCheckForUpdateOnLaunch:checkForUpdateOnLaunch];
}
- (BOOL)isShowingDirectInstallOption {
return [[BWHockeyManager sharedHockeyManager] isShowingDirectInstallOption];
}
- (void)setShowDirectInstallOption:(BOOL)showDirectInstallOption {
[[BWHockeyManager sharedHockeyManager] setShowDirectInstallOption:showDirectInstallOption];
}
- (BOOL)shouldRequireAuthorization {
return [[BWHockeyManager sharedHockeyManager] isRequireAuthorization];
}
- (void)setRequireAuthorization:(BOOL)requireAuthorization {
[[BWHockeyManager sharedHockeyManager] setRequireAuthorization:requireAuthorization];
}
- (NSString *)authenticationSecret {
return [[BWHockeyManager sharedHockeyManager] authenticationSecret];
}
- (void)setAuthenticationSecret:(NSString *)authenticationSecret {
[[BWHockeyManager sharedHockeyManager] setAuthenticationSecret:authenticationSecret];
}
- (HockeyComparisonResult)compareVersionType {
return [[BWHockeyManager sharedHockeyManager] compareVersionType];
}
- (void)setCompareVersionType:(HockeyComparisonResult)compareVersionType {
[[BWHockeyManager sharedHockeyManager] setCompareVersionType:compareVersionType];
}
- (BOOL)isAppStoreEnvironment {
return [[BWHockeyManager sharedHockeyManager] isAppStoreEnvironment];
}
- (BOOL)isUpdateAvailable {
return [[BWHockeyManager sharedHockeyManager] isUpdateAvailable];
}
- (BOOL)isCheckInProgress {
return [[BWHockeyManager sharedHockeyManager] isCheckInProgress];
}
- (void)showUpdateView {
[[BWHockeyManager sharedHockeyManager] showUpdateView];
}
- (void)checkForUpdate {
[[BWHockeyManager sharedHockeyManager] checkForUpdate];
}
- (void)checkForUpdateShowFeedback:(BOOL)feedback {
[[BWHockeyManager sharedHockeyManager] checkForUpdateShowFeedback:feedback];
}
- (BOOL)initiateAppDownload {
return [[BWHockeyManager sharedHockeyManager] initiateAppDownload];
}
- (BOOL)appVersionIsAuthorized {
return [[BWHockeyManager sharedHockeyManager] appVersionIsAuthorized];
}
- (void)checkForAuthorization {
[[BWHockeyManager sharedHockeyManager] checkForAuthorization];
}
- (BWHockeyViewController *)hockeyViewController:(BOOL)modal {
return [[BWHockeyManager sharedHockeyManager] hockeyViewController:modal];
}
#pragma mark - Private Instance Methods
- (BOOL)shouldUseLiveIdenfitier {
BOOL delegateResult = NO;
if ([delegate respondsToSelector:@selector(shouldUseLiveIdenfitier)]) {
delegateResult = [(NSObject <CNSHockeyManagerDelegate>*)delegate shouldUseLiveIdenfitier];
}
return (delegateResult) || ([[BWHockeyManager sharedHockeyManager] isAppStoreEnvironment]);
}
- (void)configureQuincyManager {
[[BWQuincyManager sharedQuincyManager] setAppIdentifier:appIdentifier];
[[BWQuincyManager sharedQuincyManager] setDelegate:(id)delegate];
}
- (void)configureHockeyManager {
[[BWHockeyManager sharedHockeyManager] setAppIdentifier:appIdentifier];
[[BWHockeyManager sharedHockeyManager] setCheckForTracker:YES];
[[BWHockeyManager sharedHockeyManager] setDelegate:(id)delegate];
// Only if JMC is part of the project
if ([[self class] isJMCPresent]) {
[[BWHockeyManager sharedHockeyManager] addObserver:self forKeyPath:@"trackerConfig" options:0 context:nil];
[[self class] disableJMCCrashReporter];
[self performSelector:@selector(configureJMC) withObject:nil afterDelay:0];
}
}
- (void)configureJMC {
// Return if JMC is already configured
if ([[self class] isJMCActive]) {
return;
}
// Return if app id is nil
if (!appIdentifier) {
return;
}
// Configure JMC from user defaults
NSDictionary *configurations = [[NSUserDefaults standardUserDefaults] valueForKey:@"CNSTrackerConfigurations"];
NSDictionary *configuration = [configurations valueForKey:appIdentifier];
if ([[self class] checkJMCConfiguration:configuration]) {
[[self class] applyJMCConfiguration:configuration];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (([object trackerConfig]) && ([[object trackerConfig] isKindOfClass:[NSDictionary class]])) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *trackerConfig = [[defaults valueForKey:@"CNSTrackerConfigurations"] mutableCopy];
if (!trackerConfig) {
trackerConfig = [[NSMutableDictionary dictionaryWithCapacity:1] retain];
}
[trackerConfig setValue:[object trackerConfig] forKey:appIdentifier];
[defaults setValue:trackerConfig forKey:@"CNSTrackerConfigurations"];
[trackerConfig release];
[defaults synchronize];
[self configureJMC];
}
}
- (void)dealloc {
[appIdentifier release], appIdentifier = nil;
delegate = nil;
[super dealloc];
}
@end

72
Classes/HockeySDK.h Normal file
View file

@ -0,0 +1,72 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde.
* 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.
*/
#ifndef HockeySDK_h
#define HockeySDK_h
#import "BITHockeyManager.h"
#import "BITHockeyManagerDelegate.h"
#import "BITCrashManager.h"
#import "BITCrashManagerDelegate.h"
#import "BITUpdateManager.h"
#import "BITUpdateManagerDelegate.h"
#import "BITUpdateViewController.h"
// Notification message which HockeyManager is listening to, to retry requesting updated from the server
#define BITHockeyNetworkDidBecomeReachableNotification @"BITHockeyNetworkDidBecomeReachable"
// hockey api error domain
typedef enum {
BITCrashErrorUnknown,
BITCrashAPIAppVersionRejected,
BITCrashAPIReceivedEmptyResponse,
BITCrashAPIErrorWithStatusCode
} BITCrashErrorReason;
static NSString *kBITCrashErrorDomain = @"BITCrashReporterErrorDomain";
// Update App Versions
// hockey api error domain
typedef enum {
BITUpdateErrorUnknown,
BITUpdateAPIServerReturnedInvalidStatus,
BITUpdateAPIServerReturnedInvalidData,
BITUpdateAPIServerReturnedEmptyResponse,
BITUpdateAPIClientAuthorizationMissingSecret,
BITUpdateAPIClientCannotCreateConnection
} BITUpdateErrorReason;
static NSString *kBITUpdateErrorDomain = @"BITUpdaterErrorDomain";
#endif

View file

@ -0,0 +1,93 @@
/*
* 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 <Foundation/Foundation.h>
#ifndef HockeySDK_HockeySDKPrivate_h
#define HockeySDK_HockeySDKPrivate_h
#define BITHOCKEY_NAME @"HockeySDK"
#define BITHOCKEY_VERSION @"2.3.0-dev"
#define BITHOCKEY_IDENTIFIER @"net.hockeyapp.sdk.ios"
#define BITHOCKEY_CRASH_SETTINGS @"BITCrashManager.plist"
#define BITHOCKEY_CRASH_ANALYZER @"BITCrashManager.analyzer"
#define kBITUpdateArrayOfLastCheck @"BITUpdateArrayOfLastCheck"
#define kBITUpdateDateOfLastCheck @"BITUpdateDateOfLastCheck"
#define kBITUpdateDateOfVersionInstallation @"BITUpdateDateOfVersionInstallation"
#define kBITUpdateUsageTimeOfCurrentVersion @"BITUpdateUsageTimeOfCurrentVersion"
#define kBITUpdateUsageTimeForVersionString @"BITUpdateUsageTimeForVersionString"
#define kBITUpdateAuthorizedVersion @"BITUpdateAuthorizedVersion"
#define kBITUpdateAuthorizedToken @"BITUpdateAuthorizedToken"
#define BITHOCKEYSDK_BUNDLE @"HockeySDKResources.bundle"
#define BITHOCKEYSDK_URL @"https://sdk.hockeyapp.net/"
#define BITHockeyLog(fmt, ...) do { if([BITHockeyManager sharedHockeyManager].isDebugLogEnabled && ![BITHockeyManager sharedHockeyManager].isAppStoreEnvironment) { NSLog((@"[HockeySDK] %s/%d " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); }} while(0)
NSBundle *BITHockeyBundle(void);
NSString *BITHockeyLocalizedString(NSString *stringToken);
NSString *BITHockeyMD5(NSString *str);
// compatibility helper
#ifdef BITHOCKEY_STATIC_LIBRARY
// If HockeySDK is built as a static library and linked into the project
// we can't use this project's deployment target to statically decide if
// native JSON is available
#define BITHOCKEY_NATIVE_JSON_AVAILABLE 0
#else
#define BITHOCKEY_NATIVE_JSON_AVAILABLE __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000
#endif
#ifndef kCFCoreFoundationVersionNumber_iPhoneOS_5_0
#define kCFCoreFoundationVersionNumber_iPhoneOS_5_0 674.0
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000
#define BITHOCKEY_IF_IOS5_OR_GREATER(...) \
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iPhoneOS_5_0) \
{ \
__VA_ARGS__ \
}
#else
#define BITHOCKEY_IF_IOS5_OR_GREATER(...)
#endif
#define BITHOCKEY_IF_PRE_IOS5(...) \
if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_5_0) \
{ \
__VA_ARGS__ \
}
#endif

View file

@ -0,0 +1,69 @@
/*
* Author: Andreas Linde <mail@andreaslinde.de>
*
* Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
* Copyright (c) 2011 Andreas Linde.
* 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 "HockeySDKPrivate.h"
#include <CommonCrypto/CommonDigest.h>
// Load the framework bundle.
NSBundle *BITHockeyBundle(void) {
static NSBundle *bundle = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
NSString* mainBundlePath = [[NSBundle mainBundle] resourcePath];
NSString* frameworkBundlePath = [mainBundlePath stringByAppendingPathComponent:BITHOCKEYSDK_BUNDLE];
bundle = [[NSBundle bundleWithPath:frameworkBundlePath] retain];
});
return bundle;
}
NSString *BITHockeyLocalizedString(NSString *stringToken) {
if (BITHockeyBundle()) {
return NSLocalizedStringFromTableInBundle(stringToken, @"HockeySDK", BITHockeyBundle(), @"");
} else {
return stringToken;
}
}
NSString *BITHockeyMD5(NSString *str) {
const char *cStr = [str UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5( cStr, strlen(cStr), result );
return [NSString
stringWithFormat: @"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1],
result[2], result[3],
result[4], result[5],
result[6], result[7],
result[8], result[9],
result[10], result[11],
result[12], result[13],
result[14], result[15]
];
}

View file

@ -1,8 +1,7 @@
//
// NSString+HockeyAdditions.h
//
// Created by Jon Crosby on 10/19/07.
// Copyright 2007 Kaboomerang LLC. All rights reserved.
// Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@ -25,11 +24,11 @@
#import <Foundation/Foundation.h>
@interface NSString (HockeyAdditions)
@interface NSString (BITHockeyAdditions)
- (NSString *)bw_URLEncodedString;
- (NSString *)bw_URLDecodedString;
- (NSString *)bit_URLEncodedString;
- (NSString *)bit_URLDecodedString;
- (NSComparisonResult)versionCompare:(NSString *)other;
- (NSComparisonResult)bit_versionCompare:(NSString *)other;
@end

View file

@ -1,8 +1,7 @@
//
// NSString+HockeyAdditions.m
//
// Created by Jon Crosby on 10/19/07.
// Copyright 2007 Kaboomerang LLC. All rights reserved.
// Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@ -23,16 +22,11 @@
// THE SOFTWARE.
#import "NSString+HockeyAdditions.h"
#import "NSString+BITHockeyAdditions.h"
#ifdef HOCKEYLIB_STATIC_LIBRARY
#import "CNSFixCategoryBug.h"
CNS_FIX_CATEGORY_BUG(NSString_HockeyAdditions)
#endif
@implementation NSString (BITHockeyAdditions)
@implementation NSString (HockeyAdditions)
- (NSString *)bw_URLEncodedString {
- (NSString *)bit_URLEncodedString {
NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)self,
NULL,
@ -42,7 +36,7 @@ CNS_FIX_CATEGORY_BUG(NSString_HockeyAdditions)
return result;
}
- (NSString*)bw_URLDecodedString {
- (NSString*)bit_URLDecodedString {
NSString *result = (NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,
(CFStringRef)self,
CFSTR(""),
@ -51,7 +45,7 @@ CNS_FIX_CATEGORY_BUG(NSString_HockeyAdditions)
return result;
}
- (NSComparisonResult)versionCompare:(NSString *)other {
- (NSComparisonResult)bit_versionCompare:(NSString *)other {
// Extract plain version number from self
NSString *plainSelf = self;
NSRange letterRange = [plainSelf rangeOfCharacterFromSet: [NSCharacterSet letterCharacterSet]];

View file

@ -1,9 +1,6 @@
//
// PSAppStoreHeader.h
// HockeyDemo
//
// Created by Peter Steinberger on 09.01.11.
// Copyright 2011 Peter Steinberger. All rights reserved.
// Copyright (c) 2011-2012 Peter Steinberger. 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

View file

@ -1,9 +1,6 @@
//
// PSAppStoreHeader.m
// HockeyDemo
//
// Created by Peter Steinberger on 09.01.11.
// Copyright 2011 Peter Steinberger. All rights reserved.
// Copyright (c) 2011-2012 Peter Steinberger. 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
@ -24,13 +21,14 @@
// THE SOFTWARE.
#import "PSAppStoreHeader.h"
#import "UIImage+HockeyAdditions.h"
#import "BWGlobal.h"
#import "UIImage+BITHockeyAdditions.h"
#import "HockeySDKPrivate.h"
#define BW_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1]
#define kLightGrayColor BW_RGBCOLOR(200, 202, 204)
#define kDarkGrayColor BW_RGBCOLOR(140, 141, 142)
#define BIT_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1]
#define kLightGrayColor BIT_RGBCOLOR(200, 202, 204)
#define kDarkGrayColor BIT_RGBCOLOR(140, 141, 142)
#define kImageHeight 57
#define kReflectionHeight 20
@ -45,9 +43,8 @@
@synthesize subHeaderLabel;
@synthesize iconImage = iconImage_;
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject
#pragma mark - NSObject
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
@ -67,9 +64,7 @@
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIView
#pragma mark - UIView
- (void)drawRect:(CGRect)rect {
CGRect bounds = self.bounds;
@ -85,8 +80,8 @@
CGGradientRelease(gradient);
// draw header name
UIColor *mainTextColor = BW_RGBCOLOR(0,0,0);
UIColor *secondaryTextColor = BW_RGBCOLOR(48,48,48);
UIColor *mainTextColor = BIT_RGBCOLOR(0,0,0);
UIColor *secondaryTextColor = BIT_RGBCOLOR(48,48,48);
UIFont *mainFont = [UIFont boldSystemFontOfSize:20];
UIFont *secondaryFont = [UIFont boldSystemFontOfSize:12];
UIFont *smallFont = [UIFont systemFontOfSize:12];
@ -101,12 +96,10 @@
// shadows are a beast
NSInteger shadowOffset = 2;
BW_IF_IOS4_OR_GREATER(if([[UIScreen mainScreen] scale] == 2) shadowOffset = 1;)
BW_IF_IOS5_OR_GREATER(shadowOffset = 1;) // iOS5 changes this - again!
BW_IF_3_2_OR_GREATER(CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset, shadowOffset), 0, myColor);)
BW_IF_PRE_3_2(shadowOffset=1;CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset, -shadowOffset), 0, myColor);)
if([[UIScreen mainScreen] scale] == 2) shadowOffset = 1;
BITHOCKEY_IF_IOS5_OR_GREATER(shadowOffset = 1;) // iOS5 changes this - again!
CGContextSetShadowWithColor(context, CGSizeMake(shadowOffset, shadowOffset), 0, myColor);
[mainTextColor set];
[headerLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin, globalWidth-kTextRow, 20) withFont:mainFont lineBreakMode:UILineBreakModeTailTruncation];
@ -124,9 +117,8 @@
CGColorSpaceRelease(myColorSpace);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Properties
#pragma mark - Properties
- (void)setHeaderLabel:(NSString *)anHeaderLabel {
if (headerLabel_ != anHeaderLabel) {
@ -157,14 +149,14 @@
[iconImage_ release];
// scale, make borders and reflection
iconImage_ = [anIconImage bw_imageToFitSize:CGSizeMake(kImageHeight, kImageHeight) honorScaleFactor:YES];
iconImage_ = [[iconImage_ bw_roundedCornerImage:kImageBorderRadius borderSize:0.0] retain];
iconImage_ = [anIconImage bit_imageToFitSize:CGSizeMake(kImageHeight, kImageHeight) honorScaleFactor:YES];
iconImage_ = [[iconImage_ bit_roundedCornerImage:kImageBorderRadius borderSize:0.0] retain];
// create reflected image
[reflectedImage_ release];
reflectedImage_ = nil;
if (anIconImage) {
reflectedImage_ = [[iconImage_ bw_reflectedImageWithHeight:kReflectionHeight fromAlpha:0.5 toAlpha:0.0] retain];
reflectedImage_ = [[iconImage_ bit_reflectedImageWithHeight:kReflectionHeight fromAlpha:0.5 toAlpha:0.0] retain];
}
[self setNeedsDisplay];
}

View file

@ -1,9 +1,6 @@
//
// PSStoreButton.h
// HockeyDemo
//
// Created by Peter Steinberger on 09.01.11.
// Copyright 2011 Peter Steinberger. All rights reserved.
// Copyright 2011-2012 Peter Steinberger. 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

View file

@ -1,9 +1,6 @@
//
// PSStoreButton.m
// HockeyDemo
//
// Created by Peter Steinberger on 09.01.11.
// Copyright 2011 Peter Steinberger. All rights reserved.
// Copyright 2011-2012 Peter Steinberger. All rights reserved.
//
// This code was inspired by https://github.com/dhmspector/ZIStoreButton
//
@ -39,9 +36,8 @@
@synthesize colors = colors_;
@synthesize enabled = enabled_;
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject
#pragma mark - NSObject
- (id)initWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag {
if ((self = [super init])) {
@ -77,9 +73,8 @@
@synthesize buttonDelegate = buttonDelegate_;
@synthesize customPadding = customPadding_;
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark private
#pragma mark - private
- (void)buttonPressed:(id)sender {
[buttonDelegate_ storeButtonFired:self];
@ -159,9 +154,8 @@
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject
#pragma mark - NSObject
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
@ -214,9 +208,8 @@
[super dealloc];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIView
#pragma mark - UIView
- (CGSize)sizeThatFits:(CGSize)size {
CGSize constr = (CGSize){.height = self.frame.size.height, .width = PS_MAX_WIDTH};
@ -241,9 +234,8 @@
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Properties
#pragma mark - Properties
- (void)setButtonData:(PSStoreButtonData *)aButtonData {
[self setButtonData:aButtonData animated:NO];
@ -258,9 +250,8 @@
[self updateButtonAnimated:animated];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Static
#pragma mark - Static
+ (NSArray *)appStoreGreenColor {
return [NSArray arrayWithObjects:(id)

View file

@ -1,9 +1,6 @@
//
// PSWebTableViewCell.h
// HockeyDemo
//
// Created by Peter Steinberger on 04.02.11.
// Copyright 2011 Peter Steinberger. All rights reserved.
// Copyright 2011-2012 Peter Steinberger. 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

View file

@ -1,9 +1,6 @@
//
// PSWebTableViewCell.m
// HockeyDemo
//
// Created by Peter Steinberger on 04.02.11.
// Copyright 2011 Peter Steinberger. All rights reserved.
// Copyright 2011-2012 Peter Steinberger. 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
@ -24,7 +21,7 @@
// THE SOFTWARE.
#import "PSWebTableViewCell.h"
#import "BWGlobal.h"
@implementation PSWebTableViewCell
@ -47,9 +44,8 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
@synthesize webViewSize = webViewSize_;
@synthesize cellBackgroundColor = cellBackgroundColor_;
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark private
#pragma mark - private
- (void)addWebView {
if(webViewContent_) {
@ -82,8 +78,8 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
else
webView_.frame = webViewRect;
NSString *deviceWidth = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? [NSString stringWithFormat:@"%d", CGRectGetWidth(self.bounds)] : @"device-width";
//BWHockeyLog(@"%@\n%@\%@", PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent);
NSString *deviceWidth = UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ? [NSString stringWithFormat:@"%.0f", CGRectGetWidth(self.bounds)] : @"device-width";
//HockeySDKLog(@"%@\n%@\%@", PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent);
NSString *contentHtml = [NSString stringWithFormat:PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent];
[webView_ loadHTMLString:contentHtml baseURL:nil];
}
@ -118,9 +114,8 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject
#pragma mark - NSObject
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
@ -135,9 +130,8 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
[super dealloc];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIView
#pragma mark - UIView
- (void)setFrame:(CGRect)aFrame {
BOOL needChange = !CGRectEqualToRect(aFrame, self.frame);
@ -148,9 +142,8 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UITableViewCell
#pragma mark - UITableViewCell
- (void)prepareForReuse {
[self removeWebView];
@ -158,9 +151,8 @@ body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px
[super prepareForReuse];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIWebView
#pragma mark - UIWebView
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if(navigationType == UIWebViewNavigationTypeOther)

View file

@ -1,9 +1,10 @@
//
// UIImage+HockeyAdditions.h
// HockeyDemo
// UIImage+BITHockeyAdditions.h
//
// Created by Peter Steinberger on 10.01.11.
// Copyright 2011 Peter Steinberger. All rights reserved.
// Copyright (c) 2011-2012 Peter Steinberger.
// Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
// 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
@ -25,14 +26,14 @@
#import <UIKit/UIKit.h>
@interface UIImage (HockeyAdditions)
@interface UIImage (BITHockeyAdditions)
- (UIImage *)bw_roundedCornerImage:(NSInteger)cornerSize borderSize:(NSInteger)borderSize;
- (UIImage *)bw_imageToFitSize:(CGSize)fitSize honorScaleFactor:(BOOL)honorScaleFactor;
- (UIImage *)bw_reflectedImageWithHeight:(NSUInteger)height fromAlpha:(float)fromAlpha toAlpha:(float)toAlpha;
- (UIImage *)bit_roundedCornerImage:(NSInteger)cornerSize borderSize:(NSInteger)borderSize;
- (UIImage *)bit_imageToFitSize:(CGSize)fitSize honorScaleFactor:(BOOL)honorScaleFactor;
- (UIImage *)bit_reflectedImageWithHeight:(NSUInteger)height fromAlpha:(float)fromAlpha toAlpha:(float)toAlpha;
- (id)bw_initWithContentsOfResolutionIndependentFile:(NSString *)path NS_RETURNS_RETAINED;
+ (UIImage*)bw_imageWithContentsOfResolutionIndependentFile:(NSString *)path;
+ (UIImage *)bw_imageNamed:(NSString *)imageName bundle:(NSString *)bundleName;
- (id)bit_initWithContentsOfResolutionIndependentFile:(NSString *)path NS_RETURNS_RETAINED;
+ (UIImage *)bit_imageWithContentsOfResolutionIndependentFile:(NSString *)path;
+ (UIImage *)bit_imageNamed:(NSString *)imageName bundle:(NSString *)bundleName;
@end

View file

@ -1,9 +1,10 @@
//
// UIImage+HockeyAdditions.m
// HockeyDemo
// UIImage+BITHockeyAdditions.m
//
// Created by Peter Steinberger on 10.01.11.
// Copyright 2011 Peter Steinberger. All rights reserved.
// Copyright (c) 2011-2012 Peter Steinberger.
// Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
// 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
@ -23,20 +24,14 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "UIImage+HockeyAdditions.h"
#import "BWGlobal.h"
#ifdef HOCKEYLIB_STATIC_LIBRARY
#import "CNSFixCategoryBug.h"
CNS_FIX_CATEGORY_BUG(UIImage_HockeyAdditionsPrivate)
#endif
#import "UIImage+BITHockeyAdditions.h"
// Private helper methods
@interface UIImage (HockeyAdditionsPrivate)
- (void)addRoundedRectToPath:(CGRect)rect context:(CGContextRef)context ovalWidth:(CGFloat)ovalWidth ovalHeight:(CGFloat)ovalHeight;
@interface UIImage (BITHockeyAdditionsPrivate)
- (void)bit_addRoundedRectToPath:(CGRect)rect context:(CGContextRef)context ovalWidth:(CGFloat)ovalWidth ovalHeight:(CGFloat)ovalHeight;
@end
@implementation UIImage (HockeyAdditions)
@implementation UIImage (BITHockeyAdditions)
CGContextRef MyOpenBitmapContext(int pixelsWide, int pixelsHigh);
CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha, float toAlpha);
@ -84,32 +79,29 @@ CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha,
// Creates a copy of this image with rounded corners
// If borderSize is non-zero, a transparent border of the given size will also be added
// Original author: Björn Sållarp. Used with permission. See: http://blog.sallarp.com/iphone-uiimage-round-corners/
- (UIImage *)bw_roundedCornerImage:(NSInteger)cornerSize borderSize:(NSInteger)borderSize {
- (UIImage *)bit_roundedCornerImage:(NSInteger)cornerSize borderSize:(NSInteger)borderSize {
// If the image does not have an alpha layer, add one
UIImage *roundedImage = nil;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
BW_IF_IOS4_OR_GREATER(
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen".
CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], CGRectMake(0, 0, self.size.width * self.scale, self.size.height * self.scale)); // cropping happens here.
// Create a clipping path with rounded corners
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);
[self addRoundedRectToPath:CGRectMake(borderSize, borderSize, self.size.width - borderSize * 2, self.size.height - borderSize * 2)
context:context
ovalWidth:cornerSize
ovalHeight:cornerSize];
CGContextClosePath(context);
CGContextClip(context);
roundedImage = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage.
[roundedImage drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)]; // the actual scaling happens here, and orientation is taken care of automatically.
CGImageRelease(sourceImg);
roundedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
)
#endif
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0); // 0.0 for scale means "correct scale for device's main screen".
CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], CGRectMake(0, 0, self.size.width * self.scale, self.size.height * self.scale)); // cropping happens here.
// Create a clipping path with rounded corners
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);
[self addRoundedRectToPath:CGRectMake(borderSize, borderSize, self.size.width - borderSize * 2, self.size.height - borderSize * 2)
context:context
ovalWidth:cornerSize
ovalHeight:cornerSize];
CGContextClosePath(context);
CGContextClip(context);
roundedImage = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage.
[roundedImage drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)]; // the actual scaling happens here, and orientation is taken care of automatically.
CGImageRelease(sourceImg);
roundedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (!roundedImage) {
// Try older method.
UIImage *image = [self imageWithAlpha];
@ -146,8 +138,7 @@ CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha,
return roundedImage;
}
#pragma mark -
#pragma mark Private helper methods
#pragma mark - Private helper methods
// Adds a rectangular path to the given context and rounds its corners by the given extents
// Original author: Björn Sållarp. Used with permission. See: http://blog.sallarp.com/iphone-uiimage-round-corners/
@ -170,16 +161,14 @@ CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha,
CGContextRestoreGState(context);
}
- (UIImage *)bw_imageToFitSize:(CGSize)fitSize honorScaleFactor:(BOOL)honorScaleFactor
- (UIImage *)bit_imageToFitSize:(CGSize)fitSize honorScaleFactor:(BOOL)honorScaleFactor
{
float imageScaleFactor = 1.0;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000
if (honorScaleFactor) {
if ([self respondsToSelector:@selector(scale)]) {
imageScaleFactor = [self scale];
}
}
#endif
float sourceWidth = [self size].width * imageScaleFactor;
float sourceHeight = [self size].height * imageScaleFactor;
@ -214,16 +203,13 @@ CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha,
// Create appropriately modified image.
UIImage *image = nil;
BW_IF_IOS4_OR_GREATER
(
UIGraphicsBeginImageContextWithOptions(destRect.size, NO, honorScaleFactor ? 0.0 : 1.0); // 0.0 for scale means "correct scale for device's main screen".
CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); // cropping happens here.
image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage.
[image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically.
CGImageRelease(sourceImg);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
)
UIGraphicsBeginImageContextWithOptions(destRect.size, NO, honorScaleFactor ? 0.0 : 1.0); // 0.0 for scale means "correct scale for device's main screen".
CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect); // cropping happens here.
image = [UIImage imageWithCGImage:sourceImg scale:0.0 orientation:self.imageOrientation]; // create cropped UIImage.
[image drawInRect:destRect]; // the actual scaling happens here, and orientation is taken care of automatically.
CGImageRelease(sourceImg);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (!image) {
// Try older method.
@ -292,7 +278,7 @@ CGContextRef MyOpenBitmapContext(int pixelsWide, int pixelsHigh) {
return UIGraphicsGetCurrentContext();
}
- (UIImage *)bw_reflectedImageWithHeight:(NSUInteger)height fromAlpha:(float)fromAlpha toAlpha:(float)toAlpha {
- (UIImage *)bit_reflectedImageWithHeight:(NSUInteger)height fromAlpha:(float)fromAlpha toAlpha:(float)toAlpha {
if(height == 0)
return nil;
@ -319,7 +305,7 @@ CGContextRef MyOpenBitmapContext(int pixelsWide, int pixelsHigh) {
return theImage;
}
- (id)bw_initWithContentsOfResolutionIndependentFile:(NSString *)path {
- (id)bit_initWithContentsOfResolutionIndependentFile:(NSString *)path {
if ([UIScreen instancesRespondToSelector:@selector(scale)] && (int)[[UIScreen mainScreen] scale] == 2.0) {
NSString *path2x = [[path stringByDeletingLastPathComponent]
stringByAppendingPathComponent:[NSString stringWithFormat:@"%@@2x.%@",
@ -334,19 +320,19 @@ CGContextRef MyOpenBitmapContext(int pixelsWide, int pixelsHigh) {
return [self initWithContentsOfFile:path];
}
+ (UIImage*)bw_imageWithContentsOfResolutionIndependentFile:(NSString *)path {
+ (UIImage*)bit_imageWithContentsOfResolutionIndependentFile:(NSString *)path {
#ifndef __clang_analyzer__
// clang alayzer in 4.2b3 thinks here's a leak, which is not the case.
return [[[UIImage alloc] bw_initWithContentsOfResolutionIndependentFile:path] autorelease];
return [[[UIImage alloc] bit_initWithContentsOfResolutionIndependentFile:path] autorelease];
#endif
}
+ (UIImage *)bw_imageNamed:(NSString *)imageName bundle:(NSString *)bundleName {
+ (UIImage *)bit_imageNamed:(NSString *)imageName bundle:(NSString *)bundleName {
NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
NSString *bundlePath = [resourcePath stringByAppendingPathComponent:bundleName];
NSString *imagePath = [bundlePath stringByAppendingPathComponent:imageName];
return [UIImage bw_imageWithContentsOfResolutionIndependentFile:imagePath];
return [UIImage bit_imageWithContentsOfResolutionIndependentFile:imagePath];
}
@end

View file

@ -1,12 +1,12 @@
Pod::Spec.new do |s|
s.name = 'HockeySDK'
s.version = '2.2.6'
s.version = '2.3.0'
s.license = 'MIT'
s.platform = :ios
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/codenauts/HockeySDK-iOS', :tag => '2.2.3' }
s.source = { :git => 'https://github.com/bitstadium/HockeySDK-iOS', :tag => '2.3.0' }
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 ' \
@ -17,7 +17,7 @@ Pod::Spec.new do |s|
'yourself when the network becomse reachable.'
s.source_files = 'Classes'
s.resources = 'Resources/Hockey.bundle', 'Resources/Quincy.bundle'
s.resources = 'Resources/HockeySDKResources.bundle'
s.frameworks = 'QuartzCore', 'SystemConfiguration', 'CrashReporter'
s.xcconfig = { 'FRAMEWORK_SEARCH_PATHS' => '"$(BUILT_PRODUCTS_DIR)/Pods/Frameworks"' }

View file

@ -1,20 +1,10 @@
## Authors
Andreas Linde <andy@buzzworks.de>
Stanley Rost <soryu2@gmail.com>
Fabian Kreiser <fabian@fabian-kreiser.com>
Tobias Höhmann
FutureTap
Kent Sutherland
Peter Steinberger <me@petersteinberger.com>
Thomas Dohmke <thomas@dohmke.de>
## Licenses
The Hockey SDK is provided under the following license:
The MIT License
Copyright (c) 2011-2012 Codenauts UG (haftungsbeschränkt). All rights reserved.
Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
All rights reserved.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
@ -41,6 +31,7 @@ Except as noted below, PLCrashReporter
is provided under the following license:
Copyright (c) 2008 - 2012 Plausible Labs Cooperative, Inc.
Copyright (c) 2012 HockeyApp, Bit Stadium GmbH.
All rights reserved.
Permission is hereby granted, free of charge, to any person

143
README.md
View file

@ -1,139 +1,26 @@
This document describes how to integrate the HockeyApp SDK into your app. The SDK has two main features:
Introduction
============
HockeySDK-iOS implements support for using HockeyApp in your iOS applications.
The following features are currently supported:
1. **Update apps:** The app will check with HockeyApp if a new version is available. If yes, it will show an alert view to the user and let him see the release notes, the version history and start the installation process right away.
2. **Collect crash reports:** If you app crashes, a crash log with the same format as from the Apple Crash Reporter is written to the device's storage. If the user starts the app again, he is asked to submit the crash report to HockeyApp. This works for both beta and live apps, i.e. those submitted to the App Store!
## Prerequisites
The main SDK class is `BITHockeyManager`. It initializes all modules and provides access to them, so they can be further adjusted if required. Additionally all modules provide their own protocols.
Prerequisites
=============
1. Before you integrate HockeySDK into your own app, you should add the app to HockeyApp if you haven't already. Read [this how-to](http://support.hockeyapp.net/kb/how-tos/how-to-create-a-new-app) on how to do it.
2. We also assume that you already have a project in Xcode and that this project is opened in Xcode 4.
3. The SDK supports iOS 4.0 or newer.
## Versioning
Documentation
=============
We suggest to handle beta and release versions in two separate *apps* on HockeyApp with their own bundle identifier (e.g. by adding "beta" to the bundle identifier), so
Documentation can be build using [AppleDoc](https://github.com/tomaz/appledoc/) and the `Documentation` target. This will build the latest version and automatically install it in Xcode.
* both apps can run on the same device or computer at the same time without interfering,
* release versions do not appear on the beta download pages, and
* easier analysis of crash reports and user feedback.
We propose the following method to set version numbers in your beta versions:
* Use both "Bundle Version" and "Bundle Version String, short" in your Info.plist.
* "Bundle Version" should contain a sequential build number, e.g. 1, 2, 3.
* "Bundle Version String, short" should contain the target official version number, e.g. 1.0.
## Download & Extract
1. Download the latest [HockeySDK](https://github.com/codenauts/HockeySDK-iOS/downloads).
2. Unzip the file. A new folder HockeySDK-iOS is created.
3. Move the folder into your project directory. We usually put 3rd-party code into a subdirectory named "Vendor", so we move the directory into it.
## Integrate into Xcode
Drag & drop the HockeySDK folder from your project directory to your Xcode project. Similar to above, our projects have a group "Vendor", so we drop it there. Select "Create groups for any added folders" and set the checkmark for your target. Then click "Finish".
## Add Frameworks
1. Select your project in the Project Navigator.
2. Select your target.
3. Select the tab "Build Phases".
4. Expand "Link Binary With Libraries".
5. You need all of the following frameworks:
* CoreGraphics.framework
* CrashReporter.framework
* Foundation.framework
* QuartzCore.framework
* SystemConfiguration.framework
* UIKit.framework
6. If one of the frameworks is missing, then click the + button, search the framework and confirm with the "Add" button.
7. HockeySDK also needs a JSON library. If you target iOS 5, you can use the built-in NSJSONSerialization classes. If your deployment target is iOS 3.x or 4.x, please include one of the following libraries:
* https://github.com/johnezang/JSONKit
* https://github.com/stig/json-framework
## Modify Source Code
1. Open your AppDelegate.h file.
2. Add the following line at the top of the file below your own #import statements:<pre><code>#import "CNSHockeyManager.h"</code></pre>
3. Let the AppDelegate implement the protocol CNSHockeyManagerDelegate:<pre><code>@interface AppDelegate : UIResponder &lt;CNSHockeyManagerDelegate, UIApplicationDelegate>
</code></pre>
4. Search for the method application:didFinishLaunchingWithOptions:
5. Open your AppDelegate.m file.
6. Add the following lines:<pre><code>[[CNSHockeyManager sharedHockeyManager] configureWithBetaIdentifier:@"BETA_IDENTIFIER"
liveIdentifier:@"LIVE_IDENTIFIER"
delegate:self];</code></pre>
7. Replace BETA_IDENTIFIER with the app identifier of your beta app. If you don't know what the app identifier is or how to find it, please read [this how-to](http://support.hockeyapp.net/kb/how-tos/how-to-find-the-app-identifier).
8. Replace LIVE_IDENTIFIER with the app identifier of your release app.
9. Add the following method:<pre><code>-(NSString *)customDeviceIdentifier {
\#ifdef BETA_BUILD
if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)])
return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)];
\#endif
return nil;
}</code></pre>This assumes that the precompiler macro BETA_BUILD is set for your ad-hoc builds, but not for store builds.
10. If you have added the lines to the method application:didFinishLaunchingWithOptions:, you should be ready to go. If you do some GCD magic or added the lines at a different place, please make sure to invoke the above code on the main thread.
## Optional Delegate Methods
Besides the crash log, HockeyApp can show you fields with information about the user and an optional description. You can fill out these fields by implementing the following methods:
* **crashReportUserID** should be a user ID or email, e.g. if your app requires to sign in into your server, you could specify the login here. The string should be no longer than 255 chars. You can also set autoSubmitDeviceUDID to YES, then crashReportUserID will be automatically set to the device's UDID.
* **crashReportContact** should be the user's name or similar. The string should be no longer than 255 chars.
* **crashReportDescription** can be as long as you want it to be and contain additional information about the
crash. For example, you can return a custom log or the last XML or JSON response from your server here.
If you implement these delegate methods and keep them in your live app too, please consider the privacy implications.
## Upload the .dSYM File
Once you have your app ready for beta testing or even to submit it to the App Store, you need to upload the .dSYM bundle to HockeyApp to enable symbolication. If you have built your app with Xcode4, menu Product > Archive, you can find the .dSYM as follows:
1. Chose Window > Organizer in Xcode.
2. Select the tab Archives.
3. Select your app in the left sidebar.
4. Right-click on the latest archive and select Show in Finder.
5. Right-click the .xcarchive in Finder and select Show Package Contents.
6. You should see a folder named dSYMs which contains your dSYM bundle. If you use Safari, just drag this file from Finder and drop it on to the corresponding drop zone in HockeyApp. If you use another browser, copy the file to a different location, then right-click it and choose Compress "YourApp.dSYM". The file will be compressed as a .zip file. Drag & drop this file to HockeyApp.
As an alternative for step 5 and 6, you can use our [HockeyMac](https://github.com/codenauts/HockeyMac) app to upload the complete archive in one step.
## Checklist if Crashes Do Not Appear in HockeyApp
1. Check if the BETA_IDENTIFIER or LIVE_IDENTIFIER matches the App ID in HockeyApp.
2. Check if CFBundleIdentifier in your Info.plist matches the Bundle Identifier of the app in HockeyApp. HockeyApp accepts crashes only if both the App ID and the Bundle Identifier equal their corresponding values in your plist and source code.
3. Unless you have set autoSubmitCrashReport to YES: If your app crashes and you start it again, is the alert shown which asks the user to send the crash report? If not, please crash your app again, then connect the debugger and set a break point in BWQuincyManager.m, method [startManager](https://github.com/codenauts/HockeySDK-iOS/blob/develop/Classes/BWQuincyManager.m#L251) to see why the alert is not shown.
4. If it still does not work, please [contact us](http://support.hockeyapp.net/discussion/new).
The documentation contains installation and setup guides, various HowTos, quick troubleshooting and documentation of the public classes and protocols.

View file

@ -1,87 +0,0 @@
/*
Hockey.strings
Hockey
Created by Andreas Linde on 11/15/10.
Copyright 2010 buzzworks.de. All rights reserved.
*/
/* Alert view */
/* For dialogs yes buttons */
"HockeyYes" = "Ja";
/* For dialogs no buttons */
"HockeyNo" = "Nein";
/* Update available */
"HockeyUpdateAvailable" = "Aktualisierung verfügbar";
/* Would you like to check out the new update? You can do this later on at any time in the In-App settings. */
"HockeyUpdateAlertText" = "Möchten Sie sich weitere Informationen zu der Aktualisierung ansehen?";
/* Update details screen */
/* Update Details */
"HockeyUpdateScreenTitle" = "Aktualisierung";
/* Settings */
/* Screen title for settings view */
"HockeySettingsTitle" = "Einstellungen";
/* Text asking the user to send user data (on/off switch) */
"HockeySettingsUserData" = "Daten senden";
/* Description text for turning on/off sending user data */
"HockeySettingsUserDataDescription" = "Daten senden liefert dem Entwickler die folgenden Daten: Programmversion, Sprache, Gerätetyp und iOS Version.";
/* Text asking the user to send usage data (on/off switch) */
"HockeySettingsUsageData" = "Testzeit senden";
/* Description text for turning on/off sending usage data */
"HockeySettingsUsageDataDescription" = "Testzeit senden informiert den Entwickler über die Summe der Testzeit, in einer Genauigkeit von 1 Minute.";
/* Title for defining when update checks may be made */
"HockeySectionCheckTitle" = "Nach Aktualisierung Prüfen";
/* On Startup */
"HockeySectionCheckStartup" = "Beim Start";
/* Daily */
"HockeySectionCheckDaily" = "Täglich";
/* Manually */
"HockeySectionCheckManually" = "Manuell";
"HockeyVersion" = "Version";
"HockeyShowPreviousVersions" = "Zeige frühere Versionen...";
"HockeyNoUpdateNeededTitle" = "Kein Update Verfügbar";
"HockeyNoUpdateNeededMessage" = "%@ ist bereits die aktuellste Version.";
"HockeyUpdateAlertTextWithAppVersion" = "%@ ist verfügbar.";
"HockeyUpdateAlertMandatoryTextWithAppVersion" = "%@ ist verfügbar und muss installiert werden!";
"HockeyIgnore" = "Ignorieren";
"HockeyShowUpdate" = "Anzeigen";
"HockeyInstallUpdate" = "Installieren";
"HockeyError" = "Fehler";
"HockeyOK" = "OK";
"HockeyWarning" = "Warnung";
"HockeyNoReleaseNotesAvailable" = "Keine Release Notes verfügbar.";
"HockeyAuthorizationProgress" = "Autorisierung...";
"HockeyAuthorizationOffline" = "Internet Verbindung wird benötigt!";
"HockeyAuthorizationDenied" = "Autorisierung verweigert. Bitte kontaktieren Sie den Entwickler.";
"HockeyiOS3Message" = "In-App download benötigt iOS 4 oder höher. Sie können die Applikation manuell updaten, indem sie das IPA von %@ laden und es via iTunes syncen.";
"HockeySimulatorMessage" = "Hockey funktioniert nicht im Simulator.";
"HockeyButtonCheck" = "PRÜFE";
"HockeyButtonSearching" = "PRÜFEN";
"HockeyButtonUpdate" = "UPDATE";
"HockeyButtonInstalling" = "INSTALLIEREN";
"HockeyButtonOffline" = "OFFLINE";
"HockeyInstalled" = "INSTALLIERT";

View file

@ -1,87 +0,0 @@
/*
Hockey.strings
Hockey
Created by Andreas Linde on 11/15/10.
Copyright 2010 buzzworks.de. All rights reserved.
*/
/* Alert view */
/* For dialogs yes buttons */
"HockeyYes" = "Yes";
/* For dialogs no buttons */
"HockeyNo" = "No";
/* Update available */
"HockeyUpdateAvailable" = "Update available";
/* Would you like to check out the new update? You can do this later on at any time in the In-App settings. */
"HockeyUpdateAlertText" = "Would you like to check out the new update?";
/* Update details screen */
/* Update Details */
"HockeyUpdateScreenTitle" = "Update";
/* Settings */
/* Screen title for settings view */
"HockeySettingsTitle" = "Settings";
/* Text asking the user to send user data (on/off switch) */
"HockeySettingsUserData" = "Send User Data";
/* Description text for turning on/off sending user data */
"HockeySettingsUserDataDescription" = "Send User Data will send the following data to the developer: app version, language, device type and iOS version.";
/* Text asking the user to send usage data (on/off switch) */
"HockeySettingsUsageData" = "Send Usage Data";
/* Description text for turning on/off sending usage data */
"HockeySettingsUsageDataDescription" = "Send Usage Data will send the amount of test time to the developer, in a granularity of 1 minute.";
/* Title for defining when update checks may be made */
"HockeySectionCheckTitle" = "Check For Update";
/* On Startup */
"HockeySectionCheckStartup" = "On Startup";
/* Daily */
"HockeySectionCheckDaily" = "Daily";
/* Manually */
"HockeySectionCheckManually" = "Manually";
"HockeyVersion" = "Version";
"HockeyShowPreviousVersions" = "Show previous versions...";
"HockeyNoUpdateNeededTitle" = "No Update Available";
"HockeyNoUpdateNeededMessage" = "%@ is already the latest version.";
"HockeyUpdateAlertTextWithAppVersion" = "%@ is available.";
"HockeyUpdateAlertMandatoryTextWithAppVersion" = "%@ is available and is a mandatory update!";
"HockeyIgnore" = "Ignore";
"HockeyShowUpdate" = "Show";
"HockeyInstallUpdate" = "Install";
"HockeyError" = "Error";
"HockeyOK" = "OK";
"HockeyWarning" = "Warning";
"HockeyNoReleaseNotesAvailable" = "No release notes available.";
"HockeyAuthorizationProgress" = "Authorizing...";
"HockeyAuthorizationOffline" = "Internet connection required!";
"HockeyAuthorizationDenied" = "Authorizing denied. Please contact the developer.";
"HockeyiOS3Message" = "In-App download requires iOS 4 or higher. You can update this application via downloading the IPA from %@ and syncing it with iTunes.";
"HockeySimulatorMessage" = "Hockey Update does not work in the Simulator.\nThe itms-services:// url scheme is implemented but nonfunctional.";
"HockeyButtonCheck" = "CHECK";
"HockeyButtonSearching" = "CHECKING";
"HockeyButtonUpdate" = "UPDATE";
"HockeyButtonInstalling" = "INSTALLING";
"HockeyButtonOffline" = "OFFLINE";
"HockeyInstalled" = "INSTALLED";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 911 B

View file

@ -1,87 +0,0 @@
/*
Hockey.strings
Hockey
Created by Andreas Linde on 11/15/10.
Copyright 2010 buzzworks.de. All rights reserved.
*/
/* Alert view */
/* For dialogs yes buttons */
"HockeyYes" = "Si";
/* For dialogs no buttons */
"HockeyNo" = "No";
/* Update available */
"HockeyUpdateAvailable" = "Aggiornamento disponibile";
/* Would you like to check out the new update? You can do this later on at any time in the In-App settings. */
"HockeyUpdateAlertText" = "Vuoi scaricare il nuovo aggiornamento ?";
/* Update details screen */
/* Update Details */
"HockeyUpdateScreenTitle" = "Aggiorna";
/* Settings */
/* Screen title for settings view */
"HockeySettingsTitle" = "Impostazioni";
/* Text asking the user to send user data (on/off switch) */
"HockeySettingsUserData" = "Invia dati di sistema";
/* Description text for turning on/off sending user data */
"HockeySettingsUserDataDescription" = "Invia le seguenti informazioni allo sviluppatore: versione app, lingua, tipo dispositivo e versione iOS.";
/* Text asking the user to send usage data (on/off switch) */
"HockeySettingsUsageData" = "Invia dati di utilizzo";
/* Description text for turning on/off sending usage data */
"HockeySettingsUsageDataDescription" = "Invia il tempo di utilizzo allo svilupatore, con un dettaglio di 1 minuto.";
/* Title for defining when update checks may be made */
"HockeySectionCheckTitle" = "Controlla gli aggiornamenti";
/* On Startup */
"HockeySectionCheckStartup" = "All'avvio";
/* Daily */
"HockeySectionCheckDaily" = "Ogni giorno";
/* Manually */
"HockeySectionCheckManually" = "Manualmente";
"HockeyVersion" = "Versione";
"HockeyShowPreviousVersions" = "Mostra le versioni precedenti...";
"HockeyNoUpdateNeededTitle" = "Nessun aggiornamento disponibile";
"HockeyNoUpdateNeededMessage" = "%@ <20> aggiornata all'ultima versione.";
"HockeyUpdateAlertTextWithAppVersion" = "%@ <20> disponibile.";
"HockeyUpdateAlertMandatoryTextWithAppVersion" = "%@ is available and is a mandatory update!";
"HockeyIgnore" = "Ignora";
"HockeyShowUpdate" = "Mostra";
"HockeyInstallUpdate" = "Installa";
"HockeyError" = "Errore";
"HockeyOK" = "OK";
"HockeyWarning" = "Avviso";
"HockeyNoReleaseNotesAvailable" = "Non sono disponibili note di rilascio.";
"HockeyAuthorizationProgress" = "Attendo autorizzazione...";
"HockeyAuthorizationOffline" = "E' richiesta la connessione internet!";
"HockeyAuthorizationDenied" = "Autorizzazione negata, contattare lo sviluppatore.";
"HockeyiOS3Message" = "L'aggioranento dall'applicazione <20> possibile con iOS 4 o superiore. Puoi aggiornare questa applicazione scaricandola da %@.";
"HockeySimulatorMessage" = "Hockey Update does not work in the Simulator.\nThe itms-services:// url scheme is implemented but nonfunctional.";
"HockeyButtonCheck" = "CONTROLLA";
"HockeyButtonSearching" = "CONTROLLO";
"HockeyButtonUpdate" = "AGGIORNA";
"HockeyButtonInstalling" = "INSTALLO";
"HockeyButtonOffline" = "OFFLINE";
"HockeyInstalled" = "INSTALLATA";

View file

@ -1,88 +0,0 @@
/*
Hockey.strings
Hockey
Created by Andreas Linde on 11/15/10.
Copyright 2010 buzzworks.de. All rights reserved.
Swedish translation by Joakim Ramer.
*/
/* Alert view */
/* For dialogs yes buttons */
"HockeyYes" = "Ja";
/* For dialogs no buttons */
"HockeyNo" = "Nej";
/* Update available */
"HockeyUpdateAvailable" = "Uppdatering tillgänglig";
/* Would you like to check out the new update? You can do this later on at any time in the In-App settings. */
"HockeyUpdateAlertText" = "Vill du hämta den nya uppdateringen?";
/* Update details screen */
/* Update Details */
"HockeyUpdateScreenTitle" = "Uppdatera";
/* Settings */
/* Screen title for settings view */
"HockeySettingsTitle" = "Inställningar";
/* Text asking the user to send user data (on/off switch) */
"HockeySettingsUserData" = "Skicka användardata";
/* Description text for turning on/off sending user data */
"HockeySettingsUserDataDescription" = "Skicka användardata skickar följande till utvecklaren: app version, språk, enhetstyp och iOS version.";
/* Text asking the user to send usage data (on/off switch) */
"HockeySettingsUsageData" = "Skicka användn.data";
/* Description text for turning on/off sending usage data */
"HockeySettingsUsageDataDescription" = "Skicka användningsdata skickar information om hur länge appen testats (i antal minuter), till utvecklaren.";
/* Title for defining when update checks may be made */
"HockeySectionCheckTitle" = "Leta efter uppdateringar";
/* On Startup */
"HockeySectionCheckStartup" = "Vid uppstart";
/* Daily */
"HockeySectionCheckDaily" = "Dagligen";
/* Manually */
"HockeySectionCheckManually" = "Manuellt";
"HockeyVersion" = "Version";
"HockeyShowPreviousVersions" = "Se tidigare versioner...";
"HockeyNoUpdateNeededTitle" = "Ingen uppdatering tillgänglig";
"HockeyNoUpdateNeededMessage" = "%@ är den senaste versionen.";
"HockeyUpdateAlertTextWithAppVersion" = "%@ finns.";
"HockeyUpdateAlertMandatoryTextWithAppVersion" = "%@ is available and is a mandatory update!";
"HockeyIgnore" = "Ignorera";
"HockeyShowUpdate" = "Visa";
"HockeyInstallUpdate" = "Installera";
"HockeyError" = "Error";
"HockeyOK" = "OK";
"HockeyWarning" = "Varning";
"HockeyNoReleaseNotesAvailable" = "Inga releasenoteringar tillgängliga.";
"HockeyAuthorizationProgress" = "Auktoriserar...";
"HockeyAuthorizationOffline" = "Internetuppkoppling krävs!";
"HockeyAuthorizationDenied" = "Auktoriseringen nekades. Kontakta utvecklaren.";
"HockeyiOS3Message" = "In-App nedladdning kräver iOS 4 eller högre. Du kan uppdatera applikationen genoma att ladda ner IPA-filen från %@ och synka in den i iTunes.";
"HockeySimulatorMessage" = "Hockey Update fungerar inte i Simulatorn.\nitms-services:// url schemat är implementerat men fungerar ej.";
"HockeyButtonCheck" = "FRÅGA";
"HockeyButtonSearching" = "FRÅGAR";
"HockeyButtonUpdate" = "UPPDATERA";
"HockeyButtonInstalling" = "INSTALLERAR";
"HockeyButtonOffline" = "OFFLINE";
"HockeyInstalled" = "INSTALLERAD";

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>net.hockeyapp.sdk.ios</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.3.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2012 HockeyApp, Bit Stadium GmbH. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1,021 B

After

Width:  |  Height:  |  Size: 1,021 B

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

View file

@ -0,0 +1,101 @@
/* General */
/* For dialogs yes buttons */
"HockeyYes" = "Ja";
/* For dialogs no buttons */
"HockeyNo" = "Nein";
/* For dialogs ok buttons */
"HockeyOK" = "OK";
/* Crash */
/* Crash dialog */
/* Title showing in the alert box when crash report data has been found */
"CrashDataFoundTitle" = "%@ unerwartet beendet";
/* Description explaining that crash data has been found and ask the user if the anonymous data might be uplaoded to the developers server */
"CrashDataFoundAnonymousDescription" = "Möchten Sie einen anonymen Absturzbericht senden, damit wir das Problem beheben können?";
/* Description explaining that crash data has been found and ask the user if the non anonymous data might be uplaoded to the developers server */
"CrashDataFoundDescription" = "Möchten Sie einen Absturzbericht senden, damit wir das Problem beheben können?";
/* Alert box button if the users wants to send crash data always automatically */
"CrashSendReportAlways" = "Immer senden";
/* Alert box button to send the crash report once */
"CrashSendReport" = "Bericht senden";
/* Alert box button to decline sending the report */
"CrashDontSendReport" = "Nicht senden";
/* Text showing in a processing box that the crash data is being uploaded to the server */
"CrashReportSending" = "Senden…";
/* Update */
/* Update Alert view */
/* Update available */
"UpdateAvailable" = "Aktualisierung verfügbar";
"UpdateAlertTextWithAppVersion" = "%@ ist verfügbar.";
"UpdateAlertMandatoryTextWithAppVersion" = "%@ ist verfügbar und muss installiert werden!";
"UpdateIgnore" = "Ignorieren";
"UpdateShow" = "Anzeigen";
"UpdateInstall" = "Installieren";
/* Update Details */
"UpdateScreenTitle" = "Aktualisierung";
"UpdateButtonCheck" = "PRÜFE";
"UpdateButtonSearching" = "PRÜFEN";
"UpdateButtonUpdate" = "UPDATE";
"UpdateButtonInstalling" = "INSTALLIEREN";
"UpdateButtonOffline" = "OFFLINE";
"UpdateInstalled" = "INSTALLIERT";
"UpdateVersion" = "Version";
"UpdateShowPreviousVersions" = "Zeige frühere Versionen...";
"UpdateNoUpdateAvailableTitle" = "Kein Update Verfügbar";
"UpdateNoUpdateAvailableMessage" = "%@ ist bereits die aktuellste Version.";
"UpdateError" = "Fehler";
"UpdateWarning" = "Warnung";
"UpdateNoReleaseNotesAvailable" = "Keine Release Notes verfügbar.";
/* Update Authorization */
"UpdateAuthorizationProgress" = "Autorisierung...";
"UpdateAuthorizationOffline" = "Internet Verbindung wird benötigt!";
"UpdateAuthorizationDenied" = "Autorisierung verweigert. Bitte kontaktieren Sie den Entwickler.";
/* Update Simulator Warning */
"UpdateSimulatorMessage" = "Hockey funktioniert nicht im Simulator.";

View file

@ -0,0 +1,101 @@
/* General */
/* For dialogs yes buttons */
"HockeyYes" = "Yes";
/* For dialogs no buttons */
"HockeyNo" = "No";
/* For dialogs ok buttons */
"HockeyOK" = "OK";
/* Crash */
/* Crash dialog */
/* Title showing in the alert box when crash report data has been found */
"CrashDataFoundTitle" = "%@ Unexpectedly Quit";
/* Description explaining that crash data has been found and ask the user if the data might be uploaded to the developers server */
"CrashDataFoundAnonymousDescription" = "Would you like to send an anonymous report so we can fix the problem?";
/* Description explaining that crash data has been found and ask the user if the non anonymous data might be uplaoded to the developers server */
"CrashDataFoundDescription" = "Would you like to send a report so we can fix the problem?";
/* Alert box button if the users wants to send crash data always automatically */
"CrashSendReportAlways" = "Always Send";
/* Alert box button to send the crash report once */
"CrashSendReport" = "Send Report";
/* Alert box button to decline sending the report */
"CrashDontSendReport" = "Don't Send";
/* Text showing in a processing box that the crash data is being uploaded to the server */
"CrashReportSending" = "Sending…";
/* Update */
/* Update Alert view */
/* Update available */
"UpdateAvailable" = "Update available";
"UpdateAlertTextWithAppVersion" = "%@ is available.";
"UpdateAlertMandatoryTextWithAppVersion" = "%@ is available and is a mandatory update!";
"UpdateIgnore" = "Ignore";
"UpdateShow" = "Show";
"UpdateInstall" = "Install";
/* Update Details */
"UpdateScreenTitle" = "Update";
"UpdateButtonCheck" = "CHECK";
"UpdateButtonSearching" = "CHECKING";
"UpdateButtonUpdate" = "UPDATE";
"UpdateButtonInstalling" = "INSTALLING";
"UpdateButtonOffline" = "OFFLINE";
"UpdateInstalled" = "INSTALLED";
"UpdateVersion" = "Version";
"UpdateShowPreviousVersions" = "Show previous versions...";
"UpdateNoUpdateAvailableTitle" = "No Update Available";
"UpdateNoUpdateAvailableMessage" = "%@ is already the latest version.";
"UpdateError" = "Error";
"UpdateWarning" = "Warning";
"UpdateNoReleaseNotesAvailable" = "No release notes available.";
/* Update Authorization */
"UpdateAuthorizationProgress" = "Authorizing...";
"UpdateAuthorizationOffline" = "Internet connection required!";
"UpdateAuthorizationDenied" = "Authorizing denied. Please contact the developer.";
/* Update Simulator Warning */
"UpdateSimulatorMessage" = "Hockey Update does not work in the Simulator.\nThe itms-services:// url scheme is implemented but nonfunctional.";

View file

@ -0,0 +1,101 @@
/* General */
/* For dialogs yes buttons */
"HockeyYes" = "Yes";
/* For dialogs no buttons */
"HockeyNo" = "No";
/* For dialogs ok buttons */
"HockeyOK" = "OK";
/* Crash */
/* Crash dialog */
/* Title showing in the alert box when crash report data has been found */
"CrashDataFoundTitle" = "Conflicto de datos detectado";
/* Description explaining that crash data has been found and ask the user if the data might be uploaded to the developers server */
"CrashDataFoundAnonymousDescription" = "La aplicación fallo previamente. ¿Le gustaría proporcionar de manera anonima información relacionado con el fallo al programador para que este pueda solucionar el problema?";
/* Description explaining that crash data has been found and ask the user if the non anonymous data might be uplaoded to the developers server */
"CrashDataFoundDescription" = "La aplicación fallo previamente. ¿Le gustaría proporcionar de manera información relacionado con el fallo al programador para que este pueda solucionar el problema?";
/* Alert box button if the users wants to send crash data always automatically */
"CrashSendReportAlways" = "Siempre";
/* Alert box button to send the crash report once */
"CrashSendReport" = "Sí";
/* Alert box button to decline sending the report */
"CrashDontSendReport" = "No";
/* Text showing in a processing box that the crash data is being uploaded to the server */
"CrashReportSending" = "Enviando…";
/* Update */
/* Update Alert view */
/* Update available */
"UpdateAvailable" = "Update available";
"UpdateAlertTextWithAppVersion" = "%@ is available.";
"UpdateAlertMandatoryTextWithAppVersion" = "%@ is available and is a mandatory update!";
"UpdateIgnore" = "Ignore";
"UpdateShow" = "Show";
"UpdateInstall" = "Install";
/* Update Details */
"UpdateScreenTitle" = "Update";
"UpdateButtonCheck" = "CHECK";
"UpdateButtonSearching" = "CHECKING";
"UpdateButtonUpdate" = "UPDATE";
"UpdateButtonInstalling" = "INSTALLING";
"UpdateButtonOffline" = "OFFLINE";
"UpdateInstalled" = "INSTALLED";
"UpdateVersion" = "Version";
"UpdateShowPreviousVersions" = "Show previous versions...";
"UpdateNoUpdateAvailableTitle" = "No Update Available";
"UpdateNoUpdateAvailableMessage" = "%@ is already the latest version.";
"UpdateError" = "Error";
"UpdateWarning" = "Warning";
"UpdateNoReleaseNotesAvailable" = "No release notes available.";
/* Update Authorization */
"UpdateAuthorizationProgress" = "Authorizing...";
"UpdateAuthorizationOffline" = "Internet connection required!";
"UpdateAuthorizationDenied" = "Authorizing denied. Please contact the developer.";
/* Update Simulator Warning */
"UpdateSimulatorMessage" = "Hockey Update does not work in the Simulator.\nThe itms-services:// url scheme is implemented but nonfunctional.";

View file

@ -0,0 +1,101 @@
/* General */
/* For dialogs yes buttons */
"HockeyYes" = "Oui";
/* For dialogs no buttons */
"HockeyNo" = "Non";
/* For dialogs ok buttons */
"HockeyOK" = "OK";
/* Crash */
/* Crash dialog */
/* Title showing in the alert box when crash report data has been found */
"CrashDataFoundTitle" = "Rapport de plantage";
/* Description explaining that crash data has been found and ask the user if the data might be uploaded to the developers server */
"CrashDataFoundAnonymousDescription" = "%@ sest fermé de façon inattendue. Souhaitez-vous envoyer un rapport dincident anonyme pour nous aider à corriger le problème ?";
/* Description explaining that crash data has been found and ask the user if the non anonymous data might be uplaoded to the developers server */
"CrashDataFoundDescription" = "%@ sest fermé de façon inattendue. Souhaitez-vous envoyer un rapport dincident pour nous aider à corriger le problème ?";
/* Alert box button if the users wants to send crash data always automatically */
"CrashSendReportAlways" = "Toujours";
/* Alert box button to send the crash report once */
"CrashSendReport" = "Oui";
/* Alert box button to decline sending the report */
"CrashDontSendReport" = "Non";
/* Text showing in a processing box that the crash data is being uploaded to the server */
"CrashReportSending" = "Envoyer…";
/* Update */
/* Update Alert view */
/* Update available */
"UpdateAvailable" = "Update available";
"UpdateAlertTextWithAppVersion" = "%@ is available.";
"UpdateAlertMandatoryTextWithAppVersion" = "%@ is available and is a mandatory update!";
"UpdateIgnore" = "Ignore";
"UpdateShow" = "Show";
"UpdateInstall" = "Install";
/* Update Details */
"UpdateScreenTitle" = "Update";
"UpdateButtonCheck" = "CHECK";
"UpdateButtonSearching" = "CHECKING";
"UpdateButtonUpdate" = "UPDATE";
"UpdateButtonInstalling" = "INSTALLING";
"UpdateButtonOffline" = "OFFLINE";
"UpdateInstalled" = "INSTALLED";
"UpdateVersion" = "Version";
"UpdateShowPreviousVersions" = "Show previous versions...";
"UpdateNoUpdateAvailableTitle" = "No Update Available";
"UpdateNoUpdateAvailableMessage" = "%@ is already the latest version.";
"UpdateError" = "Error";
"UpdateWarning" = "Warning";
"UpdateNoReleaseNotesAvailable" = "No release notes available.";
/* Update Authorization */
"UpdateAuthorizationProgress" = "Authorizing...";
"UpdateAuthorizationOffline" = "Internet connection required!";
"UpdateAuthorizationDenied" = "Authorizing denied. Please contact the developer.";
/* Update Simulator Warning */
"UpdateSimulatorMessage" = "Hockey Update does not work in the Simulator.\nThe itms-services:// url scheme is implemented but nonfunctional.";

View file

@ -0,0 +1,101 @@
/* General */
/* For dialogs yes buttons */
"HockeyYes" = "Si";
/* For dialogs no buttons */
"HockeyNo" = "No";
/* For dialogs ok buttons */
"HockeyOK" = "OK";
/* Crash */
/* Crash dialog */
/* Title showing in the alert box when crash report data has been found */
"CrashDataFoundTitle" = "Dati del crash trovati";
/* Description explaining that crash data has been found and ask the user if the data might be uploaded to the developers server */
"CrashDataFoundAnonymousDescription" = "L'applicazione precedentemente ha avuto un crash. Vuoi inviare allo sviluppatore i dati anonimi del crash in modo che possa provare a sistemare il problema?";
/* Description explaining that crash data has been found and ask the user if the non anonymous data might be uplaoded to the developers server */
"CrashDataFoundDescription" = "L'applicazione precedentemente ha avuto un crash. Vuoi inviare allo sviluppatore i dati del crash in modo che possa provare a sistemare il problema?";
/* Alert box button if the users wants to send crash data always automatically */
"CrashSendReportAlways" = "Sempre";
/* Alert box button to send the crash report once */
"CrashSendReport" = "Si";
/* Alert box button to decline sending the report */
"CrashDontSendReport" = "No";
/* Text showing in a processing box that the crash data is being uploaded to the server */
"CrashReportSending" = "Invio…";
/* Update */
/* Update Alert view */
/* Update available */
"UpdateAvailable" = "Aggiornamento disponibile";
"UpdateAlertTextWithAppVersion" = "%@ è disponibile.";
"UpdateAlertMandatoryTextWithAppVersion" = "%@ is available and is a mandatory update!";
"UpdateIgnore" = "Ignora";
"UpdateShow" = "Mostra";
"UpdateInstall" = "Installa";
/* Update Details */
"UpdateScreenTitle" = "Aggiorna";
"UpdateButtonCheck" = "CONTROLLA";
"UpdateButtonSearching" = "CONTROLLO";
"UpdateButtonUpdate" = "AGGIORNA";
"UpdateButtonInstalling" = "INSTALLO";
"UpdateButtonOffline" = "OFFLINE";
"UpdateInstalled" = "INSTALLATA";
"UpdateVersion" = "Versione";
"UpdateShowPreviousVersions" = "Mostra le versioni precedenti...";
"UpdateNoUpdateAvailableTitle" = "Nessun aggiornamento disponibile";
"UpdateNoUpdateAvailableMessage" = "%@ è aggiornata all'ultima versione.";
"UpdateError" = "Errore";
"UpdateWarning" = "Avviso";
"UpdateNoReleaseNotesAvailable" = "Non sono disponibili note di rilascio.";
/* Update Authorization */
"UpdateAuthorizationProgress" = "Attendo autorizzazione...";
"UpdateAuthorizationOffline" = "E' richiesta la connessione internet!";
"UpdateAuthorizationDenied" = "Autorizzazione negata, contattare lo sviluppatore.";
/* Update Simulator Warning */
"UpdateSimulatorMessage" = "Hockey Update does not work in the Simulator.\nThe itms-services:// url scheme is implemented but nonfunctional.";

View file

@ -0,0 +1,101 @@
/* General */
/* For dialogs yes buttons */
"HockeyYes" = "Yes";
/* For dialogs no buttons */
"HockeyNo" = "No";
/* For dialogs ok buttons */
"HockeyOK" = "保存";
/* Crash */
/* Crash dialog */
/* Title showing in the alert box when crash report data has been found */
"CrashDataFoundTitle" = "クラッシュ時のデータを検知";
/* Description explaining that crash data has been found and ask the user if the data might be uploaded to the developers server */
"CrashDataFoundAnonymousDescription" = "%@ は不正に終了しました。問題の修正のため、匿名でクラッシュレポートを送信しますか?";
/* Description explaining that crash data has been found and ask the user if the non anonymous data might be uplaoded to the developers server */
"CrashDataFoundDescription" = "%@ は不正に終了しました。問題の修正のため、クラッシュレポートを送信しますか?";
/* Alert box button if the users wants to send crash data always automatically */
"CrashSendReportAlways" = "いつも";
/* Alert box button to send the crash report once */
"CrashSendReport" = "はい";
/* Alert box button to decline sending the report */
"CrashDontSendReport" = "いいえ";
/* Text showing in a processing box that the crash data is being uploaded to the server */
"CrashReportSending" = "送信中…";
/* Update */
/* Update Alert view */
/* Update available */
"UpdateAvailable" = "Update available";
"UpdateAlertTextWithAppVersion" = "%@ is available.";
"UpdateAlertMandatoryTextWithAppVersion" = "%@ is available and is a mandatory update!";
"UpdateIgnore" = "Ignore";
"UpdateShow" = "Show";
"UpdateInstall" = "Install";
/* Update Details */
"UpdateScreenTitle" = "Update";
"UpdateButtonCheck" = "CHECK";
"UpdateButtonSearching" = "CHECKING";
"UpdateButtonUpdate" = "UPDATE";
"UpdateButtonInstalling" = "INSTALLING";
"UpdateButtonOffline" = "OFFLINE";
"UpdateInstalled" = "INSTALLED";
"UpdateVersion" = "Version";
"UpdateShowPreviousVersions" = "Show previous versions...";
"UpdateNoUpdateAvailableTitle" = "No Update Available";
"UpdateNoUpdateAvailableMessage" = "%@ is already the latest version.";
"UpdateError" = "Error";
"UpdateWarning" = "Warning";
"UpdateNoReleaseNotesAvailable" = "No release notes available.";
/* Update Authorization */
"UpdateAuthorizationProgress" = "Authorizing...";
"UpdateAuthorizationOffline" = "Internet connection required!";
"UpdateAuthorizationDenied" = "Authorizing denied. Please contact the developer.";
/* Update Simulator Warning */
"UpdateSimulatorMessage" = "Hockey Update does not work in the Simulator.\nThe itms-services:// url scheme is implemented but nonfunctional.";

View file

@ -0,0 +1,101 @@
/* General */
/* For dialogs yes buttons */
"HockeyYes" = "Ja";
/* For dialogs no buttons */
"HockeyNo" = "Nee";
/* For dialogs ok buttons */
"HockeyOK" = "OK";
/* Crash */
/* Crash dialog */
/* Title showing in the alert box when crash report data has been found */
"CrashDataFoundTitle" = "Crashdata gevonden";
/* Description explaining that crash data has been found and ask the user if the data might be uploaded to the developers server */
"CrashDataFoundAnonymousDescription" = "De applicatie is gecrasht. Wilt u de ontwikkelaars anonieme data sturen zodat zij kunnen proberen de problemen op te lossen?";
/* Description explaining that crash data has been found and ask the user if the non anonymous data might be uplaoded to the developers server */
"CrashDataFoundDescription" = "De applicatie is gecrasht. Wilt u de ontwikkelaars data sturen zodat zij kunnen proberen de problemen op te lossen?";
/* Alert box button if the users wants to send crash data always automatically */
"CrashSendReportAlways" = "Altijd";
/* Alert box button to send the crash report once */
"CrashSendReport" = "Ja";
/* Alert box button to decline sending the report */
"CrashDontSendReport" = "Nee";
/* Text showing in a processing box that the crash data is being uploaded to the server */
"CrashReportSending" = "Zenden…";
/* Update */
/* Update Alert view */
/* Update available */
"UpdateAvailable" = "Update available";
"UpdateAlertTextWithAppVersion" = "%@ is available.";
"UpdateAlertMandatoryTextWithAppVersion" = "%@ is available and is a mandatory update!";
"UpdateIgnore" = "Ignore";
"UpdateShow" = "Show";
"UpdateInstall" = "Install";
/* Update Details */
"UpdateScreenTitle" = "Update";
"UpdateButtonCheck" = "CHECK";
"UpdateButtonSearching" = "CHECKING";
"UpdateButtonUpdate" = "UPDATE";
"UpdateButtonInstalling" = "INSTALLING";
"UpdateButtonOffline" = "OFFLINE";
"UpdateInstalled" = "INSTALLED";
"UpdateVersion" = "Version";
"UpdateShowPreviousVersions" = "Show previous versions...";
"UpdateNoUpdateAvailableTitle" = "No Update Available";
"UpdateNoUpdateAvailableMessage" = "%@ is already the latest version.";
"UpdateError" = "Error";
"UpdateWarning" = "Warning";
"UpdateNoReleaseNotesAvailable" = "No release notes available.";
/* Update Authorization */
"UpdateAuthorizationProgress" = "Authorizing...";
"UpdateAuthorizationOffline" = "Internet connection required!";
"UpdateAuthorizationDenied" = "Authorizing denied. Please contact the developer.";
/* Update Simulator Warning */
"UpdateSimulatorMessage" = "Hockey Update does not work in the Simulator.\nThe itms-services:// url scheme is implemented but nonfunctional.";

Some files were not shown because too many files have changed in this diff Show more