Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 371 lines (307 sloc) 15.462 kB
dc5be1c Initial check-in.
uli authored
1 //
2 // UKUpdateChecker.m
3 // NiftyFeatures
4 //
5 // Created by Uli Kusterer on Sun Nov 23 2003.
720b0e7 @uliwitness Added licensing information to the source files.
authored
6 // Copyright (c) 2003 Uli Kusterer.
7 //
8 // This software is provided 'as-is', without any express or implied
9 // warranty. In no event will the authors be held liable for any damages
10 // arising from the use of this software.
11 //
12 // Permission is granted to anyone to use this software for any purpose,
13 // including commercial applications, and to alter it and redistribute it
14 // freely, subject to the following restrictions:
15 //
16 // 1. The origin of this software must not be misrepresented; you must not
17 // claim that you wrote the original software. If you use this software
18 // in a product, an acknowledgment in the product documentation would be
19 // appreciated but is not required.
20 //
21 // 2. Altered source versions must be plainly marked as such, and must not be
22 // misrepresented as being the original software.
23 //
24 // 3. This notice may not be removed or altered from any source
25 // distribution.
dc5be1c Initial check-in.
uli authored
26 //
27
28 #import "UKUpdateChecker.h"
29 #import "NSData+URLUserAgent.h"
30
31
32 @implementation UKUpdateChecker
33
34
35 // -----------------------------------------------------------------------------
36 // awakeFromNib:
37 // This object has been created and loaded at startup. If this is first
38 // launch, ask user whether we should check for updates periodically at
39 // startup and adjust the prefs accurately.
40 //
41 // If the user wants us to check for updates periodically, check whether
42 // it is time and if so, initiate the check.
43 //
44 // REVISIONS:
45 // 2004-03-19 witness Documented.
46 // -----------------------------------------------------------------------------
47
48 -(void) awakeFromNib
49 {
50 [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationDidLaunch:) name: NSApplicationDidFinishLaunchingNotification object: NSApp];
51 }
52
53
54 -(void) dealloc
55 {
56 [[NSNotificationCenter defaultCenter] removeObserver: self];
57
58 [super dealloc];
59 }
60
61
62 // -----------------------------------------------------------------------------
63 // applicationDidLaunch:
64 // Application finished launching. Let's check for updates.
65 //
66 // REVISIONS:
67 // 2005-07-16 witness Created.
68 // -----------------------------------------------------------------------------
69
70 -(void) applicationDidLaunch: (NSNotification*)notif
71 {
72 //UKLog(@"Just checking...");
73
74 NSNumber * doCheck = [[NSUserDefaults standardUserDefaults] objectForKey: @"UKUpdateChecker:CheckAtStartup"];
75 NSString * appName = [[NSFileManager defaultManager] displayNameAtPath: [[NSBundle mainBundle] bundlePath]];
76 NSNumber * lastCheckDateNum = [[NSUserDefaults standardUserDefaults] objectForKey: @"UKUpdateChecker:LastCheckDate"];
77 NSDate * lastCheckDate = nil;
78
79 if( doCheck == nil ) // No setting in prefs yet? First launch! Ask!
80 {
81 if( NSRunAlertPanel( NSLocalizedStringFromTable(@"Check for updates?", @"UKUpdateChecker", @"Asking whether to check for updates at startup - dialog title"),
82 NSLocalizedStringFromTable(@"Do you want to be notified when new versions of %@ become available?", @"UKUpdateChecker", @"Asking whether to check for updates at startup - dialog text"),
83 NSLocalizedString(@"Yes",nil), NSLocalizedString(@"No",nil), nil, appName ) == NSAlertDefaultReturn )
84 doCheck = [NSNumber numberWithBool:YES];
85 else
86 doCheck = [NSNumber numberWithBool:NO];
87
88 // Save user's preference to prefs file:
89 [[NSUserDefaults standardUserDefaults] setObject: doCheck forKey: @"UKUpdateChecker:CheckAtStartup"];
90 }
91
92 [prefsButton setState: [doCheck boolValue]]; // Update prefs button, if we have one.
93
94 // If user wants us to check for updates at startup, do so:
95 if( [doCheck boolValue] )
96 {
97 NSTimeInterval timeSinceLastCheck;
98
99 // Determine how long since last check:
100 if( lastCheckDateNum == nil )
101 lastCheckDate = [NSDate distantPast]; // If there's no date in prefs, use something guaranteed to be past.
102 else
103 lastCheckDate = [NSDate dateWithTimeIntervalSinceReferenceDate: [lastCheckDateNum doubleValue]];
104 timeSinceLastCheck = -[lastCheckDate timeIntervalSinceNow];
105
106 // If last check was more than DAYS_BETWEEN_CHECKS days ago, check again now:
107 if( timeSinceLastCheck > (3600 *24 *DAYS_BETWEEN_CHECKS) )
108 {
109 [NSThread detachNewThreadSelector: @selector(checkForUpdatesAndNotify:) toTarget: self withObject: [NSNumber numberWithBool: NO]];
110 [[NSUserDefaults standardUserDefaults] setObject: [NSNumber numberWithDouble: [NSDate timeIntervalSinceReferenceDate]] forKey: @"UKUpdateChecker:LastCheckDate"];
111 }
112
113 if( periodicCheckTimer )
114 {
115 [periodicCheckTimer invalidate];
116 [periodicCheckTimer release];
117 }
118 periodicCheckTimer = [[NSTimer scheduledTimerWithTimeInterval: 3600 *24 *DAYS_BETWEEN_CHECKS target: self selector:@selector(checkForUpdates:) userInfo: [NSDictionary dictionary] repeats: YES] retain];
119 }
120 }
121
122
123 // -----------------------------------------------------------------------------
124 // checkForUpdates:
125 // IBAction to hook up to the "check for updates" menu item.
126 //
127 // REVISIONS:
128 // 2004-03-19 witness Documented.
129 // -----------------------------------------------------------------------------
130
131 -(IBAction) checkForUpdates: (id)sender
132 {
133 [NSThread detachNewThreadSelector: @selector(checkForUpdatesAndNotify:) toTarget: self withObject: [NSNumber numberWithBool: YES]];
134 // YES means we *also* tell the user about failure, since this is in response to a menu item.
135 }
136
137
138 // -----------------------------------------------------------------------------
139 // latestVersionsDictionary:
140 // Load a dictionary containing info on the latest versions of this app.
141 //
142 // This first tries to get MacPAD-compatible version information. If the
143 // developer didn't provide that, it will try the old UKUpdateChecker
144 // scheme instead.
145 //
146 // REVISIONS:
147 // 2004-03-19 witness Documented.
148 // -----------------------------------------------------------------------------
149
150 -(NSDictionary*) latestVersionsDictionary
151 {
152 NSString* fpath = [[NSBundle mainBundle] pathForResource: UKUpdateCheckerURLFilename ofType: @"url"];
153
154 // Do we have a MacPAD.url file?
155 if( [[NSFileManager defaultManager] fileExistsAtPath: fpath] ) // MacPAD-compatible!
156 {
157 NSString* urlfile = [NSString stringWithContentsOfFile: fpath];
158 NSArray* lines = [urlfile componentsSeparatedByString: @"\n"];
159 NSString* urlString = [lines lastObject]; // Either this is the only line, or the line following [InternetShortcut]
160
161 if( [urlString characterAtIndex: [urlString length] -1] == '/' // Directory path? Append bundle identifier and .plist to get an actual file path to download.
162 || [urlString characterAtIndex: [urlString length] -1] == '=' ) // CGI parameter?
163 urlString = [[urlString stringByAppendingString: [[NSBundle mainBundle] bundleIdentifier]] stringByAppendingString: @".plist"];
164
165 return [NSDictionary dictionaryWithContentsOfURL: [NSURL URLWithString: urlString]]; // Download info from that URL.
166 }
167 else // Old-style UKUpdateChecker stuff:
168 {
169 NSURL* versDictURL = [NSURL URLWithString: NSLocalizedString(@"UPDATE_PLIST_URL", @"URL where the plist with the latest version numbers is.")];
170 NSDictionary* allVersionsDict = [NSDictionary dictionaryWithContentsOfURL: versDictURL];
171 return [allVersionsDict objectForKey: [[NSBundle mainBundle] bundleIdentifier]];
172 }
173 }
174
175
176 // -----------------------------------------------------------------------------
177 // checkForUpdatesAndNotify:
178 // This does the actual update checking. This is called in a new thread
179 // usually to make sure the user doesn't have to wait to work with their
180 // app until this has succeeded or even worse timed out with an error.
181 //
182 // REVISIONS:
183 // 2004-10-19 witness Documented, made to run in another thread,
184 // extracted actual notification into method
185 // notifyAboutUpdateToNewVersion:.
186 // -----------------------------------------------------------------------------
187
188 -(void) checkForUpdatesAndNotify: (NSNumber*)doNotifyBool
189 {
190 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
191 BOOL doNotify = [doNotifyBool boolValue];
192 // Load a .plist of application version info from a web URL:
193 NSDictionary * appVersionDict = [self latestVersionsDictionary];
194 BOOL succeeded = NO;
195
196 if( appVersionDict != nil ) // We were able to download a dictionary?
197 {
198 // Extract version number and URL from dictionary:
199 NSString *newVersion = [appVersionDict valueForKey: UKUpdateCheckerVersionPlistKey];
200 NSString *newUrl = [appVersionDict valueForKey: UKUpdateCheckerURLPlistKey];
201 NSString *newReleaseNotes = [appVersionDict valueForKey: UKUpdateCheckerReleaseNotesPlistKey];
202
203 if( !newVersion || !newUrl ) // Dictionary doesn't contain new MacPAD stuff? Use old UKUpdateChecker stuff instead.
204 {
205 newVersion = [appVersionDict valueForKey:UKUpdateCheckerOldVersionPlistKey];
206 newUrl = [appVersionDict valueForKey:UKUpdateCheckerOldURLPlistKey];
207 }
208
209 if( !newReleaseNotes )
210 newReleaseNotes = @"";
211
212 // Is it current? Then tell the user, or just quietly go on, depending on doNotify:
213 if( [newVersion isEqualToString: [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]] )
214 {
215 if( doNotify )
216 [self performSelectorOnMainThread: @selector(notifyAboutUpdateToNewVersion:)
217 withObject:[NSDictionary dictionaryWithObjectsAndKeys: nil] waitUntilDone: YES];
218 succeeded = YES;
219 }
220 else if( newVersion != nil ) // If there's an entry for this app:
221 {
222 // Ask user whether they'd like to open the URL for the new version:
223 [self performSelectorOnMainThread: @selector(notifyAboutUpdateToNewVersion:)
224 withObject: [NSDictionary dictionaryWithObjectsAndKeys:
225 newVersion, UKUpdateCheckerVersionPlistKey,
226 newUrl, UKUpdateCheckerURLPlistKey,
227 newReleaseNotes, UKUpdateCheckerReleaseNotesPlistKey,
228 nil] waitUntilDone: YES];
229
230 succeeded = YES; // Otherwise, it's still a success.
231 }
232 }
233
234 // Failed? File not found, no internet, there is no entry for our app?
235 if( !succeeded && doNotify )
236 [self performSelectorOnMainThread: @selector(notifyAboutUpdateToNewVersion:)
237 withObject: [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool: YES], @"isError", nil] waitUntilDone: YES];
238
239 [pool release];
240 }
241
242
243 // -----------------------------------------------------------------------------
244 // notifyAboutUpdateToNewVersion:
245 // This actually tells the user about new updates, and is therefore called
246 // on the main thread.
247 //
248 // REVISIONS:
249 // 2004-10-19 witness Documented, extracted from checkForUpdatesAndNotify:.
250 // -----------------------------------------------------------------------------
251
252 -(void) notifyAboutUpdateToNewVersion: (NSDictionary*)info
253 {
254 NSString* appName = [[NSFileManager defaultManager] displayNameAtPath: [[NSBundle mainBundle] bundlePath]];
255 NSString* newVersion = [info objectForKey: UKUpdateCheckerVersionPlistKey];
256 NSString* newUrl = [info objectForKey: UKUpdateCheckerURLPlistKey];
257 NSString* newReleaseNotes = [info objectForKey: UKUpdateCheckerReleaseNotesPlistKey];
258 NSNumber* errBoolObj = [info objectForKey: @"isError"];
259 BOOL isError = errBoolObj ? [errBoolObj boolValue] : NO;
260
261 if( newVersion == nil && !isError )
262 NSRunAlertPanel(NSLocalizedStringFromTable(@"Up to date", @"UKUpdateChecker", @"When soft is up-to-date - dialog title"),
263 NSLocalizedStringFromTable(@"There are no updates for %@ available.", @"UKUpdateChecker", @"When soft is up-to-date - dialog text"),
264 NSLocalizedStringFromTable(@"OK", @"UKUpdateChecker", @""), nil, nil, appName );
265 else if( newVersion != nil && !isError )
266 {
267 int button = NSRunAlertPanel(
268 NSLocalizedStringFromTable(@"New Version Available", @"UKUpdateChecker", @"A New Version is Available - dialog title"),
269 NSLocalizedStringFromTable(@"A new version of %@ (%@) is available:\n\n%@\n\nDownload now?", @"UKUpdateChecker", @"A New Version is Available - dialog text"),
270 NSLocalizedStringFromTable(@"OK", @"UKUpdateChecker", @""), NSLocalizedStringFromTable(@"Cancel", @"UKUpdateChecker", @""), nil,
271 appName, newVersion, newReleaseNotes );
272 if( NSOKButton == button ) // Yes?
273 [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString:newUrl]]; //Open!
274 }
275 else
276 {
277 NSRunAlertPanel( NSLocalizedStringFromTable(@"Error", @"UKUpdateChecker", @"When update test failed - dialog title"),
278 NSLocalizedStringFromTable(@"%@ encountered an unknown error.", @"UKUpdateChecker", @"When update test failed - dialog text"),
279 @"OK", nil, nil, appName );
280 }
281 }
282
283
284 // -----------------------------------------------------------------------------
285 // takeBoolFromObject:
286 // Action for the "check at startup" checkbox in your preferences.
287 //
288 // REVISIONS:
289 // 2004-10-19 witness Documented.
290 // -----------------------------------------------------------------------------
291
292 -(IBAction) takeBoolFromObject: (id)sender
293 {
294 BOOL newState = NO;
295 if( [sender respondsToSelector: @selector(boolValue)] )
296 newState = [sender boolValue];
297 else
298 newState = [sender state];
299
300 [self setCheckAtStartup: newState];
301 }
302
303
304 // -----------------------------------------------------------------------------
305 // setCheckAtStartup:
306 // Mutator for startup check (de)activation.
307 //
308 // REVISIONS:
309 // 2004-10-19 witness Documented.
310 // -----------------------------------------------------------------------------
311
312 -(void) setCheckAtStartup: (BOOL)shouldCheck
313 {
314 NSNumber* doCheck = [NSNumber numberWithBool: shouldCheck];
315 [[NSUserDefaults standardUserDefaults] setObject: doCheck forKey: @"UKUpdateChecker:CheckAtStartup"];
316
317 [prefsButton setState: shouldCheck];
318 [[NSUserDefaults standardUserDefaults] setObject: [NSNumber numberWithDouble: 0] forKey: @"UKUpdateChecker:LastCheckDate"];
319
320 if( periodicCheckTimer )
321 {
322 [periodicCheckTimer invalidate];
323 [periodicCheckTimer release];
324 }
325
326 if( shouldCheck )
327 periodicCheckTimer = [[NSTimer scheduledTimerWithTimeInterval: 3600 *24 *DAYS_BETWEEN_CHECKS target: self selector:@selector(checkForUpdates:) userInfo: [NSDictionary dictionary] repeats: YES] retain];
328 }
329
330
331 // -----------------------------------------------------------------------------
332 // checkAtStartup:
333 // Accessor for finding out whether this will check at startup.
334 //
335 // REVISIONS:
336 // 2004-10-19 witness Documented.
337 // -----------------------------------------------------------------------------
338
339 -(BOOL) checkAtStartup
340 {
341 NSNumber * doCheck = [[NSUserDefaults standardUserDefaults] objectForKey: @"UKUpdateChecker:CheckAtStartup"];
342
343 if( doCheck )
344 return [doCheck boolValue];
345 else
346 return YES;
347 }
348
349
350 // -----------------------------------------------------------------------------
351 // dictionaryFromNSURLConnectionWithURL:
352 // Download a dictionary, if possible using an NSURLRequest and user agent.
353 //
354 // REVISIONS:
355 // 2005-06-21 witness Copied over from Peter's submitted code and changed
356 // to use NSData+URLUserAgent, documented.
357 // 2004-11-23 pm Created.
358 // -----------------------------------------------------------------------------
359
360 -(NSDictionary*) dictionaryFromNSURLConnectionWithURL: (NSURL*)theURL
361 {
362 NSData * theData;
363 if( theData = [NSData dataWithContentsOfURL: theURL userAgent: nil] )
364 return [(NSDictionary*)CFPropertyListCreateFromXMLData(kCFAllocatorDefault, (CFDataRef)theData, kCFPropertyListImmutable, NULL) autorelease];
365
366 return nil;
367 }
368
369
370 @end
Something went wrong with that request. Please try again.