Initial revision with new CNSHockeyManager.

This commit is contained in:
Thomas Dohmke 2011-11-03 20:32:15 +01:00
parent 90db3e083f
commit dddc200f9b
88 changed files with 7060 additions and 0 deletions

BIN
Classes/.DS_Store vendored Normal file

Binary file not shown.

56
Classes/BWApp.h Normal file
View File

@ -0,0 +1,56 @@
//
// 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

186
Classes/BWApp.m Normal file
View File

@ -0,0 +1,186 @@
//
// 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_ class] 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

116
Classes/BWGlobal.h Normal file
View File

@ -0,0 +1,116 @@
//
// 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 HOCKEYKIT_VERSION_MAJOR 2
#define HOCKEYKIT_VERSION_MINOR 0
// 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);
#define BWHockeyLocalize(StringToken) NSLocalizedStringFromTableInBundle(StringToken, @"Hockey", hockeyBundle(), @"")
// compatibility helper
#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__ \
}

53
Classes/BWGlobal.m Normal file
View File

@ -0,0 +1,53 @@
//
// 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]
];
}

227
Classes/BWHockeyManager.h Normal file
View File

@ -0,0 +1,227 @@
//
// 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"
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_;
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_;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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;
///////////////////////////////////////////////////////////////////////////////////////////////////
// settings
// if YES, the API will return an existing JMC config
// if NO, the API will return only version information
@property (nonatomic, assign) BOOL checkForTracker;
@property (nonatomic, retain, readonly) NSDictionary *trackerConfig;
// 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;
// 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=ishowingDirectInstallOption) 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;
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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;
@end
///////////////////////////////////////////////////////////////////////////////////////////////////
@protocol BWHockeyManagerDelegate <NSObject>
@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

1152
Classes/BWHockeyManager.m Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
//
// 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

@ -0,0 +1,294 @@
//
// 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 (indexPath.section == (NSUInteger)[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 (indexPath.section == (NSUInteger)[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

