Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(341)

Side by Side Diff: chrome/app/keystone_glue.mm

Issue 437053: In-application Keystone ticket promotion (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 11 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/app/keystone_glue.h ('k') | chrome/app/keystone_glue_unittest.mm » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 #import "chrome/app/keystone_glue.h"
6
7 #include "base/logging.h"
8 #include "base/mac_util.h"
9 #import "base/worker_pool_mac.h"
10 #include "chrome/common/chrome_constants.h"
11
12 namespace {
13
14 // Provide declarations of the Keystone registration bits needed here. From
15 // KSRegistration.h.
16 typedef enum { kKSPathExistenceChecker } KSExistenceCheckerType;
17
18 NSString *KSRegistrationCheckForUpdateNotification =
19 @"KSRegistrationCheckForUpdateNotification";
20 NSString *KSRegistrationStatusKey = @"Status";
21 NSString *KSRegistrationVersionKey = @"Version";
22 NSString *KSRegistrationUpdateCheckErrorKey = @"Error";
23
24 NSString *KSRegistrationStartUpdateNotification =
25 @"KSRegistrationStartUpdateNotification";
26 NSString *KSUpdateCheckSuccessfulKey = @"CheckSuccessful";
27 NSString *KSUpdateCheckSuccessfullyInstalledKey = @"SuccessfullyInstalled";
28
29 NSString *KSRegistrationRemoveExistingTag = @"";
30 #define KSRegistrationPreserveExistingTag nil
31
32 } // namespace
33
34 @interface KSRegistration : NSObject
35
36 + (id)registrationWithProductID:(NSString*)productID;
37
38 - (BOOL)registerWithVersion:(NSString*)version
39 existenceCheckerType:(KSExistenceCheckerType)xctype
40 existenceCheckerString:(NSString*)xc
41 serverURLString:(NSString*)serverURLString
42 preserveTTToken:(BOOL)preserveToken
43 tag:(NSString*)tag;
44
45 - (void)setActive;
46 - (void)checkForUpdate;
47 - (void)startUpdate;
48
49 @end // @interface KSRegistration
50
51 @interface KeystoneGlue(Private)
52
53 // Called periodically to announce activity by pinging the Keystone server.
54 - (void)markActive:(NSTimer*)timer;
55
56 // Called when an update check or update installation is complete. Posts the
57 // kAutoupdateStatusNotification notification to the default notification
58 // center.
59 - (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version;
60
61 // These three methods are used to determine the version of the application
62 // currently installed on disk, compare that to the currently-running version,
63 // decide whether any updates have been installed, and call
64 // -updateStatus:version:.
65 //
66 // In order to check the version on disk, the installed application's
67 // Info.plist dictionary must be read; in order to see changes as updates are
68 // applied, the dictionary must be read each time, bypassing any caches such
69 // as the one that NSBundle might be maintaining. Reading files can be a
70 // blocking operation, and blocking operations are to be avoided on the main
71 // thread. I'm not quite sure what jank means, but I bet that a blocked main
72 // thread would cause some of it.
73 //
74 // -determineUpdateStatusAsync is called on the main thread to initiate the
75 // operation. It performs initial set-up work that must be done on the main
76 // thread and arranges for -determineUpdateStatusAtPath: to be called on a
77 // work queue thread managed by NSOperationQueue.
78 // -determineUpdateStatusAtPath: then reads the Info.plist, gets the version
79 // from the CFBundleShortVersionString key, and performs
80 // -determineUpdateStatusForVersion: on the main thread.
81 // -determineUpdateStatusForVersion: does the actual comparison of the version
82 // on disk with the running version and calls -updateStatus:version: with the
83 // results of its analysis.
84 - (void)determineUpdateStatusAsync;
85 - (void)determineUpdateStatusAtPath:(NSString*)appPath;
86 - (void)determineUpdateStatusForVersion:(NSString*)version;
87
88 @end // @interface KeystoneGlue(Private)
89
90 const NSString* const kAutoupdateStatusNotification =
91 @"AutoupdateStatusNotification";
92 const NSString* const kAutoupdateStatusStatus = @"status";
93 const NSString* const kAutoupdateStatusVersion = @"version";
94
95 @implementation KeystoneGlue
96
97 // TODO(jrg): use base::SingletonObjC<KeystoneGlue>
98 static KeystoneGlue* sDefaultKeystoneGlue = nil; // leaked
99
100 + (id)defaultKeystoneGlue {
101 if (!sDefaultKeystoneGlue) {
102 sDefaultKeystoneGlue = [[KeystoneGlue alloc] init];
103 [sDefaultKeystoneGlue loadParameters];
104 if (![sDefaultKeystoneGlue loadKeystoneRegistration]) {
105 [sDefaultKeystoneGlue release];
106 sDefaultKeystoneGlue = nil;
107 }
108 }
109 return sDefaultKeystoneGlue;
110 }
111
112 - (id)init {
113 if ((self = [super init])) {
114 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
115
116 [center addObserver:self
117 selector:@selector(checkForUpdateComplete:)
118 name:KSRegistrationCheckForUpdateNotification
119 object:nil];
120
121 [center addObserver:self
122 selector:@selector(installUpdateComplete:)
123 name:KSRegistrationStartUpdateNotification
124 object:nil];
125 }
126
127 return self;
128 }
129
130 - (void)dealloc {
131 [url_ release];
132 [productID_ release];
133 [version_ release];
134 [channel_ release];
135 [registration_ release];
136 [[NSNotificationCenter defaultCenter] removeObserver:self];
137 [super dealloc];
138 }
139
140 - (NSDictionary*)infoDictionary {
141 // Use mac_util::MainAppBundle() to get the app framework's dictionary.
142 return [mac_util::MainAppBundle() infoDictionary];
143 }
144
145 - (void)loadParameters {
146 NSDictionary* infoDictionary = [self infoDictionary];
147 NSString* url = [infoDictionary objectForKey:@"KSUpdateURL"];
148 NSString* product = [infoDictionary objectForKey:@"KSProductID"];
149 if (product == nil) {
150 // Use [NSBundle mainBundle] to fall back to the app's own bundle
151 // identifier, not the app framework's.
152 product = [[NSBundle mainBundle] bundleIdentifier];
153 }
154 NSString* version = [infoDictionary objectForKey:@"KSVersion"];
155 if (!product || !url || !version) {
156 // If parameters required for Keystone are missing, don't use it.
157 return;
158 }
159
160 NSString* channel = [infoDictionary objectForKey:@"KSChannelID"];
161 // The stable channel has no tag. If updating to stable, remove the
162 // dev and beta tags since we've been "promoted".
163 if (channel == nil)
164 channel = KSRegistrationRemoveExistingTag;
165
166 url_ = [url retain];
167 productID_ = [product retain];
168 version_ = [version retain];
169 channel_ = [channel retain];
170 }
171
172 - (BOOL)loadKeystoneRegistration {
173 if (!productID_ || !url_ || !version_)
174 return NO;
175
176 // Load the KeystoneRegistration framework bundle if present. It lives
177 // inside the framework, so use mac_util::MainAppBundle();
178 NSString* ksrPath =
179 [[mac_util::MainAppBundle() privateFrameworksPath]
180 stringByAppendingPathComponent:@"KeystoneRegistration.framework"];
181 NSBundle* ksrBundle = [NSBundle bundleWithPath:ksrPath];
182 [ksrBundle load];
183
184 // Harness the KSRegistration class.
185 Class ksrClass = [ksrBundle classNamed:@"KSRegistration"];
186 KSRegistration* ksr = [ksrClass registrationWithProductID:productID_];
187 if (!ksr)
188 return NO;
189
190 registration_ = [ksr retain];
191 return YES;
192 }
193
194 - (void)registerWithKeystone {
195 // The existence checks should use the path to the app bundle, not the
196 // app framework bundle, so use [NSBundle mainBundle] instead of
197 // mac_util::MainBundle().
198 [registration_ registerWithVersion:version_
199 existenceCheckerType:kKSPathExistenceChecker
200 existenceCheckerString:[[NSBundle mainBundle] bundlePath]
201 serverURLString:url_
202 preserveTTToken:YES
203 tag:channel_];
204
205 // Mark an active RIGHT NOW; don't wait an hour for the first one.
206 [registration_ setActive];
207
208 // Set up hourly activity pings.
209 timer_ = [NSTimer scheduledTimerWithTimeInterval:60 * 60 // One hour
210 target:self
211 selector:@selector(markActive:)
212 userInfo:registration_
213 repeats:YES];
214 }
215
216 - (void)stopTimer {
217 [timer_ invalidate];
218 }
219
220 - (void)markActive:(NSTimer*)timer {
221 KSRegistration* ksr = [timer userInfo];
222 [ksr setActive];
223 }
224
225 - (void)checkForUpdate {
226 DCHECK(![self asyncOperationPending]);
227
228 if (!registration_) {
229 [self updateStatus:kAutoupdateCheckFailed version:nil];
230 return;
231 }
232
233 [self updateStatus:kAutoupdateChecking version:nil];
234
235 [registration_ checkForUpdate];
236
237 // Upon completion, KSRegistrationCheckForUpdateNotification will be posted,
238 // and -checkForUpdateComplete: will be called.
239 }
240
241 - (void)checkForUpdateComplete:(NSNotification*)notification {
242 NSDictionary* userInfo = [notification userInfo];
243
244 if ([[userInfo objectForKey:KSRegistrationUpdateCheckErrorKey] boolValue]) {
245 [self updateStatus:kAutoupdateCheckFailed version:nil];
246 } else if ([[userInfo objectForKey:KSRegistrationStatusKey] boolValue]) {
247 // If an update is known to be available, go straight to
248 // -updateStatus:version:. It doesn't matter what's currently on disk.
249 NSString* version = [userInfo objectForKey:KSRegistrationVersionKey];
250 [self updateStatus:kAutoupdateAvailable version:version];
251 } else {
252 // If no updates are available, check what's on disk, because an update
253 // may have already been installed. This check happens on another thread,
254 // and -updateStatus:version: will be called on the main thread when done.
255 [self determineUpdateStatusAsync];
256 }
257 }
258
259 - (void)installUpdate {
260 DCHECK(![self asyncOperationPending]);
261
262 if (!registration_) {
263 [self updateStatus:kAutoupdateInstallFailed version:nil];
264 return;
265 }
266
267 [self updateStatus:kAutoupdateInstalling version:nil];
268
269 [registration_ startUpdate];
270
271 // Upon completion, KSRegistrationStartUpdateNotification will be posted,
272 // and -installUpdateComplete: will be called.
273 }
274
275 - (void)installUpdateComplete:(NSNotification*)notification {
276 NSDictionary* userInfo = [notification userInfo];
277
278 if (![[userInfo objectForKey:KSUpdateCheckSuccessfulKey] boolValue] ||
279 ![[userInfo objectForKey:KSUpdateCheckSuccessfullyInstalledKey]
280 intValue]) {
281 [self updateStatus:kAutoupdateInstallFailed version:nil];
282 } else {
283 updateSuccessfullyInstalled_ = YES;
284
285 // Nothing in the notification dictionary reports the version that was
286 // installed. Figure it out based on what's on disk.
287 [self determineUpdateStatusAsync];
288 }
289 }
290
291 // Runs on the main thread.
292 - (void)determineUpdateStatusAsync {
293 // NSBundle is not documented as being thread-safe. Do NSBundle operations
294 // on the main thread before jumping over to a NSOperationQueue-managed
295 // thread to do blocking file input.
296 DCHECK([NSThread isMainThread]);
297
298 SEL selector = @selector(determineUpdateStatusAtPath:);
299 NSString* appPath = [[NSBundle mainBundle] bundlePath];
300 NSInvocationOperation* operation =
301 [[[NSInvocationOperation alloc] initWithTarget:self
302 selector:selector
303 object:appPath] autorelease];
304
305 NSOperationQueue* operationQueue = [WorkerPoolObjC sharedOperationQueue];
306 [operationQueue addOperation:operation];
307 }
308
309 // Runs on a thread managed by NSOperationQueue.
310 - (void)determineUpdateStatusAtPath:(NSString*)appPath {
311 DCHECK(![NSThread isMainThread]);
312
313 NSString* appInfoPlistPath =
314 [[appPath stringByAppendingPathComponent:@"Contents"]
315 stringByAppendingPathComponent:@"Info.plist"];
316 NSDictionary* infoPlist =
317 [NSDictionary dictionaryWithContentsOfFile:appInfoPlistPath];
318 NSString* version = [infoPlist objectForKey:@"CFBundleShortVersionString"];
319
320 [self performSelectorOnMainThread:@selector(determineUpdateStatusForVersion:)
321 withObject:version
322 waitUntilDone:NO];
323 }
324
325 // Runs on the main thread.
326 - (void)determineUpdateStatusForVersion:(NSString*)version {
327 DCHECK([NSThread isMainThread]);
328
329 AutoupdateStatus status;
330 if (updateSuccessfullyInstalled_) {
331 // If an update was successfully installed and this object saw it happen,
332 // then don't even bother comparing versions.
333 status = kAutoupdateInstalled;
334 } else {
335 NSString* currentVersion =
336 [NSString stringWithUTF8String:chrome::kChromeVersion];
337 if (!version) {
338 // If the version on disk could not be determined, assume that
339 // whatever's running is current.
340 version = currentVersion;
341 status = kAutoupdateCurrent;
342 } else if ([version isEqualToString:currentVersion]) {
343 status = kAutoupdateCurrent;
344 } else {
345 // If the version on disk doesn't match what's currently running, an
346 // update must have been applied in the background, without this app's
347 // direct participation. Leave updateSuccessfullyInstalled_ alone
348 // because there's no direct knowledge of what actually happened.
349 status = kAutoupdateInstalled;
350 }
351 }
352
353 [self updateStatus:status version:version];
354 }
355
356 - (void)updateStatus:(AutoupdateStatus)status version:(NSString*)version {
357 NSNumber* statusNumber = [NSNumber numberWithInt:status];
358 NSMutableDictionary* dictionary =
359 [NSMutableDictionary dictionaryWithObject:statusNumber
360 forKey:kAutoupdateStatusStatus];
361 if (version) {
362 [dictionary setObject:version forKey:kAutoupdateStatusVersion];
363 }
364
365 NSNotification* notification =
366 [NSNotification notificationWithName:kAutoupdateStatusNotification
367 object:self
368 userInfo:dictionary];
369 recentNotification_.reset([notification retain]);
370
371 [[NSNotificationCenter defaultCenter] postNotification:notification];
372 }
373
374 - (NSNotification*)recentNotification {
375 return [[recentNotification_ retain] autorelease];
376 }
377
378 - (AutoupdateStatus)recentStatus {
379 NSDictionary* dictionary = [recentNotification_ userInfo];
380 return static_cast<AutoupdateStatus>(
381 [[dictionary objectForKey:kAutoupdateStatusStatus] intValue]);
382 }
383
384 - (BOOL)asyncOperationPending {
385 AutoupdateStatus status = [self recentStatus];
386 return status == kAutoupdateChecking || status == kAutoupdateInstalling;
387 }
388
389 @end // @implementation KeystoneGlue
OLDNEW
« no previous file with comments | « chrome/app/keystone_glue.h ('k') | chrome/app/keystone_glue_unittest.mm » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698