Move OnFileSystemMounted/BeingUnmounted to VolumeManager.
[chromium-blink-merge.git] / chrome / browser / chromeos / extensions / file_manager / event_router.cc
blob6edae569cc70f4c9a1e665680fd9eedaa14657d6
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.
5 #include "chrome/browser/chromeos/extensions/file_manager/event_router.h"
7 #include "base/bind.h"
8 #include "base/file_util.h"
9 #include "base/message_loop/message_loop.h"
10 #include "base/prefs/pref_change_registrar.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/stl_util.h"
13 #include "base/threading/sequenced_worker_pool.h"
14 #include "base/values.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
17 #include "chrome/browser/chromeos/drive/file_system_interface.h"
18 #include "chrome/browser/chromeos/drive/file_system_util.h"
19 #include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h"
20 #include "chrome/browser/chromeos/file_manager/app_id.h"
21 #include "chrome/browser/chromeos/file_manager/desktop_notifications.h"
22 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
23 #include "chrome/browser/chromeos/file_manager/open_util.h"
24 #include "chrome/browser/chromeos/file_manager/volume_manager.h"
25 #include "chrome/browser/chromeos/login/login_display_host_impl.h"
26 #include "chrome/browser/chromeos/login/screen_locker.h"
27 #include "chrome/browser/drive/drive_service_interface.h"
28 #include "chrome/browser/extensions/event_names.h"
29 #include "chrome/browser/extensions/event_router.h"
30 #include "chrome/browser/extensions/extension_service.h"
31 #include "chrome/browser/extensions/extension_system.h"
32 #include "chrome/browser/profiles/profile.h"
33 #include "chrome/common/extensions/api/file_browser_private.h"
34 #include "chrome/common/pref_names.h"
35 #include "chromeos/login/login_state.h"
36 #include "chromeos/network/network_handler.h"
37 #include "chromeos/network/network_state_handler.h"
38 #include "content/public/browser/browser_thread.h"
39 #include "content/public/browser/notification_source.h"
40 #include "webkit/common/fileapi/file_system_types.h"
41 #include "webkit/common/fileapi/file_system_util.h"
43 using chromeos::disks::DiskMountManager;
44 using chromeos::NetworkHandler;
45 using content::BrowserThread;
46 using drive::DriveIntegrationService;
47 using drive::DriveIntegrationServiceFactory;
49 namespace file_browser_private = extensions::api::file_browser_private;
51 namespace file_manager {
52 namespace {
54 const char kPathChanged[] = "changed";
55 const char kPathWatchError[] = "error";
57 // Used as a callback for FileSystem::MarkCacheFileAsUnmounted().
58 void OnMarkAsUnmounted(drive::FileError error) {
59 // Do nothing.
61 void DirectoryExistsOnBlockingPool(const base::FilePath& directory_path,
62 const base::Closure& success_callback,
63 const base::Closure& failure_callback) {
64 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
66 if (base::DirectoryExists(directory_path))
67 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, success_callback);
68 else
69 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, failure_callback);
72 void DirectoryExistsOnUIThread(const base::FilePath& directory_path,
73 const base::Closure& success_callback,
74 const base::Closure& failure_callback) {
75 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
77 content::BrowserThread::PostBlockingPoolTask(
78 FROM_HERE,
79 base::Bind(&DirectoryExistsOnBlockingPool,
80 directory_path,
81 success_callback,
82 failure_callback));
85 // Creates a base::FilePathWatcher and starts watching at |watch_path| with
86 // |callback|. Returns NULL on failure.
87 base::FilePathWatcher* CreateAndStartFilePathWatcher(
88 const base::FilePath& watch_path,
89 const base::FilePathWatcher::Callback& callback) {
90 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
91 DCHECK(!callback.is_null());
93 base::FilePathWatcher* watcher(new base::FilePathWatcher);
94 if (!watcher->Watch(watch_path, false /* recursive */, callback)) {
95 delete watcher;
96 return NULL;
99 return watcher;
102 // Constants for the "transferState" field of onFileTransferUpdated event.
103 const char kFileTransferStateStarted[] = "started";
104 const char kFileTransferStateInProgress[] = "in_progress";
105 const char kFileTransferStateCompleted[] = "completed";
106 const char kFileTransferStateFailed[] = "failed";
108 // Frequency of sending onFileTransferUpdated.
109 const int64 kFileTransferEventFrequencyInMilliseconds = 1000;
111 // Utility function to check if |job_info| is a file uploading job.
112 bool IsUploadJob(drive::JobType type) {
113 return (type == drive::TYPE_UPLOAD_NEW_FILE ||
114 type == drive::TYPE_UPLOAD_EXISTING_FILE);
117 // Utility function to check if |job_info| is a file downloading job.
118 bool IsDownloadJob(drive::JobType type) {
119 return type == drive::TYPE_DOWNLOAD_FILE;
122 // Converts the job info to its JSON (Value) form.
123 scoped_ptr<base::DictionaryValue> JobInfoToDictionaryValue(
124 const std::string& extension_id,
125 const std::string& job_status,
126 const drive::JobInfo& job_info) {
127 DCHECK(IsActiveFileTransferJobInfo(job_info));
129 scoped_ptr<base::DictionaryValue> result(new base::DictionaryValue);
130 GURL url = util::ConvertRelativeFilePathToFileSystemUrl(
131 job_info.file_path, extension_id);
132 result->SetString("fileUrl", url.spec());
133 result->SetString("transferState", job_status);
134 result->SetString("transferType",
135 IsUploadJob(job_info.job_type) ? "upload" : "download");
136 // JavaScript does not have 64-bit integers. Instead we use double, which
137 // is in IEEE 754 formant and accurate up to 52-bits in JS, and in practice
138 // in C++. Larger values are rounded.
139 result->SetDouble("processed",
140 static_cast<double>(job_info.num_completed_bytes));
141 result->SetDouble("total", static_cast<double>(job_info.num_total_bytes));
142 return result.Pass();
145 // Checks for availability of the Google+ Photos app.
146 bool IsGooglePhotosInstalled(Profile *profile) {
147 ExtensionService* service =
148 extensions::ExtensionSystem::Get(profile)->extension_service();
149 if (!service)
150 return false;
152 // Google+ Photos uses several ids for different channels. Therefore, all of
153 // them should be checked.
154 const std::string kGooglePlusPhotosIds[] = {
155 "ebpbnabdhheoknfklmpddcdijjkmklkp", // G+ Photos staging
156 "efjnaogkjbogokcnohkmnjdojkikgobo", // G+ Photos prod
157 "ejegoaikibpmikoejfephaneibodccma" // G+ Photos dev
160 for (size_t i = 0; i < arraysize(kGooglePlusPhotosIds); ++i) {
161 if (service->GetExtensionById(kGooglePlusPhotosIds[i],
162 false /* include_disable */) != NULL)
163 return true;
166 return false;
169 // This method is temporarily introduced to make a change step by step.
170 // After mounting logic is moved to VolumeManager, this should be removed.
171 std::string MountTypeToString(chromeos::MountType type) {
172 switch (type) {
173 case chromeos::MOUNT_TYPE_INVALID:
174 return "";
175 case chromeos::MOUNT_TYPE_DEVICE:
176 return util::VolumeTypeToStringEnum(VOLUME_TYPE_REMOVABLE_DISK_PARTITION);
177 case chromeos::MOUNT_TYPE_ARCHIVE:
178 return util::VolumeTypeToStringEnum(VOLUME_TYPE_MOUNTED_ARCHIVE_FILE);
179 case chromeos::MOUNT_TYPE_GOOGLE_DRIVE:
180 return util::VolumeTypeToStringEnum(VOLUME_TYPE_GOOGLE_DRIVE);
182 NOTREACHED();
183 return "";
186 // Sends an event named |event_name| with arguments |event_args| to extensions.
187 void BroadcastEvent(Profile* profile,
188 const std::string& event_name,
189 scoped_ptr<base::ListValue> event_args) {
190 extensions::ExtensionSystem::Get(profile)->event_router()->
191 BroadcastEvent(make_scoped_ptr(
192 new extensions::Event(event_name, event_args.Pass())));
195 file_browser_private::MountCompletedEvent::Status
196 MountErrorToMountCompletedStatus(chromeos::MountError error) {
197 using file_browser_private::MountCompletedEvent;
199 switch (error) {
200 case chromeos::MOUNT_ERROR_NONE:
201 return MountCompletedEvent::STATUS_SUCCESS;
202 case chromeos::MOUNT_ERROR_UNKNOWN:
203 return MountCompletedEvent::STATUS_ERROR_UNKNOWN;
204 case chromeos::MOUNT_ERROR_INTERNAL:
205 return MountCompletedEvent::STATUS_ERROR_INTERNAL;
206 case chromeos::MOUNT_ERROR_INVALID_ARGUMENT:
207 return MountCompletedEvent::STATUS_ERROR_INVALID_ARGUMENT;
208 case chromeos::MOUNT_ERROR_INVALID_PATH:
209 return MountCompletedEvent::STATUS_ERROR_INVALID_PATH;
210 case chromeos::MOUNT_ERROR_PATH_ALREADY_MOUNTED:
211 return MountCompletedEvent::STATUS_ERROR_PATH_ALREADY_MOUNTED;
212 case chromeos::MOUNT_ERROR_PATH_NOT_MOUNTED:
213 return MountCompletedEvent::STATUS_ERROR_PATH_NOT_MOUNTED;
214 case chromeos::MOUNT_ERROR_DIRECTORY_CREATION_FAILED:
215 return MountCompletedEvent::STATUS_ERROR_DIRECTORY_CREATION_FAILED;
216 case chromeos::MOUNT_ERROR_INVALID_MOUNT_OPTIONS:
217 return MountCompletedEvent::STATUS_ERROR_INVALID_MOUNT_OPTIONS;
218 case chromeos::MOUNT_ERROR_INVALID_UNMOUNT_OPTIONS:
219 return MountCompletedEvent::STATUS_ERROR_INVALID_UNMOUNT_OPTIONS;
220 case chromeos::MOUNT_ERROR_INSUFFICIENT_PERMISSIONS:
221 return MountCompletedEvent::STATUS_ERROR_INSUFFICIENT_PERMISSIONS;
222 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_NOT_FOUND:
223 return MountCompletedEvent::STATUS_ERROR_MOUNT_PROGRAM_NOT_FOUND;
224 case chromeos::MOUNT_ERROR_MOUNT_PROGRAM_FAILED:
225 return MountCompletedEvent::STATUS_ERROR_MOUNT_PROGRAM_FAILED;
226 case chromeos::MOUNT_ERROR_INVALID_DEVICE_PATH:
227 return MountCompletedEvent::STATUS_ERROR_INVALID_DEVICE_PATH;
228 case chromeos::MOUNT_ERROR_UNKNOWN_FILESYSTEM:
229 return MountCompletedEvent::STATUS_ERROR_UNKNOWN_FILESYSTEM;
230 case chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM:
231 return MountCompletedEvent::STATUS_ERROR_UNSUPORTED_FILESYSTEM;
232 case chromeos::MOUNT_ERROR_INVALID_ARCHIVE:
233 return MountCompletedEvent::STATUS_ERROR_INVALID_ARCHIVE;
234 case chromeos::MOUNT_ERROR_NOT_AUTHENTICATED:
235 return MountCompletedEvent::STATUS_ERROR_AUTHENTICATION;
236 case chromeos::MOUNT_ERROR_PATH_UNMOUNTED:
237 return MountCompletedEvent::STATUS_ERROR_PATH_UNMOUNTED;
239 NOTREACHED();
240 return MountCompletedEvent::STATUS_NONE;
243 file_browser_private::MountCompletedEvent::VolumeType
244 VolumeTypeToMountCompletedVolumeType(VolumeType volume_type) {
245 using file_browser_private::MountCompletedEvent;
247 switch (volume_type) {
248 case VOLUME_TYPE_GOOGLE_DRIVE:
249 return MountCompletedEvent::VOLUME_TYPE_DRIVE;
250 case VOLUME_TYPE_DOWNLOADS_DIRECTORY:
251 return MountCompletedEvent::VOLUME_TYPE_DOWNLOADS;
252 case VOLUME_TYPE_REMOVABLE_DISK_PARTITION:
253 return MountCompletedEvent::VOLUME_TYPE_REMOVABLE;
254 case VOLUME_TYPE_MOUNTED_ARCHIVE_FILE:
255 return MountCompletedEvent::VOLUME_TYPE_ARCHIVE;
257 NOTREACHED();
258 return MountCompletedEvent::VOLUME_TYPE_NONE;
261 void BroadcastMountCompletedEvent(
262 Profile* profile,
263 file_browser_private::MountCompletedEvent::EventType event_type,
264 chromeos::MountError error,
265 const VolumeInfo& volume_info) {
266 file_browser_private::MountCompletedEvent event;
267 event.event_type = event_type;
268 event.status = MountErrorToMountCompletedStatus(error);
269 event.source_path = volume_info.source_path.AsUTF8Unsafe();
270 event.volume_type = VolumeTypeToMountCompletedVolumeType(volume_info.type);
272 if (!volume_info.mount_path.empty()) {
273 // Convert mount point path to relative path with the external file system
274 // exposed within File API.
275 base::FilePath relative_mount_path;
276 if (util::ConvertAbsoluteFilePathToRelativeFileSystemPath(
277 profile, kFileManagerAppId, base::FilePath(volume_info.mount_path),
278 &relative_mount_path)) {
279 event.mount_path.reset(
280 new std::string("/" + relative_mount_path.AsUTF8Unsafe()));
281 } else {
282 event.status = file_browser_private::MountCompletedEvent::
283 STATUS_ERROR_PATH_UNMOUNTED;
287 BroadcastEvent(
288 profile,
289 extensions::event_names::kOnFileBrowserMountCompleted,
290 file_browser_private::OnMountCompleted::Create(event));
293 file_browser_private::CopyProgressStatus::Type
294 CopyProgressTypeToCopyProgressStatusType(
295 fileapi::FileSystemOperation::CopyProgressType type) {
296 using file_browser_private::CopyProgressStatus;
298 switch (type) {
299 case fileapi::FileSystemOperation::BEGIN_COPY_ENTRY:
300 return CopyProgressStatus::TYPE_BEGIN_ENTRY_COPY;
301 case fileapi::FileSystemOperation::END_COPY_ENTRY:
302 return CopyProgressStatus::TYPE_END_ENTRY_COPY;
303 case fileapi::FileSystemOperation::PROGRESS:
304 return CopyProgressStatus::TYPE_PROGRESS;
306 NOTREACHED();
307 return CopyProgressStatus::TYPE_NONE;
310 } // namespace
312 // Pass dummy value to JobInfo's constructor for make it default constructible.
313 EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus()
314 : job_info(drive::TYPE_DOWNLOAD_FILE) {
317 EventRouter::DriveJobInfoWithStatus::DriveJobInfoWithStatus(
318 const drive::JobInfo& info, const std::string& status)
319 : job_info(info), status(status) {
322 EventRouter::EventRouter(Profile* profile)
323 : notifications_(new DesktopNotifications(profile)),
324 pref_change_registrar_(new PrefChangeRegistrar),
325 profile_(profile),
326 weak_factory_(this) {
327 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
330 EventRouter::~EventRouter() {
333 void EventRouter::Shutdown() {
334 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
336 DLOG_IF(WARNING, !file_watchers_.empty())
337 << "Not all file watchers are "
338 << "removed. This can happen when Files.app is open during shutdown.";
339 STLDeleteValues(&file_watchers_);
340 if (!profile_) {
341 NOTREACHED();
342 return;
345 pref_change_registrar_->RemoveAll();
347 if (NetworkHandler::IsInitialized()) {
348 NetworkHandler::Get()->network_state_handler()->RemoveObserver(this,
349 FROM_HERE);
352 DriveIntegrationService* integration_service =
353 DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates(
354 profile_);
355 if (integration_service) {
356 integration_service->file_system()->RemoveObserver(this);
357 integration_service->drive_service()->RemoveObserver(this);
358 integration_service->job_list()->RemoveObserver(this);
361 VolumeManager* volume_manager = VolumeManager::Get(profile_);
362 if (volume_manager)
363 volume_manager->RemoveObserver(this);
365 profile_ = NULL;
368 void EventRouter::ObserveFileSystemEvents() {
369 if (!profile_) {
370 NOTREACHED();
371 return;
373 if (!chromeos::LoginState::IsInitialized() ||
374 !chromeos::LoginState::Get()->IsUserLoggedIn()) {
375 return;
378 // VolumeManager's construction triggers DriveIntegrationService's
379 // construction, so it is necessary to call VolumeManager's Get before
380 // accessing DriveIntegrationService.
381 VolumeManager* volume_manager = VolumeManager::Get(profile_);
382 if (volume_manager)
383 volume_manager->AddObserver(this);
385 DriveIntegrationService* integration_service =
386 DriveIntegrationServiceFactory::FindForProfileRegardlessOfStates(
387 profile_);
388 if (integration_service) {
389 integration_service->drive_service()->AddObserver(this);
390 integration_service->file_system()->AddObserver(this);
391 integration_service->job_list()->AddObserver(this);
394 if (NetworkHandler::IsInitialized()) {
395 NetworkHandler::Get()->network_state_handler()->AddObserver(this,
396 FROM_HERE);
399 pref_change_registrar_->Init(profile_->GetPrefs());
400 base::Closure callback =
401 base::Bind(&EventRouter::OnFileManagerPrefsChanged,
402 weak_factory_.GetWeakPtr());
403 pref_change_registrar_->Add(prefs::kDisableDriveOverCellular, callback);
404 pref_change_registrar_->Add(prefs::kDisableDriveHostedFiles, callback);
405 pref_change_registrar_->Add(prefs::kDisableDrive, callback);
406 pref_change_registrar_->Add(prefs::kUse24HourClock, callback);
409 // File watch setup routines.
410 void EventRouter::AddFileWatch(const base::FilePath& local_path,
411 const base::FilePath& virtual_path,
412 const std::string& extension_id,
413 const BoolCallback& callback) {
414 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
415 DCHECK(!callback.is_null());
417 base::FilePath watch_path = local_path;
418 bool is_on_drive = drive::util::IsUnderDriveMountPoint(watch_path);
419 // Tweak watch path for remote sources - we need to drop leading /special
420 // directory from there in order to be able to pair these events with
421 // their change notifications.
422 if (is_on_drive)
423 watch_path = drive::util::ExtractDrivePath(watch_path);
425 WatcherMap::iterator iter = file_watchers_.find(watch_path);
426 if (iter == file_watchers_.end()) {
427 scoped_ptr<FileWatcher> watcher(new FileWatcher(virtual_path));
428 watcher->AddExtension(extension_id);
430 if (is_on_drive) {
431 // For Drive, file watching is done via OnDirectoryChanged().
432 base::MessageLoopProxy::current()->PostTask(FROM_HERE,
433 base::Bind(callback, true));
434 } else {
435 // For local files, start watching using FileWatcher.
436 watcher->WatchLocalFile(
437 watch_path,
438 base::Bind(&EventRouter::HandleFileWatchNotification,
439 weak_factory_.GetWeakPtr()),
440 callback);
443 file_watchers_[watch_path] = watcher.release();
444 } else {
445 iter->second->AddExtension(extension_id);
446 base::MessageLoopProxy::current()->PostTask(FROM_HERE,
447 base::Bind(callback, true));
451 void EventRouter::RemoveFileWatch(const base::FilePath& local_path,
452 const std::string& extension_id) {
453 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
455 base::FilePath watch_path = local_path;
456 // Tweak watch path for remote sources - we need to drop leading /special
457 // directory from there in order to be able to pair these events with
458 // their change notifications.
459 if (drive::util::IsUnderDriveMountPoint(watch_path)) {
460 watch_path = drive::util::ExtractDrivePath(watch_path);
462 WatcherMap::iterator iter = file_watchers_.find(watch_path);
463 if (iter == file_watchers_.end())
464 return;
465 // Remove the watcher if |watch_path| is no longer watched by any extensions.
466 iter->second->RemoveExtension(extension_id);
467 if (iter->second->GetExtensionIds().empty()) {
468 delete iter->second;
469 file_watchers_.erase(iter);
473 void EventRouter::OnCopyCompleted(
474 int copy_id, const GURL& url, base::PlatformFileError error) {
475 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
477 file_browser_private::CopyProgressStatus status;
478 if (error == base::PLATFORM_FILE_OK) {
479 // Send success event.
480 status.type = file_browser_private::CopyProgressStatus::TYPE_SUCCESS;
481 status.url.reset(new std::string(url.spec()));
482 } else {
483 // Send error event.
484 status.type = file_browser_private::CopyProgressStatus::TYPE_ERROR;
485 status.error.reset(
486 new int(fileapi::PlatformFileErrorToWebFileError(error)));
489 BroadcastEvent(
490 profile_,
491 extensions::event_names::kOnFileBrowserCopyProgress,
492 file_browser_private::OnCopyProgress::Create(copy_id, status));
495 void EventRouter::OnCopyProgress(
496 int copy_id,
497 fileapi::FileSystemOperation::CopyProgressType type,
498 const GURL& url,
499 int64 size) {
500 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
502 file_browser_private::CopyProgressStatus status;
503 status.type = CopyProgressTypeToCopyProgressStatusType(type);
504 status.url.reset(new std::string(url.spec()));
505 if (type == fileapi::FileSystemOperation::PROGRESS)
506 status.size.reset(new double(size));
508 BroadcastEvent(
509 profile_,
510 extensions::event_names::kOnFileBrowserCopyProgress,
511 file_browser_private::OnCopyProgress::Create(copy_id, status));
514 void EventRouter::NetworkManagerChanged() {
515 if (!profile_ ||
516 !extensions::ExtensionSystem::Get(profile_)->event_router()) {
517 NOTREACHED();
518 return;
521 BroadcastEvent(
522 profile_,
523 extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged,
524 make_scoped_ptr(new ListValue));
527 void EventRouter::DefaultNetworkChanged(const chromeos::NetworkState* network) {
528 NetworkManagerChanged();
531 void EventRouter::OnFileManagerPrefsChanged() {
532 if (!profile_ ||
533 !extensions::ExtensionSystem::Get(profile_)->event_router()) {
534 NOTREACHED();
535 return;
538 BroadcastEvent(
539 profile_,
540 extensions::event_names::kOnFileBrowserPreferencesChanged,
541 make_scoped_ptr(new ListValue));
544 void EventRouter::OnJobAdded(const drive::JobInfo& job_info) {
545 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
546 OnJobUpdated(job_info);
549 void EventRouter::OnJobUpdated(const drive::JobInfo& job_info) {
550 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
551 if (!drive::IsActiveFileTransferJobInfo(job_info))
552 return;
554 bool is_new_job = (drive_jobs_.find(job_info.job_id) == drive_jobs_.end());
556 // Replace with the latest job info.
557 drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus(
558 job_info,
559 is_new_job ? kFileTransferStateStarted : kFileTransferStateInProgress);
561 // Fire event if needed.
562 bool always = is_new_job;
563 SendDriveFileTransferEvent(always);
566 void EventRouter::OnJobDone(const drive::JobInfo& job_info,
567 drive::FileError error) {
568 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
569 if (!drive::IsActiveFileTransferJobInfo(job_info))
570 return;
572 // Replace with the latest job info.
573 drive_jobs_[job_info.job_id] = DriveJobInfoWithStatus(
574 job_info,
575 error == drive::FILE_ERROR_OK ? kFileTransferStateCompleted
576 : kFileTransferStateFailed);
578 // Fire event if needed.
579 bool always = true;
580 SendDriveFileTransferEvent(always);
582 // Forget about the job.
583 drive_jobs_.erase(job_info.job_id);
586 void EventRouter::SendDriveFileTransferEvent(bool always) {
587 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
589 const base::Time now = base::Time::Now();
591 // When |always| flag is not set, we don't send the event until certain
592 // amount of time passes after the previous one. This is to avoid
593 // flooding the IPC between extensions by many onFileTransferUpdated events.
594 if (!always) {
595 const int64 delta = (now - last_file_transfer_event_).InMilliseconds();
596 // delta < 0 may rarely happen if system clock is synced and rewinded.
597 // To be conservative, we don't skip in that case.
598 if (0 <= delta && delta < kFileTransferEventFrequencyInMilliseconds)
599 return;
602 // Convert the current |drive_jobs_| to a JSON value.
603 scoped_ptr<base::ListValue> event_list(new base::ListValue);
604 for (std::map<drive::JobID, DriveJobInfoWithStatus>::iterator
605 iter = drive_jobs_.begin(); iter != drive_jobs_.end(); ++iter) {
607 scoped_ptr<base::DictionaryValue> job_info_dict(
608 JobInfoToDictionaryValue(kFileManagerAppId,
609 iter->second.status,
610 iter->second.job_info));
611 event_list->Append(job_info_dict.release());
614 scoped_ptr<ListValue> args(new ListValue());
615 args->Append(event_list.release());
616 scoped_ptr<extensions::Event> event(new extensions::Event(
617 extensions::event_names::kOnFileTransfersUpdated, args.Pass()));
618 extensions::ExtensionSystem::Get(profile_)->event_router()->
619 DispatchEventToExtension(kFileManagerAppId, event.Pass());
621 last_file_transfer_event_ = now;
624 void EventRouter::OnDirectoryChanged(const base::FilePath& directory_path) {
625 HandleFileWatchNotification(directory_path, false);
628 void EventRouter::OnRefreshTokenInvalid() {
629 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
631 // Raise a DriveConnectionStatusChanged event to notify the status offline.
632 BroadcastEvent(
633 profile_,
634 extensions::event_names::kOnFileBrowserDriveConnectionStatusChanged,
635 make_scoped_ptr(new ListValue));
638 void EventRouter::HandleFileWatchNotification(const base::FilePath& local_path,
639 bool got_error) {
640 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
642 WatcherMap::const_iterator iter = file_watchers_.find(local_path);
643 if (iter == file_watchers_.end()) {
644 return;
646 DispatchDirectoryChangeEvent(iter->second->virtual_path(), got_error,
647 iter->second->GetExtensionIds());
650 void EventRouter::DispatchDirectoryChangeEvent(
651 const base::FilePath& virtual_path,
652 bool got_error,
653 const std::vector<std::string>& extension_ids) {
654 if (!profile_) {
655 NOTREACHED();
656 return;
659 for (size_t i = 0; i < extension_ids.size(); ++i) {
660 const std::string& extension_id = extension_ids[i];
662 GURL target_origin_url(extensions::Extension::GetBaseURLFromExtensionId(
663 extension_id));
664 GURL base_url = fileapi::GetFileSystemRootURI(
665 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);
675 scoped_ptr<extensions::Event> event(new extensions::Event(
676 extensions::event_names::kOnDirectoryChanged, args.Pass()));
677 extensions::ExtensionSystem::Get(profile_)->event_router()->
678 DispatchEventToExtension(extension_id, event.Pass());
682 void EventRouter::ShowRemovableDeviceInFileManager(
683 const base::FilePath& mount_path) {
684 // Do not attempt to open File Manager while the login is in progress or
685 // the screen is locked.
686 if (chromeos::LoginDisplayHostImpl::default_host() ||
687 chromeos::ScreenLocker::default_screen_locker())
688 return;
690 // According to DCF (Design rule of Camera File system) by JEITA / CP-3461
691 // cameras should have pictures located in the DCIM root directory.
692 const base::FilePath dcim_path = mount_path.Append(
693 FILE_PATH_LITERAL("DCIM"));
695 // If there is no DCIM folder or an external photo importer is not available,
696 // then launch Files.app.
697 DirectoryExistsOnUIThread(
698 dcim_path,
699 IsGooglePhotosInstalled(profile_) ?
700 base::Bind(&base::DoNothing) :
701 base::Bind(&util::OpenRemovableDrive, mount_path),
702 base::Bind(&util::OpenRemovableDrive, mount_path));
705 void EventRouter::OnDiskAdded(
706 const DiskMountManager::Disk& disk, bool mounting) {
707 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
709 if (!mounting) {
710 // If the disk is not being mounted, we don't want the Scanning
711 // notification to persist.
712 notifications_->HideNotification(DesktopNotifications::DEVICE,
713 disk.system_path_prefix());
717 void EventRouter::OnDiskRemoved(const DiskMountManager::Disk& disk) {
718 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
719 // Do nothing.
722 void EventRouter::OnDeviceAdded(const std::string& device_path) {
723 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
725 // If the policy is set instead of showing the new device notification,
726 // we show a notification that the operation is not permitted.
727 if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
728 notifications_->ShowNotification(
729 DesktopNotifications::DEVICE_EXTERNAL_STORAGE_DISABLED,
730 device_path);
731 return;
734 notifications_->RegisterDevice(device_path);
735 notifications_->ShowNotificationDelayed(DesktopNotifications::DEVICE,
736 device_path,
737 base::TimeDelta::FromSeconds(5));
740 void EventRouter::OnDeviceRemoved(const std::string& device_path) {
741 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
743 notifications_->HideNotification(DesktopNotifications::DEVICE,
744 device_path);
745 notifications_->HideNotification(DesktopNotifications::DEVICE_FAIL,
746 device_path);
747 notifications_->UnregisterDevice(device_path);
750 void EventRouter::OnVolumeMounted(chromeos::MountError error_code,
751 const VolumeInfo& volume_info,
752 bool is_remounting) {
753 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
754 // profile_ is NULL if ShutdownOnUIThread() is called earlier. This can
755 // happen at shutdown. This should be removed after removing Drive mounting
756 // code in addMount. (addMount -> OnFileSystemMounted -> OnVolumeMounted is
757 // the only path to come here after Shutdown is called).
758 if (!profile_)
759 return;
761 BroadcastMountCompletedEvent(
762 profile_,
763 file_browser_private::MountCompletedEvent::EVENT_TYPE_MOUNT,
764 error_code, volume_info);
766 if (volume_info.type == VOLUME_TYPE_REMOVABLE_DISK_PARTITION &&
767 !is_remounting) {
768 notifications_->ManageNotificationsOnMountCompleted(
769 volume_info.system_path_prefix.AsUTF8Unsafe(),
770 volume_info.drive_label,
771 volume_info.is_parent,
772 error_code == chromeos::MOUNT_ERROR_NONE,
773 error_code == chromeos::MOUNT_ERROR_UNSUPPORTED_FILESYSTEM);
775 // If a new device was mounted, a new File manager window may need to be
776 // opened.
777 if (error_code == chromeos::MOUNT_ERROR_NONE)
778 ShowRemovableDeviceInFileManager(volume_info.mount_path);
782 void EventRouter::OnVolumeUnmounted(chromeos::MountError error_code,
783 const VolumeInfo& volume_info) {
784 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
785 BroadcastMountCompletedEvent(
786 profile_,
787 file_browser_private::MountCompletedEvent::EVENT_TYPE_UNMOUNT,
788 error_code, volume_info);
791 void EventRouter::OnFormatStarted(const std::string& device_path,
792 bool success) {
793 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
795 if (success) {
796 notifications_->ShowNotification(DesktopNotifications::FORMAT_START,
797 device_path);
798 } else {
799 notifications_->ShowNotification(
800 DesktopNotifications::FORMAT_START_FAIL, device_path);
804 void EventRouter::OnFormatCompleted(const std::string& device_path,
805 bool success) {
806 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
808 if (success) {
809 notifications_->HideNotification(DesktopNotifications::FORMAT_START,
810 device_path);
811 notifications_->ShowNotification(DesktopNotifications::FORMAT_SUCCESS,
812 device_path);
813 // Hide it after a couple of seconds.
814 notifications_->HideNotificationDelayed(
815 DesktopNotifications::FORMAT_SUCCESS,
816 device_path,
817 base::TimeDelta::FromSeconds(4));
818 } else {
819 notifications_->HideNotification(DesktopNotifications::FORMAT_START,
820 device_path);
821 notifications_->ShowNotification(DesktopNotifications::FORMAT_FAIL,
822 device_path);
826 } // namespace file_manager