1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/chromeos/extensions/file_manager/event_router.h" 6 7#include "base/bind.h" 8#include "base/files/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/thread_task_runner_handle.h" 14#include "base/threading/sequenced_worker_pool.h" 15#include "base/values.h" 16#include "chrome/browser/app_mode/app_mode_utils.h" 17#include "chrome/browser/chrome_notification_types.h" 18#include "chrome/browser/chromeos/drive/drive_integration_service.h" 19#include "chrome/browser/chromeos/drive/file_change.h" 20#include "chrome/browser/chromeos/drive/file_system_interface.h" 21#include "chrome/browser/chromeos/drive/file_system_util.h" 22#include "chrome/browser/chromeos/extensions/file_manager/device_event_router.h" 23#include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h" 24#include "chrome/browser/chromeos/file_manager/app_id.h" 25#include "chrome/browser/chromeos/file_manager/fileapi_util.h" 26#include "chrome/browser/chromeos/file_manager/open_util.h" 27#include "chrome/browser/chromeos/file_manager/volume_manager.h" 28#include "chrome/browser/chromeos/login/lock/screen_locker.h" 29#include "chrome/browser/chromeos/login/ui/login_display_host_impl.h" 30#include "chrome/browser/drive/drive_service_interface.h" 31#include "chrome/browser/extensions/extension_service.h" 32#include "chrome/browser/extensions/extension_util.h" 33#include "chrome/browser/profiles/profile.h" 34#include "chrome/browser/profiles/profile_manager.h" 35#include "chrome/common/chrome_switches.h" 36#include "chrome/common/pref_names.h" 37#include "chromeos/dbus/dbus_thread_manager.h" 38#include "chromeos/login/login_state.h" 39#include "chromeos/network/network_handler.h" 40#include "chromeos/network/network_state_handler.h" 41#include "content/public/browser/browser_thread.h" 42#include "content/public/browser/render_process_host.h" 43#include "content/public/browser/storage_partition.h" 44#include "extensions/browser/event_router.h" 45#include "extensions/browser/extension_host.h" 46#include "extensions/browser/extension_prefs.h" 47#include "extensions/browser/extension_system.h" 48#include "storage/common/fileapi/file_system_types.h" 49#include "storage/common/fileapi/file_system_util.h" 50 51using chromeos::disks::DiskMountManager; 52using chromeos::NetworkHandler; 53using content::BrowserThread; 54using drive::DriveIntegrationService; 55using drive::DriveIntegrationServiceFactory; 56using file_manager::util::EntryDefinition; 57using file_manager::util::FileDefinition; 58 59namespace file_manager_private = extensions::api::file_manager_private; 60 61namespace file_manager { 62namespace { 63// Constants for the "transferState" field of onFileTransferUpdated event. 64const char kFileTransferStateAdded[] = "added"; 65const char kFileTransferStateStarted[] = "started"; 66const char kFileTransferStateInProgress[] = "in_progress"; 67const char kFileTransferStateCompleted[] = "completed"; 68const char kFileTransferStateFailed[] = "failed"; 69 70// Frequency of sending onFileTransferUpdated. 71const int64 kProgressEventFrequencyInMilliseconds = 1000; 72 73// Maximim size of detailed change info on directory change event. If the size 74// exceeds the maximum size, the detailed info is omitted and the force refresh 75// is kicked. 76const size_t kDirectoryChangeEventMaxDetailInfoSize = 1000; 77 78// This time(millisecond) is used for confirm following event exists. 79const int64 kFileTransferEventDelayTimeInMilliseconds = 300; 80 81// Utility function to check if |job_info| is a file uploading job. 82bool IsUploadJob(drive::JobType type) { 83 return (type == drive::TYPE_UPLOAD_NEW_FILE || 84 type == drive::TYPE_UPLOAD_EXISTING_FILE); 85} 86 87size_t CountActiveFileTransferJobInfo( 88 const std::vector<drive::JobInfo>& job_info_list) { 89 size_t num_active_file_transfer_job_info = 0; 90 for (size_t i = 0; i < job_info_list.size(); ++i) { 91 if (IsActiveFileTransferJobInfo(job_info_list[i])) 92 ++num_active_file_transfer_job_info; 93 } 94 return num_active_file_transfer_job_info; 95} 96 97// Converts the job info to a IDL generated type. 98void JobInfoToTransferStatus( 99 Profile* profile, 100 const std::string& extension_id, 101 const std::string& job_status, 102 const drive::JobInfo& job_info, 103 file_manager_private::FileTransferStatus* status) { 104 DCHECK(IsActiveFileTransferJobInfo(job_info)); 105 106 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue); 107 GURL url = util::ConvertDrivePathToFileSystemUrl( 108 profile, job_info.file_path, extension_id); 109 status->file_url = url.spec(); 110 status->transfer_state = file_manager_private::ParseTransferState(job_status); 111 status->transfer_type = 112 IsUploadJob(job_info.job_type) ? 113 file_manager_private::TRANSFER_TYPE_UPLOAD : 114 file_manager_private::TRANSFER_TYPE_DOWNLOAD; 115 DriveIntegrationService* const integration_service = 116 DriveIntegrationServiceFactory::FindForProfile(profile); 117 status->num_total_jobs = CountActiveFileTransferJobInfo( 118 integration_service->job_list()->GetJobInfoList()); 119 // JavaScript does not have 64-bit integers. Instead we use double, which 120 // is in IEEE 754 formant and accurate up to 52-bits in JS, and in practice 121 // in C++. Larger values are rounded. 122 status->processed.reset( 123 new double(static_cast<double>(job_info.num_completed_bytes))); 124 status->total.reset( 125 new double(static_cast<double>(job_info.num_total_bytes))); 126} 127 128// Checks if the Recovery Tool is running. This is a temporary solution. 129// TODO(mtomasz): Replace with crbug.com/341902 solution. 130bool IsRecoveryToolRunning(Profile* profile) { 131 extensions::ExtensionPrefs* extension_prefs = 132 extensions::ExtensionPrefs::Get(profile); 133 if (!extension_prefs) 134 return false; 135 136 const std::string kRecoveryToolIds[] = { 137 "kkebgepbbgbcmghedmmdfcbdcodlkngh", // Recovery tool staging 138 "jndclpdbaamdhonoechobihbbiimdgai" // Recovery tool prod 139 }; 140 141 for (size_t i = 0; i < arraysize(kRecoveryToolIds); ++i) { 142 const std::string extension_id = kRecoveryToolIds[i]; 143 if (extension_prefs->IsExtensionRunning(extension_id)) 144 return true; 145 } 146 147 return false; 148} 149 150// Sends an event named |event_name| with arguments |event_args| to extensions. 151void BroadcastEvent(Profile* profile, 152 const std::string& event_name, 153 scoped_ptr<base::ListValue> event_args) { 154 extensions::EventRouter::Get(profile)->BroadcastEvent( 155 make_scoped_ptr(new extensions::Event(event_name, event_args.Pass()))); 156} 157 158file_manager_private::MountCompletedStatus 159MountErrorToMountCompletedStatus(chromeos::MountError error) { 160 switch (error) { 161 case chromeos::MOUNT_ERROR_NONE: 162 return file_manager_private::MOUNT_COMPLETED_STATUS_SUCCESS; 163 case chromeos::MOUNT_ERROR_UNKNOWN: 164 return file_manager_private::MOUNT_COMPLETED_STATUS_ERROR_UNKNOWN; 165 case chromeos::MOUNT_ERROR_INTERNAL: 166 return file_manager_private::MOUNT_COMPLETED_STATUS_ERROR_INTERNAL; 167 case chromeos::MOUNT_ERROR_INVALID_ARGUMENT: 168 return file_manager_private:: 169 MOUNT_COMPLETED_STATUS_ERROR_INVALID_ARGUMENT; 170 case chromeos::MOUNT_ERROR_INVALID_PATH: 171 return file_manager_private::MOUNT_COMPLETED_STATUS_ERROR_INVALID_PATH; 172 case chromeos::MOUNT_ERROR_PATH_ALREADY_MOUNTED: 173 return file_manager_private:: 174 MOUNT_COMPLETED_STATUS_ERROR_PATH_ALREADY_MOUNTED; 175 case chromeos::MOUNT_ERROR_PATH_NOT_MOUNTED: 176 return file_manager_private:: 177 MOUNT_COMPLETED_STATUS_ERROR_PATH_NOT_MOUNTED; 178 case chromeos::MOUNT_ERROR_DIRECTORY_CREATION_FAILED: 179 return file_manager_private 180 ::MOUNT_COMPLETED_STATUS_ERROR_DIRECTORY_CREATION_FAILED; 181 case chromeos::MOUNT_ERROR_INVALID_MOUNT_OPTIONS: 182 return file_manager_private 183 ::MOUNT_COMPLETED_STATUS_ERROR_INVALID_MOUNT_OPTIONS; 184 case chromeos::MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS: 185 return file_manager_private:: 186 MOUNT_COMPLETED_STATUS_ERROR_INVALID_UNMOUNT_OPTIONS; 187 case chromeos::MOUNT_ERROR_INSUFFICIENT_PERMISSIONS: 188 return file_manager_private:: 189 MOUNT_COMPLETED_STATUS_ERROR_INSUFFICIENT_PERMISSIONS; 190 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_NOT_FOUND: 191 return file_manager_private:: 192 MOUNT_COMPLETED_STATUS_ERROR_MOUNT_PROGRAM_NOT_FOUND; 193 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_FAILED: 194 return file_manager_private:: 195 MOUNT_COMPLETED_STATUS_ERROR_MOUNT_PROGRAM_FAILED; 196 case chromeos::MOUNT_ERROR_INVALID_DEVICE_PATH: 197 return file_manager_private:: 198 MOUNT_COMPLETED_STATUS_ERROR_INVALID_DEVICE_PATH; 199 case chromeos::MOUNT_ERROR_UNKNOWN_FILESYSTEM: 200 return file_manager_private:: 201 MOUNT_COMPLETED_STATUS_ERROR_UNKNOWN_FILESYSTEM; 202 case chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM: 203 return file_manager_private:: 204 MOUNT_COMPLETED_STATUS_ERROR_UNSUPPORTED_FILESYSTEM; 205 case chromeos::MOUNT_ERROR_INVALID_ARCHIVE: 206 return file_manager_private::MOUNT_COMPLETED_STATUS_ERROR_INVALID_ARCHIVE; 207 case chromeos::MOUNT_ERROR_NOT_AUTHENTICATED: 208 return file_manager_private::MOUNT_COMPLETED_STATUS_ERROR_AUTHENTICATION; 209 case chromeos::MOUNT_ERROR_PATH_UNMOUNTED: 210 return file_manager_private::MOUNT_COMPLETED_STATUS_ERROR_PATH_UNMOUNTED; 211 } 212 NOTREACHED(); 213 return file_manager_private::MOUNT_COMPLETED_STATUS_NONE; 214} 215 216file_manager_private::CopyProgressStatusType 217CopyProgressTypeToCopyProgressStatusType( 218 storage::FileSystemOperation::CopyProgressType type) { 219 switch (type) { 220 case storage::FileSystemOperation::BEGIN_COPY_ENTRY: 221 return file_manager_private::COPY_PROGRESS_STATUS_TYPE_BEGIN_COPY_ENTRY; 222 case storage::FileSystemOperation::END_COPY_ENTRY: 223 return file_manager_private::COPY_PROGRESS_STATUS_TYPE_END_COPY_ENTRY; 224 case storage::FileSystemOperation::PROGRESS: 225 return file_manager_private::COPY_PROGRESS_STATUS_TYPE_PROGRESS; 226 } 227 NOTREACHED(); 228 return file_manager_private::COPY_PROGRESS_STATUS_TYPE_NONE; 229} 230 231file_manager_private::ChangeType ConvertChangeTypeFromDriveToApi( 232 drive::FileChange::ChangeType type) { 233 switch (type) { 234 case drive::FileChange::ADD_OR_UPDATE: 235 return file_manager_private::CHANGE_TYPE_ADD_OR_UPDATE; 236 case drive::FileChange::DELETE: 237 return file_manager_private::CHANGE_TYPE_DELETE; 238 } 239 NOTREACHED(); 240 return file_manager_private::CHANGE_TYPE_ADD_OR_UPDATE; 241} 242 243std::string FileErrorToErrorName(base::File::Error error_code) { 244 namespace js = extensions::api::file_manager_private; 245 switch (error_code) { 246 case base::File::FILE_ERROR_NOT_FOUND: 247 return "NotFoundError"; 248 case base::File::FILE_ERROR_INVALID_OPERATION: 249 case base::File::FILE_ERROR_EXISTS: 250 case base::File::FILE_ERROR_NOT_EMPTY: 251 return "InvalidModificationError"; 252 case base::File::FILE_ERROR_NOT_A_DIRECTORY: 253 case base::File::FILE_ERROR_NOT_A_FILE: 254 return "TypeMismatchError"; 255 case base::File::FILE_ERROR_ACCESS_DENIED: 256 return "NoModificationAllowedError"; 257 case base::File::FILE_ERROR_FAILED: 258 return "InvalidStateError"; 259 case base::File::FILE_ERROR_ABORT: 260 return "AbortError"; 261 case base::File::FILE_ERROR_SECURITY: 262 return "SecurityError"; 263 case base::File::FILE_ERROR_NO_SPACE: 264 return "QuotaExceededError"; 265 case base::File::FILE_ERROR_INVALID_URL: 266 return "EncodingError"; 267 default: 268 return "InvalidModificationError"; 269 } 270} 271 272void GrantAccessForAddedProfileToRunningInstance(Profile* added_profile, 273 Profile* running_profile) { 274 extensions::ProcessManager* const process_manager = 275 extensions::ExtensionSystem::Get(running_profile)->process_manager(); 276 if (!process_manager) 277 return; 278 279 extensions::ExtensionHost* const extension_host = 280 process_manager->GetBackgroundHostForExtension(kFileManagerAppId); 281 if (!extension_host || !extension_host->render_process_host()) 282 return; 283 284 const int id = extension_host->render_process_host()->GetID(); 285 file_manager::util::SetupProfileFileAccessPermissions(id, added_profile); 286} 287 288// Checks if we should send a progress event or not according to the 289// |last_time| of sending an event. If |always| is true, the function always 290// returns true. If the function returns true, the function also updates 291// |last_time|. 292bool ShouldSendProgressEvent(bool always, base::Time* last_time) { 293 const base::Time now = base::Time::Now(); 294 const int64 delta = (now - *last_time).InMilliseconds(); 295 // delta < 0 may rarely happen if system clock is synced and rewinded. 296 // To be conservative, we don't skip in that case. 297 if (!always && 0 <= delta && delta < kProgressEventFrequencyInMilliseconds) { 298 return false; 299 } else { 300 *last_time = now; 301 return true; 302 } 303} 304 305// Obtains whether the Files.app should handle the volume or not. 306bool ShouldShowNotificationForVolume( 307 Profile* profile, 308 const DeviceEventRouter& device_event_router, 309 const VolumeInfo& volume_info) { 310 if (volume_info.type != VOLUME_TYPE_MTP && 311 volume_info.type != VOLUME_TYPE_REMOVABLE_DISK_PARTITION) { 312 return false; 313 } 314 315 if (device_event_router.is_resuming() || device_event_router.is_starting_up()) 316 return false; 317 318 // Do not attempt to open File Manager while the login is in progress or 319 // the screen is locked or running in kiosk app mode and make sure the file 320 // manager is opened only for the active user. 321 if (chromeos::LoginDisplayHostImpl::default_host() || 322 chromeos::ScreenLocker::default_screen_locker() || 323 chrome::IsRunningInForcedAppMode() || 324 profile != ProfileManager::GetActiveUserProfile()) { 325 return false; 326 } 327 328 // Do not pop-up the File Manager, if the recovery tool is running. 329 if (IsRecoveryToolRunning(profile)) 330 return false; 331 332 // If the disable-default-apps flag is on, Files.app is not opened 333 // automatically on device mount not to obstruct the manual test. 334 if (CommandLine::ForCurrentProcess()->HasSwitch( 335 switches::kDisableDefaultApps)) { 336 return false; 337 } 338 339 return true; 340} 341 342// Sub-part of the event router for handling device events. 343class DeviceEventRouterImpl : public DeviceEventRouter { 344 public: 345 explicit DeviceEventRouterImpl(Profile* profile) : profile_(profile) {} 346 347 // DeviceEventRouter overrides. 348 virtual void OnDeviceEvent(file_manager_private::DeviceEventType type, 349 const std::string& device_path) OVERRIDE { 350 DCHECK_CURRENTLY_ON(BrowserThread::UI); 351 352 file_manager_private::DeviceEvent event; 353 event.type = type; 354 event.device_path = device_path; 355 356 BroadcastEvent(profile_, 357 file_manager_private::OnDeviceChanged::kEventName, 358 file_manager_private::OnDeviceChanged::Create(event)); 359 } 360 361 // DeviceEventRouter overrides. 362 virtual bool IsExternalStorageDisabled() OVERRIDE { 363 DCHECK_CURRENTLY_ON(BrowserThread::UI); 364 return profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled); 365 } 366 367 private: 368 Profile* const profile_; 369 370 DISALLOW_COPY_AND_ASSIGN(DeviceEventRouterImpl); 371}; 372 373} // namespace 374 375// Pass dummy value to JobInfo's constructor for make it default constructible. 376EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus() 377 : job_info(drive::TYPE_DOWNLOAD_FILE) { 378} 379 380EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus( 381 const drive::JobInfo& info, const std::string& status) 382 : job_info(info), status(status) { 383} 384 385EventRouter::EventRouter(Profile* profile) 386 : pref_change_registrar_(new PrefChangeRegistrar), 387 profile_(profile), 388 device_event_router_(new DeviceEventRouterImpl(profile)), 389 weak_factory_(this) { 390 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 391 ObserveEvents(); 392} 393 394EventRouter::~EventRouter() { 395} 396 397void EventRouter::Shutdown() { 398 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 399 400 DLOG_IF(WARNING, !file_watchers_.empty()) 401 << "Not all file watchers are " 402 << "removed. This can happen when Files.app is open during shutdown."; 403 STLDeleteValues(&file_watchers_); 404 if (!profile_) { 405 NOTREACHED(); 406 return; 407 } 408 409 pref_change_registrar_->RemoveAll(); 410 411 if (NetworkHandler::IsInitialized()) { 412 NetworkHandler::Get()->network_state_handler()->RemoveObserver(this, 413 FROM_HERE); 414 } 415 416 DriveIntegrationService* const integration_service = 417 DriveIntegrationServiceFactory::FindForProfile(profile_); 418 if (integration_service) { 419 integration_service->file_system()->RemoveObserver(this); 420 integration_service->drive_service()->RemoveObserver(this); 421 integration_service->job_list()->RemoveObserver(this); 422 } 423 424 VolumeManager* const volume_manager = VolumeManager::Get(profile_); 425 if (volume_manager) { 426 volume_manager->RemoveObserver(this); 427 volume_manager->RemoveObserver(device_event_router_.get()); 428 } 429 430 chromeos::PowerManagerClient* const power_manager_client = 431 chromeos::DBusThreadManager::Get()->GetPowerManagerClient(); 432 power_manager_client->RemoveObserver(device_event_router_.get()); 433 434 profile_ = NULL; 435} 436 437void EventRouter::ObserveEvents() { 438 if (!profile_) { 439 NOTREACHED(); 440 return; 441 } 442 if (!chromeos::LoginState::IsInitialized() || 443 !chromeos::LoginState::Get()->IsUserLoggedIn()) { 444 return; 445 } 446 447 // Ignore device events for the first few seconds. 448 device_event_router_->Startup(); 449 450 // VolumeManager's construction triggers DriveIntegrationService's 451 // construction, so it is necessary to call VolumeManager's Get before 452 // accessing DriveIntegrationService. 453 VolumeManager* const volume_manager = VolumeManager::Get(profile_); 454 if (volume_manager) { 455 volume_manager->AddObserver(this); 456 volume_manager->AddObserver(device_event_router_.get()); 457 } 458 459 chromeos::PowerManagerClient* const power_manager_client = 460 chromeos::DBusThreadManager::Get()->GetPowerManagerClient(); 461 power_manager_client->AddObserver(device_event_router_.get()); 462 463 DriveIntegrationService* const integration_service = 464 DriveIntegrationServiceFactory::FindForProfile(profile_); 465 if (integration_service) { 466 integration_service->drive_service()->AddObserver(this); 467 integration_service->file_system()->AddObserver(this); 468 integration_service->job_list()->AddObserver(this); 469 } 470 471 if (NetworkHandler::IsInitialized()) { 472 NetworkHandler::Get()->network_state_handler()->AddObserver(this, 473 FROM_HERE); 474 } 475 476 pref_change_registrar_->Init(profile_->GetPrefs()); 477 base::Closure callback = 478 base::Bind(&EventRouter::OnFileManagerPrefsChanged, 479 weak_factory_.GetWeakPtr()); 480 pref_change_registrar_->Add(prefs::kDisableDriveOverCellular, callback); 481 pref_change_registrar_->Add(prefs::kDisableDriveHostedFiles, callback); 482 pref_change_registrar_->Add(prefs::kDisableDrive, callback); 483 pref_change_registrar_->Add(prefs::kUse24HourClock, callback); 484 485 notification_registrar_.Add(this, 486 chrome::NOTIFICATION_PROFILE_ADDED, 487 content::NotificationService::AllSources()); 488} 489 490// File watch setup routines. 491void EventRouter::AddFileWatch(const base::FilePath& local_path, 492 const base::FilePath& virtual_path, 493 const std::string& extension_id, 494 const BoolCallback& callback) { 495 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 496 DCHECK(!callback.is_null()); 497 498 base::FilePath watch_path = local_path; 499 bool is_on_drive = drive::util::IsUnderDriveMountPoint(watch_path); 500 // Tweak watch path for remote sources - we need to drop leading /special 501 // directory from there in order to be able to pair these events with 502 // their change notifications. 503 if (is_on_drive) 504 watch_path = drive::util::ExtractDrivePath(watch_path); 505 506 WatcherMap::iterator iter = file_watchers_.find(watch_path); 507 if (iter == file_watchers_.end()) { 508 scoped_ptr<FileWatcher> watcher(new FileWatcher(virtual_path)); 509 watcher->AddExtension(extension_id); 510 511 if (is_on_drive) { 512 // For Drive, file watching is done via OnDirectoryChanged(). 513 base::MessageLoopProxy::current()->PostTask(FROM_HERE, 514 base::Bind(callback, true)); 515 } else { 516 // For local files, start watching using FileWatcher. 517 watcher->WatchLocalFile( 518 watch_path, 519 base::Bind(&EventRouter::HandleFileWatchNotification, 520 weak_factory_.GetWeakPtr(), 521 static_cast<drive::FileChange*>(NULL)), 522 callback); 523 } 524 525 file_watchers_[watch_path] = watcher.release(); 526 } else { 527 iter->second->AddExtension(extension_id); 528 base::MessageLoopProxy::current()->PostTask(FROM_HERE, 529 base::Bind(callback, true)); 530 } 531} 532 533void EventRouter::RemoveFileWatch(const base::FilePath& local_path, 534 const std::string& extension_id) { 535 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 536 537 base::FilePath watch_path = local_path; 538 // Tweak watch path for remote sources - we need to drop leading /special 539 // directory from there in order to be able to pair these events with 540 // their change notifications. 541 if (drive::util::IsUnderDriveMountPoint(watch_path)) { 542 watch_path = drive::util::ExtractDrivePath(watch_path); 543 } 544 WatcherMap::iterator iter = file_watchers_.find(watch_path); 545 if (iter == file_watchers_.end()) 546 return; 547 // Remove the watcher if |watch_path| is no longer watched by any extensions. 548 iter->second->RemoveExtension(extension_id); 549 if (iter->second->GetExtensionIds().empty()) { 550 delete iter->second; 551 file_watchers_.erase(iter); 552 } 553} 554 555void EventRouter::OnCopyCompleted(int copy_id, 556 const GURL& source_url, 557 const GURL& destination_url, 558 base::File::Error error) { 559 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 560 561 file_manager_private::CopyProgressStatus status; 562 if (error == base::File::FILE_OK) { 563 // Send success event. 564 status.type = file_manager_private::COPY_PROGRESS_STATUS_TYPE_SUCCESS; 565 status.source_url.reset(new std::string(source_url.spec())); 566 status.destination_url.reset(new std::string(destination_url.spec())); 567 } else { 568 // Send error event. 569 status.type = file_manager_private::COPY_PROGRESS_STATUS_TYPE_ERROR; 570 status.error.reset(new std::string(FileErrorToErrorName(error))); 571 } 572 573 BroadcastEvent( 574 profile_, 575 file_manager_private::OnCopyProgress::kEventName, 576 file_manager_private::OnCopyProgress::Create(copy_id, status)); 577} 578 579void EventRouter::OnCopyProgress( 580 int copy_id, 581 storage::FileSystemOperation::CopyProgressType type, 582 const GURL& source_url, 583 const GURL& destination_url, 584 int64 size) { 585 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 586 587 file_manager_private::CopyProgressStatus status; 588 status.type = CopyProgressTypeToCopyProgressStatusType(type); 589 status.source_url.reset(new std::string(source_url.spec())); 590 if (type == storage::FileSystemOperation::END_COPY_ENTRY) 591 status.destination_url.reset(new std::string(destination_url.spec())); 592 if (type == storage::FileSystemOperation::PROGRESS) 593 status.size.reset(new double(size)); 594 595 // Should not skip events other than TYPE_PROGRESS. 596 const bool always = 597 status.type != file_manager_private::COPY_PROGRESS_STATUS_TYPE_PROGRESS; 598 if (!ShouldSendProgressEvent(always, &last_copy_progress_event_)) 599 return; 600 601 BroadcastEvent( 602 profile_, 603 file_manager_private::OnCopyProgress::kEventName, 604 file_manager_private::OnCopyProgress::Create(copy_id, status)); 605} 606 607void EventRouter::DefaultNetworkChanged(const chromeos::NetworkState* network) { 608 if (!profile_ || !extensions::EventRouter::Get(profile_)) { 609 NOTREACHED(); 610 return; 611 } 612 613 BroadcastEvent( 614 profile_, 615 file_manager_private::OnDriveConnectionStatusChanged::kEventName, 616 file_manager_private::OnDriveConnectionStatusChanged::Create()); 617} 618 619void EventRouter::OnFileManagerPrefsChanged() { 620 if (!profile_ || !extensions::EventRouter::Get(profile_)) { 621 NOTREACHED(); 622 return; 623 } 624 625 BroadcastEvent( 626 profile_, 627 file_manager_private::OnPreferencesChanged::kEventName, 628 file_manager_private::OnPreferencesChanged::Create()); 629} 630 631void EventRouter::OnJobAdded(const drive::JobInfo& job_info) { 632 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 633 if (!drive::IsActiveFileTransferJobInfo(job_info)) 634 return; 635 ScheduleDriveFileTransferEvent( 636 job_info, kFileTransferStateAdded, false /* immediate */); 637} 638 639void EventRouter::OnJobUpdated(const drive::JobInfo& job_info) { 640 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 641 if (!drive::IsActiveFileTransferJobInfo(job_info)) 642 return; 643 644 bool is_new_job = (drive_jobs_.find(job_info.job_id) == drive_jobs_.end()); 645 646 const std::string status = 647 is_new_job ? kFileTransferStateStarted : kFileTransferStateInProgress; 648 649 // Replace with the latest job info. 650 drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus(job_info, status); 651 652 ScheduleDriveFileTransferEvent(job_info, status, false /* immediate */); 653} 654 655void EventRouter::OnJobDone(const drive::JobInfo& job_info, 656 drive::FileError error) { 657 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 658 if (!drive::IsActiveFileTransferJobInfo(job_info)) 659 return; 660 661 const std::string status = error == drive::FILE_ERROR_OK 662 ? kFileTransferStateCompleted 663 : kFileTransferStateFailed; 664 665 ScheduleDriveFileTransferEvent(job_info, status, true /* immediate */); 666 667 // Forget about the job. 668 drive_jobs_.erase(job_info.job_id); 669} 670 671void EventRouter::ScheduleDriveFileTransferEvent(const drive::JobInfo& job_info, 672 const std::string& status, 673 bool immediate) { 674 const bool no_pending_task = !drive_job_info_for_scheduled_event_; 675 // Update the latest event. 676 drive_job_info_for_scheduled_event_.reset( 677 new DriveJobInfoWithStatus(job_info, status)); 678 if (immediate) { 679 SendDriveFileTransferEvent(); 680 } else if (no_pending_task) { 681 const base::TimeDelta delay = base::TimeDelta::FromMilliseconds( 682 kFileTransferEventDelayTimeInMilliseconds); 683 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( 684 FROM_HERE, 685 base::Bind(&EventRouter::SendDriveFileTransferEvent, 686 weak_factory_.GetWeakPtr()), 687 delay); 688 } 689} 690 691void EventRouter::SendDriveFileTransferEvent() { 692 if (!drive_job_info_for_scheduled_event_) 693 return; 694 695 file_manager_private::FileTransferStatus status; 696 JobInfoToTransferStatus(profile_, 697 kFileManagerAppId, 698 drive_job_info_for_scheduled_event_->status, 699 drive_job_info_for_scheduled_event_->job_info, 700 &status); 701 702 drive_job_info_for_scheduled_event_.reset(); 703 704 BroadcastEvent(profile_, 705 file_manager_private::OnFileTransfersUpdated::kEventName, 706 file_manager_private::OnFileTransfersUpdated::Create(status)); 707} 708 709void EventRouter::OnDirectoryChanged(const base::FilePath& drive_path) { 710 HandleFileWatchNotification(NULL, drive_path, false); 711} 712 713void EventRouter::OnFileChanged(const drive::FileChange& changed_files) { 714 typedef std::map<base::FilePath, drive::FileChange> FileChangeMap; 715 716 FileChangeMap map; 717 const drive::FileChange::Map& changed_file_map = changed_files.map(); 718 for (drive::FileChange::Map::const_iterator it = changed_file_map.begin(); 719 it != changed_file_map.end(); 720 it++) { 721 const base::FilePath& path = it->first; 722 map[path.DirName()].Update(path, it->second); 723 } 724 725 for (FileChangeMap::const_iterator it = map.begin(); it != map.end(); it++) { 726 HandleFileWatchNotification(&(it->second), it->first, false); 727 } 728} 729 730void EventRouter::OnDriveSyncError(drive::file_system::DriveSyncErrorType type, 731 const base::FilePath& drive_path) { 732 file_manager_private::DriveSyncErrorEvent event; 733 switch (type) { 734 case drive::file_system::DRIVE_SYNC_ERROR_DELETE_WITHOUT_PERMISSION: 735 event.type = 736 file_manager_private::DRIVE_SYNC_ERROR_TYPE_DELETE_WITHOUT_PERMISSION; 737 break; 738 case drive::file_system::DRIVE_SYNC_ERROR_SERVICE_UNAVAILABLE: 739 event.type = 740 file_manager_private::DRIVE_SYNC_ERROR_TYPE_SERVICE_UNAVAILABLE; 741 break; 742 case drive::file_system::DRIVE_SYNC_ERROR_MISC: 743 event.type = 744 file_manager_private::DRIVE_SYNC_ERROR_TYPE_MISC; 745 break; 746 } 747 event.file_url = util::ConvertDrivePathToFileSystemUrl( 748 profile_, drive_path, kFileManagerAppId).spec(); 749 BroadcastEvent( 750 profile_, 751 file_manager_private::OnDriveSyncError::kEventName, 752 file_manager_private::OnDriveSyncError::Create(event)); 753} 754 755void EventRouter::OnRefreshTokenInvalid() { 756 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 757 758 // Raise a DriveConnectionStatusChanged event to notify the status offline. 759 BroadcastEvent( 760 profile_, 761 file_manager_private::OnDriveConnectionStatusChanged::kEventName, 762 file_manager_private::OnDriveConnectionStatusChanged::Create()); 763} 764 765void EventRouter::HandleFileWatchNotification(const drive::FileChange* list, 766 const base::FilePath& local_path, 767 bool got_error) { 768 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 769 770 WatcherMap::const_iterator iter = file_watchers_.find(local_path); 771 if (iter == file_watchers_.end()) { 772 return; 773 } 774 775 if (list && list->size() > kDirectoryChangeEventMaxDetailInfoSize) { 776 // Removes the detailed information, if the list size is more than 777 // kDirectoryChangeEventMaxDetailInfoSize, since passing large list 778 // and processing it may cause more itme. 779 // This will be invoked full-refresh in Files.app. 780 list = NULL; 781 } 782 783 DispatchDirectoryChangeEvent(iter->second->virtual_path(), 784 list, 785 got_error, 786 iter->second->GetExtensionIds()); 787} 788 789void EventRouter::DispatchDirectoryChangeEvent( 790 const base::FilePath& virtual_path, 791 const drive::FileChange* list, 792 bool got_error, 793 const std::vector<std::string>& extension_ids) { 794 if (!profile_) { 795 NOTREACHED(); 796 return; 797 } 798 linked_ptr<drive::FileChange> changes; 799 if (list) 800 changes.reset(new drive::FileChange(*list)); // Copy 801 802 for (size_t i = 0; i < extension_ids.size(); ++i) { 803 std::string* extension_id = new std::string(extension_ids[i]); 804 805 FileDefinition file_definition; 806 file_definition.virtual_path = virtual_path; 807 file_definition.is_directory = true; 808 809 file_manager::util::ConvertFileDefinitionToEntryDefinition( 810 profile_, 811 *extension_id, 812 file_definition, 813 base::Bind( 814 &EventRouter::DispatchDirectoryChangeEventWithEntryDefinition, 815 weak_factory_.GetWeakPtr(), 816 changes, 817 base::Owned(extension_id), 818 got_error)); 819 } 820} 821 822void EventRouter::DispatchDirectoryChangeEventWithEntryDefinition( 823 const linked_ptr<drive::FileChange> list, 824 const std::string* extension_id, 825 bool watcher_error, 826 const EntryDefinition& entry_definition) { 827 typedef std::map<base::FilePath, drive::FileChange::ChangeList> ChangeListMap; 828 829 if (entry_definition.error != base::File::FILE_OK || 830 !entry_definition.is_directory) { 831 DVLOG(1) << "Unable to dispatch event because resolving the directory " 832 << "entry definition failed."; 833 return; 834 } 835 836 file_manager_private::FileWatchEvent event; 837 event.event_type = watcher_error 838 ? file_manager_private::FILE_WATCH_EVENT_TYPE_ERROR 839 : file_manager_private::FILE_WATCH_EVENT_TYPE_CHANGED; 840 841 // Detailed information is available. 842 if (list.get()) { 843 event.changed_files.reset( 844 new std::vector<linked_ptr<file_manager_private::FileChange> >); 845 846 if (list->map().empty()) 847 return; 848 849 for (drive::FileChange::Map::const_iterator it = list->map().begin(); 850 it != list->map().end(); 851 it++) { 852 linked_ptr<file_manager_private::FileChange> change_list( 853 new file_manager_private::FileChange); 854 855 GURL url = util::ConvertDrivePathToFileSystemUrl( 856 profile_, it->first, *extension_id); 857 change_list->url = url.spec(); 858 859 for (drive::FileChange::ChangeList::List::const_iterator change = 860 it->second.list().begin(); 861 change != it->second.list().end(); 862 change++) { 863 change_list->changes.push_back( 864 ConvertChangeTypeFromDriveToApi(change->change())); 865 } 866 867 event.changed_files->push_back(change_list); 868 } 869 } 870 871 event.entry.additional_properties.SetString( 872 "fileSystemName", entry_definition.file_system_name); 873 event.entry.additional_properties.SetString( 874 "fileSystemRoot", entry_definition.file_system_root_url); 875 event.entry.additional_properties.SetString( 876 "fileFullPath", "/" + entry_definition.full_path.value()); 877 event.entry.additional_properties.SetBoolean("fileIsDirectory", 878 entry_definition.is_directory); 879 880 BroadcastEvent(profile_, 881 file_manager_private::OnDirectoryChanged::kEventName, 882 file_manager_private::OnDirectoryChanged::Create(event)); 883} 884 885void EventRouter::OnDiskAdded( 886 const DiskMountManager::Disk& disk, bool mounting) { 887 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 888 // Do nothing. 889} 890 891void EventRouter::OnDiskRemoved(const DiskMountManager::Disk& disk) { 892 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 893 // Do nothing. 894} 895 896void EventRouter::OnDeviceAdded(const std::string& device_path) { 897 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 898 // Do nothing. 899} 900 901void EventRouter::OnDeviceRemoved(const std::string& device_path) { 902 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 903 // Do nothing. 904} 905 906void EventRouter::OnVolumeMounted(chromeos::MountError error_code, 907 const VolumeInfo& volume_info) { 908 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 909 // profile_ is NULL if ShutdownOnUIThread() is called earlier. This can 910 // happen at shutdown. This should be removed after removing Drive mounting 911 // code in addMount. (addMount -> OnFileSystemMounted -> OnVolumeMounted is 912 // the only path to come here after Shutdown is called). 913 if (!profile_) 914 return; 915 916 DispatchMountCompletedEvent( 917 file_manager_private::MOUNT_COMPLETED_EVENT_TYPE_MOUNT, 918 error_code, 919 volume_info); 920} 921 922void EventRouter::OnVolumeUnmounted(chromeos::MountError error_code, 923 const VolumeInfo& volume_info) { 924 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 925 DispatchMountCompletedEvent( 926 file_manager_private::MOUNT_COMPLETED_EVENT_TYPE_UNMOUNT, 927 error_code, 928 volume_info); 929} 930 931void EventRouter::DispatchMountCompletedEvent( 932 file_manager_private::MountCompletedEventType event_type, 933 chromeos::MountError error, 934 const VolumeInfo& volume_info) { 935 // Build an event object. 936 file_manager_private::MountCompletedEvent event; 937 event.event_type = event_type; 938 event.status = MountErrorToMountCompletedStatus(error); 939 util::VolumeInfoToVolumeMetadata( 940 profile_, volume_info, &event.volume_metadata); 941 event.should_notify = ShouldShowNotificationForVolume( 942 profile_, *device_event_router_, volume_info); 943 BroadcastEvent(profile_, 944 file_manager_private::OnMountCompleted::kEventName, 945 file_manager_private::OnMountCompleted::Create(event)); 946} 947 948void EventRouter::OnFormatStarted(const std::string& device_path, 949 bool success) { 950 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 951 // Do nothing. 952} 953 954void EventRouter::OnFormatCompleted(const std::string& device_path, 955 bool success) { 956 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 957 // Do nothing. 958} 959 960void EventRouter::Observe(int type, 961 const content::NotificationSource& source, 962 const content::NotificationDetails& details) { 963 if (type == chrome::NOTIFICATION_PROFILE_ADDED) { 964 Profile* const added_profile = content::Source<Profile>(source).ptr(); 965 if (!added_profile->IsOffTheRecord()) 966 GrantAccessForAddedProfileToRunningInstance(added_profile, profile_); 967 } 968} 969 970} // namespace file_manager 971