]> Dogcows Code - chaz/thecheat/blob - ChazUpdate.m
update contact information and project URL
[chaz/thecheat] / ChazUpdate.m
1
2 /*
3 * The Cheat - The legendary universal game trainer for Mac OS X.
4 * http://www.brokenzipper.com/trac/wiki/TheCheat
5 *
6 * Copyright (c) 2003-2011, Charles McGarvey et al.
7 *
8 * Distributable under the terms and conditions of the 2-clause BSD
9 * license; see the file COPYING for the legal text of the license.
10 */
11
12
13 #import "ChazUpdate.h"
14
15
16 // an NSError that an have an error statement associated with it
17 @interface ChazUpdateError : NSError
18 {
19 NSString *_errorString;
20 }
21 + (id)errorWithString:(NSString *)string;
22 - (id)initWithString:(NSString *)string;
23 - (void)setErrorString:(NSString *)string;
24 - (NSString *)localizedDescription;
25 @end
26 @implementation ChazUpdateError
27 + (id)errorWithString:(NSString *)string
28 {
29 return [[[ChazUpdateError alloc] initWithString:string] autorelease];
30 }
31 - (id)initWithString:(NSString *)string
32 {
33 if ( self = [super initWithDomain:@"ChazUpdateError" code:1 userInfo:nil] ) {
34 [self setErrorString:string];
35 }
36 return self;
37 }
38 - (void)setErrorString:(NSString *)string
39 {
40 [string retain];
41 [_errorString release];
42 _errorString = string;
43 }
44 - (NSString *)localizedDescription
45 {
46 return _errorString;
47 }
48 @end
49
50
51 @interface ChazUpdate : NSObject
52 {
53 NSString *_name;
54
55 NSURLConnection *_connection;
56 NSHTTPURLResponse *_response;
57 NSMutableData *_responseData;
58
59 BOOL _verbose;
60 }
61
62 - (id)initWithURL:(id)url name:(NSString *)name verbose:(BOOL)verbose;
63 - (void)kill;
64
65 @end
66
67
68 @implementation ChazUpdate
69
70
71 #pragma mark Initialization
72
73 - (id)initWithURL:(id)url name:(NSString *)name verbose:(BOOL)verbose
74 {
75 NSURL *theURL;
76
77 if ( self = [super init] )
78 {
79 // figure out what type of URL was passed
80 if ( [url isKindOfClass:[NSURL class]] ) {
81 theURL = url;
82 }
83 else if ( [url isKindOfClass:[NSString class]] ) {
84 theURL = [[[NSURL alloc] initWithString:url] autorelease];
85 }
86 else {
87 if ( verbose ) {
88 NSRunAlertPanel( @"Update Check Failed",
89 @"Could not determine the location of the version information file.", @"OK", nil, nil );
90 }
91 [self release];
92 return nil;
93 }
94 // save the name
95 if ( name ) {
96 _name = [name retain];
97 }
98 else {
99 _name = [[NSString stringWithString:@""] retain];
100 }
101 _verbose = verbose;
102
103 // create the request
104 _connection = [[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:theURL
105 cachePolicy:NSURLRequestReloadIgnoringCacheData
106 timeoutInterval:30.0] delegate:self] retain];
107 _response = nil;
108 _responseData = [[NSMutableData alloc] init];
109 }
110 return self;
111 }
112
113
114 - (void)dealloc
115 {
116 // cleanup
117 [_name release];
118
119 [_connection release];
120 [_response release];
121 [_responseData release];
122
123 [super dealloc];
124 }
125
126 - (void)kill
127 {
128 [self release];
129 self = nil;
130 }
131
132
133 #pragma mark NSURLConnection Delegate
134
135 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
136 {
137 if ( _response ) {
138 // another response, chop the data
139 [_responseData setLength:0];
140 [_response release];
141 }
142 _response = [response retain];
143 }
144
145 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
146 {
147 // just save the data
148 [_responseData appendData:data];
149 }
150
151 -(void)connectionDidFinishLoading:(NSURLConnection *)connection
152 {
153 NSDictionary *plistDictionary;
154 NSPropertyListFormat plistFormat;
155 NSString *plistError;
156
157 NSDictionary *appDictionary;
158
159 NSString *appRedirect;
160 NSString *appVersion;
161 NSString *appOnline;
162 NSDate *appReleaseDate;
163 NSString *appDownload;
164 NSString *appDescription;
165
166 // read in the recently downloaded plist
167 plistDictionary = [NSPropertyListSerialization propertyListFromData:_responseData
168 mutabilityOption:NSPropertyListImmutable
169 format:&plistFormat
170 errorDescription:&plistError];
171 if ( !plistDictionary ) {
172 [self connection:connection didFailWithError:[ChazUpdateError errorWithString:plistError]];
173 return;
174 }
175 // make sure the root object is a dictionary
176 if ( ![NSStringFromClass([plistDictionary class]) isEqualToString:@"NSCFDictionary"] ) {
177 [self connection:connection didFailWithError:[ChazUpdateError errorWithString:@"the version information file is ill-formatted"]];
178 return;
179 }
180 appDictionary = [plistDictionary objectForKey:_name];
181 if ( !appDictionary ) {
182 [self connection:connection didFailWithError:[ChazUpdateError errorWithString:@"no version information for this application"]];
183 return;
184 }
185
186 // see if the version information for this app is in another file
187 if ( appRedirect = [appDictionary objectForKey:@"Redirect"] ) {
188 // recursively follow the redirection
189 [[ChazUpdate alloc] initWithURL:appRedirect name:_name verbose:_verbose];
190 [self kill];
191 return;
192 }
193
194 // read other useful information about the app
195 appVersion = [appDictionary objectForKey:@"Version"];
196 appReleaseDate = [NSDate dateWithNaturalLanguageString:[appDictionary objectForKey:@"ReleaseDate"]];
197 appOnline = [appDictionary objectForKey:@"MoreInfoURL"];
198 appDownload = [appDictionary objectForKey:@"DownloadURL"];
199 appDescription = [appDictionary objectForKey:@"Description"];
200
201 if ( !appReleaseDate ) {
202 [self connection:connection didFailWithError:[ChazUpdateError errorWithString:@"missing version information"]];
203 return;
204 }
205
206 if ( !appVersion ) {
207 appVersion = @"";
208 }
209
210 if ( [ChazAppBuildDate() compare:appReleaseDate] == NSOrderedAscending ) {
211 // there is a new version, display message
212 int choice;
213
214 NSString *desc;
215 NSString *mainBtn;
216 NSString *alternate;
217 NSString *other;
218
219 if ( appDescription ) {
220 desc = appDescription;
221 }
222 else {
223 desc = [NSString stringWithFormat:@"A new version of %@ is available! Would you like to learn more about the new version now?", _name];
224 }
225 if ( appOnline ) {
226 mainBtn = @"Learn More";
227 other = @"Cancel";
228 }
229 else {
230 mainBtn = @"OK";
231 other = nil;
232 }
233 if ( appDownload ) {
234 alternate = @"Download";
235 }
236 else {
237 alternate = nil;
238 }
239
240 choice = NSRunInformationalAlertPanel( [NSString stringWithFormat:@"New Version: %@ %@", _name, appVersion],
241 desc, mainBtn, alternate, other );
242
243 if ( choice == NSAlertDefaultReturn && appOnline ) {
244 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:appOnline]];
245 }
246 else if ( choice == NSAlertAlternateReturn ) {
247 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:appDownload]];
248 }
249 }
250 else if ( _verbose ) {
251 // alert that the user has the latest version if we're in verbose mode
252 NSRunInformationalAlertPanel( [NSString stringWithFormat:@"%@ %@", _name, ChazAppVersion()],
253 @"You have the latest version of %@.", @"OK", nil, nil, _name );
254 }
255
256 [self kill];
257 }
258
259 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
260 {
261 if ( _verbose )
262 {
263 // check failed, display an error
264 NSRunAlertPanel( @"Update Check Failed", @"Check could not get new version data.\nError code #%i (%@)", @"OK", nil, nil, [error code], [error localizedDescription] );
265 }
266
267 [self kill];
268 }
269
270 - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
271 {
272 // handle http redirection
273 return request;
274 }
275
276
277 @end
278
279
280 #pragma mark API Functions
281
282 void ChazCheckForUpdate( id url, BOOL userRequested )
283 {
284 [[ChazUpdate alloc] initWithURL:url name:ChazAppName() verbose:userRequested];
285 }
286
287 void ChazCheckForUpdateWithName( id url, NSString *appName, BOOL userRequested )
288 {
289 [[ChazUpdate alloc] initWithURL:url name:appName verbose:userRequested];
290 }
291
292
293 NSString *ChazAppName()
294 {
295 NSString *name;
296
297 name = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleExecutable"];
298 if ( name && ![name isEqualToString:@""] ) {
299 return name;
300 }
301 name = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
302 if ( name && ![name isEqualToString:@""] ) {
303 return name;
304 }
305 return @"";
306 }
307
308 NSString *ChazAppVersion()
309 {
310 NSString *vers;
311
312 vers = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
313 if ( vers && ![vers isEqualToString:@""] ) {
314 return vers;
315 }
316 vers = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"];
317 if ( vers && ![vers isEqualToString:@""] ) {
318 return vers;
319 }
320 return @"";
321 }
322
323 NSDate *ChazAppBuildDate()
324 {
325 return [NSDate dateWithNaturalLanguageString:[NSString stringWithFormat:@"%s %s", __TIME__, __DATE__]];
326 }
327
This page took 0.042095 seconds and 4 git commands to generate.