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

Side by Side Diff: chrome/browser/chromeos/app_mode/kiosk_external_updater.cc

Issue 491403003: Update cached kiosk app crx from usb stick. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remove the refcounted implementation KioskExternalUpdater. Created 6 years, 3 months 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
OLDNEW
(Empty)
1 // Copyright 2014 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 #include "chrome/browser/chromeos/app_mode/kiosk_external_updater.h"
6
7 #include "base/bind.h"
8 #include "base/file_util.h"
9 #include "base/files/file_enumerator.h"
10 #include "base/json/json_file_value_serializer.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/version.h"
15 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
16 #include "chrome/browser/chromeos/ui/kiosk_external_update_notification.h"
17 #include "chrome/browser/extensions/sandboxed_unpacker.h"
18 #include "chrome/common/chrome_version_info.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "extensions/common/extension.h"
21 #include "grit/chromium_strings.h"
22 #include "grit/generated_resources.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/resource/resource_bundle.h"
25
26 namespace chromeos {
27
28 namespace {
29
30 const char kExternalUpdateManifest[] = "external_update.json";
31 const char kExternalCrx[] = "external_crx";
32 const char kExternalVersion[] = "external_version";
33
34 void ParseExternalUpdateManifest(
35 const base::FilePath& external_update_dir,
36 base::DictionaryValue* parsed_manifest,
37 KioskExternalUpdater::ExternalUpdateErrorCode* error_code) {
38 base::FilePath manifest =
39 external_update_dir.AppendASCII(kExternalUpdateManifest);
40 if (!base::PathExists(manifest)) {
41 *error_code = KioskExternalUpdater::ERROR_NO_MANIFEST;
42 return;
43 }
44
45 JSONFileValueSerializer serializer(manifest);
46 std::string error_msg;
47 base::Value* extensions = serializer.Deserialize(NULL, &error_msg);
48 if (!extensions) {
49 *error_code = KioskExternalUpdater::ERROR_INVALID_MANIFEST;
50 return;
51 }
52
53 base::DictionaryValue* dict_value = NULL;
54 if (!extensions->GetAsDictionary(&dict_value)) {
55 *error_code = KioskExternalUpdater::ERROR_INVALID_MANIFEST;
56 return;
57 }
58
59 parsed_manifest->Swap(dict_value);
60 *error_code = KioskExternalUpdater::ERROR_NONE;
61 }
62
63 void CacheExternalCrx(const base::FilePath& external_file,
xiyuan 2014/08/27 16:17:03 nit: Use a function name that reflects what it doe
jennyz 2014/08/28 22:41:06 Done.
64 const base::FilePath& target_file,
65 const base::FilePath& temp_dir,
66 bool* success) {
67 base::DeleteFile(temp_dir, true);
68 *success = base::CopyFile(external_file, target_file);
69 }
70
71 } // namespace
72
73 KioskExternalUpdater::ExternalUpdate::ExternalUpdate() {
74 }
75
76 KioskExternalUpdater::KioskExternalUpdater(
77 const scoped_refptr<base::SequencedTaskRunner>& backend_task_runner,
78 const base::FilePath& crx_cache_dir,
79 const base::FilePath& crx_unpack_dir)
80 : backend_task_runner_(backend_task_runner),
81 crx_cache_dir_(crx_cache_dir),
82 crx_unpack_dir_(crx_unpack_dir),
83 weak_factory_(this) {
84 // Subscribe to DiskMountManager.
85 DCHECK(disks::DiskMountManager::GetInstance());
86 disks::DiskMountManager::GetInstance()->AddObserver(this);
87 }
88
89 KioskExternalUpdater::~KioskExternalUpdater() {
90 if (disks::DiskMountManager::GetInstance())
91 disks::DiskMountManager::GetInstance()->RemoveObserver(this);
92 }
93
94 void KioskExternalUpdater::OnDiskEvent(
95 disks::DiskMountManager::DiskEvent event,
96 const disks::DiskMountManager::Disk* disk) {
97 }
98
99 void KioskExternalUpdater::OnDeviceEvent(
100 disks::DiskMountManager::DeviceEvent event,
101 const std::string& device_path) {
102 }
103
104 void KioskExternalUpdater::OnMountEvent(
105 disks::DiskMountManager::MountEvent event,
106 MountError error_code,
107 const disks::DiskMountManager::MountPointInfo& mount_info) {
108 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
109 if (mount_info.mount_type != MOUNT_TYPE_DEVICE ||
110 error_code != MOUNT_ERROR_NONE) {
111 return;
112 }
113
114 if (event == disks::DiskMountManager::MOUNTING) {
115 // If multiple disks have been mounted, skip the rest of them if kiosk
116 // update has already been found.
117 if (!external_update_path_.empty()) {
118 LOG(WARNING) << "*** external update path already found, skip "
119 << mount_info.mount_path;
120 return;
121 }
122
123 NotifyKioskUpdateProgress(
124 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
125 IDS_KIOSK_EXTERNAL_UPDATE_IN_PROGRESS));
126
127 base::DictionaryValue* parsed_manifest = new base::DictionaryValue();
128 ExternalUpdateErrorCode* parsing_error = new ExternalUpdateErrorCode;
129 backend_task_runner_->PostTaskAndReply(
130 FROM_HERE,
131 base::Bind(&ParseExternalUpdateManifest,
132 base::FilePath(mount_info.mount_path),
133 parsed_manifest,
134 parsing_error),
135 base::Bind(&KioskExternalUpdater::ProcessParsedManifest,
136 weak_factory_.GetWeakPtr(),
137 base::Owned(parsing_error),
138 base::FilePath(mount_info.mount_path),
139 base::Owned(parsed_manifest)));
140 } else { // unmounting a removable device.
141 if (external_update_path_.value().empty()) {
142 // Clear any previously displayed message.
143 DismissKioskUpdateNotificationOnUIThread();
144 } else if (external_update_path_.value() == mount_info.mount_path) {
145 DismissKioskUpdateNotificationOnUIThread();
146 if (IsExternalUpdatePending()) {
147 LOG(ERROR) << "External kiosk update is not completed when the usb "
148 "stick is unmoutned.";
149 }
150 external_updates_.clear();
151 external_update_path_.clear();
152 }
153 }
154 }
155
156 void KioskExternalUpdater::OnFormatEvent(
157 disks::DiskMountManager::FormatEvent event,
158 FormatError error_code,
159 const std::string& device_path) {
160 }
161
162 void KioskExternalUpdater::OnExtenalUpdateUnpackSuccess(
163 const std::string& app_id,
164 const std::string& version,
165 const std::string& min_browser_version,
166 const base::FilePath& temp_dir) {
167 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
168
169 // User might pull out the usb stick before updating is completed.
170 if (CheckExternalUpdateInterrupted())
171 return;
172
173 if (!ShouldDoExternalUpdate(app_id, version, min_browser_version)) {
174 external_updates_[app_id].update_status = FAILED;
175 MaybeValidateNextExternalUpdate();
176 return;
177 }
178
179 // Copy the newer version from usb stick to cache.
180 std::string filename = app_id + "-" + version + ".crx";
181 base::FilePath new_cache_file = crx_cache_dir_.AppendASCII(filename);
xiyuan 2014/08/27 16:17:03 We should not assume the filename in ExternalCache
jennyz 2014/08/28 22:41:07 Changed to use LocalExtensionCache::PutExtension t
182
183 // User might pull out the usb stick before updating is completed.
184 if (CheckExternalUpdateInterrupted())
185 return;
186
187 bool* success = new bool;
188 backend_task_runner_->PostTaskAndReply(
189 FROM_HERE,
190 base::Bind(&CacheExternalCrx,
191 external_updates_[app_id].external_crx,
192 new_cache_file,
193 temp_dir,
194 success),
195 base::Bind(&KioskExternalUpdater::OnCacheExternalCrx,
196 weak_factory_.GetWeakPtr(),
197 app_id,
198 new_cache_file,
199 base::Owned(success)));
200 }
201
202 void KioskExternalUpdater::OnExternalUpdateUnpackFailure(
203 const std::string& app_id) {
204 // User might pull out the usb stick before updating is completed.
205 if (CheckExternalUpdateInterrupted())
206 return;
207
208 external_updates_[app_id].update_status = FAILED;
209 external_updates_[app_id].error =
210 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
211 IDS_KIOSK_EXTERNAL_UPDATE_BAD_CRX);
212 MaybeValidateNextExternalUpdate();
213 }
214
215 void KioskExternalUpdater::ProcessParsedManifest(
216 ExternalUpdateErrorCode* parsing_error,
217 const base::FilePath& external_update_dir,
218 base::DictionaryValue* parsed_manifest) {
219 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
220
221 if (*parsing_error == ERROR_NO_MANIFEST) {
222 NotifyKioskUpdateProgress(
223 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
224 IDS_KIOSK_EXTERNAL_UPDATE_NO_MANIFEST));
225 return;
226 } else if (*parsing_error == ERROR_INVALID_MANIFEST) {
227 NotifyKioskUpdateProgress(
228 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
229 IDS_KIOSK_EXTERNAL_UPDATE_INVALID_MANIFEST));
230 return;
231 }
232
233 external_update_path_ = external_update_dir;
234 for (base::DictionaryValue::Iterator it(*parsed_manifest); !it.IsAtEnd();
235 it.Advance()) {
236 std::string app_id = it.key();
237 std::string cached_version_str;
238 base::FilePath cached_crx;
239 if (!KioskAppManager::Get()->GetCachedCrx(
240 app_id, &cached_crx, &cached_version_str)) {
241 LOG(WARNING) << "Can't find app in existing cache " << app_id;
242 continue;
243 }
244
245 const base::DictionaryValue* extension = NULL;
246 if (!it.value().GetAsDictionary(&extension)) {
247 LOG(ERROR) << "Found bad entry in manifest type " << it.value().GetType();
248 continue;
249 }
250
251 std::string external_crx_str;
252 if (!extension->GetString(kExternalCrx, &external_crx_str)) {
253 LOG(ERROR) << "Can't find external crx in manifest " << app_id;
254 continue;
255 }
256 // Validate path first.
257 base::FilePath external_crx =
258 external_update_path_.AppendASCII(external_crx_str);
259 if (!base::PathExists(external_crx)) {
xiyuan 2014/08/27 16:17:03 The code now runs on UI thread and file access is
jennyz 2014/08/28 22:41:06 Yes, removed the check here, if the file does not
260 LOG(ERROR) << "External crx does not exist " << external_crx.value();
261 continue;
262 }
263
264 std::string external_version_str;
265 if (!extension->GetString(kExternalVersion, &external_version_str)) {
266 LOG(ERROR) << "Can't find external version in manifest " << app_id;
xiyuan 2014/08/27 16:17:03 We don't need to require the update manifest to ha
jennyz 2014/08/28 22:41:07 Done.
267 continue;
268 }
269 base::Version external_version(external_version_str);
270 base::Version cached_version(cached_version_str);
271 switch (cached_version.CompareTo(external_version)) {
xiyuan 2014/08/27 16:17:03 nit: suggest to change to use "if", switch on int
jennyz 2014/08/28 22:41:07 Done.
272 case -1: // cached version is older, we should upgrade
273 break;
274 case 0: // cached version is same, do nothing
275 LOG(WARNING) << "External app " << app_id
276 << "is at the same version with manifest";
277 continue;
278 case 1: // cached version is newer, do nothing.
279 LOG(WARNING) << "Found external version of extension " << app_id
280 << "that is older than current version. Current version "
281 << "is: " << cached_version_str << ". New "
282 << "version is: " << external_version_str
283 << ". Keeping current version.";
284 continue;
285 }
286
287 ExternalUpdate update;
288 KioskAppManager::App app;
289 if (KioskAppManager::Get()->GetApp(app_id, &app)) {
290 update.app_name = app.name;
291 } else {
292 NOTREACHED();
293 }
294 update.external_crx = external_crx;
295 update.update_status = PENDING;
296 external_updates_[app_id] = update;
297 }
298
299 if (external_updates_.empty()) {
300 NotifyKioskUpdateProgress(
301 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
302 IDS_KIOSK_EXTERNAL_UPDATE_NO_UPDATES));
303 return;
304 }
305
306 ValidateExternalUpdates();
307 }
308
309 bool KioskExternalUpdater::CheckExternalUpdateInterrupted() {
310 if (external_updates_.empty()) {
311 // This could happen if user pulls out the usb stick before the updating
312 // operation is completed.
313 LOG(ERROR) << "external_updates_ has been cleared before external "
314 << "updating completes.";
315 return true;
316 }
317
318 return false;
319 }
320
321 void KioskExternalUpdater::ValidateExternalUpdates() {
322 for (ExternalUpdateMap::iterator it = external_updates_.begin();
323 it != external_updates_.end();
324 ++it) {
325 if (it->second.update_status == PENDING) {
326 scoped_refptr<KioskExternalUpdateValidator> crx_validator =
327 new KioskExternalUpdateValidator(backend_task_runner_,
328 it->first,
329 it->second.external_crx,
330 crx_unpack_dir_,
331 weak_factory_.GetWeakPtr());
332 crx_validator->Start();
333 break;
334 }
335 }
336 }
337
338 void KioskExternalUpdater::OnCacheExternalCrx(const std::string& app_id,
339 const base::FilePath& target_file,
340 bool* cache_success) {
341 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
342 if (!*cache_success) {
343 external_updates_[app_id].update_status = FAILED;
344 external_updates_[app_id].error = l10n_util::GetStringFUTF16(
345 IDS_KIOSK_EXTERNAL_UPDATE_CANNOT_COPY_CRX,
346 base::UTF8ToUTF16(external_updates_[app_id].external_crx.value()),
347 base::UTF8ToUTF16(target_file.value()));
348 } else {
349 external_updates_[app_id].update_status = SUCCESS;
350 }
351
352 // Validate the next pending external update.
353 MaybeValidateNextExternalUpdate();
354 }
355
356 bool KioskExternalUpdater::IsExternalUpdatePending() {
357 for (ExternalUpdateMap::iterator it = external_updates_.begin();
358 it != external_updates_.end();
359 ++it) {
360 if (it->second.update_status == PENDING) {
361 return true;
362 }
363 }
364 return false;
365 }
366
367 bool KioskExternalUpdater::ShouldDoExternalUpdate(
368 const std::string& app_id,
369 const std::string& version,
370 const std::string& min_browser_version) {
371 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
372
373 std::string existing_version_str;
374 base::FilePath existing_path;
375 bool cached = KioskAppManager::Get()->GetCachedCrx(
376 app_id, &existing_path, &existing_version_str);
377 DCHECK(cached);
378
379 // Compare app version.
380 const base::Version existing_version(existing_version_str);
381 const base::Version external_version(version);
382 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
383 switch (existing_version.CompareTo(external_version)) {
xiyuan 2014/08/27 16:17:03 nit: change this to "if"
jennyz 2014/08/28 22:41:07 Done.
384 case -1: // existing version is older, we should upgrade
385 break;
386 case 0: // existing version is same, no update
387 external_updates_[app_id].error =
388 rb.GetLocalizedString(IDS_KIOSK_EXTERNAL_UPDATE_SAME_APP_VERSION);
389 return false;
390 case 1: // existing version is newer, no update
391 external_updates_[app_id].error = l10n_util::GetStringFUTF16(
392 IDS_KIOSK_EXTERNAL_UPDATE_EXISTING_VERSION_NEWER,
393 base::UTF8ToUTF16(version),
394 base::UTF8ToUTF16(existing_version_str));
395 return false;
396 }
397
398 // Check minimum browser version.
399 if (min_browser_version.empty()) {
xiyuan 2014/08/27 16:17:03 This should not be a requirement. Many apps do not
jennyz 2014/08/28 22:41:06 Done.
400 external_updates_[app_id].error = rb.GetLocalizedString(
401 IDS_KIOSK_EXTERNAL_UPDATE_INVALID_MIN_BROWSER_VERSION);
402 return false;
403 }
404
405 Version minimum_version(min_browser_version);
406 if (!minimum_version.IsValid()) {
407 external_updates_[app_id].error = rb.GetLocalizedString(
408 IDS_KIOSK_EXTERNAL_UPDATE_INVALID_MIN_BROWSER_VERSION);
409 return false;
410 }
411
412 chrome::VersionInfo current_version_info;
413 Version current_version(current_version_info.Version());
414 if (!current_version.IsValid()) {
415 NOTREACHED();
416 return false;
417 }
418
419 base::string16 error;
420 if (current_version.CompareTo(minimum_version) < 0) {
421 external_updates_[app_id].error = l10n_util::GetStringFUTF16(
422 IDS_KIOSK_EXTERNAL_UPDATE_REQUIRE_HIGHER_BROWSER_VERSION,
423 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
424 return false;
425 }
426
427 return true;
428 }
429
430 void KioskExternalUpdater::NotifyKioskUpdateProgress(
431 const base::string16& message) {
432 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
433 content::BrowserThread::PostTask(
xiyuan 2014/08/27 16:17:02 Why do we need to PostTask to UI when we are alrea
jennyz 2014/08/28 22:41:07 Removed the PostTask, making direct call now.
434 content::BrowserThread::UI,
435 FROM_HERE,
436 base::Bind(&KioskExternalUpdater::ShowKioskUpdateProgress,
437 weak_factory_.GetWeakPtr(),
438 message));
439 }
440
441 void KioskExternalUpdater::MaybeValidateNextExternalUpdate() {
442 if (IsExternalUpdatePending())
443 ValidateExternalUpdates();
444 else
445 MayBeNotifyKioskAppUpdate();
446 }
447
448 void KioskExternalUpdater::MayBeNotifyKioskAppUpdate() {
449 if (IsExternalUpdatePending())
450 return;
451
452 NotifyKioskUpdateProgress(GetUpdateReportMessage());
453
454 content::BrowserThread::PostTask(
xiyuan 2014/08/27 16:17:03 Do we need PostTask here?
jennyz 2014/08/28 22:41:06 No need to PostTask, making direct call.
455 content::BrowserThread::UI,
456 FROM_HERE,
457 base::Bind(&KioskExternalUpdater::NotifyKioskAppUpdateAvailable,
458 weak_factory_.GetWeakPtr()));
459 }
460
461 void KioskExternalUpdater::NotifyKioskAppUpdateAvailable() {
462 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
463 for (ExternalUpdateMap::iterator it = external_updates_.begin();
464 it != external_updates_.end();
465 ++it) {
466 if (it->second.update_status == SUCCESS) {
467 KioskAppManager::Get()->OnKioskAppCacheUpdated(it->first);
468 }
469 }
470 }
471
472 void KioskExternalUpdater::ShowKioskUpdateProgress(
473 const base::string16& message) {
474 if (!notification_)
475 notification_.reset(new KioskExternalUpdateNotification(message));
476 else
477 notification_->ShowMessage(message);
478 }
479
480 void KioskExternalUpdater::DismissKioskUpdateNotificationOnUIThread() {
481 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
482 content::BrowserThread::PostTask(
xiyuan 2014/08/27 16:17:03 Do we need PostTask here?
jennyz 2014/08/28 22:41:06 Making direct call now.
483 content::BrowserThread::UI,
484 FROM_HERE,
485 base::Bind(&KioskExternalUpdater::DismissKioskUpdateNotification,
486 weak_factory_.GetWeakPtr()));
487 }
488
489 void KioskExternalUpdater::DismissKioskUpdateNotification() {
490 if (notification_.get()) {
491 notification_->Dismiss();
492 notification_.reset();
493 }
494 }
495
496 base::string16 KioskExternalUpdater::GetUpdateReportMessage() {
497 DCHECK(!IsExternalUpdatePending());
498 int updated = 0;
499 int failed = 0;
500 base::string16 updated_apps;
501 base::string16 failed_apps;
502 for (ExternalUpdateMap::iterator it = external_updates_.begin();
503 it != external_updates_.end();
504 ++it) {
505 base::string16 app_name = base::UTF8ToUTF16(it->second.app_name);
506 if (it->second.update_status == SUCCESS) {
507 ++updated;
508 if (updated_apps.empty())
509 updated_apps = app_name;
510 else
511 updated_apps = updated_apps + base::UTF8ToUTF16(", ") + app_name;
xiyuan 2014/08/27 16:17:02 nit: use base::ASCIIToUTF16 for this
jennyz 2014/08/28 22:41:06 Done.
512 } else { // FAILED
513 ++failed;
514 if (failed_apps.empty()) {
515 failed_apps = app_name + base::UTF8ToUTF16(": ") + it->second.error;
xiyuan 2014/08/27 16:17:02 nit: ASCIIToUTF16
jennyz 2014/08/28 22:41:07 Done.
516 } else {
517 failed_apps = failed_apps + base::UTF8ToUTF16("\n") + app_name +
518 base::UTF8ToUTF16(": ") + it->second.error;
xiyuan 2014/08/27 16:17:03 nit: ASCIIToUTF16
jennyz 2014/08/28 22:41:06 Done.
519 }
520 }
521 }
522
523 base::string16 message;
524 message = ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
525 IDS_KIOSK_EXTERNAL_UPDATE_COMPLETE);
526 base::string16 success_app_msg;
527 if (updated) {
528 success_app_msg = l10n_util::GetStringFUTF16(
529 IDS_KIOSK_EXTERNAL_UPDATE_SUCCESSFUL_UPDATED_APPS, updated_apps);
530 message = message + base::UTF8ToUTF16("\n") + success_app_msg;
xiyuan 2014/08/27 16:17:03 nit: ASCIIToUTF16
jennyz 2014/08/28 22:41:07 Done.
531 }
532
533 base::string16 failed_app_msg;
534 if (failed) {
535 failed_app_msg = ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
536 IDS_KIOSK_EXTERNAL_UPDATE_FAILED_UPDATED_APPS) +
537 base::UTF8ToUTF16("\n") + failed_apps;
538 message = message + base::UTF8ToUTF16("\n") + failed_app_msg;
xiyuan 2014/08/27 16:17:03 nit: ASCIIToUTF16
jennyz 2014/08/28 22:41:07 Done.
539 }
540 return message;
541 }
542
543 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698