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 39 using chromeos::disks::DiskMountManager; 40 using chromeos::NetworkHandler; 41 using content::BrowserThread; 42 using drive::DriveIntegrationService; 43 using drive::DriveIntegrationServiceFactory; 44 45 namespace file_manager { 46 namespace { 47 48 const char kPathChanged[] = "changed"; 49 const char kPathWatchError[] = "error"; 50 51 // Used as a callback for FileSystem::MarkCacheFileAsUnmounted(). 52 void OnMarkAsUnmounted(drive::FileError error) { 53 // Do nothing. 54 } 55 56 const 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 101 void 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 112 void 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. 127 base::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. 143 const char kFileTransferStateStarted[] = "started"; 144 const char kFileTransferStateInProgress[] = "in_progress"; 145 const char kFileTransferStateCompleted[] = "completed"; 146 const char kFileTransferStateFailed[] = "failed"; 147 148 // Frequency of sending onFileTransferUpdated. 149 const int64 kFileTransferEventFrequencyInMilliseconds = 1000; 150 151 // Utility function to check if |job_info| is a file uploading job. 152 bool 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. 158 bool IsDownloadJob(drive::JobType type) { 159 return type == drive::TYPE_DOWNLOAD_FILE; 160 } 161 162 // Converts the job info to its JSON (Value) form. 163 scoped_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. 186 bool 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. 212 EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus() 213 : job_info(drive::TYPE_DOWNLOAD_FILE) { 214 } 215 216 EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus( 217 const drive::JobInfo& info, const std::string& status) 218 : job_info(info), status(status) { 219 } 220 221 EventRouter::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 230 EventRouter::~EventRouter() { 231 } 232 233 void 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 266 void 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. 317 void 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 358 void 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 380 void 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 394 void 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 407 void 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 458 void 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 468 void 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 481 void EventRouter::DefaultNetworkChanged(const chromeos::NetworkState* network) { 482 NetworkManagerChanged(); 483 } 484 485 void 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 503 void 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 517 void EventRouter::OnJobAdded(const drive::JobInfo& job_info) { 518 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 519 OnJobUpdated(job_info); 520 } 521 522 void 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 539 void 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 559 void 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 597 void EventRouter::OnDirectoryChanged(const base::FilePath& directory_path) { 598 HandleFileWatchNotification(directory_path, false); 599 } 600 601 void 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 618 void 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 632 void 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 643 void 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 655 void 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 691 void 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 738 void 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 762 void 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 789 void 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 802 void 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 822 void 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 833 void EventRouter::OnDeviceScanned(const std::string& device_path) { 834 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 835 VLOG(1) << "Device scanned : " << device_path; 836 } 837 838 void 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 851 void 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