OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "base/mac_util.h" | |
6 #import "chrome/app/keystone_glue.h" | 5 #import "chrome/app/keystone_glue.h" |
7 | 6 |
8 @interface KeystoneGlue(Private) | 7 #include "base/logging.h" |
9 | 8 #include "base/mac_util.h" |
10 // Called periodically to announce activity by pinging the Keystone server. | 9 #import "base/worker_pool_mac.h" |
11 - (void)markActive:(NSTimer*)timer; | 10 #include "chrome/common/chrome_constants.h" |
12 | |
13 @end | |
14 | |
15 | 11 |
16 // Provide declarations of the Keystone registration bits needed here. From | 12 // Provide declarations of the Keystone registration bits needed here. From |
17 // KSRegistration.h. | 13 // KSRegistration.h. |
18 typedef enum { kKSPathExistenceChecker } KSExistenceCheckerType; | 14 typedef enum { kKSPathExistenceChecker } KSExistenceCheckerType; |
19 | 15 |
20 NSString *KSRegistrationCheckForUpdateNotification = | 16 NSString *KSRegistrationCheckForUpdateNotification = |
21 @"KSRegistrationCheckForUpdateNotification"; | 17 @"KSRegistrationCheckForUpdateNotification"; |
22 NSString *KSRegistrationStatusKey = @"Status"; | 18 NSString *KSRegistrationStatusKey = @"Status"; |
23 NSString *KSRegistrationVersionKey = @"Version"; | 19 NSString *KSRegistrationVersionKey = @"Version"; |
24 | 20 |
25 NSString *KSRegistrationStartUpdateNotification = | 21 NSString *KSRegistrationStartUpdateNotification = |
26 @"KSRegistrationStartUpdateNotification"; | 22 @"KSRegistrationStartUpdateNotification"; |
27 NSString *KSUpdateCheckSuccessfulKey = @"CheckSuccessful"; | 23 NSString *KSUpdateCheckSuccessfulKey = @"CheckSuccessful"; |
28 NSString *KSUpdateCheckSuccessfullyInstalledKey = @"SuccessfullyInstalled"; | 24 NSString *KSUpdateCheckSuccessfullyInstalledKey = @"SuccessfullyInstalled"; |
29 | 25 |
30 NSString *KSRegistrationRemoveExistingTag = @""; | 26 NSString *KSRegistrationRemoveExistingTag = @""; |
31 #define KSRegistrationPreserveExistingTag nil | 27 #define KSRegistrationPreserveExistingTag nil |
32 | 28 |
33 @interface KSRegistration : NSObject | 29 @interface KSRegistration : NSObject |
| 30 |
34 + (id)registrationWithProductID:(NSString*)productID; | 31 + (id)registrationWithProductID:(NSString*)productID; |
| 32 |
35 // Older API | 33 // Older API |
36 - (BOOL)registerWithVersion:(NSString*)version | 34 - (BOOL)registerWithVersion:(NSString*)version |
37 existenceCheckerType:(KSExistenceCheckerType)xctype | 35 existenceCheckerType:(KSExistenceCheckerType)xctype |
38 existenceCheckerString:(NSString*)xc | 36 existenceCheckerString:(NSString*)xc |
39 serverURLString:(NSString*)serverURLString; | 37 serverURLString:(NSString*)serverURLString; |
40 // Newer API | 38 // Newer API |
41 - (BOOL)registerWithVersion:(NSString*)version | 39 - (BOOL)registerWithVersion:(NSString*)version |
42 existenceCheckerType:(KSExistenceCheckerType)xctype | 40 existenceCheckerType:(KSExistenceCheckerType)xctype |
43 existenceCheckerString:(NSString*)xc | 41 existenceCheckerString:(NSString*)xc |
44 serverURLString:(NSString*)serverURLString | 42 serverURLString:(NSString*)serverURLString |
45 preserveTTToken:(BOOL)preserveToken | 43 preserveTTToken:(BOOL)preserveToken |
46 tag:(NSString *)tag; | 44 tag:(NSString*)tag; |
| 45 |
47 - (void)setActive; | 46 - (void)setActive; |
48 - (void)checkForUpdate; | 47 - (void)checkForUpdate; |
49 - (void)startUpdate; | 48 - (void)startUpdate; |
50 @end | |
51 | 49 |
| 50 @end // @interface KSRegistration |
| 51 |
| 52 @interface KeystoneGlue(Private) |
| 53 |
| 54 // Called periodically to announce activity by pinging the Keystone server. |
| 55 - (void)markActive:(NSTimer*)timer; |
| 56 |
| 57 // Called when an update check or update installation is complete. Posts the |
| 58 // kAutoupdateStatusNotification notification to the default notification |
| 59 // center. |
| 60 - (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version; |
| 61 |
| 62 // These three methods are used to determine the version of the application |
| 63 // currently installed on disk, compare that to the currently-running version, |
| 64 // decide whether any updates have been installed, and call |
| 65 // -updateStatus:version:. |
| 66 // |
| 67 // In order to check the version on disk, the installed application's |
| 68 // Info.plist dictionary must be read; in order to see changes as updates are |
| 69 // applied, the dictionary must be read each time, bypassing any caches such |
| 70 // as the one that NSBundle might be maintaining. Reading files can be a |
| 71 // blocking operation, and blocking operations are to be avoided on the main |
| 72 // thread. I'm not quite sure what jank means, but I bet that a blocked main |
| 73 // thread would cause some of it. |
| 74 // |
| 75 // -determineUpdateStatusAsync is called on the main thread to initiate the |
| 76 // operation. It performs initial set-up work that must be done on the main |
| 77 // thread and arranges for -determineUpdateStatusAtPath: to be called on a |
| 78 // work queue thread managed by NSOperationQueue. |
| 79 // -determineUpdateStatusAtPath: then reads the Info.plist, gets the version |
| 80 // from the CFBundleShortVersionString key, and performs |
| 81 // -determineUpdateStatusForVersion: on the main thread. |
| 82 // -determineUpdateStatusForVersion: does the actual comparison of the version |
| 83 // on disk with the running version and calls -updateStatus:version: with the |
| 84 // results of its analysis. |
| 85 - (void)determineUpdateStatusAsync; |
| 86 - (void)determineUpdateStatusAtPath:(NSString*)appPath; |
| 87 - (void)determineUpdateStatusForVersion:(NSString*)version; |
| 88 |
| 89 @end // @interface KeystoneGlue(Private) |
| 90 |
| 91 const NSString* const kAutoupdateStatusNotification = |
| 92 @"AutoupdateStatusNotification"; |
| 93 const NSString* const kAutoupdateStatusStatus = @"status"; |
| 94 const NSString* const kAutoupdateStatusVersion = @"version"; |
52 | 95 |
53 @implementation KeystoneGlue | 96 @implementation KeystoneGlue |
54 | 97 |
| 98 // TODO(jrg): use base::SingletonObjC<KeystoneGlue> |
| 99 static KeystoneGlue* sDefaultKeystoneGlue = nil; // leaked |
| 100 |
55 + (id)defaultKeystoneGlue { | 101 + (id)defaultKeystoneGlue { |
56 // TODO(jrg): use base::SingletonObjC<KeystoneGlue> | 102 if (!sDefaultKeystoneGlue) { |
57 static KeystoneGlue* sDefaultKeystoneGlue = nil; // leaked | |
58 | |
59 if (sDefaultKeystoneGlue == nil) { | |
60 sDefaultKeystoneGlue = [[KeystoneGlue alloc] init]; | 103 sDefaultKeystoneGlue = [[KeystoneGlue alloc] init]; |
61 [sDefaultKeystoneGlue loadParameters]; | 104 [sDefaultKeystoneGlue loadParameters]; |
62 if (![sDefaultKeystoneGlue loadKeystoneRegistration]) { | 105 if (![sDefaultKeystoneGlue loadKeystoneRegistration]) { |
63 [sDefaultKeystoneGlue release]; | 106 [sDefaultKeystoneGlue release]; |
64 sDefaultKeystoneGlue = nil; | 107 sDefaultKeystoneGlue = nil; |
65 } | 108 } |
66 } | 109 } |
67 return sDefaultKeystoneGlue; | 110 return sDefaultKeystoneGlue; |
68 } | 111 } |
69 | 112 |
| 113 + (void)releaseDefaultKeystoneGlue { |
| 114 [sDefaultKeystoneGlue release]; |
| 115 sDefaultKeystoneGlue = nil; |
| 116 } |
| 117 |
| 118 - (id)init { |
| 119 if ((self = [super init])) { |
| 120 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; |
| 121 |
| 122 [center addObserver:self |
| 123 selector:@selector(checkForUpdateComplete:) |
| 124 name:KSRegistrationCheckForUpdateNotification |
| 125 object:nil]; |
| 126 |
| 127 [center addObserver:self |
| 128 selector:@selector(installUpdateComplete:) |
| 129 name:KSRegistrationStartUpdateNotification |
| 130 object:nil]; |
| 131 } |
| 132 |
| 133 return self; |
| 134 } |
| 135 |
70 - (void)dealloc { | 136 - (void)dealloc { |
71 [url_ release]; | 137 [url_ release]; |
72 [productID_ release]; | 138 [productID_ release]; |
73 [version_ release]; | 139 [version_ release]; |
74 [channel_ release]; | 140 [channel_ release]; |
75 [registration_ release]; | 141 [registration_ release]; |
76 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 142 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
77 [super dealloc]; | 143 [super dealloc]; |
78 } | 144 } |
79 | 145 |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
167 | 233 |
168 - (void)stopTimer { | 234 - (void)stopTimer { |
169 [timer_ invalidate]; | 235 [timer_ invalidate]; |
170 } | 236 } |
171 | 237 |
172 - (void)markActive:(NSTimer*)timer { | 238 - (void)markActive:(NSTimer*)timer { |
173 KSRegistration* ksr = [timer userInfo]; | 239 KSRegistration* ksr = [timer userInfo]; |
174 [ksr setActive]; | 240 [ksr setActive]; |
175 } | 241 } |
176 | 242 |
177 - (void)checkComplete:(NSNotification *)notification { | 243 - (void)checkForUpdate { |
178 NSDictionary *userInfo = [notification userInfo]; | 244 if (!registration_) { |
179 BOOL updatesAvailable = [[userInfo objectForKey:KSRegistrationStatusKey] | 245 [self updateStatus:kAutoupdateCheckFailed version:nil]; |
180 boolValue]; | 246 return; |
181 NSString *latestVersion = [userInfo objectForKey:KSRegistrationVersionKey]; | 247 } |
182 | 248 |
183 [checkTarget_ upToDateCheckCompleted:updatesAvailable | 249 [registration_ checkForUpdate]; |
184 latestVersion:latestVersion]; | |
185 [checkTarget_ release]; | |
186 checkTarget_ = nil; | |
187 | 250 |
188 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; | 251 // Upon completion, KSRegistrationCheckForUpdateNotification will be posted, |
189 [center removeObserver:self | 252 // and -checkForUpdateComplete: will be called. |
190 name:KSRegistrationCheckForUpdateNotification | |
191 object:nil]; | |
192 } | 253 } |
193 | 254 |
194 - (BOOL)checkForUpdate:(NSObject<KeystoneGlueCallbacks>*)target { | 255 - (void)checkForUpdateComplete:(NSNotification*)notification { |
195 if (registration_ == nil) | 256 NSDictionary* userInfo = [notification userInfo]; |
196 return NO; | 257 BOOL updatesAvailable = |
197 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; | 258 [[userInfo objectForKey:KSRegistrationStatusKey] boolValue]; |
198 [center addObserver:self | 259 |
199 selector:@selector(checkComplete:) | 260 if (updatesAvailable) { |
200 name:KSRegistrationCheckForUpdateNotification | 261 // If an update is known to be available, go straight to |
201 object:nil]; | 262 // -updateStatus:version:. It doesn't matter what's currently on disk. |
202 checkTarget_ = [target retain]; | 263 NSString* version = [userInfo objectForKey:KSRegistrationVersionKey]; |
203 [registration_ checkForUpdate]; | 264 [self updateStatus:kAutoupdateAvailable version:version]; |
204 return YES; | 265 } else { |
| 266 // If no updates are available, check what's on disk, because an update |
| 267 // may have already been installed. This check happens on another thread, |
| 268 // and -updateStatus:version: will be called on the main thread when done. |
| 269 [self determineUpdateStatusAsync]; |
| 270 } |
205 } | 271 } |
206 | 272 |
207 - (void)startUpdateComplete:(NSNotification *)notification { | 273 - (void)installUpdate { |
208 NSDictionary *userInfo = [notification userInfo]; | 274 if (!registration_) { |
209 BOOL checkSuccessful = [[userInfo objectForKey:KSUpdateCheckSuccessfulKey] | 275 [self updateStatus:kAutoupdateInstallFailed version:nil]; |
210 boolValue]; | 276 return; |
211 int installs = [[userInfo objectForKey:KSUpdateCheckSuccessfullyInstalledKey] | 277 } |
212 intValue]; | |
213 | 278 |
214 [startTarget_ updateCompleted:checkSuccessful installs:installs]; | 279 [registration_ startUpdate]; |
215 [startTarget_ release]; | |
216 startTarget_ = nil; | |
217 | 280 |
218 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; | 281 // Upon completion, KSRegistrationStartUpdateNotification will be posted, |
219 [center removeObserver:self | 282 // and -installUpdateComplete: will be called. |
220 name:KSRegistrationStartUpdateNotification | |
221 object:nil]; | |
222 } | 283 } |
223 | 284 |
224 - (BOOL)startUpdate:(NSObject<KeystoneGlueCallbacks>*)target { | 285 - (void)installUpdateComplete:(NSNotification*)notification { |
225 if (registration_ == nil) | 286 NSDictionary* userInfo = [notification userInfo]; |
226 return NO; | 287 BOOL checkSuccessful = |
227 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; | 288 [[userInfo objectForKey:KSUpdateCheckSuccessfulKey] boolValue]; |
228 [center addObserver:self | 289 int installs = |
229 selector:@selector(startUpdateComplete:) | 290 [[userInfo objectForKey:KSUpdateCheckSuccessfullyInstalledKey] intValue]; |
230 name:KSRegistrationStartUpdateNotification | 291 |
231 object:nil]; | 292 if (!checkSuccessful || !installs) { |
232 startTarget_ = [target retain]; | 293 [self updateStatus:kAutoupdateInstallFailed version:nil]; |
233 [registration_ startUpdate]; | 294 } else { |
234 return YES; | 295 updateSuccessfullyInstalled_ = YES; |
| 296 |
| 297 // Nothing in the notification dictionary reports the version that was |
| 298 // installed. Figure it out based on what's on disk. |
| 299 [self determineUpdateStatusAsync]; |
| 300 } |
235 } | 301 } |
236 | 302 |
237 @end | 303 // Runs on the main thread. |
| 304 - (void)determineUpdateStatusAsync { |
| 305 // NSBundle is not documented as being thread-safe. Do NSBundle operations |
| 306 // on the main thread before jumping over to a NSOperationQueue-managed |
| 307 // thread to do blocking file input. |
| 308 DCHECK([NSThread isMainThread]); |
| 309 |
| 310 SEL selector = @selector(determineUpdateStatusAtPath:); |
| 311 NSString* appPath = [[NSBundle mainBundle] bundlePath]; |
| 312 NSInvocationOperation* operation = |
| 313 [[[NSInvocationOperation alloc] initWithTarget:self |
| 314 selector:selector |
| 315 object:appPath] autorelease]; |
| 316 |
| 317 NSOperationQueue* operationQueue = [WorkerPoolObjC sharedOperationQueue]; |
| 318 [operationQueue addOperation:operation]; |
| 319 } |
| 320 |
| 321 // Runs on a thread managed by NSOperationQueue. |
| 322 - (void)determineUpdateStatusAtPath:(NSString*)appPath { |
| 323 DCHECK(![NSThread isMainThread]); |
| 324 |
| 325 NSString* appInfoPlistPath = |
| 326 [[appPath stringByAppendingPathComponent:@"Contents"] |
| 327 stringByAppendingPathComponent:@"Info.plist"]; |
| 328 NSDictionary* infoPlist = |
| 329 [NSDictionary dictionaryWithContentsOfFile:appInfoPlistPath]; |
| 330 NSString* version = [infoPlist objectForKey:@"CFBundleShortVersionString"]; |
| 331 |
| 332 [self performSelectorOnMainThread:@selector(determineUpdateStatusForVersion:) |
| 333 withObject:version |
| 334 waitUntilDone:NO]; |
| 335 } |
| 336 |
| 337 // Runs on the main thread. |
| 338 - (void)determineUpdateStatusForVersion:(NSString*)version { |
| 339 DCHECK([NSThread isMainThread]); |
| 340 |
| 341 AutoupdateStatus status; |
| 342 if (updateSuccessfullyInstalled_) { |
| 343 // If an update was successfully installed and this object saw it happen, |
| 344 // then don't even bother comparing versions. |
| 345 status = kAutoupdateInstalled; |
| 346 } else { |
| 347 NSString* currentVersion = |
| 348 [NSString stringWithUTF8String:chrome::kChromeVersion]; |
| 349 if (!version) { |
| 350 // If the version on disk could not be determined, assume that |
| 351 // whatever's running is current. |
| 352 version = currentVersion; |
| 353 status = kAutoupdateCurrent; |
| 354 } else if ([version isEqualToString:currentVersion]) { |
| 355 status = kAutoupdateCurrent; |
| 356 } else { |
| 357 // If the version on disk doesn't match what's currently running, an |
| 358 // update must have been applied in the background, without this app's |
| 359 // direct participation. Leave updateSuccessfullyInstalled_ alone |
| 360 // because there's no direct knowledge of what actually happened. |
| 361 status = kAutoupdateInstalled; |
| 362 } |
| 363 } |
| 364 |
| 365 [self updateStatus:status version:version]; |
| 366 } |
| 367 |
| 368 - (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version { |
| 369 NSNumber* statusNumber = [NSNumber numberWithInt:status]; |
| 370 NSMutableDictionary* dictionary = |
| 371 [NSMutableDictionary dictionaryWithObject:statusNumber |
| 372 forKey:kAutoupdateStatusStatus]; |
| 373 if (version) { |
| 374 [dictionary setObject:version forKey:kAutoupdateStatusVersion]; |
| 375 } |
| 376 |
| 377 NSNotification* notification = |
| 378 [NSNotification notificationWithName:kAutoupdateStatusNotification |
| 379 object:self |
| 380 userInfo:dictionary]; |
| 381 recentNotification_.reset([notification retain]); |
| 382 |
| 383 [[NSNotificationCenter defaultCenter] postNotification:notification]; |
| 384 } |
| 385 |
| 386 - (NSNotification*)recentNotification { |
| 387 return [[recentNotification_ retain] autorelease]; |
| 388 } |
| 389 |
| 390 - (void)clearRecentNotification { |
| 391 recentNotification_.reset(nil); |
| 392 } |
| 393 |
| 394 @end // @implementation KeystoneGlue |
OLD | NEW |