@ -0,0 +1,65 @@
//
// 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"
#import "PSAppStoreHeader.h"
typedef enum {
AppStoreButtonStateOffline,
AppStoreButtonStateCheck,
AppStoreButtonStateSearching,
AppStoreButtonStateUpdate,
AppStoreButtonStateInstalling
} AppStoreButtonState;
@class BWHockeyManager;
@interface BWHockeyViewController : UITableViewController <PSStoreButtonDelegate> {
BWHockeyManager *hockeyManager_;
NSDictionary *cellLayout;
BOOL modal_;
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;
- (id)init:(BWHockeyManager *)newHockeyManager modal:(BOOL)newModal;
- (id)init;
@end

View File

@ -0,0 +1,626 @@
//
// 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.
#import <QuartzCore/QuartzCore.h>
#import "NSString+HockeyAdditions.h"
#import "BWHockeyViewController.h"
#import "BWHockeyManager.h"
#import "BWGlobal.h"
#import "UIImage+HockeyAdditions.h"
#import "PSWebTableViewCell.h"
#import "BWHockeySettingsViewController.h"
#define BW_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 BWHockeyViewController
@synthesize appStoreButtonState = appStoreButtonState_;
@synthesize hockeyManager = hockeyManager_;
@synthesize modal = modal_;
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark private
- (void)restoreStoreButtonStateAnimated_:(BOOL)animated {
if ([self.hockeyManager 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];
NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setDateStyle:NSDateFormatterMediumStyle];
NSMutableString *subHeaderString = [NSMutableString string];
if (app.date) {
[subHeaderString appendString:[formatter stringFromDate:app.date]];
}
if (app.size) {
if ([subHeaderString length]) {
[subHeaderString appendString:@" - "];
}
[subHeaderString appendString:app.sizeInMB];
}
appStoreHeader_.subHeaderLabel = subHeaderString;
}
- (void)appDidBecomeActive_ {
if (self.appStoreButtonState == AppStoreButtonStateInstalling) {
[self setAppStoreButtonState:AppStoreButtonStateUpdate animated:YES];
} else if (![self.hockeyManager 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);)
[image drawAtPoint:CGPointZero];
UIImage *iconGradient = [UIImage bw_imageNamed:@"IconGradient.png" bundle:kHockeyBundleName];
[iconGradient drawInRect:CGRectMake(0, 0, image.size.width, image.size.height) blendMode:kCGBlendModeNormal alpha:0.5];
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
#define kMinPreviousVersionButtonHeight 50
- (void)realignPreviousVersionButton {
// manually collect actual table height size
NSUInteger tableViewContentHeight = 0;
for (int i=0; i < [self tableView:self.tableView numberOfRowsInSection:0]; i++) {
tableViewContentHeight += [self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
}
tableViewContentHeight += self.tableView.tableHeaderView.frame.size.height;
NSUInteger footerViewSize = kMinPreviousVersionButtonHeight;
NSUInteger frameHeight = self.view.frame.size.height;
if(tableViewContentHeight < frameHeight && (frameHeight - tableViewContentHeight > 100)) {
footerViewSize = frameHeight - tableViewContentHeight;
}
// update footer view
if(self.tableView.tableFooterView) {
CGRect frame = self.tableView.tableFooterView.frame;
frame.size.height = footerViewSize;
self.tableView.tableFooterView.frame = frame;
}
}
- (void)changePreviousVersionButtonBackground:(id)sender {
[(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)];
}
- (void)changePreviousVersionButtonBackgroundHighlighted:(id)sender {
[(UIButton *)sender setBackgroundColor:BW_RGBCOLOR(183,183,183)];
}
- (void)showHidePreviousVersionsButton {
BOOL multipleVersionButtonNeeded = [self.hockeyManager.apps 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);
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.titleLabel.font = [UIFont boldSystemFontOfSize:14];
[footerButton setTitle:BWHockeyLocalize(@"HockeyShowPreviousVersions") 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.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);
[footerView addSubview:footerButton];
self.tableView.tableFooterView = footerView;
[self realignPreviousVersionButton];
[footerView release];
} else {
self.tableView.tableFooterView = nil;
self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204);
}
}
- (void)configureWebCell:(PSWebTableViewCell *)cell forApp_:(BWApp *)app {
// 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 ([app isEqual:self.hockeyManager.app]) {
if ([app.notes length] > 0) {
installed = [NSString stringWithFormat:@"<p>&nbsp;%@</p>", installed];
cell.webViewContent = [NSString stringWithFormat:@"%@%@", installed, app.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")];
}
} 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.cellBackgroundColor = BW_RGBCOLOR(200, 202, 204);
[cell addWebView];
// hack
cell.textLabel.text = @"";
[cell addObserver:self forKeyPath:@"webViewSize" options:0 context:nil];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject
- (id)init:(BWHockeyManager *)newHockeyManager modal:(BOOL)newModal {
if ((self = [super initWithStyle:UITableViewStylePlain])) {
self.hockeyManager = newHockeyManager;
self.modal = newModal;
self.title = BWHockeyLocalize(@"HockeyUpdateScreenTitle");
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;
}
return self;
}
- (id)init {
return [self init:[BWHockeyManager sharedHockeyManager] modal:NO];
}
- (void)dealloc {
[self viewDidUnload];
for (UITableViewCell *cell in cells_) {
[cell removeObserver:self forKeyPath:@"webViewSize"];
}
[cells_ release];
[super dealloc];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark View lifecycle
- (void)onAction:(id)sender {
if (self.modal) {
// Note that as of 5.0, parentViewController will no longer return the presenting view controller
UIViewController *presentingViewController = nil;
BW_IF_IOS5_OR_GREATER(presentingViewController = self.navigationController.presentingViewController;);
BW_IF_PRE_IOS5(presentingViewController = self.navigationController.parentViewController;)
if (presentingViewController) {
[self.navigationController dismissModalViewControllerAnimated:YES];
} else {
[self.navigationController.view removeFromSuperview];
}
}
else
[self.navigationController popViewControllerAnimated:YES];
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_];
}
- (CAGradientLayer *)backgroundLayer {
UIColor *colorOne = [UIColor colorWithWhite:0.9 alpha:1.0];
UIColor *colorTwo = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.85 alpha:1.0];
UIColor *colorThree = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.7 alpha:1.0];
UIColor *colorFour = [UIColor colorWithHue:0.625 saturation:0.0 brightness:0.4 alpha:1.0];
NSArray *colors = [NSArray arrayWithObjects:(id)colorOne.CGColor, colorTwo.CGColor, colorThree.CGColor, colorFour.CGColor, nil];
NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
NSNumber *stopTwo = [NSNumber numberWithFloat:0.02];
NSNumber *stopThree = [NSNumber numberWithFloat:0.99];
NSNumber *stopFour = [NSNumber numberWithFloat:1.0];
NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, stopThree, stopFour, nil];
CAGradientLayer *headerLayer = [CAGradientLayer layer];
//headerLayer.frame = CGRectMake(0.0, 0.0, 320.0, 77.0);
headerLayer.colors = colors;
headerLayer.locations = locations;
return headerLayer;
}
- (void)viewDidLoad {
[super viewDidLoad];
// add notifications only to loaded view
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
[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;
self.tableView.backgroundColor = BW_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);
[self.tableView addSubview:topView];
appStoreHeader_ = [[PSAppStoreHeader alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kAppStoreViewHeight)];
[self updateAppStoreHeader_];
NSString *iconString = nil;
NSArray *icons = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFiles"];
if (!icons) {
iconString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"];
if (!iconString) {
iconString = @"Icon.png";
}
} else {
BOOL useHighResIcon = NO;
BW_IF_IOS4_OR_GREATER(if ([UIScreen mainScreen].scale == 2.0f) useHighResIcon = YES;)
for(NSString *icon in icons) {
iconString = icon;
UIImage *iconImage = [UIImage imageNamed:icon];
if (iconImage.size.height == 57 && !useHighResIcon) {
// found!
break;
}
if (iconImage.size.height == 114 && useHighResIcon) {
// found!
break;
}
}
}
BOOL addGloss = YES;
NSNumber *prerendered = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIPrerenderedIcon"];
if (prerendered) {
addGloss = ![prerendered boolValue];
}
if (addGloss) {
appStoreHeader_.iconImage = [self addGlossToImage_:[UIImage imageNamed:iconString]];
} else {
appStoreHeader_.iconImage = [UIImage imageNamed:iconString];
}
self.tableView.tableHeaderView = appStoreHeader_;
if (self.modal) {
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(onAction:)] autorelease];
}
PSStoreButton *storeButton = [[[PSStoreButton alloc] initWithPadding:CGPointMake(5, 40)] autorelease];
storeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
storeButton.buttonDelegate = self;
[self.tableView.tableHeaderView addSubview:storeButton];
storeButton.buttonData = [PSStoreButtonData dataWithLabel:@"" colors:[PSStoreButton appStoreGrayColor] enabled:NO];
self.appStoreButtonState = AppStoreButtonStateCheck;
[storeButton alignToSuperview];
appStoreButton_ = [storeButton retain];
}
- (void)viewWillAppear:(BOOL)animated {
self.hockeyManager.currentHockeyViewController = self;
[super viewWillAppear:animated];
statusBarStyle_ = [[UIApplication sharedApplication] statusBarStyle];
[[UIApplication sharedApplication] setStatusBarStyle:(self.navigationController.navigationBar.barStyle == UIBarStyleDefault) ? UIStatusBarStyleDefault : UIStatusBarStyleBlackOpaque];
[self redrawTableView];
}
- (void)viewWillDisappear:(BOOL)animated {
self.hockeyManager.currentHockeyViewController = nil;
//if the popover is still visible, dismiss it
[popOverController_ dismissPopoverAnimated:YES];
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle_];
}
- (void)redrawTableView {
[self restoreStoreButtonStateAnimated_:NO];
[self updateAppStoreHeader_];
// clean up and remove any pending overservers
for (UITableViewCell *cell in cells_) {
[cell removeObserver:self forKeyPath:@"webViewSize"];
}
[cells_ removeAllObjects];
int i = 0;
BOOL breakAfterThisApp = NO;
for (BWApp *app in self.hockeyManager.apps) {
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 (i == 1) {
breakAfterThisApp = YES;
} else {
break;
}
}
}
PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease];
[self configureWebCell:cell forApp_:app];
[cells_ addObject:cell];
if (breakAfterThisApp) break;
}
[self.tableView reloadData];
[self showHidePreviousVersionsButton];
}
- (void)showPreviousVersionAction {
showAllVersions_ = YES;
BOOL showAllPending = NO;
for (BWApp *app in self.hockeyManager.apps) {
if (!showAllPending) {
if ([app.version isEqualToString:[self.hockeyManager currentAppVersion]]) {
showAllPending = YES;
if (app == self.hockeyManager.app) {
continue; // skip this version already if it the latest version is the installed one
}
} else {
continue; // skip already shown
}
}
PSWebTableViewCell *cell = [[[PSWebTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kWebCellIdentifier] autorelease];
[self configureWebCell:cell forApp_:app];
[cells_ addObject:cell];
}
[self.tableView reloadData];
[self showHidePreviousVersionsButton];
}
- (void)viewDidUnload {
[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;
}
[super viewDidUnload];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat rowHeight = 0;
if ([cells_ count] > 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 (rowHeight == 0) {
rowHeight = indexPath.row == 0 ? 250 : 44; // fill screen on startup
self.tableView.backgroundColor = BW_RGBCOLOR(200, 202, 204);
}
return rowHeight;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger cellCount = [cells_ count];
return cellCount;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// only make changes if we are visible
if(self.view.window) {
if ([keyPath isEqualToString:@"webViewSize"]) {
[self.tableView reloadData];
[self realignPreviousVersionButton];
} else if ([keyPath isEqualToString:@"checkInProgress"]) {
if (self.hockeyManager.isCheckInProgress) {
[self setAppStoreButtonState:AppStoreButtonStateSearching animated:YES];
}else {
[self restoreStoreButtonStateAnimated_:YES];
}
} else if ([keyPath isEqualToString:@"isUpdateURLOffline"]) {
[self restoreStoreButtonStateAnimated_:YES];
} else if ([keyPath isEqualToString:@"updateAvailable"]) {
[self restoreStoreButtonStateAnimated_:YES];
} else if ([keyPath isEqualToString:@"apps"]) {
[self redrawTableView];
}
}
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([cells_ count] > indexPath.row) {
return [cells_ objectAtIndex:indexPath.row];
} else {
BWHockeyLog(@"Warning: cells_ and indexPath do not match? forgot calling redrawTableView?");
}
return nil;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Rotation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
BOOL shouldAutorotate;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
shouldAutorotate = (interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
interfaceOrientation == UIInterfaceOrientationLandscapeRight ||
interfaceOrientation == UIInterfaceOrientationPortrait);
} else {
shouldAutorotate = YES;
}
return shouldAutorotate;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
// update all cells
[cells_ makeObjectsPerformSelector:@selector(addWebView)];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark PSAppStoreHeaderDelegate
- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState {
[self setAppStoreButtonState:anAppStoreButtonState animated:NO];
}
- (void)setAppStoreButtonState:(AppStoreButtonState)anAppStoreButtonState animated:(BOOL)animated {
appStoreButtonState_ = anAppStoreButtonState;
switch (anAppStoreButtonState) {
case AppStoreButtonStateOffline:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonOffline") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
break;
case AppStoreButtonStateCheck:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonCheck") colors:[PSStoreButton appStoreGreenColor] enabled:YES] animated:animated];
break;
case AppStoreButtonStateSearching:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonSearching") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
break;
case AppStoreButtonStateUpdate:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonUpdate") colors:[PSStoreButton appStoreBlueColor] enabled:YES] animated:animated];
break;
case AppStoreButtonStateInstalling:
[appStoreButton_ setButtonData:[PSStoreButtonData dataWithLabel:BWHockeyLocalize(@"HockeyButtonInstalling") colors:[PSStoreButton appStoreGrayColor] enabled:NO] animated:animated];
break;
default:
break;
}
}
- (void)storeButtonFired:(PSStoreButton *)button {
switch (appStoreButtonState_) {
case AppStoreButtonStateCheck:
[self.hockeyManager checkForUpdateShowFeedback:YES];
break;
case AppStoreButtonStateUpdate:
if ([self.hockeyManager initiateAppDownload]) {
[self setAppStoreButtonState:AppStoreButtonStateInstalling animated:YES];
};
break;
default:
break;
}
}
@end

221
Classes/BWQuincyManager.h Executable file
View File

@ -0,0 +1,221 @@
/*
* 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>
#import <MessageUI/MessageUI.h>
#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,
CrashReportStatusDiscontinued = 4,
CrashReportStatusMoreInfo = 5,
} 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;
-(void) askForCrashInfo:(NSString*)messageBody;
// 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;
@end
@interface BWQuincyManager : NSObject <NSXMLParserDelegate> {
NSString *_submissionURL;
id <BWQuincyManagerDelegate> _delegate;
BOOL _showAlwaysButton;
BOOL _feedbackActivated;
BOOL _autoSubmitCrashReport;
BOOL _autoSubmitDeviceUDID;
NSString *_appIdentifier;
NSString *_feedbackRequestID;
float _feedbackDelayInterval;
NSMutableString *_contentOfProperty;
CrashReportStatus _serverResult;
int _analyzerStarted;
NSString *_crashesDir;
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
// 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;
// if YES, the device UDID will be submitted as the user id, without the need to define it in the crashReportUserID delegate (meant for beta versions!)
// if NO, the crashReportUserID delegate defines what to be sent as user id (default)
@property (nonatomic, assign, getter=isAutoSubmitDeviceUDID) BOOL autoSubmitDeviceUDID;
// If you want to use HockeyApp instead of your own server, this is required
@property (nonatomic, retain) NSString *appIdentifier;
@end

772
Classes/BWQuincyManager.m Executable file
View File

@ -0,0 +1,772 @@
/*
* 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 "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;
@end
@implementation BWQuincyManager
@synthesize delegate = _delegate;
@synthesize submissionURL = _submissionURL;
@synthesize showAlwaysButton = _showAlwaysButton;
@synthesize feedbackActivated = _feedbackActivated;
@synthesize autoSubmitCrashReport = _autoSubmitCrashReport;
@synthesize autoSubmitDeviceUDID = _autoSubmitDeviceUDID;
@synthesize languageStyle = _languageStyle;
@synthesize appIdentifier = _appIdentifier;
#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;
self.delegate = nil;
self.feedbackActivated = NO;
self.showAlwaysButton = NO;
self.autoSubmitCrashReport = NO;
self.autoSubmitDeviceUDID = 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 ([[NSUserDefaults standardUserDefaults] stringForKey:kAutomaticallySendCrashReports]) {
self.autoSubmitCrashReport = [[NSUserDefaults standardUserDefaults] boolForKey: kAutomaticallySendCrashReports];
}
if (_crashReportActivated) {
_crashFiles = [[NSMutableArray alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
_crashesDir = [[NSString stringWithFormat:@"%@", [[paths objectAtIndex:0] stringByAppendingPathComponent:@"/crashes/"]] retain];
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:_crashesDir]) {
NSDictionary *attributes = [NSDictionary dictionaryWithObject: [NSNumber numberWithUnsignedLong: 0755] forKey: NSFilePosixPermissions];
NSError *theError = NULL;
[fm createDirectoryAtPath:_crashesDir withIntermediateDirectories: YES attributes: attributes error: &theError];
}
PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
NSError *error = NULL;
// Check if we previously crashed
if ([crashReporter hasPendingCrashReport]) {
[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];
}
}
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];
[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
// begin the startup process
- (void)startManager {
if (!_sendingInProgress && [self hasPendingCrashReport]) {
_sendingInProgress = YES;
if (!self.autoSubmitCrashReport && [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) {
NSFileManager *fm = [NSFileManager defaultManager];
if ([_crashFiles count] == 0 && [fm fileExistsAtPath:_crashesDir]) {
NSString *file = nil;
NSError *error = NULL;
NSDirectoryEnumerator *dirEnum = [fm enumeratorAtPath: _crashesDir];
while ((file = [dirEnum nextObject])) {
NSDictionary *fileAttributes = [fm attributesOfItemAtPath:[_crashesDir stringByAppendingPathComponent:file] error:&error];
if ([[fileAttributes objectForKey:NSFileSize] intValue] > 0) {
[_crashFiles addObject:file];
}
}
}
if ([_crashFiles count] > 0) {
return YES;
} else
return NO;
} else
return NO;
}
- (void) showCrashStatusMessage {
UIAlertView *alertView = nil;
if (_serverResult >= CrashReportStatusAssigned &&
_crashIdenticalCurrentVersion) {
// 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;
case CrashReportStatusMoreInfo:
if ([MFMailComposeViewController canSendMail]) {
alertView = [[UIAlertView alloc] initWithTitle: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseTitle"), appName ]
message: [NSString stringWithFormat:BWQuincyLocalize(@"CrashResponseMoreInfo"), appName]
delegate: self
cancelButtonTitle: BWQuincyLocalize(@"Skip")
otherButtonTitles: BWQuincyLocalize(@"Send Email"), 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 {
switch ([alertView tag]) {
case QuincyKitAlertTypeSend:
switch (buttonIndex) {
case 0:
_sendingInProgress = NO;
[self _cleanCrashReports];
break;
case 1:
[self _sendCrashReports];
break;
case 2:
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:kAutomaticallySendCrashReports];
[self _sendCrashReports];
break;
}
break;
case QuincyKitAlertTypeFeedback:
switch (buttonIndex) {
case 0:
break;
case 1:
[self.delegate askForCrashInfo:@"Please describe what you were doing when the crash occured:\n\n"];
}
break;
default:
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];
}
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 *)_getOSVersionBuild {
size_t size = 0;
NSString *osBuildVersion = nil;
sysctlbyname("kern.osversion", NULL, &size, NULL, 0);
char *answer = (char*)malloc(size);
int result = sysctlbyname("kern.osversion", answer, &size, NULL, 0);
if (result >= 0) {
osBuildVersion = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding];
}
return osBuildVersion;
}
- (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;
}
- (NSString *)deviceIdentifier {
if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)]) {
return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)];
}
else {
return @"invalid";
}
}
- (void)_performSendingCrashReports {
NSMutableDictionary *approvedCrashReports = [NSMutableDictionary dictionaryWithDictionary:[[NSUserDefaults standardUserDefaults] dictionaryForKey: kApprovedCrashReports]];
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = NULL;
NSString *userid = @"";
NSString *contact = @"";
NSString *description = @"";
if (self.autoSubmitDeviceUDID) {
userid = [self deviceIdentifier];
} else 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,
userid,
contact,
description];
// 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
[fm removeItemAtPath:filename error:&error];
}
}
[[NSUserDefaults standardUserDefaults] setObject:approvedCrashReports forKey:kApprovedCrashReports];
[[NSUserDefaults standardUserDefaults] synchronize];
if (crashes != nil) {
[self _postXML:[NSString stringWithFormat:@"<crashes>%@</crashes>", crashes]
toURL:[NSURL URLWithString:self.submissionURL]];
}
}
- (void)_cleanCrashReports {
NSError *error = NULL;
NSFileManager *fm = [NSFileManager defaultManager];
for (NSUInteger i=0; i < [_crashFiles count]; i++) {
[fm 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];
}
- (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",
self.submissionURL,
[self.appIdentifier stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
]
]];
} 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) {
_sendingInProgress = NO;
}
}
#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 {
[_responseData release];
_responseData = nil;
_urlConnection = nil;
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionClosed)]) {
[self.delegate connectionClosed];
}
_sendingInProgress = NO;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (_statusCode >= 200 && _statusCode < 400) {
[self _cleanCrashReports];
if (self.appIdentifier) {
// HockeyApp uses PList XML format
NSMutableDictionary *response = [NSPropertyListSerialization propertyListFromData:_responseData
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:nil
errorDescription:NULL];
_serverResult = (CrashReportStatus)[[response objectForKey:@"status"] intValue];
_feedbackRequestID = [[NSString alloc] initWithString:[response objectForKey:@"id"]];
_feedbackDelayInterval = [[response objectForKey:@"delay"] floatValue];
if (_feedbackDelayInterval > 0)
_feedbackDelayInterval *= 0.01;
} else {
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]) {
if (self.appIdentifier) {
// only proceed if the server did not report any problem
if (_serverResult == CrashReportStatusQueued) {
// the report is still in the queue
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_checkForFeedbackStatus) object:nil];
[self performSelector:@selector(_checkForFeedbackStatus) withObject:nil afterDelay:_feedbackDelayInterval];
} else {
// we do have a status, show it if needed
[self showCrashStatusMessage];
}
} else {
[self showCrashStatusMessage];
}
}
}
[_responseData release];
_responseData = nil;
_urlConnection = nil;
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(connectionClosed)]) {
[self.delegate connectionClosed];
}
_sendingInProgress = NO;
}
#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

@ -0,0 +1,29 @@
// 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.
@interface CNSHockeyManager : NSObject
+ (CNSHockeyManager *)sharedHockeyManager;
- (void)configureWithIdentifier:(NSString *)appIdentifier delegate:(id)delegate;
@end

111
Classes/CNSHockeyManager.m Normal file
View File

@ -0,0 +1,111 @@
// 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"
#ifdef JMC_PRESENT
#import "JMC.h"
#endif
@interface CNSHockeyManager ()
#ifdef JMC_PRESENT
- (void)configureJMC;
#endif
@end
@implementation CNSHockeyManager
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000
+ (CNSHockeyManager *)sharedHockeyManager {
static CNSHockeyManager *sharedInstance = nil;
static dispatch_once_t pred;
if (sharedInstance) {
return sharedInstance;
}
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
- (void)configureWithIdentifier:(NSString *)appIdentifier delegate:(id)delegate {
// Crash Reporting
[[BWQuincyManager sharedQuincyManager] setAppIdentifier:appIdentifier];
// Distribution
[[BWHockeyManager sharedHockeyManager] setAppIdentifier:appIdentifier];
[[BWHockeyManager sharedHockeyManager] setUpdateURL:@"http://192.168.178.53:3000"];
[[BWHockeyManager sharedHockeyManager] setCheckForTracker:YES];
#ifdef JMC_PRESENT
// JMC
[[[JMC instance] options] setCrashReportingEnabled:NO];
[[BWHockeyManager sharedHockeyManager] addObserver:self forKeyPath:@"trackerConfig" options:0 context:nil];
[self performSelector:@selector(configureJMC) withObject:nil afterDelay:0];
#endif
}
#ifdef JMC_PRESENT
- (void)configureJMC {
// Return if JMC is already configured
if ([[JMC instance] url]) {
return;
}
// Configure JMC from user defaults
NSDictionary *config = [[NSUserDefaults standardUserDefaults] valueForKey:@"CNSTrackerConfig"];
if (([[config valueForKey:@"enabled"] boolValue]) &&
([[config valueForKey:@"url"] length] > 0) &&
([[config valueForKey:@"key"] length] > 0) &&
([[config valueForKey:@"project"] length] > 0)) {
[[JMC instance] configureJiraConnect:[config valueForKey:@"url"] projectKey:[config valueForKey:@"project"] apiKey:[config valueForKey:@"key"]];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([object trackerConfig]) {
[[NSUserDefaults standardUserDefaults] setValue:[object trackerConfig] forKey:@"CNSTrackerConfig"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self configureJMC];
}
}
#endif
@end

View File

@ -0,0 +1,33 @@
//
// NSString+HockeyAdditions.h
//
// Created by Jon Crosby on 10/19/07.
// Copyright 2007 Kaboomerang LLC. 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 NSString (HockeyAdditions)
- (NSString *)bw_URLEncodedString;
- (NSString *)bw_URLDecodedString;
@end

View File

@ -0,0 +1,50 @@
//
// NSString+HockeyAdditions.m
//
// Created by Jon Crosby on 10/19/07.
// Copyright 2007 Kaboomerang LLC. 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 "NSString+HockeyAdditions.h"
@implementation NSString (HockeyAdditions)
- (NSString *)bw_URLEncodedString {
NSString *result = (NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)self,
NULL,
CFSTR("!*'();:@&=+$,/?%#[]"),
kCFStringEncodingUTF8);
[result autorelease];
return result;
}
- (NSString*)bw_URLDecodedString {
NSString *result = (NSString *)CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,
(CFStringRef)self,
CFSTR(""),
kCFStringEncodingUTF8);
[result autorelease];
return result;
}
@end

View File

@ -0,0 +1,42 @@
//
// PSAppStoreHeader.h
// HockeyDemo
//
// Created by Peter Steinberger on 09.01.11.
// Copyright 2011 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>
@interface PSAppStoreHeader : UIView {
NSString *headerLabel_;
NSString *middleHeaderLabel_;
NSString *subHeaderLabel;
UIImage *iconImage_;
UIImage *reflectedImage_;
}
@property (nonatomic, copy) NSString *headerLabel;
@property (nonatomic, copy) NSString *middleHeaderLabel;
@property (nonatomic, copy) NSString *subHeaderLabel;
@property (nonatomic, retain) UIImage *iconImage;
@end

173
Classes/PSAppStoreHeader.m Normal file
View File

@ -0,0 +1,173 @@
//
// PSAppStoreHeader.m
// HockeyDemo
//
// Created by Peter Steinberger on 09.01.11.
// Copyright 2011 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 "PSAppStoreHeader.h"
#import "UIImage+HockeyAdditions.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]
#define kLightGrayColor BW_RGBCOLOR(200, 202, 204)
#define kDarkGrayColor BW_RGBCOLOR(140, 141, 142)
#define kImageHeight 57
#define kReflectionHeight 20
#define kImageBorderRadius 10
#define kImageMargin 8
#define kTextRow kImageMargin*2 + kImageHeight
@implementation PSAppStoreHeader
@synthesize headerLabel = headerLabel_;
@synthesize middleHeaderLabel = middleHeaderLabel_;
@synthesize subHeaderLabel;
@synthesize iconImage = iconImage_;
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.backgroundColor = kLightGrayColor;
}
return self;
}
- (void)dealloc {
[headerLabel_ release];
[middleHeaderLabel_ release];
[subHeaderLabel release];
[iconImage_ release];
[super dealloc];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIView
- (void)drawRect:(CGRect)rect {
CGRect bounds = self.bounds;
CGFloat globalWidth = self.frame.size.width;
CGContextRef context = UIGraphicsGetCurrentContext();
// draw the gradient
NSArray *colors = [NSArray arrayWithObjects:(id)kDarkGrayColor.CGColor, (id)kLightGrayColor.CGColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(CGColorGetColorSpace((CGColorRef)[colors objectAtIndex:0]), (CFArrayRef)colors, (CGFloat[2]){0, 1});
CGPoint top = CGPointMake(CGRectGetMidX(bounds), bounds.origin.y);
CGPoint bottom = CGPointMake(CGRectGetMidX(bounds), CGRectGetMaxY(bounds)-kReflectionHeight);
CGContextDrawLinearGradient(context, gradient, top, bottom, 0);
CGGradientRelease(gradient);
// draw header name
UIColor *mainTextColor = BW_RGBCOLOR(0,0,0);
UIColor *secondaryTextColor = BW_RGBCOLOR(48,48,48);
UIFont *mainFont = [UIFont boldSystemFontOfSize:20];
UIFont *secondaryFont = [UIFont boldSystemFontOfSize:12];
UIFont *smallFont = [UIFont systemFontOfSize:12];
float myColorValues[] = {255, 255, 255, .6};
CGColorSpaceRef myColorSpace = CGColorSpaceCreateDeviceRGB();
CGColorRef myColor = CGColorCreate(myColorSpace, myColorValues);
// icon
[iconImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin)];
[reflectedImage_ drawAtPoint:CGPointMake(kImageMargin, kImageMargin+kImageHeight)];
// 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);)
[mainTextColor set];
[headerLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin, globalWidth-kTextRow, 20) withFont:mainFont lineBreakMode:UILineBreakModeTailTruncation];
// middle
[secondaryTextColor set];
[middleHeaderLabel_ drawInRect:CGRectMake(kTextRow, kImageMargin + 25, globalWidth-kTextRow, 20) withFont:secondaryFont lineBreakMode:UILineBreakModeTailTruncation];
CGContextSetShadowWithColor(context, CGSizeZero, 0, nil);
// sub
[secondaryTextColor set];
[subHeaderLabel drawAtPoint:CGPointMake(kTextRow, kImageMargin+kImageHeight-12) forWidth:globalWidth-kTextRow withFont:smallFont lineBreakMode:UILineBreakModeTailTruncation];
CGColorRelease(myColor);
CGColorSpaceRelease(myColorSpace);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Properties
- (void)setHeaderLabel:(NSString *)anHeaderLabel {
if (headerLabel_ != anHeaderLabel) {
[headerLabel_ release];
headerLabel_ = [anHeaderLabel copy];
[self setNeedsDisplay];
}
}
- (void)setMiddleHeaderLabel:(NSString *)aMiddleHeaderLabel {
if (middleHeaderLabel_ != aMiddleHeaderLabel) {
[middleHeaderLabel_ release];
middleHeaderLabel_ = [aMiddleHeaderLabel copy];
[self setNeedsDisplay];
}
}
- (void)setSubHeaderLabel:(NSString *)aSubHeaderLabel {
if (subHeaderLabel != aSubHeaderLabel) {
[subHeaderLabel release];
subHeaderLabel = [aSubHeaderLabel copy];
[self setNeedsDisplay];
}
}
- (void)setIconImage:(UIImage *)anIconImage {
if (iconImage_ != anIconImage) {
[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];
// create reflected image
[reflectedImage_ release];
reflectedImage_ = nil;
if (anIconImage) {
reflectedImage_ = [[iconImage_ bw_reflectedImageWithHeight:kReflectionHeight fromAlpha:0.5 toAlpha:0.0] retain];
}
[self setNeedsDisplay];
}
}
@end

81
Classes/PSStoreButton.h Normal file
View File

@ -0,0 +1,81 @@
//
// PSStoreButton.h
// HockeyDemo
//
// Created by Peter Steinberger on 09.01.11.
// Copyright 2011 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 <UIKit/UIKit.h>
// defines a button action set (data container)
@interface PSStoreButtonData : NSObject {
CGPoint customPadding_;
NSString *label_;
NSArray *colors_;
BOOL enabled_;
}
+ (id)dataWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag;
@property (nonatomic, copy) NSString *label;
@property (nonatomic, retain) NSArray *colors;
@property (nonatomic, assign, getter=isEnabled) BOOL enabled;
@end
@class PSStoreButton;
@protocol PSStoreButtonDelegate
- (void)storeButtonFired:(PSStoreButton *)button;
@end
// Simulate the Paymeny-Button from the AppStore
// The interface is flexible, so there is now fixed order
@interface PSStoreButton : UIButton {
PSStoreButtonData *buttonData_;
id<PSStoreButtonDelegate> buttonDelegate_;
CAGradientLayer *gradient_;
CGPoint customPadding_;
}
- (id)initWithFrame:(CGRect)frame;
- (id)initWithPadding:(CGPoint)padding;
// action delegate
@property (nonatomic, assign) id<PSStoreButtonDelegate> buttonDelegate;
// change the button layer
@property (nonatomic, retain) PSStoreButtonData *buttonData;
- (void)setButtonData:(PSStoreButtonData *)aButtonData animated:(BOOL)animated;
// align helper
@property (nonatomic, assign) CGPoint customPadding;
- (void)alignToSuperview;
// helpers to mimic an AppStore button
+ (NSArray *)appStoreGreenColor;
+ (NSArray *)appStoreBlueColor;
+ (NSArray *)appStoreGrayColor;
@end

295
Classes/PSStoreButton.m Normal file
View File

@ -0,0 +1,295 @@
//
// PSStoreButton.m
// HockeyDemo
//
// Created by Peter Steinberger on 09.01.11.
// Copyright 2011 Peter Steinberger. All rights reserved.
//
// This code was inspired by https://github.com/dhmspector/ZIStoreButton
//
// 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 "PSStoreButton.h"
#ifdef DEBUG
#define PSLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
#define PSLog(...)
#endif
#define PS_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1]
#define PS_MIN_HEIGHT 25.0f
#define PS_MAX_WIDTH 120.0f
#define PS_PADDING 12.0f
#define kDefaultButtonAnimationTime 0.25f
@implementation PSStoreButtonData
@synthesize label = label_;
@synthesize colors = colors_;
@synthesize enabled = enabled_;
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject
- (id)initWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag {
if ((self = [super init])) {
self.label = aLabel;
self.colors = aColors;
self.enabled = flag;
}
return self;
}
+ (id)dataWithLabel:(NSString*)aLabel colors:(NSArray*)aColors enabled:(BOOL)flag {
return [[[[self class] alloc] initWithLabel:aLabel colors:aColors enabled:flag] autorelease];
}
- (void)dealloc {
[label_ release];
[colors_ release];
[super dealloc];
}
@end
@interface PSStoreButton ()
// call when buttonData was updated
- (void)updateButtonAnimated:(BOOL)animated;
@end
@implementation PSStoreButton
@synthesize buttonData = buttonData_;
@synthesize buttonDelegate = buttonDelegate_;
@synthesize customPadding = customPadding_;
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark private
- (void)touchedUpOutside:(id)sender {
PSLog(@"touched outside...");
}
- (void)buttonPressed:(id)sender {
PSLog(@"calling delegate:storeButtonFired for %@", sender);
[buttonDelegate_ storeButtonFired:self];
}
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
// show text again, but only if animation did finish (or else another animation is on the way)
if ([finished boolValue]) {
[self setTitle:self.buttonData.label forState:UIControlStateNormal];
}
}
- (void)updateButtonAnimated:(BOOL)animated {
if (animated) {
// hide text, then start animation
[self setTitle:@"" forState:UIControlStateNormal];
[UIView beginAnimations:@"storeButtonUpdate" context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:kDefaultButtonAnimationTime];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
}else {
[self setTitle:self.buttonData.label forState:UIControlStateNormal];
}
self.enabled = self.buttonData.isEnabled;
gradient_.colors = self.buttonData.colors;
// show white or gray text, depending on the state
if (self.buttonData.isEnabled) {
[self setTitleShadowColor:[UIColor colorWithWhite:0.200 alpha:1.000] forState:UIControlStateNormal];
[self.titleLabel setShadowOffset:CGSizeMake(0.0, -0.6)];
[self setTitleColor:[UIColor colorWithWhite:1.0 alpha:1.000] forState:UIControlStateNormal];
}else {
[self.titleLabel setShadowOffset:CGSizeMake(0.0, 0.0)];
[self setTitleColor:PS_RGBCOLOR(148,150,151) forState:UIControlStateNormal];
}
// calculate optimal new size
CGSize sizeThatFits = [self sizeThatFits:CGSizeZero];
// move sublayer (can't be animated explcitely)
for (CALayer *aLayer in self.layer.sublayers) {
[CATransaction begin];
if (animated) {
[CATransaction setAnimationDuration:kDefaultButtonAnimationTime];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
}else {
// frame is calculated and explicitely animated. so we absolutely need kCATransactionDisableActions
[CATransaction setValue:[NSNumber numberWithBool:YES] forKey:kCATransactionDisableActions];
}
CGRect newFrame = aLayer.frame;
newFrame.size.width = sizeThatFits.width;
aLayer.frame = newFrame;
[CATransaction commit];
}
// set outer frame changes
self.titleEdgeInsets = UIEdgeInsetsMake(2.0, self.titleEdgeInsets.left, 0.0, 0.0);
[self alignToSuperview];
if (animated) {
[UIView commitAnimations];
}
}
- (void)alignToSuperview {
[self sizeToFit];
if (self.superview) {
CGRect cr = self.frame;
cr.origin.y = customPadding_.y;
cr.origin.x = self.superview.frame.size.width - cr.size.width - customPadding_.x * 2;
self.frame = cr;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
self.layer.needsDisplayOnBoundsChange = YES;
// setup title label
[self.titleLabel setFont:[UIFont boldSystemFontOfSize:13.0]];
// register for touch events
[self addTarget:self action:@selector(touchedUpOutside:) forControlEvents:UIControlEventTouchUpOutside];
[self addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
// border layers for more sex!
CAGradientLayer *bevelLayer = [CAGradientLayer layer];
bevelLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithWhite:0.4 alpha:1.0] CGColor], [[UIColor whiteColor] CGColor], nil];
bevelLayer.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(frame), CGRectGetHeight(frame));
bevelLayer.cornerRadius = 2.5;
bevelLayer.needsDisplayOnBoundsChange = YES;
[self.layer addSublayer:bevelLayer];
CAGradientLayer *topBorderLayer = [CAGradientLayer layer];
topBorderLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor darkGrayColor] CGColor], [[UIColor lightGrayColor] CGColor], nil];
topBorderLayer.frame = CGRectMake(0.5, 0.5, CGRectGetWidth(frame) - 1.0, CGRectGetHeight(frame) - 1.0);
topBorderLayer.cornerRadius = 2.6;
topBorderLayer.needsDisplayOnBoundsChange = YES;
[self.layer addSublayer:topBorderLayer];
// main gradient layer
gradient_ = [[CAGradientLayer layer] retain];
gradient_.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:1.0], nil];//[NSNumber numberWithFloat:0.500], [NSNumber numberWithFloat:0.5001],
gradient_.frame = CGRectMake(0.75, 0.75, CGRectGetWidth(frame) - 1.5, CGRectGetHeight(frame) - 1.5);
gradient_.cornerRadius = 2.5;
gradient_.needsDisplayOnBoundsChange = YES;
[self.layer addSublayer:gradient_];
[self bringSubviewToFront:self.titleLabel];
}
return self;
}
- (id)initWithPadding:(CGPoint)padding {
if ((self = [self initWithFrame:CGRectMake(0, 0, 40, PS_MIN_HEIGHT)])) {
customPadding_ = padding;
}
return self;
}
- (void)dealloc {
[buttonData_ release];
[gradient_ release];
[super dealloc];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIView
- (CGSize)sizeThatFits:(CGSize)size {
CGSize constr = (CGSize){.height = self.frame.size.height, .width = PS_MAX_WIDTH};
CGSize newSize = [self.buttonData.label sizeWithFont:self.titleLabel.font constrainedToSize:constr lineBreakMode:UILineBreakModeMiddleTruncation];
CGFloat newWidth = newSize.width + (PS_PADDING * 2);
CGFloat newHeight = PS_MIN_HEIGHT > newSize.height ? PS_MIN_HEIGHT : newSize.height;
CGSize sizeThatFits = CGSizeMake(newWidth, newHeight);
return sizeThatFits;
}
- (void)setFrame:(CGRect)aRect {
[super setFrame:aRect];
// copy frame changes to sublayers (but watch out for NaN's)
for (CALayer *aLayer in self.layer.sublayers) {
CGRect rect = aLayer.frame;
rect.size.width = self.frame.size.width;
rect.size.height = self.frame.size.height;
aLayer.frame = rect;
[aLayer layoutIfNeeded];
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Properties
- (void)setButtonData:(PSStoreButtonData *)aButtonData {
[self setButtonData:aButtonData animated:NO];
}
- (void)setButtonData:(PSStoreButtonData *)aButtonData animated:(BOOL)animated {
if (buttonData_ != aButtonData) {
[buttonData_ release];
buttonData_ = [aButtonData retain];
}
[self updateButtonAnimated:animated];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark Static
+ (NSArray *)appStoreGreenColor {
return [NSArray arrayWithObjects:(id)
[UIColor colorWithRed:0.482 green:0.674 blue:0.406 alpha:1.000].CGColor,
[UIColor colorWithRed:0.299 green:0.606 blue:0.163 alpha:1.000].CGColor, nil];
}
+ (NSArray *)appStoreBlueColor {
return [NSArray arrayWithObjects:(id)
[UIColor colorWithRed:0.306 green:0.380 blue:0.547 alpha:1.000].CGColor,
[UIColor colorWithRed:0.129 green:0.220 blue:0.452 alpha:1.000].CGColor, nil];
}
+ (NSArray *)appStoreGrayColor {
return [NSArray arrayWithObjects:(id)
PS_RGBCOLOR(187,189,191).CGColor,
PS_RGBCOLOR(210,210,210).CGColor, nil];
}
@end

View File

@ -0,0 +1,44 @@
//
// PSWebTableViewCell.h
// HockeyDemo
//
// Created by Peter Steinberger on 04.02.11.
// Copyright 2011 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>
#import <UIKit/UIKit.h>
@interface PSWebTableViewCell : UITableViewCell <UIWebViewDelegate> {
UIWebView *webView_;
NSString *webViewContent_;
CGSize webViewSize_;
UIColor *cellBackgroundColor_;
}
@property (nonatomic, retain) UIWebView *webView;
@property (nonatomic, copy) NSString *webViewContent;
@property (nonatomic, assign) CGSize webViewSize;
@property (nonatomic, retain) UIColor *cellBackgroundColor;
- (void)addWebView;
@end

View File

@ -0,0 +1,189 @@
//
// PSWebTableViewCell.m
// HockeyDemo
//
// Created by Peter Steinberger on 04.02.11.
// Copyright 2011 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 "PSWebTableViewCell.h"
#import "BWGlobal.h"
@implementation PSWebTableViewCell
static NSString* PSWebTableViewCellHtmlTemplate = @"\
<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\
<html xmlns=\"http://www.w3.org/1999/xhtml\">\
<head>\
<style type=\"text/css\">\
body { font: 13px 'Helvetica Neue', Helvetica; word-wrap:break-word; padding:8px;} p {margin:0;} ul {padding-left: 18px;}\
</style>\
<meta name=\"viewport\" content=\"user-scalable=no width=%@\" /></head>\
<body>\
%@\
</body>\
</html>\
";
@synthesize webView = webView_;
@synthesize webViewContent = webViewContent_;
@synthesize webViewSize = webViewSize_;
@synthesize cellBackgroundColor = cellBackgroundColor_;
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark private
- (void)addWebView {
if(webViewContent_) {
CGRect webViewRect = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
if(!webView_) {
webView_ = [[[UIWebView alloc] initWithFrame:webViewRect] retain];
[self addSubview:webView_];
webView_.hidden = YES;
webView_.backgroundColor = self.cellBackgroundColor;
webView_.opaque = NO;
webView_.delegate = self;
webView_.autoresizingMask = UIViewAutoresizingFlexibleWidth;
for(UIView* subView in webView_.subviews){
if([subView isKindOfClass:[UIScrollView class]]){
// disable scrolling
UIScrollView *sv = (UIScrollView *)subView;
sv.scrollEnabled = NO;
sv.bounces = NO;
// hide shadow
for (UIView* shadowView in [subView subviews]) {
if ([shadowView isKindOfClass:[UIImageView class]]) {
shadowView.hidden = YES;
}
}
}
}
}
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 *contentHtml = [NSString stringWithFormat:PSWebTableViewCellHtmlTemplate, deviceWidth, self.webViewContent];
[webView_ loadHTMLString:contentHtml baseURL:nil];
}
}
- (void)showWebView {
webView_.hidden = NO;
self.textLabel.text = @"";
[self setNeedsDisplay];
}
- (void)removeWebView {
if(webView_) {
webView_.delegate = nil;
[webView_ resignFirstResponder];
[webView_ removeFromSuperview];
[webView_ release];
}
webView_ = nil;
[self setNeedsDisplay];
}
- (void)setWebViewContent:(NSString *)aWebViewContent {
if (webViewContent_ != aWebViewContent) {
[webViewContent_ release];
webViewContent_ = [aWebViewContent retain];
// add basic accessiblity (prevents "snarfed from ivar layout") logs
self.accessibilityLabel = aWebViewContent;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark NSObject
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
self.cellBackgroundColor = [UIColor clearColor];
}
return self;
}
- (void)dealloc {
[self removeWebView];
[webViewContent_ release];
[super dealloc];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIView
- (void)setFrame:(CGRect)aFrame {
BOOL needChange = !CGRectEqualToRect(aFrame, self.frame);
[super setFrame:aFrame];
if (needChange) {
[self addWebView];
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UITableViewCell
- (void)prepareForReuse {
[self removeWebView];
self.webViewContent = nil;
[super prepareForReuse];
}
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
#pragma mark UIWebView
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if(navigationType == UIWebViewNavigationTypeOther)
return YES;
return NO;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if(webViewContent_)
[self showWebView];
CGRect frame = webView_.frame;
frame.size.height = 1;
webView_.frame = frame;
CGSize fittingSize = [webView_ sizeThatFits:CGSizeZero];
frame.size = fittingSize;
webView_.frame = frame;
// sizeThatFits is not reliable - use javascript for optimal height
NSString *output = [webView_ stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"];
self.webViewSize = CGSizeMake(fittingSize.width, [output integerValue]);
}
@end

View File

@ -0,0 +1,38 @@
//
// UIImage+HockeyAdditions.h
// HockeyDemo
//
// Created by Peter Steinberger on 10.01.11.
// Copyright 2011 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>
@interface UIImage (HockeyAdditions)
- (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;
- (id)bw_initWithContentsOfResolutionIndependentFile:(NSString *)path NS_RETURNS_RETAINED;
+ (UIImage*)bw_imageWithContentsOfResolutionIndependentFile:(NSString *)path;
+ (UIImage *)bw_imageNamed:(NSString *)imageName bundle:(NSString *)bundleName;
@end

View File

@ -0,0 +1,346 @@
//
// UIImage+HockeyAdditions.m
// HockeyDemo
//
// Created by Peter Steinberger on 10.01.11.
// Copyright 2011 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 "UIImage+HockeyAdditions.h"
#import "BWGlobal.h"
// Private helper methods
@interface UIImage (HockeyAdditionsPrivate)
- (void)addRoundedRectToPath:(CGRect)rect context:(CGContextRef)context ovalWidth:(CGFloat)ovalWidth ovalHeight:(CGFloat)ovalHeight;
CGContextRef MyOpenBitmapContext(int pixelsWide, int pixelsHigh);
CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha, float toAlpha);
@end
@implementation UIImage (HockeyAdditions)
// Returns true if the image has an alpha layer
- (BOOL)hasAlpha {
CGImageAlphaInfo alpha = CGImageGetAlphaInfo(self.CGImage);
return (alpha == kCGImageAlphaFirst ||
alpha == kCGImageAlphaLast ||
alpha == kCGImageAlphaPremultipliedFirst ||
alpha == kCGImageAlphaPremultipliedLast);
}
// Returns a copy of the given image, adding an alpha channel if it doesn't already have one
- (UIImage *)imageWithAlpha {
if ([self hasAlpha]) {
return self;
}
CGImageRef imageRef = self.CGImage;
size_t width = CGImageGetWidth(imageRef) * self.scale;
size_t height = CGImageGetHeight(imageRef) * self.scale;
// The bitsPerComponent and bitmapInfo values are hard-coded to prevent an "unsupported parameter combination" error
CGContextRef offscreenContext = CGBitmapContextCreate(NULL,
width,
height,
8,
0,
CGImageGetColorSpace(imageRef),
kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
// Draw the image into the context and retrieve the new image, which will now have an alpha layer
CGContextDrawImage(offscreenContext, CGRectMake(0, 0, width, height), imageRef);
CGImageRef imageRefWithAlpha = CGBitmapContextCreateImage(offscreenContext);
UIImage *imageWithAlpha = [UIImage imageWithCGImage:imageRefWithAlpha];
// Clean up
CGContextRelease(offscreenContext);
CGImageRelease(imageRefWithAlpha);
return imageWithAlpha;
}
// 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 {
// 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
if (!roundedImage) {
// Try older method.
UIImage *image = [self imageWithAlpha];
// Build a context that's the same dimensions as the new size
CGContextRef context = CGBitmapContextCreate(NULL,
image.size.width,
image.size.height,
CGImageGetBitsPerComponent(image.CGImage),
0,
CGImageGetColorSpace(image.CGImage),
CGImageGetBitmapInfo(image.CGImage));
// Create a clipping path with rounded corners
CGContextBeginPath(context);
[self addRoundedRectToPath:CGRectMake(borderSize, borderSize, image.size.width - borderSize * 2, image.size.height - borderSize * 2)
context:context
ovalWidth:cornerSize
ovalHeight:cornerSize];
CGContextClosePath(context);
CGContextClip(context);
// Draw the image to the context; the clipping path will make anything outside the rounded rect transparent
CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
// Create a CGImage from the context
CGImageRef clippedImage = CGBitmapContextCreateImage(context);
CGContextRelease(context);
// Create a UIImage from the CGImage
roundedImage = [UIImage imageWithCGImage:clippedImage];
CGImageRelease(clippedImage);
}
return roundedImage;
}
#pragma mark -
#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/
- (void)addRoundedRectToPath:(CGRect)rect context:(CGContextRef)context ovalWidth:(CGFloat)ovalWidth ovalHeight:(CGFloat)ovalHeight {
if (ovalWidth == 0 || ovalHeight == 0) {
CGContextAddRect(context, rect);
return;
}
CGContextSaveGState(context);
CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextScaleCTM(context, ovalWidth, ovalHeight);
CGFloat fw = CGRectGetWidth(rect) / ovalWidth;
CGFloat fh = CGRectGetHeight(rect) / ovalHeight;
CGContextMoveToPoint(context, fw, fh/2);
CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1);
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1);
CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
CGContextClosePath(context);
CGContextRestoreGState(context);
}
- (UIImage *)bw_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;
float targetWidth = fitSize.width;
float targetHeight = fitSize.height;
// Calculate aspect ratios
float sourceRatio = sourceWidth / sourceHeight;
float targetRatio = targetWidth / targetHeight;
// Determine what side of the source image to use for proportional scaling
BOOL scaleWidth = (sourceRatio <= targetRatio);
// Deal with the case of just scaling proportionally to fit, without cropping
scaleWidth = !scaleWidth;
// Proportionally scale source image
float scalingFactor, scaledWidth, scaledHeight;
if (scaleWidth) {
scalingFactor = 1.0 / sourceRatio;
scaledWidth = targetWidth;
scaledHeight = round(targetWidth * scalingFactor);
} else {
scalingFactor = sourceRatio;
scaledWidth = round(targetHeight * scalingFactor);
scaledHeight = targetHeight;
}
// Calculate compositing rectangles
CGRect sourceRect, destRect;
sourceRect = CGRectMake(0, 0, sourceWidth, sourceHeight);
destRect = CGRectMake(0, 0, scaledWidth, scaledHeight);
// 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();
)
if (!image) {
// Try older method.
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, scaledWidth, scaledHeight, 8, (fitSize.width * 4),
colorSpace, kCGImageAlphaPremultipliedLast);
CGImageRef sourceImg = CGImageCreateWithImageInRect([self CGImage], sourceRect);
CGContextDrawImage(context, destRect, sourceImg);
CGImageRelease(sourceImg);
CGImageRef finalImage = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
image = [UIImage imageWithCGImage:finalImage];
CGImageRelease(finalImage);
}
return image;
}
CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh, float fromAlpha, float toAlpha) {
CGImageRef theCGImage = NULL;
// gradient is always black-white and the mask must be in the gray colorspace
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
// create the bitmap context
CGContextRef gradientBitmapContext = CGBitmapContextCreate(NULL, pixelsWide, pixelsHigh,
8, 0, colorSpace, kCGImageAlphaNone);
// define the start and end grayscale values (with the alpha, even though
// our bitmap context doesn't support alpha the gradient requires it)
CGFloat colors[] = {toAlpha, 1.0, fromAlpha, 1.0};
// create the CGGradient and then release the gray color space
CGGradientRef grayScaleGradient = CGGradientCreateWithColorComponents(colorSpace, colors, NULL, 2);
CGColorSpaceRelease(colorSpace);
// create the start and end points for the gradient vector (straight down)
CGPoint gradientEndPoint = CGPointZero;
CGPoint gradientStartPoint = CGPointMake(0, pixelsHigh);
// draw the gradient into the gray bitmap context
CGContextDrawLinearGradient(gradientBitmapContext, grayScaleGradient, gradientStartPoint,
gradientEndPoint, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(grayScaleGradient);
// convert the context into a CGImageRef and release the context
theCGImage = CGBitmapContextCreateImage(gradientBitmapContext);
CGContextRelease(gradientBitmapContext);
// return the imageref containing the gradient
return theCGImage;
}
CGContextRef MyOpenBitmapContext(int pixelsWide, int pixelsHigh) {
CGSize size = CGSizeMake(pixelsWide, pixelsHigh);
if (UIGraphicsBeginImageContextWithOptions != NULL) {
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
}
else {
UIGraphicsBeginImageContext(size);
}
return UIGraphicsGetCurrentContext();
}
- (UIImage *)bw_reflectedImageWithHeight:(NSUInteger)height fromAlpha:(float)fromAlpha toAlpha:(float)toAlpha {
if(height == 0)
return nil;
// create a bitmap graphics context the size of the image
CGContextRef mainViewContentContext = MyOpenBitmapContext(self.size.width, height);
// create a 2 bit CGImage containing a gradient that will be used for masking the
// main view content to create the 'fade' of the reflection. The CGImageCreateWithMask
// function will stretch the bitmap image as required, so we can create a 1 pixel wide gradient
CGImageRef gradientMaskImage = CreateGradientImage(1, height, fromAlpha, toAlpha);
// create an image by masking the bitmap of the mainView content with the gradient view
// then release the pre-masked content bitmap and the gradient bitmap
CGContextClipToMask(mainViewContentContext, CGRectMake(0.0, 0.0, self.size.width, height), gradientMaskImage);
CGImageRelease(gradientMaskImage);
// draw the image into the bitmap context
CGContextDrawImage(mainViewContentContext, CGRectMake(0, 0, self.size.width, self.size.height), self.CGImage);
// convert the finished reflection image to a UIImage
UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext(); // returns autoreleased
UIGraphicsEndImageContext();
return theImage;
}
- (id)bw_initWithContentsOfResolutionIndependentFile:(NSString *)path {
if ([UIScreen instancesRespondToSelector:@selector(scale)] && (int)[[UIScreen mainScreen] scale] == 2.0) {
NSString *path2x = [[path stringByDeletingLastPathComponent]
stringByAppendingPathComponent:[NSString stringWithFormat:@"%@@2x.%@",
[[path lastPathComponent] stringByDeletingPathExtension],
[path pathExtension]]];
if ([[NSFileManager defaultManager] fileExistsAtPath:path2x]) {
return [self initWithContentsOfFile:path2x];
}
}
return [self initWithContentsOfFile:path];
}
+ (UIImage*)bw_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];
#endif
}
+ (UIImage *)bw_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];
}
@end

81
LICENSE.txt Executable file
View File

@ -0,0 +1,81 @@
## 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 Codenauts UG (haftungsbeschränkt). 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.
Except as noted below, PLCrashReporter
is provided under the following license:
Copyright (c) 2008 - 2009 Plausible Labs Cooperative, Inc.
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.
The protobuf-c library, as well as the PLCrashLogWriterEncoding.c
file are licensed as follows:
Copyright 2008, Dave Benson.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with
the License. You may obtain a copy of the License
at http://www.apache.org/licenses/LICENSE-2.0 Unless
required by applicable law or agreed to in writing,
software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.

BIN
Resources/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,86 @@
/*
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.";
"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

@ -0,0 +1,86 @@
/*
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.";
"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.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

View File

@ -0,0 +1,86 @@
/*
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.";
"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

@ -0,0 +1,87 @@
/*
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.";
"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";

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
Versions/Current/CrashReporter

1
Vendor/CrashReporter.framework/Headers vendored Symbolic link
View File

@ -0,0 +1 @@
Versions/Current/Headers

1
Vendor/CrashReporter.framework/Resources vendored Symbolic link
View File

@ -0,0 +1 @@
Versions/Current/Resources

Binary file not shown.

View File

@ -0,0 +1,229 @@
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc.
* 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>
#ifdef __APPLE__
#import <AvailabilityMacros.h>
#endif
#import "PLCrashReporter.h"
#import "PLCrashReport.h"
#import "PLCrashReportTextFormatter.h"
/**
* @defgroup functions Crash Reporter Functions Reference
*/
/**
* @defgroup types Crash Reporter Data Types Reference
*/
/**
* @defgroup constants Crash Reporter Constants Reference
*/
/**
* @internal
* @defgroup plcrash_internal Crash Reporter Internal Documentation
*/
/**
* @defgroup enums Enumerations
* @ingroup constants
*/
/**
* @defgroup globals Global Variables
* @ingroup constants
*/
/**
* @defgroup exceptions Exceptions
* @ingroup constants
*/
/* Exceptions */
extern NSString *PLCrashReporterException;
/* Error Domain and Codes */
extern NSString *PLCrashReporterErrorDomain;
/**
* NSError codes in the Plausible Crash Reporter error domain.
* @ingroup enums
*/
typedef enum {
/** An unknown error has occured. If this
* code is received, it is a bug, and should be reported. */
PLCrashReporterErrorUnknown = 0,
/** An Mach or POSIX operating system error has occured. The underlying NSError cause may be fetched from the userInfo
* dictionary using the NSUnderlyingErrorKey key. */
PLCrashReporterErrorOperatingSystem = 1,
/** The crash report log file is corrupt or invalid */
PLCrashReporterErrorCrashReportInvalid = 2,
} PLCrashReporterError;
/* Library Imports */
#import "PLCrashReporter.h"
#import "PLCrashReport.h"
#import "PLCrashReportTextFormatter.h"
/**
* @mainpage Plausible Crash Reporter
*
* @section intro_sec Introduction
*
* Plausile CrashReporter implements in-process crash reporting on the iPhone and Mac OS X.
*
* The following features are supported:
*
* - Implemented as an in-process signal handler.
* - Does not interfer with debugging in gdb..
* - Handles both uncaught Objective-C exceptions and fatal signals (SIGSEGV, SIGBUS, etc).
* - Full thread state for all active threads (backtraces, register dumps) is provided.
*
* If your application crashes, a crash report will be written. When the application is next run, you may check for a
* pending crash report, and submit the report to your own HTTP server, send an e-mail, or even introspect the
* report locally.
*
* @section intro_encoding Crash Report Format
*
* Crash logs are encoded using <a href="http://code.google.com/p/protobuf/">google protobuf</a>, and may be decoded
* using the provided PLCrashReport API. Additionally, the include plcrashutil handles conversion of binary crash reports to the
* symbolicate-compatible iPhone text format.
*
* @section doc_sections Documentation Sections
* - @subpage example_usage_iphone
* - @subpage error_handling
* - @subpage async_safety
*/
/**
* @page example_usage_iphone Example iPhone Usage
*
* @code
* //
* // Called to handle a pending crash report.
* //
* - (void) handleCrashReport {
* PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
* NSData *crashData;
* NSError *error;
*
* // Try loading the crash report
* crashData = [crashReporter loadPendingCrashReportDataAndReturnError: &error];
* if (crashData == nil) {
* NSLog(@"Could not load crash report: %@", error);
* goto finish;
* }
*
* // We could send the report from here, but we'll just print out
* // some debugging info instead
* PLCrashReport *report = [[[PLCrashReport alloc] initWithData: crashData error: &error] autorelease];
* if (report == nil) {
* NSLog(@"Could not parse crash report");
* goto finish;
* }
*
* NSLog(@"Crashed on %@", report.systemInfo.timestamp);
* NSLog(@"Crashed with signal %@ (code %@, address=0x%" PRIx64 ")", report.signalInfo.name,
* report.signalInfo.code, report.signalInfo.address);
*
* // Purge the report
* finish:
* [crashReporter purgePendingCrashReport];
* return;
* }
*
* // from UIApplicationDelegate protocol
* - (void) applicationDidFinishLaunching: (UIApplication *) application {
* PLCrashReporter *crashReporter = [PLCrashReporter sharedReporter];
* NSError *error;
*
* // Check if we previously crashed
* if ([crashReporter hasPendingCrashReport])
* [self handleCrashReport];
* // Enable the Crash Reporter
* if (![crashReporter enableCrashReporterAndReturnError: &error])
* NSLog(@"Warning: Could not enable crash reporter: %@", error);
*
* }
* @endcode
*
*/
/**
* @page error_handling Error Handling Programming Guide
*
* Where a method may return an error, Plausible Crash Reporter provides access to the underlying
* cause via an optional NSError argument.
*
* All returned errors will be a member of one of the below defined domains, however, new domains and
* error codes may be added at any time. If you do not wish to report on the error cause, many methods
* support a simple form that requires no NSError argument.
*
* @section error_domains Error Domains, Codes, and User Info
*
* @subsection crashreporter_errors Crash Reporter Errors
*
* Any errors in Plausible Crash Reporter use the #PLCrashReporterErrorDomain error domain, and and one
* of the error codes defined in #PLCrashReporterError.
*/
/**
* @page async_safety Async-Safe Programming Guide
*
* Plausible CrashReporter provides support for executing an application specified function in the context of the
* crash reporter's signal handler, after the crash report has been written to disk. This was a regularly requested
* feature, and provides the ability to implement application finalization in the event of a crash. However, writing
* code intended for execution inside of a signal handler is exceptionally difficult, and is not recommended.
*
* @section program_flow Program Flow and Signal Handlers
*
* When the signal handler is called the normal flow of the program is interrupted, and your program is an unknown
* state. Locks may be held, the heap may be corrupt (or in the process of being updated), and your signal
* handler may invoke a function that was being executed at the time of the signal. This may result in deadlocks,
* data corruption, and program termination.
*
* @section functions Async-Safe Functions
*
* A subset of functions are defined to be async-safe by the OS, and are safely callable from within a signal handler. If
* you do implement a custom post-crash handler, it must be async-safe. A table of POSIX-defined async-safe functions
* and additional information is available from the
* <a href="https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers">CERT programming guide - SIG30-C</a>
*
* Most notably, the Objective-C runtime itself is not async-safe, and Objective-C may not be used within a signal
* handler.
*
* @sa PLCrashReporter::setCrashCallbacks:
*/

View File

@ -0,0 +1,169 @@
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2010 Plausible Labs Cooperative, Inc.
* 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 "PLCrashReportSystemInfo.h"
#import "PLCrashReportMachineInfo.h"
#import "PLCrashReportApplicationInfo.h"
#import "PLCrashReportProcessInfo.h"
#import "PLCrashReportSignalInfo.h"
#import "PLCrashReportThreadInfo.h"
#import "PLCrashReportBinaryImageInfo.h"
#import "PLCrashReportExceptionInfo.h"
/**
* @ingroup constants
* Crash file magic identifier */
#define PLCRASH_REPORT_FILE_MAGIC "plcrash"
/**
* @ingroup constants
* Crash format version byte identifier. Will not change outside of the introduction of
* an entirely new crash log format. */
#define PLCRASH_REPORT_FILE_VERSION 1
/**
* @ingroup types
* Crash log file header format.
*
* Crash log files start with 7 byte magic identifier (#PLCRASH_REPORT_FILE_MAGIC),
* followed by a single unsigned byte version number (#PLCRASH_REPORT_FILE_VERSION).
* The crash log message format itself is extensible, so this version number will only
* be incremented in the event of an incompatible encoding or format change.
*/
struct PLCrashReportFileHeader {
/** Crash log magic identifier, not NULL terminated */
const char magic[7];
/** Crash log encoding/format version */
const uint8_t version;
/** File data */
const uint8_t data[];
} __attribute__((packed));
/**
* @internal
* Private decoder instance variables (used to hide the underlying protobuf parser).
*/
typedef struct _PLCrashReportDecoder _PLCrashReportDecoder;
@interface PLCrashReport : NSObject {
@private
/** Private implementation variables (used to hide the underlying protobuf parser) */
_PLCrashReportDecoder *_decoder;
/** System info */
PLCrashReportSystemInfo *_systemInfo;
/** Machine info */
PLCrashReportMachineInfo *_machineInfo;
/** Application info */
PLCrashReportApplicationInfo *_applicationInfo;
/** Process info */
PLCrashReportProcessInfo *_processInfo;
/** Signal info */
PLCrashReportSignalInfo *_signalInfo;
/** Thread info (PLCrashReportThreadInfo instances) */
NSArray *_threads;
/** Binary images (PLCrashReportBinaryImageInfo instances */
NSArray *_images;
/** Exception information (may be nil) */
PLCrashReportExceptionInfo *_exceptionInfo;
}
- (id) initWithData: (NSData *) encodedData error: (NSError **) outError;
- (PLCrashReportBinaryImageInfo *) imageForAddress: (uint64_t) address;
/**
* System information.
*/
@property(nonatomic, readonly) PLCrashReportSystemInfo *systemInfo;
/**
* YES if machine information is available.
*/
@property(nonatomic, readonly) BOOL hasMachineInfo;
/**
* Machine information. Only available in later (v1.1+) crash report format versions. If not available,
* will be nil.
*/
@property(nonatomic, readonly) PLCrashReportMachineInfo *machineInfo;
/**
* Application information.
*/
@property(nonatomic, readonly) PLCrashReportApplicationInfo *applicationInfo;
/**
* YES if process information is available.
*/
@property(nonatomic, readonly) BOOL hasProcessInfo;
/**
* Process information. Only available in later (v1.1+) crash report format versions. If not available,
* will be nil.
*/
@property(nonatomic, readonly) PLCrashReportProcessInfo *processInfo;
/**
* Signal information. This provides the signal and signal code of the fatal signal.
*/
@property(nonatomic, readonly) PLCrashReportSignalInfo *signalInfo;
/**
* Thread information. Returns a list of PLCrashReportThreadInfo instances.
*/
@property(nonatomic, readonly) NSArray *threads;
/**
* Binary image information. Returns a list of PLCrashReportBinaryImageInfo instances.
*/
@property(nonatomic, readonly) NSArray *images;
/**
* YES if exception information is available.
*/
@property(nonatomic, readonly) BOOL hasExceptionInfo;
/**
* Exception information. Only available if a crash was caused by an uncaught exception,
* otherwise nil.
*/
@property(nonatomic, readonly) PLCrashReportExceptionInfo *exceptionInfo;
@end

View File

@ -0,0 +1,53 @@
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc.
* 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 PLCrashReportApplicationInfo : NSObject {
@private
/** Application identifier */
NSString *_applicationIdentifier;
/** Application version */
NSString *_applicationVersion;
}
- (id) initWithApplicationIdentifier: (NSString *) applicationIdentifier
applicationVersion: (NSString *) applicationVersion;
/**
* The application identifier. This is usually the application's CFBundleIdentifier value.
*/
@property(nonatomic, readonly) NSString *applicationIdentifier;
/**
* The application version. This is usually the application's CFBundleVersion value.
*/
@property(nonatomic, readonly) NSString *applicationVersion;
@end

View File

@ -0,0 +1,90 @@
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc.
* 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 "PLCrashReportProcessorInfo.h"
@interface PLCrashReportBinaryImageInfo : NSObject {
@private
/** Code type */
PLCrashReportProcessorInfo *_processorInfo;
/** Base image address */
uint64_t _baseAddress;
/** Image segment size */
uint64_t _imageSize;
/** Name of binary image */
NSString *_imageName;
/** If the UUID is available */
BOOL _hasImageUUID;
/** 128-bit object UUID. May be nil. */
NSString *_imageUUID;
}
- (id) initWithCodeType: (PLCrashReportProcessorInfo *) processorInfo
baseAddress: (uint64_t) baseAddress
size: (uint64_t) imageSize
name: (NSString *) imageName
uuid: (NSData *) uuid;
/**
* Image code type, or nil if unavailable.
*/
@property(nonatomic, readonly) PLCrashReportProcessorInfo *codeType;
/**
* Image base address.
*/
@property(nonatomic, readonly) uint64_t imageBaseAddress;
/**
* Segment size.
*/
@property(nonatomic, readonly) uint64_t imageSize;
/**
* Image name (absolute path)
*/
@property(nonatomic, readonly) NSString *imageName;
/**
* YES if this image has an associated UUID.
*/
@property(nonatomic, readonly) BOOL hasImageUUID;
/**
* 128-bit object UUID (matches Mach-O DWARF dSYM files). May be nil if unavailable.
*/
@property(nonatomic, readonly) NSString *imageUUID;
@end

View File

@ -0,0 +1,65 @@
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc.
* 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 "PLCrashReportThreadInfo.h"
@interface PLCrashReportExceptionInfo : NSObject {
@private
/** Name */
NSString *_name;
/** Reason */
NSString *_reason;
/** Ordered list of PLCrashReportStackFrame instances, or nil if unavailable. */
NSArray *_stackFrames;
}
- (id) initWithExceptionName: (NSString *) name reason: (NSString *) reason;
- (id) initWithExceptionName: (NSString *) name
reason: (NSString *) reason
stackFrames: (NSArray *) stackFrames;
/**
* The exception name.
*/
@property(nonatomic, readonly) NSString *exceptionName;
/**
* The exception reason.
*/
@property(nonatomic, readonly) NSString *exceptionReason;
/* The exception's original call stack, as an array of PLCrashReportStackFrameInfo instances, or nil if unavailable.
* This may be preserved across rethrow of an exception, and can be used to determine the original call stack. */
@property(nonatomic, readonly) NSArray *stackFrames;
@end

View File

@ -0,0 +1,51 @@
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2010 Plausible Labs Cooperative, Inc.
* 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 "PLCrashReport.h"
/**
* A crash report formatter accepts a PLCrashReport instance, formats it according to implementation-specified rules,
* (such as implementing text output support), and returns the result.
*/
@protocol PLCrashReportFormatter
/**
* Format the provided @a report.
*
* @param report Report to be formatted.
* @param outError A pointer to an NSError object variable. If an error occurs, this pointer will contain an error
* object indicating why the pending crash report could not be formatted. If no error occurs, this parameter will
* be left unmodified. You may specify nil for this parameter, and no error information will be provided.
*
* @return Returns the formatted report data on success, or nil on failure.
*/
- (NSData *) formatReport: (PLCrashReport *) report error: (NSError **) outError;
@end

View File

@ -0,0 +1,73 @@
/*
* Author: Landon Fuller <landonf@plausible.coop>
*
* Copyright (c) 2008-2011 Plausible Labs Cooperative, Inc.
* 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 "PLCrashReportProcessorInfo.h"
@interface PLCrashReportMachineInfo : NSObject {
@private
/** The hardware model name (eg, MacBookPro6,1). This may be unavailable, and this property will be nil. */
NSString *_modelName;
/** The processor type. */
PLCrashReportProcessorInfo *_processorInfo;
/* The number of actual physical processor cores. */
NSUInteger _processorCount;
/* The number of logical processors. */
NSUInteger _logicalProcessorCount;
}
- (id) initWithModelName: (NSString *) modelName
processorInfo: (PLCrashReportProcessorInfo *) processorInfo
processorCount: (NSUInteger) processorCount
logicalProcessorCount: (NSUInteger) logicalProcessorCount;
/** The hardware model name (eg, MacBookPro6,1). This may be unavailable, and this property will be nil. */
@property(nonatomic, readonly) NSString *modelName;
/** The processor type. */
@property(nonatomic, readonly) PLCrashReportProcessorInfo *processorInfo;
/*
* The number of actual physical processor cores. Note that the number of active processors may be managed by the
* operating system's power management system, and this value may not reflect the number of active
* processors at the time of the crash.
*/
@property(nonatomic, readonly) NSUInteger processorCount;
/*
* The number of logical processors. Note that the number of active processors may be managed by the
* operating system's power management system, and this value may not reflect the number of active
* processors at the time of the crash.
*/
@property(nonatomic, readonly) NSUInteger logicalProcessorCount;
@end

View File

@ -0,0 +1,92 @@
/*
* Author: Damian Morris <damian@moso.com.au>
*
* Copyright (c) 2010 MOSO Corporation, Pty Ltd.
* Copyright (c) 2010 Plausible Labs Cooperative, Inc.
*
* 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 PLCrashReportProcessInfo : NSObject {
@private
/** Process name */
NSString *_processName;
/** Process ID */
NSUInteger _processID;
/** Process path */
NSString* _processPath;
/** Parent process name */
NSString *_parentProcessName;
/** Parent process ID */
NSUInteger _parentProcessID;
/** If false, the process is being run via process-level CPU emulation (such as Rosetta). */
BOOL _native;
}
- (id) initWithProcessName: (NSString *) processName
processID: (NSUInteger) processID
processPath: (NSString *) processPath
parentProcessName: (NSString *) parentProcessName
parentProcessID: (NSUInteger) parentProcessID
native: (BOOL) native;
/**
* The process name. This value may not be included in the crash report, in which case this property
* will be nil.
*/
@property(nonatomic, readonly) NSString *processName;
/**
* The process ID.
*/
@property(nonatomic, readonly) NSUInteger processID;
/**
* The path to the process executable. This value may not be included in the crash report, in which case this property
* will be nil.
*/
@property(nonatomic, readonly) NSString *processPath;
/**
* The parent process name. This value may not be included in the crash report, in which case this property
* will be nil.
*/
@property(nonatomic, readonly) NSString *parentProcessName;
/**
* The parent process ID.
*/
@property(nonatomic, readonly) NSUInteger parentProcessID;
/** The process' native execution status. If false, the process is being run via process-level CPU emulation (such as Rosetta). */
@property(nonatomic, readonly) BOOL native;
@end

