| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/extensions/file_manager/event_router.h" |
| |
| #include "base/bind.h" |
| #include "base/file_util.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/prefs/pref_change_registrar.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/stl_util.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "base/values.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/chromeos/drive/drive_integration_service.h" |
| #include "chrome/browser/chromeos/drive/file_system_interface.h" |
| #include "chrome/browser/chromeos/drive/file_system_util.h" |
| #include "chrome/browser/chromeos/extensions/file_manager/desktop_notifications.h" |
| #include "chrome/browser/chromeos/extensions/file_manager/file_manager_util.h" |
| #include "chrome/browser/chromeos/extensions/file_manager/mounted_disk_monitor.h" |
| #include "chrome/browser/chromeos/login/login_display_host_impl.h" |
| #include "chrome/browser/chromeos/login/screen_locker.h" |
| #include "chrome/browser/drive/drive_service_interface.h" |
| #include "chrome/browser/extensions/event_names.h" |
| #include "chrome/browser/extensions/event_router.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_system.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/login/login_state.h" |
| #include "chromeos/network/network_handler.h" |
| #include "chromeos/network/network_state_handler.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_source.h" |
| #include "webkit/common/fileapi/file_system_types.h" |
| #include "webkit/common/fileapi/file_system_util.h" |
| |
| using chromeos::disks::DiskMountManager; |
| using chromeos::NetworkHandler; |
| using content::BrowserThread; |
| using drive::DriveIntegrationService; |
| using drive::DriveIntegrationServiceFactory; |
| |
| namespace file_manager { |
| namespace { |
| |
| const char kPathChanged[] = "changed"; |
| const char kPathWatchError[] = "error"; |
| |
| // Used as a callback for FileSystem::MarkCacheFileAsUnmounted(). |
| void OnMarkAsUnmounted(drive::FileError error) { |
| // Do nothing. |
| } |
| |
| const char* MountErrorToString(chromeos::MountError error) { |
| switch (error) { |
| case chromeos::MOUNT_ERROR_NONE: |
| return "success"; |
| case chromeos::MOUNT_ERROR_UNKNOWN: |
| return "error_unknown"; |
| case chromeos::MOUNT_ERROR_INTERNAL: |
| return "error_internal"; |
| case chromeos::MOUNT_ERROR_INVALID_ARGUMENT: |
| return "error_invalid_argument"; |
| case chromeos::MOUNT_ERROR_INVALID_PATH: |
| return "error_invalid_path"; |
| case chromeos::MOUNT_ERROR_PATH_ALREADY_MOUNTED: |
| return "error_path_already_mounted"; |
| case chromeos::MOUNT_ERROR_PATH_NOT_MOUNTED: |
| return "error_path_not_mounted"; |
| case chromeos::MOUNT_ERROR_DIRECTORY_CREATION_FAILED: |
| return "error_directory_creation_failed"; |
| case chromeos::MOUNT_ERROR_INVALID_MOUNT_OPTIONS: |
| return "error_invalid_mount_options"; |
| case chromeos::MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS: |
| return "error_invalid_unmount_options"; |
| case chromeos::MOUNT_ERROR_INSUFFICIENT_PERMISSIONS: |
| return "error_insufficient_permissions"; |
| case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_NOT_FOUND: |
| return "error_mount_program_not_found"; |
| case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_FAILED: |
| return "error_mount_program_failed"; |
| case chromeos::MOUNT_ERROR_INVALID_DEVICE_PATH: |
| return "error_invalid_device_path"; |
| case chromeos::MOUNT_ERROR_UNKNOWN_FILESYSTEM: |
| return "error_unknown_filesystem"; |
| case chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM: |
| return "error_unsuported_filesystem"; |
| case chromeos::MOUNT_ERROR_INVALID_ARCHIVE: |
| return "error_invalid_archive"; |
| case chromeos::MOUNT_ERROR_NOT_AUTHENTICATED: |
| return "error_authentication"; |
| case chromeos::MOUNT_ERROR_PATH_UNMOUNTED: |
| return "error_path_unmounted"; |
| } |
| NOTREACHED(); |
| return ""; |
| } |
| |
| void DirectoryExistsOnBlockingPool(const base::FilePath& directory_path, |
| const base::Closure& success_callback, |
| const base::Closure& failure_callback) { |
| DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
| |
| if (base::DirectoryExists(directory_path)) |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, success_callback); |
| else |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, failure_callback); |
| }; |
| |
| void DirectoryExistsOnUIThread(const base::FilePath& directory_path, |
| const base::Closure& success_callback, |
| const base::Closure& failure_callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| content::BrowserThread::PostBlockingPoolTask( |
| FROM_HERE, |
| base::Bind(&DirectoryExistsOnBlockingPool, |
| directory_path, |
| success_callback, |
| failure_callback)); |
| }; |
| |
| // Creates a base::FilePathWatcher and starts watching at |watch_path| with |
| // |callback|. Returns NULL on failure. |
| base::FilePathWatcher* CreateAndStartFilePathWatcher( |
| const base::FilePath& watch_path, |
| const base::FilePathWatcher::Callback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| DCHECK(!callback.is_null()); |
| |
| base::FilePathWatcher* watcher(new base::FilePathWatcher); |
| if (!watcher->Watch(watch_path, false /* recursive */, callback)) { |
| delete watcher; |
| return NULL; |
| } |
| |
| return watcher; |
| } |
| |
| // Constants for the "transferState" field of onFileTransferUpdated event. |
| const char kFileTransferStateStarted[] = "started"; |
| const char kFileTransferStateInProgress[] = "in_progress"; |
| const char kFileTransferStateCompleted[] = "completed"; |
| const char kFileTransferStateFailed[] = "failed"; |
| |
| // Frequency of sending onFileTransferUpdated. |
| const int64 kFileTransferEventFrequencyInMilliseconds = 1000; |
| |
| // Utility function to check if |job_info| is a file uploading job. |
| bool IsUploadJob(drive::JobType type) { |
| return (type == drive::TYPE_UPLOAD_NEW_FILE || |
| type == drive::TYPE_UPLOAD_EXISTING_FILE); |
| } |
| |
| // Utility function to check if |job_info| is a file downloading job. |
| bool IsDownloadJob(drive::JobType type) { |
| return type == drive::TYPE_DOWNLOAD_FILE; |
| } |
| |
| // Converts the job info to its JSON (Value) form. |
| scoped_ptr<base::DictionaryValue> JobInfoToDictionaryValue( |
| const std::string& extension_id, |
| const std::string& job_status, |
| const drive::JobInfo& job_info) { |
| DCHECK(IsActiveFileTransferJobInfo(job_info)); |
| |
| scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue); |
| GURL url = util::ConvertRelativePathToFileSystemUrl( |
| job_info.file_path, extension_id); |
| result->SetString("fileUrl", url.spec()); |
| result->SetString("transferState", job_status); |
| result->SetString("transferType", |
| IsUploadJob(job_info.job_type) ? "upload" : "download"); |
| // JavaScript does not have 64-bit integers. Instead we use double, which |
| // is in IEEE 754 formant and accurate up to 52-bits in JS, and in practice |
| // in C++. Larger values are rounded. |
| result->SetDouble("processed", |
| static_cast<double>(job_info.num_completed_bytes)); |
| result->SetDouble("total", static_cast<double>(job_info.num_total_bytes)); |
| return result.Pass(); |
| } |
| |
| // Checks for availability of the Google+ Photos app. |
| bool IsGooglePhotosInstalled(Profile *profile) { |
| ExtensionService* service = |
| extensions::ExtensionSystem::Get(profile)->extension_service(); |
| if (!service) |
| return false; |
| |
| // Google+ Photos uses several ids for different channels. Therefore, all of |
| // them should be checked. |
| const std::string kGooglePlusPhotosIds[] = { |
| "ebpbnabdhheoknfklmpddcdijjkmklkp", // G+ Photos staging |
| "efjnaogkjbogokcnohkmnjdojkikgobo", // G+ Photos prod |
| "ejegoaikibpmikoejfephaneibodccma" // G+ Photos dev |
| }; |
| |
| for (size_t i = 0; i < arraysize(kGooglePlusPhotosIds); ++i) { |
| if (service->GetExtensionById(kGooglePlusPhotosIds[i], |
| false /* include_disable */) != NULL) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| } // namespace |
| |
| // Pass dummy value to JobInfo's constructor for make it default constructible. |
| EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus() |
| : job_info(drive::TYPE_DOWNLOAD_FILE) { |
| } |
| |
| EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus( |
| const drive::JobInfo& info, const std::string& status) |
| : job_info(info), status(status) { |
| } |
| |
| EventRouter::EventRouter( |
| Profile* profile) |
| : notifications_(new DesktopNotifications(profile)), |
| pref_change_registrar_(new PrefChangeRegistrar), |
| profile_(profile), |
| weak_factory_(this) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| } |
| |
| EventRouter::~EventRouter() { |
| } |
| |
| void EventRouter::Shutdown() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DLOG_IF(WARNING, !file_watchers_.empty()) |
| << "Not all file watchers are " |
| << "removed. This can happen when Files.app is open during shutdown."; |
| STLDeleteValues(&file_watchers_); |
| if (!profile_) { |
| NOTREACHED(); |
| return; |
| } |
| |
| DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); |
| if (disk_mount_manager) |
| disk_mount_manager->RemoveObserver(this); |
| |
| DriveIntegrationService* integration_service = |
| DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates( |
| profile_); |
| if (integration_service) { |
| integration_service->RemoveObserver(this); |
| integration_service->file_system()->RemoveObserver(this); |
| integration_service->drive_service()->RemoveObserver(this); |
| integration_service->job_list()->RemoveObserver(this); |
| } |
| |
| if (NetworkHandler::IsInitialized()) { |
| NetworkHandler::Get()->network_state_handler()->RemoveObserver(this, |
| FROM_HERE); |
| } |
| profile_ = NULL; |
| } |
| |
| void EventRouter::ObserveFileSystemEvents() { |
| if (!profile_) { |
| NOTREACHED(); |
| return; |
| } |
| if (!chromeos::LoginState::IsInitialized() || |
| !chromeos::LoginState::Get()->IsUserLoggedIn()) { |
| return; |
| } |
| |
| DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); |
| if (disk_mount_manager) { |
| disk_mount_manager->RemoveObserver(this); |
| disk_mount_manager->AddObserver(this); |
| disk_mount_manager->RequestMountInfoRefresh(); |
| } |
| |
| DriveIntegrationService* integration_service = |
| DriveIntegrationServiceFactory::GetForProfileRegardlessOfStates( |
| profile_); |
| if (integration_service) { |
| integration_service->AddObserver(this); |
| integration_service->drive_service()->AddObserver(this); |
| integration_service->file_system()->AddObserver(this); |
| integration_service->job_list()->AddObserver(this); |
| } |
| |
| if (NetworkHandler::IsInitialized()) { |
| NetworkHandler::Get()->network_state_handler()->AddObserver(this, |
| FROM_HERE); |
| } |
| |
| mounted_disk_monitor_.reset(new MountedDiskMonitor()); |
| |
| pref_change_registrar_->Init(profile_->GetPrefs()); |
| |
| pref_change_registrar_->Add( |
| prefs::kExternalStorageDisabled, |
| base::Bind(&EventRouter::OnExternalStorageDisabledChanged, |
| weak_factory_.GetWeakPtr())); |
| |
| base::Closure callback = |
| base::Bind(&EventRouter::OnFileManagerPrefsChanged, |
| weak_factory_.GetWeakPtr()); |
| pref_change_registrar_->Add(prefs::kDisableDriveOverCellular, callback); |
| pref_change_registrar_->Add(prefs::kDisableDriveHostedFiles, callback); |
| pref_change_registrar_->Add(prefs::kDisableDrive, callback); |
| pref_change_registrar_->Add(prefs::kUse24HourClock, callback); |
| } |
| |
| // File watch setup routines. |
| void EventRouter::AddFileWatch(const base::FilePath& local_path, |
| const base::FilePath& virtual_path, |
| const std::string& extension_id, |
| const BoolCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| base::FilePath watch_path = local_path; |
| bool is_on_drive = drive::util::IsUnderDriveMountPoint(watch_path); |
| // Tweak watch path for remote sources - we need to drop leading /special |
| // directory from there in order to be able to pair these events with |
| // their change notifications. |
| if (is_on_drive) |
| watch_path = drive::util::ExtractDrivePath(watch_path); |
| |
| WatcherMap::iterator iter = file_watchers_.find(watch_path); |
| if (iter == file_watchers_.end()) { |
| scoped_ptr<FileWatcher> watcher(new FileWatcher(virtual_path)); |
| watcher->AddExtension(extension_id); |
| |
| if (is_on_drive) { |
| // For Drive, file watching is done via OnDirectoryChanged(). |
| base::MessageLoopProxy::current()->PostTask(FROM_HERE, |
| base::Bind(callback, true)); |
| } else { |
| // For local files, start watching using FileWatcher. |
| watcher->WatchLocalFile( |
| watch_path, |
| base::Bind(&EventRouter::HandleFileWatchNotification, |
| weak_factory_.GetWeakPtr()), |
| callback); |
| } |
| |
| file_watchers_[watch_path] = watcher.release(); |
| } else { |
| iter->second->AddExtension(extension_id); |
| base::MessageLoopProxy::current()->PostTask(FROM_HERE, |
| base::Bind(callback, true)); |
| } |
| } |
| |
| void EventRouter::RemoveFileWatch(const base::FilePath& local_path, |
| const std::string& extension_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| base::FilePath watch_path = local_path; |
| // Tweak watch path for remote sources - we need to drop leading /special |
| // directory from there in order to be able to pair these events with |
| // their change notifications. |
| if (drive::util::IsUnderDriveMountPoint(watch_path)) { |
| watch_path = drive::util::ExtractDrivePath(watch_path); |
| } |
| WatcherMap::iterator iter = file_watchers_.find(watch_path); |
| if (iter == file_watchers_.end()) |
| return; |
| // Remove the watcher if |watch_path| is no longer watched by any extensions. |
| iter->second->RemoveExtension(extension_id); |
| if (iter->second->GetExtensionIds().empty()) { |
| delete iter->second; |
| file_watchers_.erase(iter); |
| } |
| } |
| |
| void EventRouter::OnDiskEvent(DiskMountManager::DiskEvent event, |
| const DiskMountManager::Disk* disk) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Disregard hidden devices. |
| if (disk->is_hidden()) |
| return; |
| if (event == DiskMountManager::DISK_ADDED) { |
| OnDiskAdded(disk); |
| } else if (event == DiskMountManager::DISK_REMOVED) { |
| OnDiskRemoved(disk); |
| } |
| } |
| |
| void EventRouter::OnDeviceEvent(DiskMountManager::DeviceEvent event, |
| const std::string& device_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (event == DiskMountManager::DEVICE_ADDED) { |
| OnDeviceAdded(device_path); |
| } else if (event == DiskMountManager::DEVICE_REMOVED) { |
| OnDeviceRemoved(device_path); |
| } else if (event == DiskMountManager::DEVICE_SCANNED) { |
| OnDeviceScanned(device_path); |
| } |
| } |
| |
| void EventRouter::OnMountEvent( |
| DiskMountManager::MountEvent event, |
| chromeos::MountError error_code, |
| const DiskMountManager::MountPointInfo& mount_info) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| // profile_ is NULL if ShutdownOnUIThread() is called earlier. This can |
| // happen at shutdown. |
| if (!profile_) |
| return; |
| |
| DCHECK(mount_info.mount_type != chromeos::MOUNT_TYPE_INVALID); |
| |
| DispatchMountEvent(event, error_code, mount_info); |
| |
| if (mount_info.mount_type == chromeos::MOUNT_TYPE_DEVICE && |
| event == DiskMountManager::MOUNTING) { |
| DiskMountManager* disk_mount_manager = DiskMountManager::GetInstance(); |
| const DiskMountManager::Disk* disk = |
| disk_mount_manager->FindDiskBySourcePath(mount_info.source_path); |
| if (!disk || mounted_disk_monitor_->DiskIsRemounting(*disk)) |
| return; |
| |
| notifications_->ManageNotificationsOnMountCompleted( |
| disk->system_path_prefix(), disk->drive_label(), disk->is_parent(), |
| error_code == chromeos::MOUNT_ERROR_NONE, |
| error_code == chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM); |
| |
| // If a new device was mounted, a new File manager window may need to be |
| // opened. |
| if (error_code == chromeos::MOUNT_ERROR_NONE) |
| ShowRemovableDeviceInFileManager( |
| *disk, |
| base::FilePath::FromUTF8Unsafe(mount_info.mount_path)); |
| } else if (mount_info.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) { |
| // Clear the "mounted" state for archive files in drive cache |
| // when mounting failed or unmounting succeeded. |
| if ((event == DiskMountManager::MOUNTING) != |
| (error_code == chromeos::MOUNT_ERROR_NONE)) { |
| DriveIntegrationService* integration_service = |
| DriveIntegrationServiceFactory::GetForProfile(profile_); |
| drive::FileSystemInterface* file_system = |
| integration_service ? integration_service->file_system() : NULL; |
| if (file_system) { |
| file_system->MarkCacheFileAsUnmounted( |
| base::FilePath(mount_info.source_path), |
| base::Bind(&OnMarkAsUnmounted)); |
| } |
| } |
| } |
| } |
| |
| void EventRouter::OnFormatEvent(DiskMountManager::FormatEvent event, |
| chromeos::FormatError error_code, |
| const std::string& device_path) { |
| if (event == DiskMountManager::FORMAT_STARTED) { |
| OnFormatStarted(device_path, error_code == chromeos::FORMAT_ERROR_NONE); |
| } else if (event == DiskMountManager::FORMAT_COMPLETED) { |
| OnFormatCompleted(device_path, error_code == chromeos::FORMAT_ERROR_NONE); |
| } |
| } |
| |
| void EventRouter::NetworkManagerChanged() { |
| if (!profile_ || |
| !extensions::ExtensionSystem::Get(profile_)->event_router()) { |
| NOTREACHED(); |
| return; |
| } |
| scoped_ptr<extensions::Event> event(new extensions::Event( |
| extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged, |
| scoped_ptr<ListValue>(new ListValue()))); |
| extensions::ExtensionSystem::Get(profile_)->event_router()-> |
| BroadcastEvent(event.Pass()); |
| } |
| |
| void EventRouter::DefaultNetworkChanged(const chromeos::NetworkState* network) { |
| NetworkManagerChanged(); |
| } |
| |
| void EventRouter::OnExternalStorageDisabledChanged() { |
| // If the policy just got disabled we have to unmount every device currently |
| // mounted. The opposite is fine - we can let the user re-plug her device to |
| // make it available. |
| if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { |
| DiskMountManager* manager = DiskMountManager::GetInstance(); |
| DiskMountManager::MountPointMap mounts(manager->mount_points()); |
| for (DiskMountManager::MountPointMap::const_iterator it = mounts.begin(); |
| it != mounts.end(); ++it) { |
| LOG(INFO) << "Unmounting " << it->second.mount_path |
| << " because of policy."; |
| manager->UnmountPath(it->second.mount_path, |
| chromeos::UNMOUNT_OPTIONS_NONE, |
| DiskMountManager::UnmountPathCallback()); |
| } |
| } |
| } |
| |
| void EventRouter::OnFileManagerPrefsChanged() { |
| if (!profile_ || |
| !extensions::ExtensionSystem::Get(profile_)->event_router()) { |
| NOTREACHED(); |
| return; |
| } |
| |
| scoped_ptr<extensions::Event> event(new extensions::Event( |
| extensions::event_names::kOnFileBrowserPreferencesChanged, |
| scoped_ptr<ListValue>(new ListValue()))); |
| extensions::ExtensionSystem::Get(profile_)->event_router()-> |
| BroadcastEvent(event.Pass()); |
| } |
| |
| void EventRouter::OnJobAdded(const drive::JobInfo& job_info) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| OnJobUpdated(job_info); |
| } |
| |
| void EventRouter::OnJobUpdated(const drive::JobInfo& job_info) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (!drive::IsActiveFileTransferJobInfo(job_info)) |
| return; |
| |
| bool is_new_job = (drive_jobs_.find(job_info.job_id) == drive_jobs_.end()); |
| |
| // Replace with the latest job info. |
| drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus( |
| job_info, |
| is_new_job ? kFileTransferStateStarted : kFileTransferStateInProgress); |
| |
| // Fire event if needed. |
| bool always = is_new_job; |
| SendDriveFileTransferEvent(always); |
| } |
| |
| void EventRouter::OnJobDone(const drive::JobInfo& job_info, |
| drive::FileError error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (!drive::IsActiveFileTransferJobInfo(job_info)) |
| return; |
| |
| // Replace with the latest job info. |
| drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus( |
| job_info, |
| error == drive::FILE_ERROR_OK ? kFileTransferStateCompleted |
| : kFileTransferStateFailed); |
| |
| // Fire event if needed. |
| bool always = true; |
| SendDriveFileTransferEvent(always); |
| |
| // Forget about the job. |
| drive_jobs_.erase(job_info.job_id); |
| } |
| |
| void EventRouter::SendDriveFileTransferEvent(bool always) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| const base::Time now = base::Time::Now(); |
| |
| // When |always| flag is not set, we don't send the event until certain |
| // amount of time passes after the previous one. This is to avoid |
| // flooding the IPC between extensions by many onFileTransferUpdated events. |
| if (!always) { |
| const int64 delta = (now - last_file_transfer_event_).InMilliseconds(); |
| // delta < 0 may rarely happen if system clock is synced and rewinded. |
| // To be conservative, we don't skip in that case. |
| if (0 <= delta && delta < kFileTransferEventFrequencyInMilliseconds) |
| return; |
| } |
| |
| // Convert the current |drive_jobs_| to a JSON value. |
| scoped_ptr<base::ListValue> event_list(new base::ListValue); |
| for (std::map<drive::JobID, DriveJobInfoWithStatus>::iterator |
| iter = drive_jobs_.begin(); iter != drive_jobs_.end(); ++iter) { |
| |
| scoped_ptr<base::DictionaryValue> job_info_dict( |
| JobInfoToDictionaryValue(kFileBrowserDomain, |
| iter->second.status, |
| iter->second.job_info)); |
| event_list->Append(job_info_dict.release()); |
| } |
| |
| scoped_ptr<ListValue> args(new ListValue()); |
| args->Append(event_list.release()); |
| scoped_ptr<extensions::Event> event(new extensions::Event( |
| extensions::event_names::kOnFileTransfersUpdated, args.Pass())); |
| extensions::ExtensionSystem::Get(profile_)->event_router()-> |
| DispatchEventToExtension(kFileBrowserDomain, event.Pass()); |
| |
| last_file_transfer_event_ = now; |
| } |
| |
| void EventRouter::OnDirectoryChanged(const base::FilePath& directory_path) { |
| HandleFileWatchNotification(directory_path, false); |
| } |
| |
| void EventRouter::OnFileSystemMounted() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| const std::string& drive_path = drive::util::GetDriveMountPointPathAsString(); |
| DiskMountManager::MountPointInfo mount_info( |
| drive_path, |
| drive_path, |
| chromeos::MOUNT_TYPE_GOOGLE_DRIVE, |
| chromeos::disks::MOUNT_CONDITION_NONE); |
| |
| // Raise mount event. |
| // We can pass chromeos::MOUNT_ERROR_NONE even when authentication is failed |
| // or network is unreachable. These two errors will be handled later. |
| OnMountEvent(DiskMountManager::MOUNTING, chromeos::MOUNT_ERROR_NONE, |
| mount_info); |
| } |
| |
| void EventRouter::OnFileSystemBeingUnmounted() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Raise a mount event to notify the File Manager. |
| const std::string& drive_path = drive::util::GetDriveMountPointPathAsString(); |
| DiskMountManager::MountPointInfo mount_info( |
| drive_path, |
| drive_path, |
| chromeos::MOUNT_TYPE_GOOGLE_DRIVE, |
| chromeos::disks::MOUNT_CONDITION_NONE); |
| OnMountEvent(DiskMountManager::UNMOUNTING, chromeos::MOUNT_ERROR_NONE, |
| mount_info); |
| } |
| |
| void EventRouter::OnRefreshTokenInvalid() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Raise a DriveConnectionStatusChanged event to notify the status offline. |
| scoped_ptr<extensions::Event> event(new extensions::Event( |
| extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged, |
| scoped_ptr<ListValue>(new ListValue()))); |
| extensions::ExtensionSystem::Get(profile_)->event_router()-> |
| BroadcastEvent(event.Pass()); |
| } |
| |
| void EventRouter::HandleFileWatchNotification(const base::FilePath& local_path, |
| bool got_error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| WatcherMap::const_iterator iter = file_watchers_.find(local_path); |
| if (iter == file_watchers_.end()) { |
| return; |
| } |
| DispatchDirectoryChangeEvent(iter->second->virtual_path(), got_error, |
| iter->second->GetExtensionIds()); |
| } |
| |
| void EventRouter::DispatchDirectoryChangeEvent( |
| const base::FilePath& virtual_path, |
| bool got_error, |
| const std::vector<std::string>& extension_ids) { |
| if (!profile_) { |
| NOTREACHED(); |
| return; |
| } |
| |
| for (size_t i = 0; i < extension_ids.size(); ++i) { |
| const std::string& extension_id = extension_ids[i]; |
| |
| GURL target_origin_url(extensions::Extension::GetBaseURLFromExtensionId( |
| extension_id)); |
| GURL base_url = fileapi::GetFileSystemRootURI( |
| target_origin_url, |
| fileapi::kFileSystemTypeExternal); |
| GURL target_directory_url = GURL(base_url.spec() + virtual_path.value()); |
| scoped_ptr<ListValue> args(new ListValue()); |
| DictionaryValue* watch_info = new DictionaryValue(); |
| args->Append(watch_info); |
| watch_info->SetString("directoryUrl", target_directory_url.spec()); |
| watch_info->SetString("eventType", |
| got_error ? kPathWatchError : kPathChanged); |
| |
| // TODO(mtomasz): Pass set of entries. http://crbug.com/157834 |
| ListValue* watch_info_entries = new ListValue(); |
| watch_info->Set("changedEntries", watch_info_entries); |
| |
| scoped_ptr<extensions::Event> event(new extensions::Event( |
| extensions::event_names::kOnDirectoryChanged, args.Pass())); |
| extensions::ExtensionSystem::Get(profile_)->event_router()-> |
| DispatchEventToExtension(extension_id, event.Pass()); |
| } |
| } |
| |
| void EventRouter::DispatchMountEvent( |
| DiskMountManager::MountEvent event, |
| chromeos::MountError error_code, |
| const DiskMountManager::MountPointInfo& mount_info) { |
| scoped_ptr<ListValue> args(new ListValue()); |
| DictionaryValue* mount_info_value = new DictionaryValue(); |
| args->Append(mount_info_value); |
| mount_info_value->SetString( |
| "eventType", |
| event == DiskMountManager::MOUNTING ? "mount" : "unmount"); |
| mount_info_value->SetString("status", MountErrorToString(error_code)); |
| mount_info_value->SetString( |
| "mountType", |
| DiskMountManager::MountTypeToString(mount_info.mount_type)); |
| |
| // Add sourcePath to the event. |
| mount_info_value->SetString("sourcePath", mount_info.source_path); |
| |
| base::FilePath relative_mount_path; |
| |
| // If there were no error or some special conditions occurred, add mountPath |
| // to the event. |
| if (event == DiskMountManager::UNMOUNTING || |
| error_code == chromeos::MOUNT_ERROR_NONE || |
| mount_info.mount_condition) { |
| // Convert mount point path to relative path with the external file system |
| // exposed within File API. |
| if (util::ConvertFileToRelativeFileSystemPath( |
| profile_, |
| kFileBrowserDomain, |
| base::FilePath(mount_info.mount_path), |
| &relative_mount_path)) { |
| mount_info_value->SetString("mountPath", |
| "/" + relative_mount_path.value()); |
| } else { |
| mount_info_value->SetString( |
| "status", |
| MountErrorToString(chromeos::MOUNT_ERROR_PATH_UNMOUNTED)); |
| } |
| } |
| |
| scoped_ptr<extensions::Event> extension_event(new extensions::Event( |
| extensions::event_names::kOnFileBrowserMountCompleted, args.Pass())); |
| extensions::ExtensionSystem::Get(profile_)->event_router()-> |
| BroadcastEvent(extension_event.Pass()); |
| } |
| |
| void EventRouter::ShowRemovableDeviceInFileManager( |
| const DiskMountManager::Disk& disk, |
| const base::FilePath& mount_path) { |
| // Do not attempt to open File Manager while the login is in progress or |
| // the screen is locked. |
| if (chromeos::LoginDisplayHostImpl::default_host() || |
| chromeos::ScreenLocker::default_screen_locker()) |
| return; |
| |
| // According to DCF (Design rule of Camera File system) by JEITA / CP-3461 |
| // cameras should have pictures located in the DCIM root directory. |
| const base::FilePath dcim_path = mount_path.Append( |
| FILE_PATH_LITERAL("DCIM")); |
| |
| // If there is no DCIM folder or an external photo importer is not available, |
| // then launch Files.app. |
| DirectoryExistsOnUIThread( |
| dcim_path, |
| IsGooglePhotosInstalled(profile_) ? |
| base::Bind(&base::DoNothing) : |
| base::Bind(&util::ViewRemovableDrive, mount_path), |
| base::Bind(&util::ViewRemovableDrive, mount_path)); |
| } |
| |
| void EventRouter::OnDiskAdded(const DiskMountManager::Disk* disk) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| VLOG(1) << "Disk added: " << disk->device_path(); |
| if (disk->device_path().empty()) { |
| VLOG(1) << "Empty system path for " << disk->device_path(); |
| return; |
| } |
| |
| // If disk is not mounted yet and it has media and there is no policy |
| // forbidding external storage, give it a try. |
| if (disk->mount_path().empty() && disk->has_media() && |
| !profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { |
| // Initiate disk mount operation. MountPath auto-detects the filesystem |
| // format if the second argument is empty. The third argument (mount label) |
| // is not used in a disk mount operation. |
| DiskMountManager::GetInstance()->MountPath( |
| disk->device_path(), std::string(), std::string(), |
| chromeos::MOUNT_TYPE_DEVICE); |
| } else { |
| // Either the disk was mounted or it has no media. In both cases we don't |
| // want the Scanning notification to persist. |
| notifications_->HideNotification(DesktopNotifications::DEVICE, |
| disk->system_path_prefix()); |
| } |
| } |
| |
| void EventRouter::OnDiskRemoved(const DiskMountManager::Disk* disk) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| VLOG(1) << "Disk removed: " << disk->device_path(); |
| |
| if (!disk->mount_path().empty()) { |
| DiskMountManager::GetInstance()->UnmountPath( |
| disk->mount_path(), |
| chromeos::UNMOUNT_OPTIONS_LAZY, |
| DiskMountManager::UnmountPathCallback()); |
| } |
| } |
| |
| void EventRouter::OnDeviceAdded(const std::string& device_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| VLOG(1) << "Device added : " << device_path; |
| |
| // If the policy is set instead of showing the new device notification we show |
| // a notification that the operation is not permitted. |
| if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { |
| notifications_->ShowNotification( |
| DesktopNotifications::DEVICE_EXTERNAL_STORAGE_DISABLED, |
| device_path); |
| return; |
| } |
| |
| notifications_->RegisterDevice(device_path); |
| notifications_->ShowNotificationDelayed(DesktopNotifications::DEVICE, |
| device_path, |
| base::TimeDelta::FromSeconds(5)); |
| } |
| |
| void EventRouter::OnDeviceRemoved(const std::string& device_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| VLOG(1) << "Device removed : " << device_path; |
| notifications_->HideNotification(DesktopNotifications::DEVICE, |
| device_path); |
| notifications_->HideNotification(DesktopNotifications::DEVICE_FAIL, |
| device_path); |
| notifications_->UnregisterDevice(device_path); |
| } |
| |
| void EventRouter::OnDeviceScanned(const std::string& device_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| VLOG(1) << "Device scanned : " << device_path; |
| } |
| |
| void EventRouter::OnFormatStarted(const std::string& device_path, |
| bool success) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (success) { |
| notifications_->ShowNotification(DesktopNotifications::FORMAT_START, |
| device_path); |
| } else { |
| notifications_->ShowNotification( |
| DesktopNotifications::FORMAT_START_FAIL, device_path); |
| } |
| } |
| |
| void EventRouter::OnFormatCompleted(const std::string& device_path, |
| bool success) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (success) { |
| notifications_->HideNotification(DesktopNotifications::FORMAT_START, |
| device_path); |
| notifications_->ShowNotification(DesktopNotifications::FORMAT_SUCCESS, |
| device_path); |
| // Hide it after a couple of seconds. |
| notifications_->HideNotificationDelayed( |
| DesktopNotifications::FORMAT_SUCCESS, |
| device_path, |
| base::TimeDelta::FromSeconds(4)); |
| // MountPath auto-detects filesystem format if second argument is empty. |
| // The third argument (mount label) is not used in a disk mount operation. |
| DiskMountManager::GetInstance()->MountPath(device_path, std::string(), |
| std::string(), |
| chromeos::MOUNT_TYPE_DEVICE); |
| } else { |
| notifications_->HideNotification(DesktopNotifications::FORMAT_START, |
| device_path); |
| notifications_->ShowNotification(DesktopNotifications::FORMAT_FAIL, |
| device_path); |
| } |
| } |
| |
| } // namespace file_manager |