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