View File

@ -0,0 +1,74 @@
/*
* Author: Landon Fuller <landonf@plausible.coop>
*
* Copyright (c) 2008-2011 Plausible Labs Cooperative, Inc.
* 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 <mach/machine.h>
/**
* @ingroup constants
*
* The type encodings supported for CPU types and subtypes. Currently only Apple
* Mach-O defined encodings are supported.
*
* @internal
* These enum values match the protobuf values. Keep them synchronized.
*/
typedef enum {
/** Unknown cpu type encoding. */
PLCrashReportProcessorTypeEncodingUnknown = 0,
/** Apple Mach-defined processor types. */
PLCrashReportProcessorTypeEncodingMach = 1
} PLCrashReportProcessorTypeEncoding;
@interface PLCrashReportProcessorInfo : NSObject {
@private
/** Type encoding */
PLCrashReportProcessorTypeEncoding _typeEncoding;
/** CPU type */
uint64_t _type;
/** CPU subtype */
uint64_t _subtype;
}
- (id) initWithTypeEncoding: (PLCrashReportProcessorTypeEncoding) typeEncoding
type: (uint64_t) type
subtype: (uint64_t) subtype;
/** The CPU type encoding. */
@property(nonatomic, readonly) PLCrashReportProcessorTypeEncoding typeEncoding;
/** The CPU type. */
@property(nonatomic, readonly) uint64_t type;
/** The CPU subtype. */
@property(nonatomic, readonly) uint64_t subtype;
@end

