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