Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
328 lines (270 sloc) 8.15 KB
/*
* The Cheat - The legendary universal game trainer for Mac OS X.
* http://www.brokenzipper.com/trac/wiki/TheCheat
*
* Copyright (c) 2003-2011, Charles McGarvey et al.
*
* Distributable under the terms and conditions of the 2-clause BSD
* license; see the file COPYING for the legal text of the license.
*/
#import "ChazUpdate.h"
// an NSError that an have an error statement associated with it
@interface ChazUpdateError : NSError
{
NSString *_errorString;
}
+ (id)errorWithString:(NSString *)string;
- (id)initWithString:(NSString *)string;
- (void)setErrorString:(NSString *)string;
- (NSString *)localizedDescription;
@end
@implementation ChazUpdateError
+ (id)errorWithString:(NSString *)string
{
return [[[ChazUpdateError alloc] initWithString:string] autorelease];
}
- (id)initWithString:(NSString *)string
{
if ( self = [super initWithDomain:@"ChazUpdateError" code:1 userInfo:nil] ) {
[self setErrorString:string];
}
return self;
}
- (void)setErrorString:(NSString *)string
{
[string retain];
[_errorString release];
_errorString = string;
}
- (NSString *)localizedDescription
{
return _errorString;
}
@end
@interface ChazUpdate : NSObject
{
NSString *_name;
NSURLConnection *_connection;
NSHTTPURLResponse *_response;
NSMutableData *_responseData;
BOOL _verbose;
}
- (id)initWithURL:(id)url name:(NSString *)name verbose:(BOOL)verbose;
- (void)kill;
@end
@implementation ChazUpdate
#pragma mark Initialization
- (id)initWithURL:(id)url name:(NSString *)name verbose:(BOOL)verbose
{
NSURL *theURL;
if ( self = [super init] )
{
// figure out what type of URL was passed
if ( [url isKindOfClass:[NSURL class]] ) {
theURL = url;
}
else if ( [url isKindOfClass:[NSString class]] ) {
theURL = [[[NSURL alloc] initWithString:url] autorelease];
}
else {
if ( verbose ) {
NSRunAlertPanel( @"Update Check Failed",
@"Could not determine the location of the version information file.", @"OK", nil, nil );
}
[self release];
return nil;
}
// save the name
if ( name ) {
_name = [name retain];
}
else {
_name = [[NSString stringWithString:@""] retain];
}
_verbose = verbose;
// create the request
_connection = [[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:theURL
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:30.0] delegate:self] retain];
_response = nil;
_responseData = [[NSMutableData alloc] init];
}
return self;
}
- (void)dealloc
{
// cleanup
[_name release];
[_connection release];
[_response release];
[_responseData release];
[super dealloc];
}
- (void)kill
{
[self release];
self = nil;
}
#pragma mark NSURLConnection Delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if ( _response ) {
// another response, chop the data
[_responseData setLength:0];
[_response release];
}
_response = [response retain];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// just save the data
[_responseData appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSDictionary *plistDictionary;
NSPropertyListFormat plistFormat;
NSString *plistError;
NSDictionary *appDictionary;
NSString *appRedirect;
NSString *appVersion;
NSString *appOnline;
NSDate *appReleaseDate;
NSString *appDownload;
NSString *appDescription;
// read in the recently downloaded plist
plistDictionary = [NSPropertyListSerialization propertyListFromData:_responseData
mutabilityOption:NSPropertyListImmutable
format:&plistFormat
errorDescription:&plistError];
if ( !plistDictionary ) {
[self connection:connection didFailWithError:[ChazUpdateError errorWithString:plistError]];
return;
}
// make sure the root object is a dictionary
if ( ![NSStringFromClass([plistDictionary class]) isEqualToString:@"NSCFDictionary"] ) {
[self connection:connection didFailWithError:[ChazUpdateError errorWithString:@"the version information file is ill-formatted"]];
return;
}
appDictionary = [plistDictionary objectForKey:_name];
if ( !appDictionary ) {
[self connection:connection didFailWithError:[ChazUpdateError errorWithString:@"no version information for this application"]];
return;
}
// see if the version information for this app is in another file
if ( appRedirect = [appDictionary objectForKey:@"Redirect"] ) {
// recursively follow the redirection
[[ChazUpdate alloc] initWithURL:appRedirect name:_name verbose:_verbose];
[self kill];
return;
}
// read other useful information about the app
appVersion = [appDictionary objectForKey:@"Version"];
appReleaseDate = [NSDate dateWithNaturalLanguageString:[appDictionary objectForKey:@"ReleaseDate"]];
appOnline = [appDictionary objectForKey:@"MoreInfoURL"];
appDownload = [appDictionary objectForKey:@"DownloadURL"];
appDescription = [appDictionary objectForKey:@"Description"];
if ( !appReleaseDate ) {
[self connection:connection didFailWithError:[ChazUpdateError errorWithString:@"missing version information"]];
return;
}
if ( !appVersion ) {
appVersion = @"";
}
if ( [ChazAppBuildDate() compare:appReleaseDate] == NSOrderedAscending ) {
// there is a new version, display message
int choice;
NSString *desc;
NSString *mainBtn;
NSString *alternate;
NSString *other;
if ( appDescription ) {
desc = appDescription;
}
else {
desc = [NSString stringWithFormat:@"A new version of %@ is available! Would you like to learn more about the new version now?", _name];
}
if ( appOnline ) {
mainBtn = @"Learn More";
other = @"Cancel";
}
else {
mainBtn = @"OK";
other = nil;
}
if ( appDownload ) {
alternate = @"Download";
}
else {
alternate = nil;
}
choice = NSRunInformationalAlertPanel( [NSString stringWithFormat:@"New Version: %@ %@", _name, appVersion],
desc, mainBtn, alternate, other );
if ( choice == NSAlertDefaultReturn && appOnline ) {
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:appOnline]];
}
else if ( choice == NSAlertAlternateReturn ) {
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:appDownload]];
}
}
else if ( _verbose ) {
// alert that the user has the latest version if we're in verbose mode
NSRunInformationalAlertPanel( [NSString stringWithFormat:@"%@ %@", _name, ChazAppVersion()],
@"You have the latest version of %@.", @"OK", nil, nil, _name );
}
[self kill];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
if ( _verbose )
{
// check failed, display an error
NSRunAlertPanel( @"Update Check Failed", @"Check could not get new version data.\nError code #%i (%@)", @"OK", nil, nil, [error code], [error localizedDescription] );
}
[self kill];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
{
// handle http redirection
return request;
}
@end
#pragma mark API Functions
void ChazCheckForUpdate( id url, BOOL userRequested )
{
[[ChazUpdate alloc] initWithURL:url name:ChazAppName() verbose:userRequested];
}
void ChazCheckForUpdateWithName( id url, NSString *appName, BOOL userRequested )
{
[[ChazUpdate alloc] initWithURL:url name:appName verbose:userRequested];
}
NSString *ChazAppName()
{
NSString *name;
name = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"];
if ( name && ![name isEqualToString:@""] ) {
return name;
}
name = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
if ( name && ![name isEqualToString:@""] ) {
return name;
}
return @"";
}
NSString *ChazAppVersion()
{
NSString *vers;
vers = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
if ( vers && ![vers isEqualToString:@""] ) {
return vers;
}
vers = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
if ( vers && ![vers isEqualToString:@""] ) {
return vers;
}
return @"";
}
NSDate *ChazAppBuildDate()
{
return [NSDate dateWithNaturalLanguageString:[NSString stringWithFormat:@"%s %s", __TIME__, __DATE__]];
}