View File

@ -0,0 +1,60 @@
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc.
* 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 PLCrashReportSignalInfo : NSObject {
@private
/** Signal name */
NSString *_name;
/** Signal code */
NSString *_code;
/** Fauling instruction or address */
uint64_t _address;
}
- (id) initWithSignalName: (NSString *) name code: (NSString *) code address: (uint64_t) address;
/**
* The signal name.
*/
@property(nonatomic, readonly) NSString *name;
/**
* The signal code.
*/
@property(nonatomic, readonly) NSString *code;
/**
* The faulting instruction or address.
*/
@property(nonatomic, readonly) uint64_t address;
@end

View File

@ -0,0 +1,142 @@
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc.
* 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>
/**
* @ingroup constants
*
* Indicates the Operating System under which a Crash Log was generated.
*
* @internal
* These enum values match the protobuf values. Keep them synchronized.
*/
typedef enum {
/** Mac OS X. */
PLCrashReportOperatingSystemMacOSX = 0,
/** iPhone OS */
PLCrashReportOperatingSystemiPhoneOS = 1,
/** iPhone Simulator (Mac OS X with additional simulator-specific runtime libraries) */
PLCrashReportOperatingSystemiPhoneSimulator = 2,
/** Unknown operating system */
PLCrashReportOperatingSystemUnknown = 3,
} PLCrashReportOperatingSystem;
/**
* @ingroup constants
*
* Indicates the architecture under which a Crash Log was generated.
*
* @deprecated The architecture value has been deprecated in v1.1 and later crash reports. All new reports
* will make use of the new PLCrashReportProcessorInfo CPU type encodings.
*
* @internal
* These enum values match the protobuf values. Keep them synchronized.
*/
typedef enum {
/** x86-32. */
PLCrashReportArchitectureX86_32 = 0,
/** x86-64 */
PLCrashReportArchitectureX86_64 = 1,
/** ARMv6 */
PLCrashReportArchitectureARMv6 = 2,
/**
* ARMv6
* @deprecated
* @sa PLCrashReportArchitectureARMv6
*/
PLCrashReportArchitectureARM = PLCrashReportArchitectureARMv6,
/** PPC */
PLCrashReportArchitecturePPC = 3,
/** PPC64 */
PLCrashReportArchitecturePPC64 = 4,
/** ARMv7 */
PLCrashReportArchitectureARMv7 = 5,
/** Unknown */
PLCrashReportArchitectureUnknown = 6
} PLCrashReportArchitecture;
extern PLCrashReportOperatingSystem PLCrashReportHostOperatingSystem;
extern PLCrashReportArchitecture PLCrashReportHostArchitecture;
@interface PLCrashReportSystemInfo : NSObject {
@private
/** Operating system */
PLCrashReportOperatingSystem _operatingSystem;
/** Operating system version */
NSString *_osVersion;
/** OS build. May be nil. */
NSString *_osBuild;
/** Architecture */
PLCrashReportArchitecture _architecture;
/** Date crash report was generated. May be nil if the date is unknown. */
NSDate *_timestamp;
}
- (id) initWithOperatingSystem: (PLCrashReportOperatingSystem) operatingSystem
operatingSystemVersion: (NSString *) operatingSystemVersion
architecture: (PLCrashReportArchitecture) architecture
timestamp: (NSDate *) timestamp;
- (id) initWithOperatingSystem: (PLCrashReportOperatingSystem) operatingSystem
operatingSystemVersion: (NSString *) operatingSystemVersion
operatingSystemBuild: (NSString *) operatingSystemBuild
architecture: (PLCrashReportArchitecture) architecture
timestamp: (NSDate *) timestamp;
/** The operating system. */
@property(nonatomic, readonly) PLCrashReportOperatingSystem operatingSystem;
/** The operating system's release version. */
@property(nonatomic, readonly) NSString *operatingSystemVersion;
/** The operating system's build identifier (eg, 10J869). This may be unavailable, and this property will be nil. */
@property(nonatomic, readonly) NSString *operatingSystemBuild;
/** Architecture. */
@property(nonatomic, readonly) PLCrashReportArchitecture architecture;
/** Date and time that the crash report was generated. This may be unavailable, and this property will be nil. */
@property(nonatomic, readonly) NSDate *timestamp;
@end

