2 // **********************************************************************
3 // The Cheat - A universal game cheater for Mac OS X
4 // (C) 2003-2005 Chaz McGarvey (BrokenZipper)
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 1, or (at your option)
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 #import "ChazUpdate.h"
25 // an NSError that an have an error statement associated with it
26 @interface ChazUpdateError
: NSError
28 NSString
*_errorString
;
30 + (id)errorWithString
:(NSString
*)string
;
31 - (id)initWithString
:(NSString
*)string
;
32 - (void)setErrorString
:(NSString
*)string
;
33 - (NSString
*)localizedDescription
;
35 @implementation ChazUpdateError
36 + (id)errorWithString
:(NSString
*)string
38 return [[[ChazUpdateError alloc
] initWithString
:string
] autorelease
];
40 - (id)initWithString
:(NSString
*)string
42 if ( self = [super initWithDomain
:@
"ChazUpdateError" code
:1 userInfo
:nil] ) {
43 [self setErrorString
:string
];
47 - (void)setErrorString
:(NSString
*)string
50 [_errorString release
];
51 _errorString
= string
;
53 - (NSString
*)localizedDescription
60 @interface ChazUpdate
: NSObject
64 NSURLConnection
*_connection
;
65 NSHTTPURLResponse
*_response
;
66 NSMutableData
*_responseData
;
71 - (id)initWithURL
:(id)url name
:(NSString
*)name verbose
:(BOOL)verbose
;
77 @implementation ChazUpdate
80 #pragma mark Initialization
82 - (id)initWithURL
:(id)url name
:(NSString
*)name verbose
:(BOOL)verbose
86 if ( self = [super init
] )
88 // figure out what type of URL was passed
89 if ( [url isKindOfClass
:[NSURL
class]] ) {
92 else if ( [url isKindOfClass
:[NSString
class]] ) {
93 theURL
= [[[NSURL alloc
] initWithString
:url
] autorelease
];
97 NSRunAlertPanel( @
"Update Check Failed",
98 @
"Could not determine the location of the version information file.", @
"OK", nil, nil );
105 _name
= [name retain
];
108 _name
= [[NSString stringWithString
:@
""] retain
];
112 // create the request
113 _connection
= [[NSURLConnection connectionWithRequest
:[NSURLRequest requestWithURL
:theURL
114 cachePolicy
:NSURLRequestReloadIgnoringCacheData
115 timeoutInterval
:30.0] delegate
:self] retain
];
117 _responseData
= [[NSMutableData alloc
] init
];
128 [_connection release
];
130 [_responseData release
];
142 #pragma mark NSURLConnection Delegate
144 - (void)connection
:(NSURLConnection
*)connection didReceiveResponse
:(NSURLResponse
*)response
147 // another response, chop the data
148 [_responseData setLength
:0];
151 _response
= [response retain
];
154 - (void)connection
:(NSURLConnection
*)connection didReceiveData
:(NSData
*)data
156 // just save the data
157 [_responseData appendData
:data
];
160 -(void)connectionDidFinishLoading
:(NSURLConnection
*)connection
162 NSDictionary
*plistDictionary
;
163 NSPropertyListFormat plistFormat
;
164 NSString
*plistError
;
166 NSDictionary
*appDictionary
;
168 NSString
*appRedirect
;
169 NSString
*appVersion
;
171 NSDate
*appReleaseDate
;
172 NSString
*appDownload
;
173 NSString
*appDescription
;
175 // read in the recently downloaded plist
176 plistDictionary
= [NSPropertyListSerialization propertyListFromData
:_responseData
177 mutabilityOption
:NSPropertyListImmutable
179 errorDescription
:&plistError
];
180 if ( !plistDictionary
) {
181 [self connection
:connection didFailWithError
:[ChazUpdateError errorWithString
:plistError
]];
184 // make sure the root object is a dictionary
185 if ( ![NSStringFromClass([plistDictionary
class]) isEqualToString
:@
"NSCFDictionary"] ) {
186 [self connection
:connection didFailWithError
:[ChazUpdateError errorWithString
:@
"the version information file is ill-formatted"]];
189 appDictionary
= [plistDictionary objectForKey
:_name
];
190 if ( !appDictionary
) {
191 [self connection
:connection didFailWithError
:[ChazUpdateError errorWithString
:@
"no version information for this application"]];
195 // see if the version information for this app is in another file
196 if ( appRedirect
= [appDictionary objectForKey
:@
"Redirect"] ) {
197 // recursively follow the redirection
198 [[ChazUpdate alloc
] initWithURL
:appRedirect name
:_name verbose
:_verbose
];
203 // read other useful information about the app
204 appVersion
= [appDictionary objectForKey
:@
"Version"];
205 appReleaseDate
= [NSDate dateWithNaturalLanguageString
:[appDictionary objectForKey
:@
"ReleaseDate"]];
206 appOnline
= [appDictionary objectForKey
:@
"MoreInfoURL"];
207 appDownload
= [appDictionary objectForKey
:@
"DownloadURL"];
208 appDescription
= [appDictionary objectForKey
:@
"Description"];
210 if ( !appReleaseDate
) {
211 [self connection
:connection didFailWithError
:[ChazUpdateError errorWithString
:@
"missing version information"]];
219 if ( [ChazAppBuildDate() compare
:appReleaseDate
] == NSOrderedAscending
) {
220 // there is a new version, display message
228 if ( appDescription
) {
229 desc
= appDescription
;
232 desc
= [NSString stringWithFormat
:@
"A new version of %@ is available! Would you like to learn more about the new version now?", _name
];
235 mainBtn
= @
"Learn More";
243 alternate
= @
"Download";
249 choice
= NSRunInformationalAlertPanel( [NSString stringWithFormat
:@
"New Version: %@ %@", _name
, appVersion
],
250 desc
, mainBtn
, alternate
, other
);
252 if ( choice
== NSAlertDefaultReturn
&& appOnline
) {
253 [[NSWorkspace sharedWorkspace
] openURL
:[NSURL URLWithString
:appOnline
]];
255 else if ( choice
== NSAlertAlternateReturn
) {
256 [[NSWorkspace sharedWorkspace
] openURL
:[NSURL URLWithString
:appDownload
]];
259 else if ( _verbose
) {
260 // alert that the user has the latest version if we're in verbose mode
261 NSRunInformationalAlertPanel( [NSString stringWithFormat
:@
"%@ %@", _name
, ChazAppVersion()],
262 @
"You have the latest version of %@.", @
"OK", nil, nil, _name
);
268 - (void)connection
:(NSURLConnection
*)connection didFailWithError
:(NSError
*)error
272 // check failed, display an error
273 NSRunAlertPanel( @
"Update Check Failed", @
"Check could not get new version data.\nError code #%i (%@)", @
"OK", nil, nil, [error code
], [error localizedDescription
] );
279 - (NSURLRequest
*)connection
:(NSURLConnection
*)connection willSendRequest
:(NSURLRequest
*)request redirectResponse
:(NSURLResponse
*)redirectResponse
281 // handle http redirection
289 #pragma mark API Functions
291 void ChazCheckForUpdate( id url
, BOOL userRequested
)
293 [[ChazUpdate alloc
] initWithURL
:url name
:ChazAppName() verbose
:userRequested
];
296 void ChazCheckForUpdateWithName( id url
, NSString
*appName
, BOOL userRequested
)
298 [[ChazUpdate alloc
] initWithURL
:url name
:appName verbose
:userRequested
];
302 NSString
*ChazAppName()
306 name
= [[[NSBundle mainBundle
] infoDictionary
] objectForKey
:@
"CFBundleExecutable"];
307 if ( name
&& ![name isEqualToString
:@
""] ) {
310 name
= [[[NSBundle mainBundle
] infoDictionary
] objectForKey
:@
"CFBundleName"];
311 if ( name
&& ![name isEqualToString
:@
""] ) {
317 NSString
*ChazAppVersion()
321 vers
= [[[NSBundle mainBundle
] infoDictionary
] objectForKey
:@
"CFBundleShortVersionString"];
322 if ( vers
&& ![vers isEqualToString
:@
""] ) {
325 vers
= [[[NSBundle mainBundle
] infoDictionary
] objectForKey
:@
"CFBundleVersion"];
326 if ( vers
&& ![vers isEqualToString
:@
""] ) {
332 NSDate
*ChazAppBuildDate()
334 return [NSDate dateWithNaturalLanguageString
:[NSString stringWithFormat
:@
"%s %s", __TIME__
, __DATE__
]];