event_router.cc revision bb1529ce867d8845a77ec7cdf3e3003ef1771a40
1// Copyright (c) 2012 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/extensions/file_manager/event_router.h" 6 7#include "base/bind.h" 8#include "base/file_util.h" 9#include "base/message_loop/message_loop.h" 10#include "base/prefs/pref_change_registrar.h" 11#include "base/prefs/pref_service.h" 12#include "base/stl_util.h" 13#include "base/threading/sequenced_worker_pool.h" 14#include "base/values.h" 15#include "chrome/browser/chrome_notification_types.h" 16#include "chrome/browser/chromeos/drive/drive_integration_service.h" 17#include "chrome/browser/chromeos/drive/file_system_interface.h" 18#include "chrome/browser/chromeos/drive/file_system_util.h" 19#include "chrome/browser/chromeos/extensions/file_manager/desktop_notifications.h" 20#include "chrome/browser/chromeos/extensions/file_manager/file_manager_util.h" 21#include "chrome/browser/chromeos/extensions/file_manager/mounted_disk_monitor.h" 22#include "chrome/browser/chromeos/login/login_display_host_impl.h" 23#include "chrome/browser/chromeos/login/screen_locker.h" 24#include "chrome/browser/chromeos/net/connectivity_state_helper.h" 25#include "chrome/browser/drive/drive_service_interface.h" 26#include "chrome/browser/extensions/event_names.h" 27#include "chrome/browser/extensions/event_router.h" 28#include "chrome/browser/extensions/extension_service.h" 29#include "chrome/browser/extensions/extension_system.h" 30#include "chrome/browser/profiles/profile.h" 31#include "chrome/common/pref_names.h" 32#include "chromeos/login/login_state.h" 33#include "content/public/browser/browser_thread.h" 34#include "content/public/browser/notification_source.h" 35#include "webkit/common/fileapi/file_system_types.h" 36#include "webkit/common/fileapi/file_system_util.h" 37 38using chromeos::disks::DiskMountManager; 39using content::BrowserThread; 40using drive::DriveIntegrationService; 41using drive::DriveIntegrationServiceFactory; 42 43namespace file_manager { 44namespace { 45 46const char kPathChanged[] = "changed"; 47const char kPathWatchError[] = "error"; 48 49// Used as a callback for FileSystem::MarkCacheFileAsUnmounted(). 50void OnMarkAsUnmounted(drive::FileError error) { 51 // Do nothing. 52} 53 54const char* MountErrorToString(chromeos::MountError error) { 55 switch (error) { 56 case chromeos::MOUNT_ERROR_NONE: 57 return "success"; 58 case chromeos::MOUNT_ERROR_UNKNOWN: 59 return "error_unknown"; 60 case chromeos::MOUNT_ERROR_INTERNAL: 61 return "error_internal"; 62 case chromeos::MOUNT_ERROR_INVALID_ARGUMENT: 63 return "error_invalid_argument"; 64 case chromeos::MOUNT_ERROR_INVALID_PATH: 65 return "error_invalid_path"; 66 case chromeos::MOUNT_ERROR_PATH_ALREADY_MOUNTED: 67 return "error_path_already_mounted"; 68 case chromeos::MOUNT_ERROR_PATH_NOT_MOUNTED: 69 return "error_path_not_mounted"; 70 case chromeos::MOUNT_ERROR_DIRECTORY_CREATION_FAILED: 71 return "error_directory_creation_failed"; 72 case chromeos::MOUNT_ERROR_INVALID_MOUNT_OPTIONS: 73 return "error_invalid_mount_options"; 74 case chromeos::MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS: 75 return "error_invalid_unmount_options"; 76 case chromeos::MOUNT_ERROR_INSUFFICIENT_PERMISSIONS: 77 return "error_insufficient_permissions"; 78 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_NOT_FOUND: 79 return "error_mount_program_not_found"; 80 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_FAILED: 81 return "error_mount_program_failed"; 82 case chromeos::MOUNT_ERROR_INVALID_DEVICE_PATH: 83 return "error_invalid_device_path"; 84 case chromeos::MOUNT_ERROR_UNKNOWN_FILESYSTEM: 85 return "error_unknown_filesystem"; 86 case chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM: 87 return "error_unsuported_filesystem"; 88 case chromeos::MOUNT_ERROR_INVALID_ARCHIVE: 89 return "error_invalid_archive"; 90 case chromeos::MOUNT_ERROR_NOT_AUTHENTICATED: 91 return "error_authentication"; 92 case chromeos::MOUNT_ERROR_PATH_UNMOUNTED: 93 return "error_path_unmounted"; 94 } 95 NOTREACHED(); 96 return ""; 97} 98 99void DirectoryExistsOnBlockingPool(const base::FilePath& directory_path, 100 const base::Closure& success_callback, 101 const base::Closure& failure_callback) { 102 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 103 104 if (base::DirectoryExists(directory_path)) 105 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, success_callback); 106 else 107 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, failure_callback); 108}; 109 110void DirectoryExistsOnUIThread(const base::FilePath& directory_path, 111 const base::Closure& success_callback, 112 const base::Closure& failure_callback) { 113 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 114 115 content::BrowserThread::PostBlockingPoolTask( 116 FROM_HERE, 117 base::Bind(&DirectoryExistsOnBlockingPool, 118 directory_path, 119 success_callback, 120 failure_callback)); 121}; 122 123// Creates a base::FilePathWatcher and starts watching at |watch_path| with 124// |callback|. Returns NULL on failure. 125base::FilePathWatcher* CreateAndStartFilePathWatcher( 126 const base::FilePath& watch_path, 127 const base::FilePathWatcher::Callback& callback) { 128 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 129 DCHECK(!callback.is_null()); 130 131 base::FilePathWatcher* watcher(new base::FilePathWatcher); 132 if (!watcher->Watch(watch_path, false /* recursive */, callback)) { 133 delete watcher; 134 return NULL; 135 } 136 137 return watcher; 138} 139 140// Constants for the "transferState" field of onFileTransferUpdated event. 141const char kFileTransferStateStarted[] = "started"; 142const char kFileTransferStateInProgress[] = "in_progress"; 143const char kFileTransferStateCompleted[] = "completed"; 144const char kFileTransferStateFailed[] = "failed"; 145 146// Frequency of sending onFileTransferUpdated. 147const int64 kFileTransferEventFrequencyInMilliseconds = 1000; 148 149// Utility function to check if |job_info| is a file uploading job. 150bool IsUploadJob(drive::JobType type) { 151 return (type == drive::TYPE_UPLOAD_NEW_FILE || 152 type == drive::TYPE_UPLOAD_EXISTING_FILE); 153} 154 155// Utility function to check if |job_info| is a file downloading job. 156bool IsDownloadJob(drive::JobType type) { 157 return type == drive::TYPE_DOWNLOAD_FILE; 158} 159 160// Converts the job info to its JSON (Value) form. 161scoped_ptr<base::DictionaryValue> JobInfoToDictionaryValue( 162 const std::string& extension_id, 163 const std::string& job_status, 164 const drive::JobInfo& job_info) { 165 DCHECK(IsActiveFileTransferJobInfo(job_info)); 166 167 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue); 168 GURL url = util::ConvertRelativePathToFileSystemUrl( 169 job_info.file_path, extension_id); 170 result->SetString("fileUrl", url.spec()); 171 result->SetString("transferState", job_status); 172 result->SetString("transferType", 173 IsUploadJob(job_info.job_type) ? "upload" : "download"); 174 // JavaScript does not have 64-bit integers. Instead we use double, which 175 // is in IEEE 754 formant and accurate up to 52-bits in JS, and in practice 176 // in C++. Larger values are rounded. 177 result->SetDouble("processed", 178 static_cast<double>(job_info.num_completed_bytes)); 179 result->SetDouble("total", static_cast<double>(job_info.num_total_bytes)); 180 return result.Pass(); 181} 182 183// Checks for availability of the Google+ Photos app. 184bool IsGooglePhotosInstalled(Profile *profile) { 185 ExtensionService* service = 186 extensions::ExtensionSystem::Get(profile)->extension_service(); 187 if (!service) 188 return false; 189 190 // Google+ Photos uses several ids for different channels. Therefore, all of 191 // them should be checked. 192 const std::string kGooglePlusPhotosIds[] = { 193 "ebpbnabdhheoknfklmpddcdijjkmklkp", // G+ Photos staging 194 "efjnaogkjbogokcnohkmnjdojkikgobo", // G+ Photos prod 195 "ejegoaikibpmikoejfephaneibodccma" // G+ Photos dev 196 }; 197 198 for (size_t i = 0; i < arraysize(kGooglePlusPhotosIds); ++i) { 199 if (service->GetExtensionById(kGooglePlusPhotosIds[i], 200 false /* include_disable */) != NULL) 201 return true; 202 } 203 204 return false; 205} 206 207} // namespace 208 209// Pass dummy value to JobInfo's constructor for make it default constructible. 210EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus() 211 : job_info(drive::TYPE_DOWNLOAD_FILE) { 212} 213 214EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus( 215 const drive::JobInfo& info, const std::string& status) 216 : job_info(info), status(status) { 217} 218 219EventRouter::EventRouter( 220 Profile* profile) 221 : notifications_(new DesktopNotifications(profile)), 222 pref_change_registrar_(new PrefChangeRegistrar), 223 profile_(profile), 224 weak_factory_(this) { 225 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 226} 227 228EventRouter::~EventRouter() { 229} 230 231void EventRouter::Shutdown() { 232 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 233 234 DLOG_IF(WARNING, !file_watchers_.empty()) 235 << "Not all file watchers are " 236 << "removed. This can happen when Files.app is open during shutdown."; 237 STLDeleteValues(&file_watchers_); 238 if (!profile_) { 239 NOTREACHED(); 240 return; 241 } 242 243 DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); 244 if (disk_mount_manager) 245 disk_mount_manager->RemoveObserver(this); 246 247 DriveIntegrationService* integration_service = 248 DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates( 249 profile_); 250 if (integration_service) { 251 integration_service->RemoveObserver(this); 252 integration_service->file_system()->RemoveObserver(this); 253 integration_service->drive_service()->RemoveObserver(this); 254 integration_service->job_list()->RemoveObserver(this); 255 } 256 257 if (chromeos::ConnectivityStateHelper::IsInitialized()) { 258 chromeos::ConnectivityStateHelper::Get()-> 259 RemoveNetworkManagerObserver(this); 260 } 261 profile_ = NULL; 262} 263 264void EventRouter::ObserveFileSystemEvents() { 265 if (!profile_) { 266 NOTREACHED(); 267 return; 268 } 269 if (!chromeos::LoginState::IsInitialized() || 270 !chromeos::LoginState::Get()->IsUserLoggedIn()) { 271 return; 272 } 273 274 DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); 275 if (disk_mount_manager) { 276 disk_mount_manager->RemoveObserver(this); 277 disk_mount_manager->AddObserver(this); 278 disk_mount_manager->RequestMountInfoRefresh(); 279 } 280 281 DriveIntegrationService* integration_service = 282 DriveIntegrationServiceFactory::GetForProfileRegardlessOfStates( 283 profile_); 284 if (integration_service) { 285 integration_service->AddObserver(this); 286 integration_service->drive_service()->AddObserver(this); 287 integration_service->file_system()->AddObserver(this); 288 integration_service->job_list()->AddObserver(this); 289 } 290 291 if (chromeos::ConnectivityStateHelper::IsInitialized()) { 292 chromeos::ConnectivityStateHelper::Get()-> 293 AddNetworkManagerObserver(this); 294 } 295 296 mounted_disk_monitor_.reset(new MountedDiskMonitor()); 297 298 pref_change_registrar_->Init(profile_->GetPrefs()); 299 300 pref_change_registrar_->Add( 301 prefs::kExternalStorageDisabled, 302 base::Bind(&EventRouter::OnExternalStorageDisabledChanged, 303 weak_factory_.GetWeakPtr())); 304 305 base::Closure callback = 306 base::Bind(&EventRouter::OnFileManagerPrefsChanged, 307 weak_factory_.GetWeakPtr()); 308 pref_change_registrar_->Add(prefs::kDisableDriveOverCellular, callback); 309 pref_change_registrar_->Add(prefs::kDisableDriveHostedFiles, callback); 310 pref_change_registrar_->Add(prefs::kDisableDrive, callback); 311 pref_change_registrar_->Add(prefs::kUse24HourClock, callback); 312} 313 314// File watch setup routines. 315void EventRouter::AddFileWatch(const base::FilePath& local_path, 316 const base::FilePath& virtual_path, 317 const std::string& extension_id, 318 const BoolCallback& callback) { 319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 320 DCHECK(!callback.is_null()); 321 322 base::FilePath watch_path = local_path; 323 bool is_on_drive = drive::util::IsUnderDriveMountPoint(watch_path); 324 // Tweak watch path for remote sources - we need to drop leading /special 325 // directory from there in order to be able to pair these events with 326 // their change notifications. 327 if (is_on_drive) 328 watch_path = drive::util::ExtractDrivePath(watch_path); 329 330 WatcherMap::iterator iter = file_watchers_.find(watch_path); 331 if (iter == file_watchers_.end()) { 332 scoped_ptr<FileWatcher> watcher(new FileWatcher(virtual_path)); 333 watcher->AddExtension(extension_id); 334 335 if (is_on_drive) { 336 // For Drive, file watching is done via OnDirectoryChanged(). 337 base::MessageLoopProxy::current()->PostTask(FROM_HERE, 338 base::Bind(callback, true)); 339 } else { 340 // For local files, start watching using FileWatcher. 341 watcher->WatchLocalFile( 342 watch_path, 343 base::Bind(&EventRouter::HandleFileWatchNotification, 344 weak_factory_.GetWeakPtr()), 345 callback); 346 } 347 348 file_watchers_[watch_path] = watcher.release(); 349 } else { 350 iter->second->AddExtension(extension_id); 351 base::MessageLoopProxy::current()->PostTask(FROM_HERE, 352 base::Bind(callback, true)); 353 } 354} 355 356void EventRouter::RemoveFileWatch(const base::FilePath& local_path, 357 const std::string& extension_id) { 358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 359 360 base::FilePath watch_path = local_path; 361 // Tweak watch path for remote sources - we need to drop leading /special 362 // directory from there in order to be able to pair these events with 363 // their change notifications. 364 if (drive::util::IsUnderDriveMountPoint(watch_path)) { 365 watch_path = drive::util::ExtractDrivePath(watch_path); 366 } 367 WatcherMap::iterator iter = file_watchers_.find(watch_path); 368 if (iter == file_watchers_.end()) 369 return; 370 // Remove the watcher if |watch_path| is no longer watched by any extensions. 371 iter->second->RemoveExtension(extension_id); 372 if (iter->second->GetExtensionIds().empty()) { 373 delete iter->second; 374 file_watchers_.erase(iter); 375 } 376} 377 378void EventRouter::OnDiskEvent(DiskMountManager::DiskEvent event, 379 const DiskMountManager::Disk* disk) { 380 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 381 382 // Disregard hidden devices. 383 if (disk->is_hidden()) 384 return; 385 if (event == DiskMountManager::DISK_ADDED) { 386 OnDiskAdded(disk); 387 } else if (event == DiskMountManager::DISK_REMOVED) { 388 OnDiskRemoved(disk); 389 } 390} 391 392void EventRouter::OnDeviceEvent(DiskMountManager::DeviceEvent event, 393 const std::string& device_path) { 394 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 395 396 if (event == DiskMountManager::DEVICE_ADDED) { 397 OnDeviceAdded(device_path); 398 } else if (event == DiskMountManager::DEVICE_REMOVED) { 399 OnDeviceRemoved(device_path); 400 } else if (event == DiskMountManager::DEVICE_SCANNED) { 401 OnDeviceScanned(device_path); 402 } 403} 404 405void EventRouter::OnMountEvent( 406 DiskMountManager::MountEvent event, 407 chromeos::MountError error_code, 408 const DiskMountManager::MountPointInfo& mount_info) { 409 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 410 // profile_ is NULL if ShutdownOnUIThread() is called earlier. This can 411 // happen at shutdown. 412 if (!profile_) 413 return; 414 415 DCHECK(mount_info.mount_type != chromeos::MOUNT_TYPE_INVALID); 416 417 DispatchMountEvent(event, error_code, mount_info); 418 419 if (mount_info.mount_type == chromeos::MOUNT_TYPE_DEVICE && 420 event == DiskMountManager::MOUNTING) { 421 DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); 422 const DiskMountManager::Disk* disk = 423 disk_mount_manager->FindDiskBySourcePath(mount_info.source_path); 424 if (!disk || mounted_disk_monitor_->DiskIsRemounting(*disk)) 425 return; 426 427 notifications_->ManageNotificationsOnMountCompleted( 428 disk->system_path_prefix(), disk->drive_label(), disk->is_parent(), 429 error_code == chromeos::MOUNT_ERROR_NONE, 430 error_code == chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM); 431 432 // If a new device was mounted, a new File manager window may need to be 433 // opened. 434 if (error_code == chromeos::MOUNT_ERROR_NONE) 435 ShowRemovableDeviceInFileManager( 436 *disk, 437 base::FilePath::FromUTF8Unsafe(mount_info.mount_path)); 438 } else if (mount_info.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) { 439 // Clear the "mounted" state for archive files in drive cache 440 // when mounting failed or unmounting succeeded. 441 if ((event == DiskMountManager::MOUNTING) != 442 (error_code == chromeos::MOUNT_ERROR_NONE)) { 443 DriveIntegrationService* integration_service = 444 DriveIntegrationServiceFactory::GetForProfile(profile_); 445 drive::FileSystemInterface* file_system = 446 integration_service ? integration_service->file_system() : NULL; 447 if (file_system) { 448 file_system->MarkCacheFileAsUnmounted( 449 base::FilePath(mount_info.source_path), 450 base::Bind(&OnMarkAsUnmounted)); 451 } 452 } 453 } 454} 455 456void EventRouter::OnFormatEvent(DiskMountManager::FormatEvent event, 457 chromeos::FormatError error_code, 458 const std::string& device_path) { 459 if (event == DiskMountManager::FORMAT_STARTED) { 460 OnFormatStarted(device_path, error_code == chromeos::FORMAT_ERROR_NONE); 461 } else if (event == DiskMountManager::FORMAT_COMPLETED) { 462 OnFormatCompleted(device_path, error_code == chromeos::FORMAT_ERROR_NONE); 463 } 464} 465 466void EventRouter::NetworkManagerChanged() { 467 if (!profile_ || 468 !extensions::ExtensionSystem::Get(profile_)->event_router()) { 469 NOTREACHED(); 470 return; 471 } 472 scoped_ptr<extensions::Event> event(new extensions::Event( 473 extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged, 474 scoped_ptr<ListValue>(new ListValue()))); 475 extensions::ExtensionSystem::Get(profile_)->event_router()-> 476 BroadcastEvent(event.Pass()); 477} 478 479void EventRouter::DefaultNetworkChanged() { 480 NetworkManagerChanged(); 481} 482 483void EventRouter::OnExternalStorageDisabledChanged() { 484 // If the policy just got disabled we have to unmount every device currently 485 // mounted. The opposite is fine - we can let the user re-plug her device to 486 // make it available. 487 if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { 488 DiskMountManager* manager = DiskMountManager::GetInstance(); 489 DiskMountManager::MountPointMap mounts(manager->mount_points()); 490 for (DiskMountManager::MountPointMap::const_iterator it = mounts.begin(); 491 it != mounts.end(); ++it) { 492 LOG(INFO) << "Unmounting " << it->second.mount_path 493 << " because of policy."; 494 manager->UnmountPath(it->second.mount_path, 495 chromeos::UNMOUNT_OPTIONS_NONE, 496 DiskMountManager::UnmountPathCallback()); 497 } 498 } 499} 500 501void EventRouter::OnFileManagerPrefsChanged() { 502 if (!profile_ || 503 !extensions::ExtensionSystem::Get(profile_)->event_router()) { 504 NOTREACHED(); 505 return; 506 } 507 508 scoped_ptr<extensions::Event> event(new extensions::Event( 509 extensions::event_names::kOnFileBrowserPreferencesChanged, 510 scoped_ptr<ListValue>(new ListValue()))); 511 extensions::ExtensionSystem::Get(profile_)->event_router()-> 512 BroadcastEvent(event.Pass()); 513} 514 515void EventRouter::OnJobAdded(const drive::JobInfo& job_info) { 516 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 517 OnJobUpdated(job_info); 518} 519 520void EventRouter::OnJobUpdated(const drive::JobInfo& job_info) { 521 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 522 if (!drive::IsActiveFileTransferJobInfo(job_info)) 523 return; 524 525 bool is_new_job = (drive_jobs_.find(job_info.job_id) == drive_jobs_.end()); 526 527 // Replace with the latest job info. 528 drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus( 529 job_info, 530 is_new_job ? kFileTransferStateStarted : kFileTransferStateInProgress); 531 532 // Fire event if needed. 533 bool always = is_new_job; 534 SendDriveFileTransferEvent(always); 535} 536 537void EventRouter::OnJobDone(const drive::JobInfo& job_info, 538 drive::FileError error) { 539 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 540 if (!drive::IsActiveFileTransferJobInfo(job_info)) 541 return; 542 543 // Replace with the latest job info. 544 drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus( 545 job_info, 546 error == drive::FILE_ERROR_OK ? kFileTransferStateCompleted 547 : kFileTransferStateFailed); 548 549 // Fire event if needed. 550 bool always = true; 551 SendDriveFileTransferEvent(always); 552 553 // Forget about the job. 554 drive_jobs_.erase(job_info.job_id); 555} 556 557void EventRouter::SendDriveFileTransferEvent(bool always) { 558 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 559 560 const base::Time now = base::Time::Now(); 561 562 // When |always| flag is not set, we don't send the event until certain 563 // amount of time passes after the previous one. This is to avoid 564 // flooding the IPC between extensions by many onFileTransferUpdated events. 565 if (!always) { 566 const int64 delta = (now - last_file_transfer_event_).InMilliseconds(); 567 // delta < 0 may rarely happen if system clock is synced and rewinded. 568 // To be conservative, we don't skip in that case. 569 if (0 <= delta && delta < kFileTransferEventFrequencyInMilliseconds) 570 return; 571 } 572 573 // Convert the current |drive_jobs_| to a JSON value. 574 scoped_ptr<base::ListValue> event_list(new base::ListValue); 575 for (std::map<drive::JobID, DriveJobInfoWithStatus>::iterator 576 iter = drive_jobs_.begin(); iter != drive_jobs_.end(); ++iter) { 577 578 scoped_ptr<base::DictionaryValue> job_info_dict( 579 JobInfoToDictionaryValue(kFileBrowserDomain, 580 iter->second.status, 581 iter->second.job_info)); 582 event_list->Append(job_info_dict.release()); 583 } 584 585 scoped_ptr<ListValue> args(new ListValue()); 586 args->Append(event_list.release()); 587 scoped_ptr<extensions::Event> event(new extensions::Event( 588 extensions::event_names::kOnFileTransfersUpdated, args.Pass())); 589 extensions::ExtensionSystem::Get(profile_)->event_router()-> 590 DispatchEventToExtension(kFileBrowserDomain, event.Pass()); 591 592 last_file_transfer_event_ = now; 593} 594 595void EventRouter::OnDirectoryChanged(const base::FilePath& directory_path) { 596 HandleFileWatchNotification(directory_path, false); 597} 598 599void EventRouter::OnFileSystemMounted() { 600 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 601 602 const std::string& drive_path = drive::util::GetDriveMountPointPathAsString(); 603 DiskMountManager::MountPointInfo mount_info( 604 drive_path, 605 drive_path, 606 chromeos::MOUNT_TYPE_GOOGLE_DRIVE, 607 chromeos::disks::MOUNT_CONDITION_NONE); 608 609 // Raise mount event. 610 // We can pass chromeos::MOUNT_ERROR_NONE even when authentication is failed 611 // or network is unreachable. These two errors will be handled later. 612 OnMountEvent(DiskMountManager::MOUNTING, chromeos::MOUNT_ERROR_NONE, 613 mount_info); 614} 615 616void EventRouter::OnFileSystemBeingUnmounted() { 617 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 618 619 // Raise a mount event to notify the File Manager. 620 const std::string& drive_path = drive::util::GetDriveMountPointPathAsString(); 621 DiskMountManager::MountPointInfo mount_info( 622 drive_path, 623 drive_path, 624 chromeos::MOUNT_TYPE_GOOGLE_DRIVE, 625 chromeos::disks::MOUNT_CONDITION_NONE); 626 OnMountEvent(DiskMountManager::UNMOUNTING, chromeos::MOUNT_ERROR_NONE, 627 mount_info); 628} 629 630void EventRouter::OnRefreshTokenInvalid() { 631 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 632 633 // Raise a DriveConnectionStatusChanged event to notify the status offline. 634 scoped_ptr<extensions::Event> event(new extensions::Event( 635 extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged, 636 scoped_ptr<ListValue>(new ListValue()))); 637 extensions::ExtensionSystem::Get(profile_)->event_router()-> 638 BroadcastEvent(event.Pass()); 639} 640 641void EventRouter::HandleFileWatchNotification(const base::FilePath& local_path, 642 bool got_error) { 643 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 644 645 WatcherMap::const_iterator iter = file_watchers_.find(local_path); 646 if (iter == file_watchers_.end()) { 647 return; 648 } 649 DispatchDirectoryChangeEvent(iter->second->virtual_path(), got_error, 650 iter->second->GetExtensionIds()); 651} 652 653void EventRouter::DispatchDirectoryChangeEvent( 654 const base::FilePath& virtual_path, 655 bool got_error, 656 const std::vector<std::string>& extension_ids) { 657 if (!profile_) { 658 NOTREACHED(); 659 return; 660 } 661 662 for (size_t i = 0; i < extension_ids.size(); ++i) { 663 const std::string& extension_id = extension_ids[i]; 664 665 GURL target_origin_url(extensions::Extension::GetBaseURLFromExtensionId( 666 extension_id)); 667 GURL base_url = fileapi::GetFileSystemRootURI( 668 target_origin_url, 669 fileapi::kFileSystemTypeExternal); 670 GURL target_directory_url = GURL(base_url.spec() + virtual_path.value()); 671 scoped_ptr<ListValue> args(new ListValue()); 672 DictionaryValue* watch_info = new DictionaryValue(); 673 args->Append(watch_info); 674 watch_info->SetString("directoryUrl", target_directory_url.spec()); 675 watch_info->SetString("eventType", 676 got_error ? kPathWatchError : kPathChanged); 677 678 // TODO(mtomasz): Pass set of entries. http://crbug.com/157834 679 ListValue* watch_info_entries = new ListValue(); 680 watch_info->Set("changedEntries", watch_info_entries); 681 682 scoped_ptr<extensions::Event> event(new extensions::Event( 683 extensions::event_names::kOnDirectoryChanged, args.Pass())); 684 extensions::ExtensionSystem::Get(profile_)->event_router()-> 685 DispatchEventToExtension(extension_id, event.Pass()); 686 } 687} 688 689void EventRouter::DispatchMountEvent( 690 DiskMountManager::MountEvent event, 691 chromeos::MountError error_code, 692 const DiskMountManager::MountPointInfo& mount_info) { 693 scoped_ptr<ListValue> args(new ListValue()); 694 DictionaryValue* mount_info_value = new DictionaryValue(); 695 args->Append(mount_info_value); 696 mount_info_value->SetString( 697 "eventType", 698 event == DiskMountManager::MOUNTING ? "mount" : "unmount"); 699 mount_info_value->SetString("status", MountErrorToString(error_code)); 700 mount_info_value->SetString( 701 "mountType", 702 DiskMountManager::MountTypeToString(mount_info.mount_type)); 703 704 // Add sourcePath to the event. 705 mount_info_value->SetString("sourcePath", mount_info.source_path); 706 707 base::FilePath relative_mount_path; 708 709 // If there were no error or some special conditions occurred, add mountPath 710 // to the event. 711 if (event == DiskMountManager::UNMOUNTING || 712 error_code == chromeos::MOUNT_ERROR_NONE || 713 mount_info.mount_condition) { 714 // Convert mount point path to relative path with the external file system 715 // exposed within File API. 716 if (util::ConvertFileToRelativeFileSystemPath( 717 profile_, 718 kFileBrowserDomain, 719 base::FilePath(mount_info.mount_path), 720 &relative_mount_path)) { 721 mount_info_value->SetString("mountPath", 722 "/" + relative_mount_path.value()); 723 } else { 724 mount_info_value->SetString( 725 "status", 726 MountErrorToString(chromeos::MOUNT_ERROR_PATH_UNMOUNTED)); 727 } 728 } 729 730 scoped_ptr<extensions::Event> extension_event(new extensions::Event( 731 extensions::event_names::kOnFileBrowserMountCompleted, args.Pass())); 732 extensions::ExtensionSystem::Get(profile_)->event_router()-> 733 BroadcastEvent(extension_event.Pass()); 734} 735 736void EventRouter::ShowRemovableDeviceInFileManager( 737 const DiskMountManager::Disk& disk, 738 const base::FilePath& mount_path) { 739 // Do not attempt to open File Manager while the login is in progress or 740 // the screen is locked. 741 if (chromeos::LoginDisplayHostImpl::default_host() || 742 chromeos::ScreenLocker::default_screen_locker()) 743 return; 744 745 // According to DCF (Design rule of Camera File system) by JEITA / CP-3461 746 // cameras should have pictures located in the DCIM root directory. 747 const base::FilePath dcim_path = mount_path.Append( 748 FILE_PATH_LITERAL("DCIM")); 749 750 // If there is no DCIM folder or an external photo importer is not available, 751 // then launch Files.app. 752 DirectoryExistsOnUIThread( 753 dcim_path, 754 IsGooglePhotosInstalled(profile_) ? 755 base::Bind(&base::DoNothing) : 756 base::Bind(&util::ViewRemovableDrive, mount_path), 757 base::Bind(&util::ViewRemovableDrive, mount_path)); 758} 759 760void EventRouter::OnDiskAdded(const DiskMountManager::Disk* disk) { 761 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 762 763 VLOG(1) << "Disk added: " << disk->device_path(); 764 if (disk->device_path().empty()) { 765 VLOG(1) << "Empty system path for " << disk->device_path(); 766 return; 767 } 768 769 // If disk is not mounted yet and it has media and there is no policy 770 // forbidding external storage, give it a try. 771 if (disk->mount_path().empty() && disk->has_media() && 772 !profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { 773 // Initiate disk mount operation. MountPath auto-detects the filesystem 774 // format if the second argument is empty. The third argument (mount label) 775 // is not used in a disk mount operation. 776 DiskMountManager::GetInstance()->MountPath( 777 disk->device_path(), std::string(), std::string(), 778 chromeos::MOUNT_TYPE_DEVICE); 779 } else { 780 // Either the disk was mounted or it has no media. In both cases we don't 781 // want the Scanning notification to persist. 782 notifications_->HideNotification(DesktopNotifications::DEVICE, 783 disk->system_path_prefix()); 784 } 785} 786 787void EventRouter::OnDiskRemoved(const DiskMountManager::Disk* disk) { 788 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 789 790 VLOG(1) << "Disk removed: " << disk->device_path(); 791 792 if (!disk->mount_path().empty()) { 793 DiskMountManager::GetInstance()->UnmountPath( 794 disk->mount_path(), 795 chromeos::UNMOUNT_OPTIONS_LAZY, 796 DiskMountManager::UnmountPathCallback()); 797 } 798} 799 800void EventRouter::OnDeviceAdded(const std::string& device_path) { 801 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 802 803 VLOG(1) << "Device added : " << device_path; 804 805 // If the policy is set instead of showing the new device notification we show 806 // a notification that the operation is not permitted. 807 if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { 808 notifications_->ShowNotification( 809 DesktopNotifications::DEVICE_EXTERNAL_STORAGE_DISABLED, 810 device_path); 811 return; 812 } 813 814 notifications_->RegisterDevice(device_path); 815 notifications_->ShowNotificationDelayed(DesktopNotifications::DEVICE, 816 device_path, 817 base::TimeDelta::FromSeconds(5)); 818} 819 820void EventRouter::OnDeviceRemoved(const std::string& device_path) { 821 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 822 823 VLOG(1) << "Device removed : " << device_path; 824 notifications_->HideNotification(DesktopNotifications::DEVICE, 825 device_path); 826 notifications_->HideNotification(DesktopNotifications::DEVICE_FAIL, 827 device_path); 828 notifications_->UnregisterDevice(device_path); 829} 830 831void EventRouter::OnDeviceScanned(const std::string& device_path) { 832 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 833 VLOG(1) << "Device scanned : " << device_path; 834} 835 836void EventRouter::OnFormatStarted(const std::string& device_path, 837 bool success) { 838 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 839 840 if (success) { 841 notifications_->ShowNotification(DesktopNotifications::FORMAT_START, 842 device_path); 843 } else { 844 notifications_->ShowNotification( 845 DesktopNotifications::FORMAT_START_FAIL, device_path); 846 } 847} 848 849void EventRouter::OnFormatCompleted(const std::string& device_path, 850 bool success) { 851 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 852 853 if (success) { 854 notifications_->HideNotification(DesktopNotifications::FORMAT_START, 855 device_path); 856 notifications_->ShowNotification(DesktopNotifications::FORMAT_SUCCESS, 857 device_path); 858 // Hide it after a couple of seconds. 859 notifications_->HideNotificationDelayed( 860 DesktopNotifications::FORMAT_SUCCESS, 861 device_path, 862 base::TimeDelta::FromSeconds(4)); 863 // MountPath auto-detects filesystem format if second argument is empty. 864 // The third argument (mount label) is not used in a disk mount operation. 865 DiskMountManager::GetInstance()->MountPath(device_path, std::string(), 866 std::string(), 867 chromeos::MOUNT_TYPE_DEVICE); 868 } else { 869 notifications_->HideNotification(DesktopNotifications::FORMAT_START, 870 device_path); 871 notifications_->ShowNotification(DesktopNotifications::FORMAT_FAIL, 872 device_path); 873 } 874} 875 876} // namespace file_manager 877