View File

@ -0,0 +1,62 @@
/*
* Authors:
* Landon Fuller <landonf@plausiblelabs.com>
* Damian Morris <damian@moso.com.au>
*
* Copyright (c) 2008-2010 Plausible Labs Cooperative, Inc.
* Copyright (c) 2010 MOSO Corporation, Pty Ltd.
* 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 "PLCrashReportFormatter.h"
/**
* Supported text output formats.
*
* @ingroup enums
*/
typedef enum {
/** An iOS-compatible crash log text format. Compatible with the crash logs generated by the device and available
* through iTunes Connect. */
PLCrashReportTextFormatiOS = 0
} PLCrashReportTextFormat;
@interface PLCrashReportTextFormatter : NSObject <PLCrashReportFormatter> {
@private
/** Text output format. */
PLCrashReportTextFormat _textFormat;
/** Encoding to use for string output. */
NSStringEncoding _stringEncoding;
}
+ (NSString *) stringValueForCrashReport: (PLCrashReport *) report withTextFormat: (PLCrashReportTextFormat) textFormat;
- (id) initWithTextFormat: (PLCrashReportTextFormat) textFormat stringEncoding: (NSStringEncoding) stringEncoding;
@end

View File

@ -0,0 +1,114 @@
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc.
* 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 PLCrashReportStackFrameInfo : NSObject {
@private
/** Frame instruction pointer. */
uint64_t _instructionPointer;
}
- (id) initWithInstructionPointer: (uint64_t) instructionPointer;
/**
* Frame's instruction pointer.
*/
@property(nonatomic, readonly) uint64_t instructionPointer;
@end
@interface PLCrashReportRegisterInfo : NSObject {
@private
/** Register name */
NSString *_registerName;
/** Register value */
uint64_t _registerValue;
}
- (id) initWithRegisterName: (NSString *) registerName registerValue: (uint64_t) registerValue;
/**
* Register name.
*/
@property(nonatomic, readonly) NSString *registerName;
/**
* Register value.
*/
@property(nonatomic, readonly) uint64_t registerValue;
@end
@interface PLCrashReportThreadInfo : NSObject {
@private
/** The thread number. Should be unique within a given crash log. */
NSInteger _threadNumber;
/** Ordered list of PLCrashReportStackFrame instances */
NSArray *_stackFrames;
/** YES if this thread crashed. */
BOOL _crashed;
/** List of PLCrashReportRegister instances. Will be empty if _crashed is NO. */
NSArray *_registers;
}
- (id) initWithThreadNumber: (NSInteger) threadNumber
stackFrames: (NSArray *) stackFrames
crashed: (BOOL) crashed
registers: (NSArray *) registers;
/**
* Application thread number.
*/
@property(nonatomic, readonly) NSInteger threadNumber;
/**
* Thread backtrace. Provides an array of PLCrashReportStackFrameInfo instances.
* The array is ordered, last callee to first.
*/
@property(nonatomic, readonly) NSArray *stackFrames;
/**
* If this thread crashed, set to YES.
*/
@property(nonatomic, readonly) BOOL crashed;
/**
* State of the general purpose and related registers, as a list of
* PLCrashReportRegister instances. If this thead did not crash (crashed returns NO),
* this list will be empty.
*/
@property(nonatomic, readonly) NSArray *registers;
@end

