mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
1220 lines
47 KiB
Objective-C
1220 lines
47 KiB
Objective-C
//
|
|
// BWHockeyManager.m
|
|
//
|
|
// 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 "BWHockeyManager.h"
|
|
#import "BWGlobal.h"
|
|
#import "BWApp.h"
|
|
#import "NSString+HockeyAdditions.h"
|
|
#import "UIImage+HockeyAdditions.h"
|
|
#import <sys/sysctl.h>
|
|
#import <Foundation/Foundation.h>
|
|
|
|
// API defines - do not change
|
|
#define BETA_DOWNLOAD_TYPE_PROFILE @"profile"
|
|
#define BETA_UPDATE_RESULT @"result"
|
|
#define BETA_UPDATE_TITLE @"title"
|
|
#define BETA_UPDATE_SUBTITLE @"subtitle"
|
|
#define BETA_UPDATE_NOTES @"notes"
|
|
#define BETA_UPDATE_VERSION @"version"
|
|
#define BETA_UPDATE_TIMESTAMP @"timestamp"
|
|
#define BETA_UPDATE_APPSIZE @"appsize"
|
|
|
|
@interface BWHockeyManager ()
|
|
- (NSString *)getDevicePlatform_;
|
|
- (id)parseJSONResultString:(NSString *)jsonString;
|
|
- (void)connectionOpened_;
|
|
- (void)connectionClosed_;
|
|
- (BOOL)shouldCheckForUpdates;
|
|
- (void)startUsage;
|
|
- (void)stopUsage;
|
|
- (void)startManager;
|
|
- (void)showAuthorizationScreen:(NSString *)message image:(NSString *)image;
|
|
- (BOOL)canSendUserData;
|
|
- (BOOL)canSendUsageTime;
|
|
- (NSString *)currentUsageString;
|
|
- (NSString *)installationDateString;
|
|
- (NSString *)authenticationToken;
|
|
- (HockeyAuthorizationState)authorizationState;
|
|
|
|
@property (nonatomic, assign, getter=isUpdateAvailable) BOOL updateAvailable;
|
|
@property (nonatomic, assign, getter=isCheckInProgress) BOOL checkInProgress;
|
|
@property (nonatomic, retain) NSMutableData *receivedData;
|
|
@property (nonatomic, copy) NSDate *lastCheck;
|
|
@property (nonatomic, copy) NSArray *apps;
|
|
@property (nonatomic, retain) NSURLConnection *urlConnection;
|
|
@property (nonatomic, copy) NSDate *usageStartTimestamp;
|
|
@property (nonatomic, retain) UIView *authorizeView;
|
|
@property (nonatomic, retain) NSDictionary *trackerConfig;
|
|
@end
|
|
|
|
// hockey api error domain
|
|
typedef enum {
|
|
HockeyErrorUnknown,
|
|
HockeyAPIServerReturnedInvalidStatus,
|
|
HockeyAPIServerReturnedInvalidData,
|
|
HockeyAPIServerReturnedEmptyResponse,
|
|
HockeyAPIClientAuthorizationMissingSecret,
|
|
HockeyAPIClientCannotCreateConnection
|
|
} HockeyErrorReason;
|
|
static NSString *kHockeyErrorDomain = @"HockeyErrorDomain";
|
|
|
|
@implementation BWHockeyManager
|
|
|
|
@synthesize delegate = delegate_;
|
|
@synthesize updateURL = updateURL_;
|
|
@synthesize appIdentifier = appIdentifier_;
|
|
@synthesize urlConnection = urlConnection_;
|
|
@synthesize loggingEnabled = loggingEnabled_;
|
|
@synthesize checkInProgress = checkInProgress_;
|
|
@synthesize receivedData = receivedData_;
|
|
@synthesize sendUserData = sendUserData_;
|
|
@synthesize sendUsageTime = sendUsageTime_;
|
|
@synthesize allowUserToDisableSendData = allowUserToDisableSendData_;
|
|
@synthesize userAllowsSendUserData = userAllowsSendUserData_;
|
|
@synthesize userAllowsSendUsageTime = userAllowsSendUsageTime_;
|
|
@synthesize alwaysShowUpdateReminder = showUpdateReminder_;
|
|
@synthesize checkForUpdateOnLaunch = checkForUpdateOnLaunch_;
|
|
@synthesize compareVersionType = compareVersionType_;
|
|
@synthesize lastCheck = lastCheck_;
|
|
@synthesize showUserSettings = showUserSettings_;
|
|
@synthesize updateSetting = updateSetting_;
|
|
@synthesize apps = apps_;
|
|
@synthesize updateAvailable = updateAvailable_;
|
|
@synthesize usageStartTimestamp = usageStartTimestamp_;
|
|
@synthesize currentHockeyViewController = currentHockeyViewController_;
|
|
@synthesize showDirectInstallOption = showDirectInstallOption_;
|
|
@synthesize requireAuthorization = requireAuthorization_;
|
|
@synthesize authenticationSecret = authenticationSecret_;
|
|
@synthesize authorizeView = authorizeView_;
|
|
@synthesize isAppStoreEnvironment = isAppStoreEnvironment_;
|
|
@synthesize checkForTracker = checkForTracker_;
|
|
@synthesize trackerConfig = trackerConfig_;
|
|
@synthesize barStyle = barStyle_;
|
|
@synthesize modalPresentationStyle = modalPresentationStyle_;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark -
|
|
#pragma mark static
|
|
|
|
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000
|
|
+(BWHockeyManager *)sharedHockeyManager
|
|
{
|
|
static BWHockeyManager *sharedInstance = nil;
|
|
static dispatch_once_t pred;
|
|
|
|
if (sharedInstance) return sharedInstance;
|
|
|
|
dispatch_once(&pred, ^{
|
|
sharedInstance = [BWHockeyManager alloc];
|
|
sharedInstance = [sharedInstance init];
|
|
});
|
|
|
|
return sharedInstance;
|
|
}
|
|
#else
|
|
+ (BWHockeyManager *)sharedHockeyManager {
|
|
static BWHockeyManager *hockeyManager = nil;
|
|
|
|
if (hockeyManager == nil) {
|
|
hockeyManager = [[BWHockeyManager alloc] init];
|
|
}
|
|
|
|
return hockeyManager;
|
|
}
|
|
#endif
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark -
|
|
#pragma mark private
|
|
|
|
- (void)reportError_:(NSError *)error {
|
|
BWHockeyLog(@"Error: %@", [error localizedDescription]);
|
|
lastCheckFailed_ = YES;
|
|
|
|
// only show error if we enable that
|
|
if (showFeedback_) {
|
|
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyError")
|
|
message:[error localizedDescription]
|
|
delegate:nil
|
|
cancelButtonTitle:BWHockeyLocalize(@"OK") otherButtonTitles:nil];
|
|
[alert show];
|
|
[alert release];
|
|
showFeedback_ = NO;
|
|
}
|
|
}
|
|
|
|
- (NSString *)encodedAppIdentifier_ {
|
|
return (self.appIdentifier ? [self.appIdentifier bw_URLEncodedString] : [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"] bw_URLEncodedString]);
|
|
}
|
|
|
|
- (NSString *)getDevicePlatform_ {
|
|
size_t size;
|
|
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
|
|
char *answer = (char*)malloc(size);
|
|
sysctlbyname("hw.machine", answer, &size, NULL, 0);
|
|
NSString *platform = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding];
|
|
free(answer);
|
|
return platform;
|
|
}
|
|
|
|
- (void)connectionOpened_ {
|
|
if ([self.delegate respondsToSelector:@selector(connectionOpened)])
|
|
[(id)self.delegate connectionOpened];
|
|
}
|
|
|
|
- (void)connectionClosed_ {
|
|
if ([self.delegate respondsToSelector:@selector(connectionClosed)])
|
|
[(id)self.delegate connectionClosed];
|
|
}
|
|
|
|
- (void)startUsage {
|
|
self.usageStartTimestamp = [NSDate date];
|
|
BOOL newVersion = NO;
|
|
|
|
if (![[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeForVersionString]) {
|
|
newVersion = YES;
|
|
} else {
|
|
if ([(NSString *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeForVersionString] compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] != NSOrderedSame) {
|
|
newVersion = YES;
|
|
}
|
|
}
|
|
|
|
if (newVersion) {
|
|
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:[[NSDate date] timeIntervalSinceReferenceDate]] forKey:kDateOfVersionInstallation];
|
|
[[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kUsageTimeForVersionString];
|
|
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:0] forKey:kUsageTimeOfCurrentVersion];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
}
|
|
}
|
|
|
|
- (void)stopUsage {
|
|
double timeDifference = [[NSDate date] timeIntervalSinceReferenceDate] - [usageStartTimestamp_ timeIntervalSinceReferenceDate];
|
|
double previousTimeDifference = [(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeOfCurrentVersion] doubleValue];
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithDouble:previousTimeDifference + timeDifference] forKey:kUsageTimeOfCurrentVersion];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
}
|
|
|
|
- (NSString *)currentUsageString {
|
|
double currentUsageTime = [(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kUsageTimeOfCurrentVersion] doubleValue];
|
|
|
|
if (currentUsageTime > 0) {
|
|
// round (up) to 1 minute
|
|
return [NSString stringWithFormat:@"%.0f", ceil(currentUsageTime / 60.0)*60];
|
|
} else {
|
|
return @"0";
|
|
}
|
|
}
|
|
|
|
- (NSString *)installationDateString {
|
|
NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
|
|
[formatter setDateFormat:@"MM/dd/yyyy"];
|
|
return [formatter stringFromDate:[NSDate dateWithTimeIntervalSinceReferenceDate:[(NSNumber *)[[NSUserDefaults standardUserDefaults] valueForKey:kDateOfVersionInstallation] doubleValue]]];
|
|
}
|
|
|
|
- (NSString *)deviceIdentifier {
|
|
if ([[UIDevice currentDevice] respondsToSelector:@selector(uniqueIdentifier)]) {
|
|
return [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)];
|
|
}
|
|
else {
|
|
return @"invalid";
|
|
}
|
|
}
|
|
|
|
- (NSString *)authenticationToken {
|
|
return [BWmd5([NSString stringWithFormat:@"%@%@%@%@",
|
|
authenticationSecret_,
|
|
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"],
|
|
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"],
|
|
[self deviceIdentifier]
|
|
]
|
|
) lowercaseString];
|
|
}
|
|
|
|
- (HockeyAuthorizationState)authorizationState {
|
|
NSString *version = [[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAuthorizedVersion];
|
|
NSString *token = [[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAuthorizedToken];
|
|
|
|
if (version != nil && token != nil) {
|
|
if ([version compare:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]] == NSOrderedSame) {
|
|
// if it is denied, block the screen permanently
|
|
if ([token compare:[self authenticationToken]] != NSOrderedSame) {
|
|
return HockeyAuthorizationDenied;
|
|
} else {
|
|
return HockeyAuthorizationAllowed;
|
|
}
|
|
}
|
|
}
|
|
return HockeyAuthorizationPending;
|
|
}
|
|
|
|
- (void)checkUpdateAvailable_ {
|
|
// check if there is an update available
|
|
if (self.compareVersionType == HockeyComparisonResultGreater) {
|
|
self.updateAvailable = ([self.app.version versionCompare:self.currentAppVersion] == NSOrderedDescending);
|
|
} else {
|
|
self.updateAvailable = ([self.app.version compare:self.currentAppVersion] != NSOrderedSame);
|
|
}
|
|
}
|
|
|
|
- (void)loadAppCache_ {
|
|
NSData *savedHockeyData = [[NSUserDefaults standardUserDefaults] objectForKey:kArrayOfLastHockeyCheck];
|
|
NSArray *savedHockeyCheck = nil;
|
|
if (savedHockeyData) {
|
|
savedHockeyCheck = [NSKeyedUnarchiver unarchiveObjectWithData:savedHockeyData];
|
|
}
|
|
if (savedHockeyCheck) {
|
|
self.apps = [NSArray arrayWithArray:savedHockeyCheck];
|
|
[self checkUpdateAvailable_];
|
|
} else {
|
|
self.apps = nil;
|
|
}
|
|
}
|
|
|
|
- (void)saveAppCache_ {
|
|
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.apps];
|
|
[[NSUserDefaults standardUserDefaults] setObject:data forKey:kArrayOfLastHockeyCheck];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
}
|
|
|
|
- (UIWindow *)findVisibleWindow {
|
|
UIWindow *visibleWindow = nil;
|
|
|
|
// if the rootViewController property (available >= iOS 4.0) of the main window is set, we present the modal view controller on top of the rootViewController
|
|
NSArray *windows = [[UIApplication sharedApplication] windows];
|
|
for (UIWindow *window in windows) {
|
|
if (!window.hidden && !visibleWindow) {
|
|
visibleWindow = window;
|
|
}
|
|
if ([UIWindow instancesRespondToSelector:@selector(rootViewController)]) {
|
|
if ([window rootViewController]) {
|
|
visibleWindow = window;
|
|
BWHockeyLog(@"UIWindow with rootViewController found: %@", visibleWindow);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return visibleWindow;
|
|
}
|
|
|
|
- (BOOL)canSendUserData {
|
|
if (self.shouldSendUserData) {
|
|
if (self.allowUserToDisableSendData) {
|
|
return self.userAllowsSendUserData;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)canSendUsageTime {
|
|
if (self.shouldSendUsageTime) {
|
|
if (self.allowUserToDisableSendData) {
|
|
return self.userAllowsSendUsageTime;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark -
|
|
#pragma mark NSObject
|
|
|
|
- (id)init {
|
|
if ((self = [super init])) {
|
|
updateURL_ = nil;
|
|
appIdentifier_ = nil;
|
|
checkInProgress_ = NO;
|
|
dataFound = NO;
|
|
updateAvailable_ = NO;
|
|
lastCheckFailed_ = NO;
|
|
currentAppVersion_ = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
|
navController_ = nil;
|
|
authorizeView_ = nil;
|
|
requireAuthorization_ = NO;
|
|
authenticationSecret_= nil;
|
|
loggingEnabled_ = NO;
|
|
lastCheck_ = nil;
|
|
|
|
// check if we are really not in an app store environment
|
|
if ([[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"]) {
|
|
isAppStoreEnvironment_ = NO;
|
|
} else {
|
|
isAppStoreEnvironment_ = YES;
|
|
}
|
|
|
|
#if TARGET_IPHONE_SIMULATOR
|
|
isAppStoreEnvironment_ = NO;
|
|
#endif
|
|
|
|
// set defaults
|
|
self.showDirectInstallOption = NO;
|
|
self.requireAuthorization = NO;
|
|
self.sendUserData = YES;
|
|
self.sendUsageTime = YES;
|
|
self.allowUserToDisableSendData = YES;
|
|
self.alwaysShowUpdateReminder = YES;
|
|
self.checkForUpdateOnLaunch = YES;
|
|
self.showUserSettings = YES;
|
|
self.compareVersionType = HockeyComparisonResultGreater;
|
|
self.barStyle = UIBarStyleDefault;
|
|
self.modalPresentationStyle = UIModalPresentationFormSheet;
|
|
|
|
// load update setting from user defaults and check value
|
|
if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAutoUpdateSetting]) {
|
|
self.updateSetting = (HockeyUpdateSetting)[[NSUserDefaults standardUserDefaults] integerForKey:kHockeyAutoUpdateSetting];
|
|
} else {
|
|
self.updateSetting = HockeyUpdateCheckStartup;
|
|
}
|
|
|
|
if ([[NSUserDefaults standardUserDefaults] objectForKey:kDateOfLastHockeyCheck]) {
|
|
// we did write something else in the past, so for compatibility reasons do this
|
|
id tempLastCheck = [[NSUserDefaults standardUserDefaults] objectForKey:kDateOfLastHockeyCheck];
|
|
if ([tempLastCheck isKindOfClass:[NSDate class]]) {
|
|
lastCheck_ = tempLastCheck;
|
|
}
|
|
}
|
|
if (!lastCheck_) {
|
|
lastCheck_ = [NSDate distantPast];
|
|
}
|
|
|
|
if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAllowUserSetting]) {
|
|
self.userAllowsSendUserData = [[NSUserDefaults standardUserDefaults] boolForKey:kHockeyAllowUserSetting];
|
|
} else {
|
|
self.userAllowsSendUserData = YES;
|
|
}
|
|
|
|
if ([[NSUserDefaults standardUserDefaults] objectForKey:kHockeyAllowUsageSetting]) {
|
|
self.userAllowsSendUsageTime = [[NSUserDefaults standardUserDefaults] boolForKey:kHockeyAllowUsageSetting];
|
|
} else {
|
|
self.userAllowsSendUsageTime = YES;
|
|
}
|
|
|
|
if (!hockeyBundle()) {
|
|
NSLog(@"WARNING: Hockey.bundle is missing, make sure it is added!");
|
|
}
|
|
|
|
[self loadAppCache_];
|
|
|
|
[self startUsage];
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(startManager)
|
|
name:BWHockeyNetworkBecomeReachable
|
|
object:nil];
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(stopUsage)
|
|
name:UIApplicationWillTerminateNotification
|
|
object:nil];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:BWHockeyNetworkBecomeReachable object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
|
|
|
|
BW_IF_IOS4_OR_GREATER(
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
|
|
)
|
|
self.delegate = nil;
|
|
|
|
[urlConnection_ cancel];
|
|
self.urlConnection = nil;
|
|
|
|
[navController_ release];
|
|
[authorizeView_ release];
|
|
[currentHockeyViewController_ release];
|
|
[updateURL_ release];
|
|
[apps_ release];
|
|
[receivedData_ release];
|
|
[lastCheck_ release];
|
|
[usageStartTimestamp_ release];
|
|
[authenticationSecret_ release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark -
|
|
#pragma mark BetaUpdateUI
|
|
|
|
- (BWHockeyViewController *)hockeyViewController:(BOOL)modal {
|
|
return [[[BWHockeyViewController alloc] init:self modal:modal] autorelease];
|
|
}
|
|
|
|
- (void)showUpdateView {
|
|
if (isAppStoreEnvironment_) {
|
|
NSLog(@"this should not be called from an app store build.");
|
|
return;
|
|
}
|
|
|
|
if (currentHockeyViewController_) {
|
|
BWHockeyLog(@"update view already visible, aborting");
|
|
return;
|
|
}
|
|
|
|
UIViewController *parentViewController = nil;
|
|
|
|
if ([[self delegate] respondsToSelector:@selector(viewControllerForHockeyController:)]) {
|
|
parentViewController = [[self delegate] viewControllerForHockeyController:self];
|
|
}
|
|
|
|
UIWindow *visibleWindow = [self findVisibleWindow];
|
|
|
|
if (parentViewController == nil && [UIWindow instancesRespondToSelector:@selector(rootViewController)]) {
|
|
parentViewController = [visibleWindow rootViewController];
|
|
}
|
|
|
|
// use topmost modal view
|
|
while (parentViewController.modalViewController) {
|
|
parentViewController = parentViewController.modalViewController;
|
|
}
|
|
|
|
// special addition to get rootViewController from three20 which has it's own controller handling
|
|
if (NSClassFromString(@"TTNavigator")) {
|
|
parentViewController = [[NSClassFromString(@"TTNavigator") performSelector:(NSSelectorFromString(@"navigator"))] visibleViewController];
|
|
}
|
|
|
|
if (navController_ != nil) [navController_ release];
|
|
|
|
BWHockeyViewController *hockeyViewController = [self hockeyViewController:YES];
|
|
navController_ = [[UINavigationController alloc] initWithRootViewController:hockeyViewController];
|
|
navController_.navigationBar.barStyle = barStyle_;
|
|
navController_.modalPresentationStyle = modalPresentationStyle_;
|
|
|
|
if (parentViewController) {
|
|
if ([navController_ respondsToSelector:@selector(setModalTransitionStyle:)]) {
|
|
navController_.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
|
|
}
|
|
|
|
// page sheet for the iPad
|
|
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && [navController_ respondsToSelector:@selector(setModalPresentationStyle:)]) {
|
|
navController_.modalPresentationStyle = UIModalPresentationFormSheet;
|
|
}
|
|
|
|
[parentViewController presentModalViewController:navController_ animated:YES];
|
|
} else {
|
|
// if not, we add a subview to the window. A bit hacky but should work in most circumstances.
|
|
// Also, we don't get a nice animation for free, but hey, this is for beta not production users ;)
|
|
BWHockeyLog(@"No rootViewController found, using UIWindow-approach: %@", visibleWindow);
|
|
[visibleWindow addSubview:navController_.view];
|
|
}
|
|
}
|
|
|
|
|
|
- (void)showCheckForUpdateAlert_ {
|
|
if (isAppStoreEnvironment_) return;
|
|
|
|
if (!updateAlertShowing_) {
|
|
if ([self.app.mandatory boolValue] ) {
|
|
UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyUpdateAvailable")
|
|
message:[NSString stringWithFormat:BWHockeyLocalize(@"HockeyUpdateAlertMandatoryTextWithAppVersion"), [self.app nameAndVersionString]]
|
|
delegate:self
|
|
cancelButtonTitle:BWHockeyLocalize(@"HockeyInstallUpdate")
|
|
otherButtonTitles:nil
|
|
] autorelease];
|
|
[alertView setTag:2];
|
|
[alertView show];
|
|
updateAlertShowing_ = YES;
|
|
} else {
|
|
UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyUpdateAvailable")
|
|
message:[NSString stringWithFormat:BWHockeyLocalize(@"HockeyUpdateAlertTextWithAppVersion"), [self.app nameAndVersionString]]
|
|
delegate:self
|
|
cancelButtonTitle:BWHockeyLocalize(@"HockeyIgnore")
|
|
otherButtonTitles:BWHockeyLocalize(@"HockeyShowUpdate"), nil
|
|
] autorelease];
|
|
BW_IF_IOS4_OR_GREATER(
|
|
if (self.isShowingDirectInstallOption) {
|
|
[alertView addButtonWithTitle:BWHockeyLocalize(@"HockeyInstallUpdate")];
|
|
}
|
|
)
|
|
[alertView setTag:0];
|
|
[alertView show];
|
|
updateAlertShowing_ = YES;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// nag the user with neverending alerts if we cannot find out the window for presenting the covering sheet
|
|
- (void)alertFallback:(NSString *)message {
|
|
UIAlertView *alertView = [[[UIAlertView alloc] initWithTitle:nil
|
|
message:message
|
|
delegate:self
|
|
cancelButtonTitle:@"Ok"
|
|
otherButtonTitles:nil
|
|
] autorelease];
|
|
[alertView setTag:1];
|
|
[alertView show];
|
|
}
|
|
|
|
|
|
// open an authorization screen
|
|
- (void)showAuthorizationScreen:(NSString *)message image:(NSString *)image {
|
|
self.authorizeView = nil;
|
|
|
|
UIWindow *visibleWindow = [self findVisibleWindow];
|
|
if (visibleWindow == nil) {
|
|
[self alertFallback:message];
|
|
return;
|
|
}
|
|
|
|
CGRect frame = [visibleWindow frame];
|
|
|
|
self.authorizeView = [[[UIView alloc] initWithFrame:frame] autorelease];
|
|
UIImageView *backgroundView = [[[UIImageView alloc] initWithImage:[UIImage bw_imageNamed:@"bg.png" bundle:kHockeyBundleName]] autorelease];
|
|
backgroundView.contentMode = UIViewContentModeScaleAspectFill;
|
|
backgroundView.frame = frame;
|
|
[self.authorizeView addSubview:backgroundView];
|
|
|
|
if (image != nil) {
|
|
UIImageView *imageView = [[[UIImageView alloc] initWithImage:[UIImage bw_imageNamed:image bundle:kHockeyBundleName]] autorelease];
|
|
imageView.contentMode = UIViewContentModeCenter;
|
|
imageView.frame = frame;
|
|
[self.authorizeView addSubview:imageView];
|
|
}
|
|
|
|
if (message != nil) {
|
|
frame.origin.x = 20;
|
|
frame.origin.y = frame.size.height - 140;
|
|
frame.size.width -= 40;
|
|
frame.size.height = 40;
|
|
|
|
UILabel *label = [[[UILabel alloc] initWithFrame:frame] autorelease];
|
|
label.text = message;
|
|
label.textAlignment = UITextAlignmentCenter;
|
|
label.numberOfLines = 2;
|
|
label.backgroundColor = [UIColor clearColor];
|
|
|
|
[self.authorizeView addSubview:label];
|
|
}
|
|
|
|
[visibleWindow addSubview:self.authorizeView];
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark -
|
|
#pragma mark JSONParsing
|
|
|
|
- (id)parseJSONResultString:(NSString *)jsonString {
|
|
NSError *error = nil;
|
|
id feedResult = nil;
|
|
|
|
#if BW_NATIVE_JSON_AVAILABLE
|
|
feedResult = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
|
|
#else
|
|
id nsjsonClass = NSClassFromString(@"NSJSONSerialization");
|
|
SEL nsjsonSelect = NSSelectorFromString(@"JSONObjectWithData:options:error:");
|
|
SEL sbJSONSelector = NSSelectorFromString(@"JSONValue");
|
|
SEL jsonKitSelector = NSSelectorFromString(@"objectFromJSONStringWithParseOptions:error:");
|
|
SEL yajlSelector = NSSelectorFromString(@"yajl_JSONWithOptions:error:");
|
|
|
|
if (nsjsonClass && [nsjsonClass respondsToSelector:nsjsonSelect]) {
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[nsjsonClass methodSignatureForSelector:nsjsonSelect]];
|
|
invocation.target = nsjsonClass;
|
|
invocation.selector = nsjsonSelect;
|
|
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
|
|
[invocation setArgument:&jsonData atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
|
|
NSUInteger readOptions = kNilOptions;
|
|
[invocation setArgument:&readOptions atIndex:3];
|
|
[invocation setArgument:&error atIndex:4];
|
|
[invocation invoke];
|
|
[invocation getReturnValue:&feedResult];
|
|
} else if (jsonKitSelector && [jsonString respondsToSelector:jsonKitSelector]) {
|
|
// first try JSONkit
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jsonString methodSignatureForSelector:jsonKitSelector]];
|
|
invocation.target = jsonString;
|
|
invocation.selector = jsonKitSelector;
|
|
int parseOptions = 0;
|
|
[invocation setArgument:&parseOptions atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
|
|
[invocation setArgument:&error atIndex:3];
|
|
[invocation invoke];
|
|
[invocation getReturnValue:&feedResult];
|
|
} else if (sbJSONSelector && [jsonString respondsToSelector:sbJSONSelector]) {
|
|
// now try SBJson
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jsonString methodSignatureForSelector:sbJSONSelector]];
|
|
invocation.target = jsonString;
|
|
invocation.selector = sbJSONSelector;
|
|
[invocation invoke];
|
|
[invocation getReturnValue:&feedResult];
|
|
} else if (yajlSelector && [jsonString respondsToSelector:yajlSelector]) {
|
|
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[jsonString methodSignatureForSelector:yajlSelector]];
|
|
invocation.target = jsonString;
|
|
invocation.selector = yajlSelector;
|
|
|
|
NSUInteger yajlParserOptions = 0;
|
|
[invocation setArgument:&yajlParserOptions atIndex:2]; // arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
|
|
[invocation setArgument:&error atIndex:3];
|
|
|
|
[invocation invoke];
|
|
[invocation getReturnValue:&feedResult];
|
|
} else {
|
|
NSLog(@"Error: You need a JSON Framework in your runtime!");
|
|
[self doesNotRecognizeSelector:_cmd];
|
|
}
|
|
#endif
|
|
|
|
if (error) {
|
|
BWHockeyLog(@"Error while parsing response feed: %@", [error localizedDescription]);
|
|
[self reportError_:error];
|
|
return nil;
|
|
}
|
|
|
|
return feedResult;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark -
|
|
#pragma mark RequestComments
|
|
|
|
- (BOOL)shouldCheckForUpdates {
|
|
BOOL checkForUpdate = NO;
|
|
switch (self.updateSetting) {
|
|
case HockeyUpdateCheckStartup:
|
|
checkForUpdate = YES;
|
|
break;
|
|
case HockeyUpdateCheckDaily: {
|
|
NSTimeInterval dateDiff = fabs([self.lastCheck timeIntervalSinceNow]);
|
|
if (dateDiff != 0)
|
|
dateDiff = dateDiff / (60*60*24);
|
|
|
|
checkForUpdate = (dateDiff >= 1);
|
|
break;
|
|
}
|
|
case HockeyUpdateCheckManually:
|
|
checkForUpdate = NO;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return checkForUpdate;
|
|
}
|
|
|
|
- (void)checkForAuthorization {
|
|
NSMutableString *parameter = [NSMutableString stringWithFormat:@"api/2/apps/%@", [self encodedAppIdentifier_]];
|
|
|
|
[parameter appendFormat:@"?format=json&authorize=yes&app_version=%@&udid=%@",
|
|
[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] bw_URLEncodedString],
|
|
([self isAppStoreEnvironment] ? @"appstore" : [[self deviceIdentifier] bw_URLEncodedString])
|
|
];
|
|
|
|
// build request & send
|
|
NSString *url = [NSString stringWithFormat:@"%@%@", self.updateURL, parameter];
|
|
BWHockeyLog(@"sending api request to %@", url);
|
|
|
|
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:1 timeoutInterval:10.0];
|
|
[request setHTTPMethod:@"GET"];
|
|
[request setValue:@"Hockey/iOS" forHTTPHeaderField:@"User-Agent"];
|
|
|
|
NSURLResponse *response = nil;
|
|
NSError *error = NULL;
|
|
BOOL failed = YES;
|
|
|
|
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
|
|
|
|
if ([responseData length]) {
|
|
NSString *responseString = [[[NSString alloc] initWithBytes:[responseData bytes] length:[responseData length] encoding: NSUTF8StringEncoding] autorelease];
|
|
|
|
NSDictionary *feedDict = (NSDictionary *)[self parseJSONResultString:responseString];
|
|
|
|
// server returned empty response?
|
|
if (![feedDict count]) {
|
|
[self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedEmptyResponse userInfo:
|
|
[NSDictionary dictionaryWithObjectsAndKeys:@"Server returned empty response.", NSLocalizedDescriptionKey, nil]]];
|
|
return;
|
|
} else {
|
|
BWHockeyLog(@"Received API response: %@", responseString);
|
|
NSString *token = [[feedDict objectForKey:@"authcode"] lowercaseString];
|
|
failed = NO;
|
|
if ([[self authenticationToken] compare:token] == NSOrderedSame) {
|
|
// identical token, activate this version
|
|
|
|
// store the new data
|
|
[[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kHockeyAuthorizedVersion];
|
|
[[NSUserDefaults standardUserDefaults] setObject:token forKey:kHockeyAuthorizedToken];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
|
|
self.requireAuthorization = NO;
|
|
self.authorizeView = nil;
|
|
|
|
// now continue with an update check right away
|
|
if (self.checkForUpdateOnLaunch) {
|
|
[self checkForUpdate];
|
|
}
|
|
} else {
|
|
// different token, block this version
|
|
BWHockeyLog(@"AUTH FAILURE: %@", [self authenticationToken]);
|
|
|
|
// store the new data
|
|
[[NSUserDefaults standardUserDefaults] setObject:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] forKey:kHockeyAuthorizedVersion];
|
|
[[NSUserDefaults standardUserDefaults] setObject:token forKey:kHockeyAuthorizedToken];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
|
|
[self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationDenied") image:@"authorize_denied.png"];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (failed) {
|
|
[self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationOffline") image:@"authorize_request.png"];
|
|
}
|
|
}
|
|
|
|
- (void)checkForUpdate {
|
|
if (self.requireAuthorization) return;
|
|
if (self.isUpdateAvailable && [self.app.mandatory boolValue]) {
|
|
[self showCheckForUpdateAlert_];
|
|
return;
|
|
}
|
|
[self checkForUpdateShowFeedback:NO];
|
|
}
|
|
|
|
- (void)checkForUpdateShowFeedback:(BOOL)feedback {
|
|
if (self.isCheckInProgress) return;
|
|
|
|
showFeedback_ = feedback;
|
|
self.checkInProgress = YES;
|
|
|
|
// do we need to update?
|
|
if (![self shouldCheckForUpdates] && !currentHockeyViewController_) {
|
|
BWHockeyLog(@"update not needed right now");
|
|
self.checkInProgress = NO;
|
|
return;
|
|
}
|
|
|
|
NSMutableString *parameter = [NSMutableString stringWithFormat:@"api/2/apps/%@?format=json&udid=%@",
|
|
[[self encodedAppIdentifier_] bw_URLEncodedString],
|
|
([self isAppStoreEnvironment] ? @"appstore" : [[self deviceIdentifier] bw_URLEncodedString])];
|
|
|
|
// add additional statistics if user didn't disable flag
|
|
if ([self canSendUserData]) {
|
|
[parameter appendFormat:@"&app_version=%@&os=iOS&os_version=%@&device=%@&lang=%@&first_start_at=%@",
|
|
[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"] bw_URLEncodedString],
|
|
[[[UIDevice currentDevice] systemVersion] bw_URLEncodedString],
|
|
[[self getDevicePlatform_] bw_URLEncodedString],
|
|
[[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0] bw_URLEncodedString],
|
|
[[self installationDateString] bw_URLEncodedString]
|
|
];
|
|
if ([self canSendUsageTime]) {
|
|
[parameter appendFormat:@"&usage_time=%@",
|
|
[[self currentUsageString] bw_URLEncodedString]
|
|
];
|
|
}
|
|
}
|
|
|
|
if ([self checkForTracker]) {
|
|
[parameter appendFormat:@"&jmc=yes"];
|
|
}
|
|
|
|
// build request & send
|
|
NSString *url = [NSString stringWithFormat:@"%@%@", self.updateURL, parameter];
|
|
BWHockeyLog(@"sending api request to %@", url);
|
|
|
|
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:1 timeoutInterval:10.0];
|
|
[request setHTTPMethod:@"GET"];
|
|
[request setValue:@"Hockey/iOS" forHTTPHeaderField:@"User-Agent"];
|
|
[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
|
|
|
|
self.urlConnection = [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
|
|
if (!urlConnection_) {
|
|
self.checkInProgress = NO;
|
|
[self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIClientCannotCreateConnection userInfo:
|
|
[NSDictionary dictionaryWithObjectsAndKeys:@"Url Connection could not be created.", NSLocalizedDescriptionKey, nil]]];
|
|
}
|
|
}
|
|
|
|
- (BOOL)initiateAppDownload {
|
|
if ([self isAppStoreEnvironment]) return NO;
|
|
|
|
if (!self.isUpdateAvailable) {
|
|
BWHockeyLog(@"Warning: No update available. Aborting.");
|
|
return NO;
|
|
}
|
|
|
|
BW_IF_PRE_IOS4
|
|
(
|
|
NSString *message = [NSString stringWithFormat:BWHockeyLocalize(@"HockeyiOS3Message"), self.updateURL];
|
|
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyWarning") message:message delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil] autorelease];
|
|
[alert show];
|
|
return NO;
|
|
)
|
|
|
|
#if TARGET_IPHONE_SIMULATOR
|
|
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyWarning") message:BWHockeyLocalize(@"HockeySimulatorMessage") delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil] autorelease];
|
|
[alert show];
|
|
return NO;
|
|
#endif
|
|
|
|
NSString *extraParameter = [NSString string];
|
|
if ([self canSendUserData]) {
|
|
extraParameter = [NSString stringWithFormat:@"&udid=%@", [self deviceIdentifier]];
|
|
}
|
|
|
|
NSString *hockeyAPIURL = [NSString stringWithFormat:@"%@api/2/apps/%@?format=plist%@", self.updateURL, [self encodedAppIdentifier_], extraParameter];
|
|
NSString *iOSUpdateURL = [NSString stringWithFormat:@"itms-services://?action=download-manifest&url=%@", [hockeyAPIURL bw_URLEncodedString]];
|
|
|
|
BWHockeyLog(@"API Server Call: %@, calling iOS with %@", hockeyAPIURL, iOSUpdateURL);
|
|
BOOL success = [[UIApplication sharedApplication] openURL:[NSURL URLWithString:iOSUpdateURL]];
|
|
BWHockeyLog(@"System returned: %d", success);
|
|
return success;
|
|
}
|
|
|
|
|
|
// checks wether this app version is authorized
|
|
- (BOOL)appVersionIsAuthorized {
|
|
if (self.requireAuthorization && !authenticationSecret_) {
|
|
[self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIClientAuthorizationMissingSecret userInfo:
|
|
[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication secret is not set but required.", NSLocalizedDescriptionKey, nil]]];
|
|
|
|
return NO;
|
|
}
|
|
|
|
if (!self.requireAuthorization) {
|
|
self.authorizeView = nil;
|
|
return YES;
|
|
}
|
|
|
|
HockeyAuthorizationState state = [self authorizationState];
|
|
if (state == HockeyAuthorizationDenied) {
|
|
[self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationDenied") image:@"authorize_denied.png"];
|
|
} else if (state == HockeyAuthorizationAllowed) {
|
|
self.requireAuthorization = NO;
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
|
|
// begin the startup process
|
|
- (void)startManager {
|
|
if (![self appVersionIsAuthorized]) {
|
|
if ([self authorizationState] == HockeyAuthorizationPending) {
|
|
[self showAuthorizationScreen:BWHockeyLocalize(@"HockeyAuthorizationProgress") image:@"authorize_request.png"];
|
|
|
|
[self performSelector:@selector(checkForAuthorization) withObject:nil afterDelay:0.0f];
|
|
}
|
|
} else {
|
|
if ([self shouldCheckForUpdates]) {
|
|
[self performSelector:@selector(checkForUpdate) withObject:nil afterDelay:0.0f];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark -
|
|
#pragma mark NSURLRequest
|
|
|
|
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse {
|
|
[self connectionOpened_];
|
|
NSURLRequest *newRequest = request;
|
|
if (redirectResponse) {
|
|
newRequest = nil;
|
|
}
|
|
return newRequest;
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
|
|
if ([response respondsToSelector:@selector(statusCode)]) {
|
|
int statusCode = [((NSHTTPURLResponse *)response) statusCode];
|
|
if (statusCode == 404) {
|
|
[connection cancel]; // stop connecting; no more delegate messages
|
|
NSString *errorStr = [NSString stringWithFormat:@"Hockey API received HTTP Status Code %d", statusCode];
|
|
[self connectionClosed_];
|
|
[self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedInvalidStatus userInfo:
|
|
[NSDictionary dictionaryWithObjectsAndKeys:errorStr, NSLocalizedDescriptionKey, nil]]];
|
|
return;
|
|
}
|
|
}
|
|
|
|
self.receivedData = [NSMutableData data];
|
|
[receivedData_ setLength:0];
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
|
|
[receivedData_ appendData:data];
|
|
}
|
|
|
|
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
|
|
[self connectionClosed_];
|
|
self.receivedData = nil;
|
|
self.urlConnection = nil;
|
|
self.checkInProgress = NO;
|
|
[self reportError_:error];
|
|
}
|
|
|
|
// api call returned, parsing
|
|
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
|
|
[self connectionClosed_];
|
|
self.checkInProgress = NO;
|
|
|
|
if ([self.receivedData length]) {
|
|
NSString *responseString = [[[NSString alloc] initWithBytes:[receivedData_ bytes] length:[receivedData_ length] encoding: NSUTF8StringEncoding] autorelease];
|
|
BWHockeyLog(@"Received API response: %@", responseString);
|
|
|
|
id json = [self parseJSONResultString:responseString];
|
|
self.trackerConfig = ([self checkForTracker] ? [json valueForKey:@"tracker"] : nil);
|
|
|
|
if (![self isAppStoreEnvironment]) {
|
|
NSArray *feedArray = (NSArray *)([self checkForTracker] ? [json valueForKey:@"versions"] : json);
|
|
|
|
self.receivedData = nil;
|
|
self.urlConnection = nil;
|
|
|
|
// remember that we just checked the server
|
|
self.lastCheck = [NSDate date];
|
|
|
|
// server returned empty response?
|
|
if (![feedArray count]) {
|
|
[self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedEmptyResponse userInfo:
|
|
[NSDictionary dictionaryWithObjectsAndKeys:@"Server returned empty response.", NSLocalizedDescriptionKey, nil]]];
|
|
return;
|
|
} else {
|
|
lastCheckFailed_ = NO;
|
|
}
|
|
|
|
|
|
NSString *currentAppCacheVersion = [[[self app].version copy] autorelease];
|
|
|
|
// clear cache and reload with new data
|
|
NSMutableArray *tmpApps = [NSMutableArray arrayWithCapacity:[feedArray count]];
|
|
for (NSDictionary *dict in feedArray) {
|
|
BWApp *app = [BWApp appFromDict:dict];
|
|
if ([app isValid]) {
|
|
[tmpApps addObject:app];
|
|
} else {
|
|
[self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedInvalidData userInfo:
|
|
[NSDictionary dictionaryWithObjectsAndKeys:@"Invalid data received from server.", NSLocalizedDescriptionKey, nil]]];
|
|
}
|
|
}
|
|
// only set if different!
|
|
if (![self.apps isEqualToArray:tmpApps]) {
|
|
self.apps = [[tmpApps copy] autorelease];
|
|
}
|
|
[self saveAppCache_];
|
|
|
|
[self checkUpdateAvailable_];
|
|
BOOL newVersionDiffersFromCachedVersion = ![self.app.version isEqualToString:currentAppCacheVersion];
|
|
|
|
// show alert if we are on the latest & greatest
|
|
if (showFeedback_ && !self.isUpdateAvailable) {
|
|
// use currentVersionString, as version still may differ (e.g. server: 1.2, client: 1.3)
|
|
NSString *versionString = [self currentAppVersion];
|
|
NSString *shortVersionString = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
|
|
shortVersionString = shortVersionString ? [NSString stringWithFormat:@"%@ ", shortVersionString] : @"";
|
|
versionString = [shortVersionString length] ? [NSString stringWithFormat:@"(%@)", versionString] : versionString;
|
|
NSString *currentVersionString = [NSString stringWithFormat:@"%@ %@ %@%@", self.app.name, BWHockeyLocalize(@"HockeyVersion"), shortVersionString, versionString];
|
|
NSString *alertMsg = [NSString stringWithFormat:BWHockeyLocalize(@"HockeyNoUpdateNeededMessage"), currentVersionString];
|
|
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:BWHockeyLocalize(@"HockeyNoUpdateNeededTitle") message:alertMsg delegate:nil cancelButtonTitle:BWHockeyLocalize(@"HockeyOK") otherButtonTitles:nil];
|
|
[alert show];
|
|
[alert release];
|
|
}
|
|
|
|
if (self.isUpdateAvailable && (self.alwaysShowUpdateReminder || newVersionDiffersFromCachedVersion || [self.app.mandatory boolValue])) {
|
|
if (updateAvailable_ && !currentHockeyViewController_) {
|
|
[self showCheckForUpdateAlert_];
|
|
}
|
|
}
|
|
showFeedback_ = NO;
|
|
}
|
|
} else {
|
|
[self reportError_:[NSError errorWithDomain:kHockeyErrorDomain code:HockeyAPIServerReturnedEmptyResponse userInfo:
|
|
[NSDictionary dictionaryWithObjectsAndKeys:@"Server returned an empty response.", NSLocalizedDescriptionKey, nil]]];
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark -
|
|
#pragma mark Properties
|
|
|
|
- (void)setCurrentHockeyViewController:(BWHockeyViewController *)aCurrentHockeyViewController {
|
|
if (currentHockeyViewController_ != aCurrentHockeyViewController) {
|
|
[currentHockeyViewController_ release];
|
|
currentHockeyViewController_ = [aCurrentHockeyViewController retain];
|
|
//BWHockeyLog(@"active hockey view controller: %@", aCurrentHockeyViewController);
|
|
}
|
|
}
|
|
|
|
- (void)setUpdateURL:(NSString *)anUpdateURL {
|
|
// ensure url ends with a trailing slash
|
|
if (![anUpdateURL hasSuffix:@"/"]) {
|
|
anUpdateURL = [NSString stringWithFormat:@"%@/", anUpdateURL];
|
|
}
|
|
|
|
BW_IF_IOS4_OR_GREATER(
|
|
// register/deregister logic
|
|
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
|
|
if (!updateURL_ && anUpdateURL) {
|
|
[dnc addObserver:self selector:@selector(startUsage) name:UIApplicationDidBecomeActiveNotification object:nil];
|
|
[dnc addObserver:self selector:@selector(stopUsage) name:UIApplicationWillResignActiveNotification object:nil];
|
|
} else if (updateURL_ && !anUpdateURL) {
|
|
[dnc removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
|
[dnc removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
|
|
}
|
|
)
|
|
|
|
if (updateURL_ != anUpdateURL) {
|
|
[updateURL_ release];
|
|
updateURL_ = [anUpdateURL copy];
|
|
}
|
|
|
|
[self performSelector:@selector(startManager) withObject:nil afterDelay:0.0f];
|
|
}
|
|
|
|
- (void)setAppIdentifier:(NSString *)anAppIdentifier {
|
|
if (appIdentifier_ != anAppIdentifier) {
|
|
[appIdentifier_ release];
|
|
appIdentifier_ = [anAppIdentifier copy];
|
|
}
|
|
|
|
[self setUpdateURL:@"https://rink.hockeyapp.net/"];
|
|
}
|
|
|
|
- (void)setCheckForUpdateOnLaunch:(BOOL)flag {
|
|
if (checkForUpdateOnLaunch_ != flag) {
|
|
checkForUpdateOnLaunch_ = flag;
|
|
BW_IF_IOS4_OR_GREATER(
|
|
NSNotificationCenter *dnc = [NSNotificationCenter defaultCenter];
|
|
if (flag) {
|
|
[dnc addObserver:self selector:@selector(checkForUpdate) name:UIApplicationDidBecomeActiveNotification object:nil];
|
|
} else {
|
|
[dnc removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
- (void)setUserAllowsSendUserData:(BOOL)flag {
|
|
userAllowsSendUserData_ = flag;
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:userAllowsSendUserData_] forKey:kHockeyAllowUserSetting];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
}
|
|
|
|
- (void)setUserAllowsSendUsageTime:(BOOL)flag {
|
|
userAllowsSendUsageTime_ = flag;
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:userAllowsSendUsageTime_] forKey:kHockeyAllowUsageSetting];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
}
|
|
|
|
- (NSString *)currentAppVersion {
|
|
return currentAppVersion_;
|
|
}
|
|
|
|
|
|
- (void)setUpdateSetting:(HockeyUpdateSetting)anUpdateSetting {
|
|
if (anUpdateSetting > HockeyUpdateCheckManually) {
|
|
updateSetting_ = HockeyUpdateCheckStartup;
|
|
}
|
|
|
|
updateSetting_ = anUpdateSetting;
|
|
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:updateSetting_] forKey:kHockeyAutoUpdateSetting];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
}
|
|
|
|
- (void)setLastCheck:(NSDate *)aLastCheck {
|
|
if (lastCheck_ != aLastCheck) {
|
|
[lastCheck_ release];
|
|
lastCheck_ = [aLastCheck copy];
|
|
|
|
[[NSUserDefaults standardUserDefaults] setObject:lastCheck_ forKey:kDateOfLastHockeyCheck];
|
|
[[NSUserDefaults standardUserDefaults] synchronize];
|
|
}
|
|
}
|
|
|
|
- (void)setApps:(NSArray *)anApps {
|
|
if (apps_ != anApps || !apps_) {
|
|
[apps_ release];
|
|
[self willChangeValueForKey:@"apps"];
|
|
|
|
// populate with default values (if empty)
|
|
if (![anApps count]) {
|
|
BWApp *defaultApp = [[[BWApp alloc] init] autorelease];
|
|
defaultApp.name = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
|
|
defaultApp.version = currentAppVersion_;
|
|
defaultApp.shortVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
|
|
apps_ = [[NSArray arrayWithObject:defaultApp] retain];
|
|
}else {
|
|
apps_ = [anApps copy];
|
|
}
|
|
[self didChangeValueForKey:@"apps"];
|
|
}
|
|
}
|
|
|
|
- (BWApp *)app {
|
|
BWApp *app = [apps_ objectAtIndex:0];
|
|
return app;
|
|
}
|
|
|
|
- (void)setAuthorizeView:(UIView *)anAuthorizeView {
|
|
if (authorizeView_ != anAuthorizeView) {
|
|
[authorizeView_ removeFromSuperview];
|
|
[authorizeView_ release];
|
|
authorizeView_ = [anAuthorizeView retain];
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark -
|
|
#pragma mark UIAlertViewDelegate
|
|
|
|
// invoke the selected action from the actionsheet for a location element
|
|
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
|
|
if ([alertView tag] == 2) {
|
|
[self initiateAppDownload];
|
|
updateAlertShowing_ = NO;
|
|
return;
|
|
} else if ([alertView tag] == 1) {
|
|
[self alertFallback:[alertView message]];
|
|
return;
|
|
}
|
|
|
|
updateAlertShowing_ = NO;
|
|
if (buttonIndex == [alertView firstOtherButtonIndex]) {
|
|
// YES button has been clicked
|
|
[self showUpdateView];
|
|
} else if (buttonIndex == [alertView firstOtherButtonIndex] + 1) {
|
|
// YES button has been clicked
|
|
[self initiateAppDownload];
|
|
}
|
|
}
|
|
|
|
@end
|