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