View File

@ -0,0 +1,97 @@
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc.
* 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>
/**
* @ingroup functions
*
* Prototype of a callback function used to execute additional user code with signal information as provided
* by PLCrashReporter. Called upon completion of crash handling, after the crash report has been written to disk.
*
* @param info The signal info.
* @param uap The crash's threads context.
* @param context The API client's supplied context value.
*
* @sa @ref async_safety
* @sa PLCrashReporter::setPostCrashCallbacks:
*/
typedef void (*PLCrashReporterPostCrashSignalCallback)(siginfo_t *info, ucontext_t *uap, void *context);
/**
* @ingroup types
*
* This structure contains callbacks supported by PLCrashReporter to allow the host application to perform
* additional tasks prior to program termination after a crash has occured.
*
* @sa @ref async_safety
*/
typedef struct PLCrashReporterCallbacks {
/** The version number of this structure. If not one of the defined version numbers for this type, the behavior
* is undefined. The current version of this structure is 0. */
uint16_t version;
/** An arbitrary user-supplied context value. This value may be NULL. */
void *context;
/** The callback used to report caught signal information. In version 0 of this structure, all crashes will be
* reported via this function. */
PLCrashReporterPostCrashSignalCallback handleSignal;
} PLCrashReporterCallbacks;
@interface PLCrashReporter : NSObject {
@private
/** YES if the crash reporter has been enabled */
BOOL _enabled;
/** Application identifier */
NSString *_applicationIdentifier;
/** Application version */
NSString *_applicationVersion;
/** Path to the crash reporter internal data directory */
NSString *_crashReportDirectory;
}
+ (PLCrashReporter *) sharedReporter;
- (BOOL) hasPendingCrashReport;
- (NSData *) loadPendingCrashReportData;
- (NSData *) loadPendingCrashReportDataAndReturnError: (NSError **) outError;
- (BOOL) purgePendingCrashReport;
- (BOOL) purgePendingCrashReportAndReturnError: (NSError **) outError;
- (BOOL) enableCrashReporter;
- (BOOL) enableCrashReporterAndReturnError: (NSError **) outError;
- (void) setCrashCallbacks: (PLCrashReporterCallbacks *) callbacks;
@end

View File

@ -0,0 +1,38 @@
<?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>BuildMachineOSBuild</key>
<string>11C74</string>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>CrashReporter</string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.CrashReporter</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>CrashReporter</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>DTCompiler</key>
<string></string>
<key>DTPlatformBuild</key>
<string>4D199</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>11C63</string>
<key>DTSDKName</key>
<string>macosx10.7</string>
<key>DTXcode</key>
<string>0420</string>
<key>DTXcodeBuild</key>
<string>4D199</string>
</dict>
</plist>

View File

@ -0,0 +1 @@
A