--- /dev/null
+//
+// ChazUpdate.m
+// The Cheat
+//
+// Created by Chaz McGarvey on 1/5/05.
+// Copyright 2005 Chaz McGarvey. All rights reserved.
+//
+
+#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__]];
+}
+