event_router.cc revision f2477e01787aa58f445919b809d89e252beef54f
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/private_api_util.h" 20#include "chrome/browser/chromeos/file_manager/app_id.h" 21#include "chrome/browser/chromeos/file_manager/desktop_notifications.h" 22#include "chrome/browser/chromeos/file_manager/fileapi_util.h" 23#include "chrome/browser/chromeos/file_manager/open_util.h" 24#include "chrome/browser/chromeos/file_manager/volume_manager.h" 25#include "chrome/browser/chromeos/login/login_display_host_impl.h" 26#include "chrome/browser/chromeos/login/screen_locker.h" 27#include "chrome/browser/drive/drive_service_interface.h" 28#include "chrome/browser/extensions/event_names.h" 29#include "chrome/browser/extensions/extension_service.h" 30#include "chrome/browser/extensions/extension_system.h" 31#include "chrome/browser/profiles/profile.h" 32#include "chrome/common/extensions/api/file_browser_private.h" 33#include "chrome/common/pref_names.h" 34#include "chromeos/login/login_state.h" 35#include "chromeos/network/network_handler.h" 36#include "chromeos/network/network_state_handler.h" 37#include "content/public/browser/browser_thread.h" 38#include "content/public/browser/notification_source.h" 39#include "extensions/browser/event_router.h" 40#include "webkit/common/fileapi/file_system_types.h" 41#include "webkit/common/fileapi/file_system_util.h" 42 43using chromeos::disks::DiskMountManager; 44using chromeos::NetworkHandler; 45using content::BrowserThread; 46using drive::DriveIntegrationService; 47using drive::DriveIntegrationServiceFactory; 48 49namespace file_browser_private = extensions::api::file_browser_private; 50 51namespace file_manager { 52namespace { 53 54const char kPathChanged[] = "changed"; 55const char kPathWatchError[] = "error"; 56 57void DirectoryExistsOnBlockingPool(const base::FilePath& directory_path, 58 const base::Closure& success_callback, 59 const base::Closure& failure_callback) { 60 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 61 62 if (base::DirectoryExists(directory_path)) 63 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, success_callback); 64 else 65 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, failure_callback); 66}; 67 68void DirectoryExistsOnUIThread(const base::FilePath& directory_path, 69 const base::Closure& success_callback, 70 const base::Closure& failure_callback) { 71 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 72 73 content::BrowserThread::PostBlockingPoolTask( 74 FROM_HERE, 75 base::Bind(&DirectoryExistsOnBlockingPool, 76 directory_path, 77 success_callback, 78 failure_callback)); 79}; 80 81// Constants for the "transferState" field of onFileTransferUpdated event. 82const char kFileTransferStateStarted[] = "started"; 83const char kFileTransferStateInProgress[] = "in_progress"; 84const char kFileTransferStateCompleted[] = "completed"; 85const char kFileTransferStateFailed[] = "failed"; 86 87// Frequency of sending onFileTransferUpdated. 88const int64 kFileTransferEventFrequencyInMilliseconds = 1000; 89 90// Utility function to check if |job_info| is a file uploading job. 91bool IsUploadJob(drive::JobType type) { 92 return (type == drive::TYPE_UPLOAD_NEW_FILE || 93 type == drive::TYPE_UPLOAD_EXISTING_FILE); 94} 95 96// Converts the job info to its JSON (Value) form. 97scoped_ptr<base::DictionaryValue> JobInfoToDictionaryValue( 98 const std::string& extension_id, 99 const std::string& job_status, 100 const drive::JobInfo& job_info) { 101 DCHECK(IsActiveFileTransferJobInfo(job_info)); 102 103 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue); 104 GURL url = util::ConvertRelativeFilePathToFileSystemUrl( 105 job_info.file_path, extension_id); 106 result->SetString("fileUrl", url.spec()); 107 result->SetString("transferState", job_status); 108 result->SetString("transferType", 109 IsUploadJob(job_info.job_type) ? "upload" : "download"); 110 // JavaScript does not have 64-bit integers. Instead we use double, which 111 // is in IEEE 754 formant and accurate up to 52-bits in JS, and in practice 112 // in C++. Larger values are rounded. 113 result->SetDouble("processed", 114 static_cast<double>(job_info.num_completed_bytes)); 115 result->SetDouble("total", static_cast<double>(job_info.num_total_bytes)); 116 return result.Pass(); 117} 118 119// Checks for availability of the Google+ Photos app. 120bool IsGooglePhotosInstalled(Profile *profile) { 121 ExtensionService* service = 122 extensions::ExtensionSystem::Get(profile)->extension_service(); 123 if (!service) 124 return false; 125 126 // Google+ Photos uses several ids for different channels. Therefore, all of 127 // them should be checked. 128 const std::string kGooglePlusPhotosIds[] = { 129 "ebpbnabdhheoknfklmpddcdijjkmklkp", // G+ Photos staging 130 "efjnaogkjbogokcnohkmnjdojkikgobo", // G+ Photos prod 131 "ejegoaikibpmikoejfephaneibodccma" // G+ Photos dev 132 }; 133 134 for (size_t i = 0; i < arraysize(kGooglePlusPhotosIds); ++i) { 135 if (service->GetExtensionById(kGooglePlusPhotosIds[i], 136 false /* include_disable */) != NULL) 137 return true; 138 } 139 140 return false; 141} 142 143// Sends an event named |event_name| with arguments |event_args| to extensions. 144void BroadcastEvent(Profile* profile, 145 const std::string& event_name, 146 scoped_ptr<base::ListValue> event_args) { 147 extensions::ExtensionSystem::Get(profile)->event_router()-> 148 BroadcastEvent(make_scoped_ptr( 149 new extensions::Event(event_name, event_args.Pass()))); 150} 151 152file_browser_private::MountCompletedEvent::Status 153MountErrorToMountCompletedStatus(chromeos::MountError error) { 154 using file_browser_private::MountCompletedEvent; 155 156 switch (error) { 157 case chromeos::MOUNT_ERROR_NONE: 158 return MountCompletedEvent::STATUS_SUCCESS; 159 case chromeos::MOUNT_ERROR_UNKNOWN: 160 return MountCompletedEvent::STATUS_ERROR_UNKNOWN; 161 case chromeos::MOUNT_ERROR_INTERNAL: 162 return MountCompletedEvent::STATUS_ERROR_INTERNAL; 163 case chromeos::MOUNT_ERROR_INVALID_ARGUMENT: 164 return MountCompletedEvent::STATUS_ERROR_INVALID_ARGUMENT; 165 case chromeos::MOUNT_ERROR_INVALID_PATH: 166 return MountCompletedEvent::STATUS_ERROR_INVALID_PATH; 167 case chromeos::MOUNT_ERROR_PATH_ALREADY_MOUNTED: 168 return MountCompletedEvent::STATUS_ERROR_PATH_ALREADY_MOUNTED; 169 case chromeos::MOUNT_ERROR_PATH_NOT_MOUNTED: 170 return MountCompletedEvent::STATUS_ERROR_PATH_NOT_MOUNTED; 171 case chromeos::MOUNT_ERROR_DIRECTORY_CREATION_FAILED: 172 return MountCompletedEvent::STATUS_ERROR_DIRECTORY_CREATION_FAILED; 173 case chromeos::MOUNT_ERROR_INVALID_MOUNT_OPTIONS: 174 return MountCompletedEvent::STATUS_ERROR_INVALID_MOUNT_OPTIONS; 175 case chromeos::MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS: 176 return MountCompletedEvent::STATUS_ERROR_INVALID_UNMOUNT_OPTIONS; 177 case chromeos::MOUNT_ERROR_INSUFFICIENT_PERMISSIONS: 178 return MountCompletedEvent::STATUS_ERROR_INSUFFICIENT_PERMISSIONS; 179 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_NOT_FOUND: 180 return MountCompletedEvent::STATUS_ERROR_MOUNT_PROGRAM_NOT_FOUND; 181 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_FAILED: 182 return MountCompletedEvent::STATUS_ERROR_MOUNT_PROGRAM_FAILED; 183 case chromeos::MOUNT_ERROR_INVALID_DEVICE_PATH: 184 return MountCompletedEvent::STATUS_ERROR_INVALID_DEVICE_PATH; 185 case chromeos::MOUNT_ERROR_UNKNOWN_FILESYSTEM: 186 return MountCompletedEvent::STATUS_ERROR_UNKNOWN_FILESYSTEM; 187 case chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM: 188 return MountCompletedEvent::STATUS_ERROR_UNSUPORTED_FILESYSTEM; 189 case chromeos::MOUNT_ERROR_INVALID_ARCHIVE: 190 return MountCompletedEvent::STATUS_ERROR_INVALID_ARCHIVE; 191 case chromeos::MOUNT_ERROR_NOT_AUTHENTICATED: 192 return MountCompletedEvent::STATUS_ERROR_AUTHENTICATION; 193 case chromeos::MOUNT_ERROR_PATH_UNMOUNTED: 194 return MountCompletedEvent::STATUS_ERROR_PATH_UNMOUNTED; 195 } 196 NOTREACHED(); 197 return MountCompletedEvent::STATUS_NONE; 198} 199 200void BroadcastMountCompletedEvent( 201 Profile* profile, 202 file_browser_private::MountCompletedEvent::EventType event_type, 203 chromeos::MountError error, 204 const VolumeInfo& volume_info) { 205 file_browser_private::MountCompletedEvent event; 206 event.event_type = event_type; 207 event.status = MountErrorToMountCompletedStatus(error); 208 util::VolumeInfoToVolumeMetadata( 209 profile, volume_info, &event.volume_metadata); 210 211 if (!volume_info.mount_path.empty() && 212 event.volume_metadata.mount_path.empty()) { 213 event.status = 214 file_browser_private::MountCompletedEvent::STATUS_ERROR_PATH_UNMOUNTED; 215 } 216 217 BroadcastEvent( 218 profile, 219 extensions::event_names::kOnFileBrowserMountCompleted, 220 file_browser_private::OnMountCompleted::Create(event)); 221} 222 223file_browser_private::CopyProgressStatus::Type 224CopyProgressTypeToCopyProgressStatusType( 225 fileapi::FileSystemOperation::CopyProgressType type) { 226 using file_browser_private::CopyProgressStatus; 227 228 switch (type) { 229 case fileapi::FileSystemOperation::BEGIN_COPY_ENTRY: 230 return CopyProgressStatus::TYPE_BEGIN_COPY_ENTRY; 231 case fileapi::FileSystemOperation::END_COPY_ENTRY: 232 return CopyProgressStatus::TYPE_END_COPY_ENTRY; 233 case fileapi::FileSystemOperation::PROGRESS: 234 return CopyProgressStatus::TYPE_PROGRESS; 235 } 236 NOTREACHED(); 237 return CopyProgressStatus::TYPE_NONE; 238} 239 240} // namespace 241 242// Pass dummy value to JobInfo's constructor for make it default constructible. 243EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus() 244 : job_info(drive::TYPE_DOWNLOAD_FILE) { 245} 246 247EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus( 248 const drive::JobInfo& info, const std::string& status) 249 : job_info(info), status(status) { 250} 251 252EventRouter::EventRouter(Profile* profile) 253 : notifications_(new DesktopNotifications(profile)), 254 pref_change_registrar_(new PrefChangeRegistrar), 255 profile_(profile), 256 weak_factory_(this) { 257 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 258} 259 260EventRouter::~EventRouter() { 261} 262 263void EventRouter::Shutdown() { 264 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 265 266 DLOG_IF(WARNING, !file_watchers_.empty()) 267 << "Not all file watchers are " 268 << "removed. This can happen when Files.app is open during shutdown."; 269 STLDeleteValues(&file_watchers_); 270 if (!profile_) { 271 NOTREACHED(); 272 return; 273 } 274 275 pref_change_registrar_->RemoveAll(); 276 277 if (NetworkHandler::IsInitialized()) { 278 NetworkHandler::Get()->network_state_handler()->RemoveObserver(this, 279 FROM_HERE); 280 } 281 282 DriveIntegrationService* integration_service = 283 DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates( 284 profile_); 285 if (integration_service) { 286 integration_service->file_system()->RemoveObserver(this); 287 integration_service->drive_service()->RemoveObserver(this); 288 integration_service->job_list()->RemoveObserver(this); 289 } 290 291 VolumeManager* volume_manager = VolumeManager::Get(profile_); 292 if (volume_manager) 293 volume_manager->RemoveObserver(this); 294 295 profile_ = NULL; 296} 297 298void EventRouter::ObserveFileSystemEvents() { 299 if (!profile_) { 300 NOTREACHED(); 301 return; 302 } 303 if (!chromeos::LoginState::IsInitialized() || 304 !chromeos::LoginState::Get()->IsUserLoggedIn()) { 305 return; 306 } 307 308 // VolumeManager's construction triggers DriveIntegrationService's 309 // construction, so it is necessary to call VolumeManager's Get before 310 // accessing DriveIntegrationService. 311 VolumeManager* volume_manager = VolumeManager::Get(profile_); 312 if (volume_manager) 313 volume_manager->AddObserver(this); 314 315 DriveIntegrationService* integration_service = 316 DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates( 317 profile_); 318 if (integration_service) { 319 integration_service->drive_service()->AddObserver(this); 320 integration_service->file_system()->AddObserver(this); 321 integration_service->job_list()->AddObserver(this); 322 } 323 324 if (NetworkHandler::IsInitialized()) { 325 NetworkHandler::Get()->network_state_handler()->AddObserver(this, 326 FROM_HERE); 327 } 328 329 pref_change_registrar_->Init(profile_->GetPrefs()); 330 base::Closure callback = 331 base::Bind(&EventRouter::OnFileManagerPrefsChanged, 332 weak_factory_.GetWeakPtr()); 333 pref_change_registrar_->Add(prefs::kDisableDriveOverCellular, callback); 334 pref_change_registrar_->Add(prefs::kDisableDriveHostedFiles, callback); 335 pref_change_registrar_->Add(prefs::kDisableDrive, callback); 336 pref_change_registrar_->Add(prefs::kUse24HourClock, callback); 337} 338 339// File watch setup routines. 340void EventRouter::AddFileWatch(const base::FilePath& local_path, 341 const base::FilePath& virtual_path, 342 const std::string& extension_id, 343 const BoolCallback& callback) { 344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 345 DCHECK(!callback.is_null()); 346 347 base::FilePath watch_path = local_path; 348 bool is_on_drive = drive::util::IsUnderDriveMountPoint(watch_path); 349 // Tweak watch path for remote sources - we need to drop leading /special 350 // directory from there in order to be able to pair these events with 351 // their change notifications. 352 if (is_on_drive) 353 watch_path = drive::util::ExtractDrivePath(watch_path); 354 355 WatcherMap::iterator iter = file_watchers_.find(watch_path); 356 if (iter == file_watchers_.end()) { 357 scoped_ptr<FileWatcher> watcher(new FileWatcher(virtual_path)); 358 watcher->AddExtension(extension_id); 359 360 if (is_on_drive) { 361 // For Drive, file watching is done via OnDirectoryChanged(). 362 base::MessageLoopProxy::current()->PostTask(FROM_HERE, 363 base::Bind(callback, true)); 364 } else { 365 // For local files, start watching using FileWatcher. 366 watcher->WatchLocalFile( 367 watch_path, 368 base::Bind(&EventRouter::HandleFileWatchNotification, 369 weak_factory_.GetWeakPtr()), 370 callback); 371 } 372 373 file_watchers_[watch_path] = watcher.release(); 374 } else { 375 iter->second->AddExtension(extension_id); 376 base::MessageLoopProxy::current()->PostTask(FROM_HERE, 377 base::Bind(callback, true)); 378 } 379} 380 381void EventRouter::RemoveFileWatch(const base::FilePath& local_path, 382 const std::string& extension_id) { 383 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 384 385 base::FilePath watch_path = local_path; 386 // Tweak watch path for remote sources - we need to drop leading /special 387 // directory from there in order to be able to pair these events with 388 // their change notifications. 389 if (drive::util::IsUnderDriveMountPoint(watch_path)) { 390 watch_path = drive::util::ExtractDrivePath(watch_path); 391 } 392 WatcherMap::iterator iter = file_watchers_.find(watch_path); 393 if (iter == file_watchers_.end()) 394 return; 395 // Remove the watcher if |watch_path| is no longer watched by any extensions. 396 iter->second->RemoveExtension(extension_id); 397 if (iter->second->GetExtensionIds().empty()) { 398 delete iter->second; 399 file_watchers_.erase(iter); 400 } 401} 402 403void EventRouter::OnCopyCompleted(int copy_id, 404 const GURL& source_url, 405 const GURL& destination_url, 406 base::PlatformFileError error) { 407 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 408 409 file_browser_private::CopyProgressStatus status; 410 if (error == base::PLATFORM_FILE_OK) { 411 // Send success event. 412 status.type = file_browser_private::CopyProgressStatus::TYPE_SUCCESS; 413 status.source_url.reset(new std::string(source_url.spec())); 414 status.destination_url.reset(new std::string(destination_url.spec())); 415 } else { 416 // Send error event. 417 status.type = file_browser_private::CopyProgressStatus::TYPE_ERROR; 418 status.error.reset( 419 new int(fileapi::PlatformFileErrorToWebFileError(error))); 420 } 421 422 BroadcastEvent( 423 profile_, 424 extensions::event_names::kOnFileBrowserCopyProgress, 425 file_browser_private::OnCopyProgress::Create(copy_id, status)); 426} 427 428void EventRouter::OnCopyProgress( 429 int copy_id, 430 fileapi::FileSystemOperation::CopyProgressType type, 431 const GURL& source_url, 432 const GURL& destination_url, 433 int64 size) { 434 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 435 436 file_browser_private::CopyProgressStatus status; 437 status.type = CopyProgressTypeToCopyProgressStatusType(type); 438 status.source_url.reset(new std::string(source_url.spec())); 439 if (type == fileapi::FileSystemOperation::END_COPY_ENTRY) 440 status.destination_url.reset(new std::string(destination_url.spec())); 441 if (type == fileapi::FileSystemOperation::PROGRESS) 442 status.size.reset(new double(size)); 443 444 BroadcastEvent( 445 profile_, 446 extensions::event_names::kOnFileBrowserCopyProgress, 447 file_browser_private::OnCopyProgress::Create(copy_id, status)); 448} 449 450void EventRouter::DefaultNetworkChanged(const chromeos::NetworkState* network) { 451 if (!profile_ || 452 !extensions::ExtensionSystem::Get(profile_)->event_router()) { 453 NOTREACHED(); 454 return; 455 } 456 457 BroadcastEvent( 458 profile_, 459 extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged, 460 make_scoped_ptr(new ListValue)); 461} 462 463void EventRouter::OnFileManagerPrefsChanged() { 464 if (!profile_ || 465 !extensions::ExtensionSystem::Get(profile_)->event_router()) { 466 NOTREACHED(); 467 return; 468 } 469 470 BroadcastEvent( 471 profile_, 472 extensions::event_names::kOnFileBrowserPreferencesChanged, 473 make_scoped_ptr(new ListValue)); 474} 475 476void EventRouter::OnJobAdded(const drive::JobInfo& job_info) { 477 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 478 OnJobUpdated(job_info); 479} 480 481void EventRouter::OnJobUpdated(const drive::JobInfo& job_info) { 482 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 483 if (!drive::IsActiveFileTransferJobInfo(job_info)) 484 return; 485 486 bool is_new_job = (drive_jobs_.find(job_info.job_id) == drive_jobs_.end()); 487 488 // Replace with the latest job info. 489 drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus( 490 job_info, 491 is_new_job ? kFileTransferStateStarted : kFileTransferStateInProgress); 492 493 // Fire event if needed. 494 bool always = is_new_job; 495 SendDriveFileTransferEvent(always); 496} 497 498void EventRouter::OnJobDone(const drive::JobInfo& job_info, 499 drive::FileError error) { 500 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 501 if (!drive::IsActiveFileTransferJobInfo(job_info)) 502 return; 503 504 // Replace with the latest job info. 505 drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus( 506 job_info, 507 error == drive::FILE_ERROR_OK ? kFileTransferStateCompleted 508 : kFileTransferStateFailed); 509 510 // Fire event if needed. 511 bool always = true; 512 SendDriveFileTransferEvent(always); 513 514 // Forget about the job. 515 drive_jobs_.erase(job_info.job_id); 516} 517 518void EventRouter::SendDriveFileTransferEvent(bool always) { 519 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 520 521 const base::Time now = base::Time::Now(); 522 523 // When |always| flag is not set, we don't send the event until certain 524 // amount of time passes after the previous one. This is to avoid 525 // flooding the IPC between extensions by many onFileTransferUpdated events. 526 if (!always) { 527 const int64 delta = (now - last_file_transfer_event_).InMilliseconds(); 528 // delta < 0 may rarely happen if system clock is synced and rewinded. 529 // To be conservative, we don't skip in that case. 530 if (0 <= delta && delta < kFileTransferEventFrequencyInMilliseconds) 531 return; 532 } 533 534 // Convert the current |drive_jobs_| to a JSON value. 535 scoped_ptr<base::ListValue> event_list(new base::ListValue); 536 for (std::map<drive::JobID, DriveJobInfoWithStatus>::iterator 537 iter = drive_jobs_.begin(); iter != drive_jobs_.end(); ++iter) { 538 539 scoped_ptr<base::DictionaryValue> job_info_dict( 540 JobInfoToDictionaryValue(kFileManagerAppId, 541 iter->second.status, 542 iter->second.job_info)); 543 event_list->Append(job_info_dict.release()); 544 } 545 546 scoped_ptr<ListValue> args(new ListValue()); 547 args->Append(event_list.release()); 548 scoped_ptr<extensions::Event> event(new extensions::Event( 549 extensions::event_names::kOnFileTransfersUpdated, args.Pass())); 550 extensions::ExtensionSystem::Get(profile_)->event_router()-> 551 DispatchEventToExtension(kFileManagerAppId, event.Pass()); 552 553 last_file_transfer_event_ = now; 554} 555 556void EventRouter::OnDirectoryChanged(const base::FilePath& directory_path) { 557 HandleFileWatchNotification(directory_path, false); 558} 559 560void EventRouter::OnRefreshTokenInvalid() { 561 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 562 563 // Raise a DriveConnectionStatusChanged event to notify the status offline. 564 BroadcastEvent( 565 profile_, 566 extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged, 567 make_scoped_ptr(new ListValue)); 568} 569 570void EventRouter::HandleFileWatchNotification(const base::FilePath& local_path, 571 bool got_error) { 572 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 573 574 WatcherMap::const_iterator iter = file_watchers_.find(local_path); 575 if (iter == file_watchers_.end()) { 576 return; 577 } 578 DispatchDirectoryChangeEvent(iter->second->virtual_path(), got_error, 579 iter->second->GetExtensionIds()); 580} 581 582void EventRouter::DispatchDirectoryChangeEvent( 583 const base::FilePath& virtual_path, 584 bool got_error, 585 const std::vector<std::string>& extension_ids) { 586 if (!profile_) { 587 NOTREACHED(); 588 return; 589 } 590 591 for (size_t i = 0; i < extension_ids.size(); ++i) { 592 const std::string& extension_id = extension_ids[i]; 593 594 GURL target_origin_url(extensions::Extension::GetBaseURLFromExtensionId( 595 extension_id)); 596 GURL base_url = fileapi::GetFileSystemRootURI( 597 target_origin_url, 598 fileapi::kFileSystemTypeExternal); 599 GURL target_directory_url = GURL(base_url.spec() + virtual_path.value()); 600 scoped_ptr<ListValue> args(new ListValue()); 601 DictionaryValue* watch_info = new DictionaryValue(); 602 args->Append(watch_info); 603 watch_info->SetString("directoryUrl", target_directory_url.spec()); 604 watch_info->SetString("eventType", 605 got_error ? kPathWatchError : kPathChanged); 606 607 scoped_ptr<extensions::Event> event(new extensions::Event( 608 extensions::event_names::kOnDirectoryChanged, args.Pass())); 609 extensions::ExtensionSystem::Get(profile_)->event_router()-> 610 DispatchEventToExtension(extension_id, event.Pass()); 611 } 612} 613 614void EventRouter::ShowRemovableDeviceInFileManager( 615 const base::FilePath& mount_path) { 616 // Do not attempt to open File Manager while the login is in progress or 617 // the screen is locked. 618 if (chromeos::LoginDisplayHostImpl::default_host() || 619 chromeos::ScreenLocker::default_screen_locker()) 620 return; 621 622 // According to DCF (Design rule of Camera File system) by JEITA / CP-3461 623 // cameras should have pictures located in the DCIM root directory. 624 const base::FilePath dcim_path = mount_path.Append( 625 FILE_PATH_LITERAL("DCIM")); 626 627 // If there is no DCIM folder or an external photo importer is not available, 628 // then launch Files.app. 629 DirectoryExistsOnUIThread( 630 dcim_path, 631 IsGooglePhotosInstalled(profile_) ? 632 base::Bind(&base::DoNothing) : 633 base::Bind(&util::OpenRemovableDrive, mount_path), 634 base::Bind(&util::OpenRemovableDrive, mount_path)); 635} 636 637void EventRouter::OnDiskAdded( 638 const DiskMountManager::Disk& disk, bool mounting) { 639 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 640 641 if (!mounting) { 642 // If the disk is not being mounted, we don't want the Scanning 643 // notification to persist. 644 notifications_->HideNotification(DesktopNotifications::DEVICE, 645 disk.system_path_prefix()); 646 } 647} 648 649void EventRouter::OnDiskRemoved(const DiskMountManager::Disk& disk) { 650 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 651 // Do nothing. 652} 653 654void EventRouter::OnDeviceAdded(const std::string& device_path) { 655 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 656 657 // If the policy is set instead of showing the new device notification, 658 // we show a notification that the operation is not permitted. 659 if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { 660 notifications_->ShowNotification( 661 DesktopNotifications::DEVICE_EXTERNAL_STORAGE_DISABLED, 662 device_path); 663 return; 664 } 665 666 notifications_->RegisterDevice(device_path); 667 notifications_->ShowNotificationDelayed(DesktopNotifications::DEVICE, 668 device_path, 669 base::TimeDelta::FromSeconds(5)); 670} 671 672void EventRouter::OnDeviceRemoved(const std::string& device_path) { 673 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 674 675 notifications_->HideNotification(DesktopNotifications::DEVICE, 676 device_path); 677 notifications_->HideNotification(DesktopNotifications::DEVICE_FAIL, 678 device_path); 679 notifications_->UnregisterDevice(device_path); 680} 681 682void EventRouter::OnVolumeMounted(chromeos::MountError error_code, 683 const VolumeInfo& volume_info, 684 bool is_remounting) { 685 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 686 // profile_ is NULL if ShutdownOnUIThread() is called earlier. This can 687 // happen at shutdown. This should be removed after removing Drive mounting 688 // code in addMount. (addMount -> OnFileSystemMounted -> OnVolumeMounted is 689 // the only path to come here after Shutdown is called). 690 if (!profile_) 691 return; 692 693 BroadcastMountCompletedEvent( 694 profile_, 695 file_browser_private::MountCompletedEvent::EVENT_TYPE_MOUNT, 696 error_code, volume_info); 697 698 if (volume_info.type == VOLUME_TYPE_REMOVABLE_DISK_PARTITION && 699 !is_remounting) { 700 notifications_->ManageNotificationsOnMountCompleted( 701 volume_info.system_path_prefix.AsUTF8Unsafe(), 702 volume_info.drive_label, 703 volume_info.is_parent, 704 error_code == chromeos::MOUNT_ERROR_NONE, 705 error_code == chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM); 706 707 // If a new device was mounted, a new File manager window may need to be 708 // opened. 709 if (error_code == chromeos::MOUNT_ERROR_NONE) 710 ShowRemovableDeviceInFileManager(volume_info.mount_path); 711 } 712} 713 714void EventRouter::OnVolumeUnmounted(chromeos::MountError error_code, 715 const VolumeInfo& volume_info) { 716 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 717 BroadcastMountCompletedEvent( 718 profile_, 719 file_browser_private::MountCompletedEvent::EVENT_TYPE_UNMOUNT, 720 error_code, volume_info); 721} 722 723void EventRouter::OnFormatStarted(const std::string& device_path, 724 bool success) { 725 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 726 727 if (success) { 728 notifications_->ShowNotification(DesktopNotifications::FORMAT_START, 729 device_path); 730 } else { 731 notifications_->ShowNotification( 732 DesktopNotifications::FORMAT_START_FAIL, device_path); 733 } 734} 735 736void EventRouter::OnFormatCompleted(const std::string& device_path, 737 bool success) { 738 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 739 740 if (success) { 741 notifications_->HideNotification(DesktopNotifications::FORMAT_START, 742 device_path); 743 notifications_->ShowNotification(DesktopNotifications::FORMAT_SUCCESS, 744 device_path); 745 // Hide it after a couple of seconds. 746 notifications_->HideNotificationDelayed( 747 DesktopNotifications::FORMAT_SUCCESS, 748 device_path, 749 base::TimeDelta::FromSeconds(4)); 750 } else { 751 notifications_->HideNotification(DesktopNotifications::FORMAT_START, 752 device_path); 753 notifications_->ShowNotification(DesktopNotifications::FORMAT_FAIL, 754 device_path); 755 } 756} 757 758} // namespace file_manager 759