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