volume_manager.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
1// Copyright 2013 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/file_manager/volume_manager.h"
6
7#include "base/basictypes.h"
8#include "base/bind.h"
9#include "base/command_line.h"
10#include "base/files/file_path.h"
11#include "base/logging.h"
12#include "base/metrics/histogram.h"
13#include "base/prefs/pref_service.h"
14#include "base/strings/stringprintf.h"
15#include "base/strings/utf_string_conversions.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/file_manager/mounted_disk_monitor.h"
20#include "chrome/browser/chromeos/file_manager/path_util.h"
21#include "chrome/browser/chromeos/file_manager/snapshot_manager.h"
22#include "chrome/browser/chromeos/file_manager/volume_manager_factory.h"
23#include "chrome/browser/chromeos/file_manager/volume_manager_observer.h"
24#include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h"
25#include "chrome/browser/chromeos/profiles/profile_helper.h"
26#include "chrome/browser/local_discovery/storage/privet_filesystem_constants.h"
27#include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h"
28#include "chrome/browser/profiles/profile.h"
29#include "chrome/common/chrome_switches.h"
30#include "chrome/common/pref_names.h"
31#include "chromeos/chromeos_switches.h"
32#include "chromeos/disks/disk_mount_manager.h"
33#include "components/storage_monitor/storage_monitor.h"
34#include "content/public/browser/browser_context.h"
35#include "content/public/browser/browser_thread.h"
36#include "webkit/browser/fileapi/external_mount_points.h"
37
38namespace file_manager {
39namespace {
40
41// A named constant to be passed to the |is_remounting| parameter.
42const bool kNotRemounting = false;
43
44const char kFileManagerMTPMountNamePrefix[] = "fileman-mtp-";
45const char kMtpVolumeIdPrefix [] = "mtp:";
46
47// Registers |path| as the "Downloads" folder to the FileSystem API backend.
48// If another folder is already mounted. It revokes and overrides the old one.
49bool RegisterDownloadsMountPoint(Profile* profile, const base::FilePath& path) {
50  // Although we show only profile's own "Downloads" folder in Files.app,
51  // in the backend we need to mount all profile's download directory globally.
52  // Otherwise, Files.app cannot support cross-profile file copies, etc.
53  // For this reason, we need to register to the global GetSystemInstance().
54  const std::string mount_point_name =
55      file_manager::util::GetDownloadsMountPointName(profile);
56  fileapi::ExternalMountPoints* const mount_points =
57      fileapi::ExternalMountPoints::GetSystemInstance();
58
59  // In some tests we want to override existing Downloads mount point, so we
60  // first revoke the existing mount point (if any).
61  mount_points->RevokeFileSystem(mount_point_name);
62  return mount_points->RegisterFileSystem(
63      mount_point_name, fileapi::kFileSystemTypeNativeLocal,
64      fileapi::FileSystemMountOption(), path);
65}
66
67// Finds the path register as the "Downloads" folder to FileSystem API backend.
68// Returns false if it is not registered.
69bool FindDownloadsMountPointPath(Profile* profile, base::FilePath* path) {
70  const std::string mount_point_name =
71      util::GetDownloadsMountPointName(profile);
72  fileapi::ExternalMountPoints* const mount_points =
73      fileapi::ExternalMountPoints::GetSystemInstance();
74
75  return mount_points->GetRegisteredPath(mount_point_name, path);
76}
77
78VolumeType MountTypeToVolumeType(chromeos::MountType type) {
79  switch (type) {
80    case chromeos::MOUNT_TYPE_INVALID:
81      // We don't expect this value, but list here, so that when any value
82      // is added to the enum definition but this is not edited, the compiler
83      // warns it.
84      break;
85    case chromeos::MOUNT_TYPE_DEVICE:
86      return VOLUME_TYPE_REMOVABLE_DISK_PARTITION;
87    case chromeos::MOUNT_TYPE_ARCHIVE:
88      return VOLUME_TYPE_MOUNTED_ARCHIVE_FILE;
89  }
90
91  NOTREACHED();
92  return VOLUME_TYPE_DOWNLOADS_DIRECTORY;
93}
94
95// Returns a string representation of the given volume type.
96std::string VolumeTypeToString(VolumeType type) {
97  switch (type) {
98    case VOLUME_TYPE_GOOGLE_DRIVE:
99      return "drive";
100    case VOLUME_TYPE_DOWNLOADS_DIRECTORY:
101      return "downloads";
102    case VOLUME_TYPE_REMOVABLE_DISK_PARTITION:
103      return "removable";
104    case VOLUME_TYPE_MOUNTED_ARCHIVE_FILE:
105      return "archive";
106    case VOLUME_TYPE_CLOUD_DEVICE:
107      return "cloud_device";
108    case VOLUME_TYPE_PROVIDED:
109      return "provided";
110    case VOLUME_TYPE_MTP:
111      return "mtp";
112    case VOLUME_TYPE_TESTING:
113      return "testing";
114    case NUM_VOLUME_TYPE:
115      break;
116  }
117  NOTREACHED();
118  return "";
119}
120
121// Generates a unique volume ID for the given volume info.
122std::string GenerateVolumeId(const VolumeInfo& volume_info) {
123  // For the same volume type, base names are unique, as mount points are
124  // flat for the same volume type.
125  return (VolumeTypeToString(volume_info.type) + ":" +
126          volume_info.mount_path.BaseName().AsUTF8Unsafe());
127}
128
129// Returns the VolumeInfo for Drive file system.
130VolumeInfo CreateDriveVolumeInfo(Profile* profile) {
131  const base::FilePath& drive_path =
132      drive::util::GetDriveMountPointPath(profile);
133
134  VolumeInfo volume_info;
135  volume_info.type = VOLUME_TYPE_GOOGLE_DRIVE;
136  volume_info.device_type = chromeos::DEVICE_TYPE_UNKNOWN;
137  volume_info.source_path = drive_path;
138  volume_info.mount_path = drive_path;
139  volume_info.mount_condition = chromeos::disks::MOUNT_CONDITION_NONE;
140  volume_info.is_parent = false;
141  volume_info.is_read_only = false;
142  volume_info.volume_id = GenerateVolumeId(volume_info);
143  return volume_info;
144}
145
146VolumeInfo CreateDownloadsVolumeInfo(const base::FilePath& downloads_path) {
147  VolumeInfo volume_info;
148  volume_info.type = VOLUME_TYPE_DOWNLOADS_DIRECTORY;
149  volume_info.device_type = chromeos::DEVICE_TYPE_UNKNOWN;
150  // Keep source_path empty.
151  volume_info.mount_path = downloads_path;
152  volume_info.mount_condition = chromeos::disks::MOUNT_CONDITION_NONE;
153  volume_info.is_parent = false;
154  volume_info.is_read_only = false;
155  volume_info.volume_id = GenerateVolumeId(volume_info);
156  return volume_info;
157}
158
159VolumeInfo CreateTestingVolumeInfo(const base::FilePath& path,
160                                   VolumeType volume_type,
161                                   chromeos::DeviceType device_type) {
162  VolumeInfo volume_info;
163  volume_info.type = volume_type;
164  volume_info.device_type = device_type;
165  // Keep source_path empty.
166  volume_info.mount_path = path;
167  volume_info.mount_condition = chromeos::disks::MOUNT_CONDITION_NONE;
168  volume_info.is_parent = false;
169  volume_info.is_read_only = false;
170  volume_info.volume_id = GenerateVolumeId(volume_info);
171  return volume_info;
172}
173
174VolumeInfo CreateVolumeInfoFromMountPointInfo(
175    const chromeos::disks::DiskMountManager::MountPointInfo& mount_point,
176    const chromeos::disks::DiskMountManager::Disk* disk) {
177  VolumeInfo volume_info;
178  volume_info.type = MountTypeToVolumeType(mount_point.mount_type);
179  volume_info.source_path = base::FilePath(mount_point.source_path);
180  volume_info.mount_path = base::FilePath(mount_point.mount_path);
181  volume_info.mount_condition = mount_point.mount_condition;
182  volume_info.volume_label = volume_info.mount_path.BaseName().AsUTF8Unsafe();
183  if (disk) {
184    volume_info.device_type = disk->device_type();
185    volume_info.system_path_prefix =
186        base::FilePath(disk->system_path_prefix());
187    volume_info.is_parent = disk->is_parent();
188    volume_info.is_read_only = disk->is_read_only();
189  } else {
190    volume_info.device_type = chromeos::DEVICE_TYPE_UNKNOWN;
191    volume_info.is_parent = false;
192    volume_info.is_read_only =
193        (mount_point.mount_type == chromeos::MOUNT_TYPE_ARCHIVE);
194  }
195  volume_info.volume_id = GenerateVolumeId(volume_info);
196
197  return volume_info;
198}
199
200VolumeInfo CreatePrivetVolumeInfo(
201    const local_discovery::PrivetVolumeLister::VolumeInfo& privet_volume_info) {
202  VolumeInfo volume_info;
203  volume_info.type = VOLUME_TYPE_CLOUD_DEVICE;
204  volume_info.mount_path = privet_volume_info.volume_path;
205  volume_info.mount_condition = chromeos::disks::MOUNT_CONDITION_NONE;
206  volume_info.is_parent = true;
207  volume_info.is_read_only = true;
208  volume_info.volume_id = GenerateVolumeId(volume_info);
209  return volume_info;
210}
211
212VolumeInfo CreateProvidedFileSystemVolumeInfo(
213    const chromeos::file_system_provider::ProvidedFileSystemInfo&
214        file_system_info) {
215  VolumeInfo volume_info;
216  volume_info.file_system_id = file_system_info.file_system_id();
217  volume_info.extension_id = file_system_info.extension_id();
218  volume_info.volume_label = file_system_info.file_system_name();
219  volume_info.type = VOLUME_TYPE_PROVIDED;
220  volume_info.mount_path = file_system_info.mount_path();
221  volume_info.mount_condition = chromeos::disks::MOUNT_CONDITION_NONE;
222  volume_info.is_parent = true;
223  volume_info.is_read_only = true;
224  volume_info.volume_id = GenerateVolumeId(volume_info);
225  return volume_info;
226}
227
228std::string GetMountPointNameForMediaStorage(
229    const storage_monitor::StorageInfo& info) {
230  std::string name(kFileManagerMTPMountNamePrefix);
231  name += info.device_id();
232  return name;
233}
234
235}  // namespace
236
237VolumeInfo::VolumeInfo()
238    : type(VOLUME_TYPE_GOOGLE_DRIVE),
239      device_type(chromeos::DEVICE_TYPE_UNKNOWN),
240      mount_condition(chromeos::disks::MOUNT_CONDITION_NONE),
241      is_parent(false),
242      is_read_only(false) {
243}
244
245VolumeInfo::~VolumeInfo() {
246}
247
248VolumeManager::VolumeManager(
249    Profile* profile,
250    drive::DriveIntegrationService* drive_integration_service,
251    chromeos::PowerManagerClient* power_manager_client,
252    chromeos::disks::DiskMountManager* disk_mount_manager,
253    chromeos::file_system_provider::Service* file_system_provider_service)
254    : profile_(profile),
255      drive_integration_service_(drive_integration_service),
256      disk_mount_manager_(disk_mount_manager),
257      mounted_disk_monitor_(
258          new MountedDiskMonitor(power_manager_client, disk_mount_manager)),
259      file_system_provider_service_(file_system_provider_service),
260      snapshot_manager_(new SnapshotManager(profile_)),
261      weak_ptr_factory_(this) {
262  DCHECK(disk_mount_manager);
263}
264
265VolumeManager::~VolumeManager() {
266}
267
268VolumeManager* VolumeManager::Get(content::BrowserContext* context) {
269  return VolumeManagerFactory::Get(context);
270}
271
272void VolumeManager::Initialize() {
273  // If in Sign in profile, then skip mounting and listening for mount events.
274  if (chromeos::ProfileHelper::IsSigninProfile(profile_))
275    return;
276
277  // Path to mount user folders have changed several times. We need to migrate
278  // the old preferences on paths to the new format when needed. For the detail,
279  // see the comments in file_manager::util::MigratePathFromOldFormat,
280  // Note: Preferences related to downloads are handled in download_prefs.cc.
281  // TODO(kinaba): Remove this after several rounds of releases.
282  const base::FilePath old_path =
283      profile_->GetPrefs()->GetFilePath(prefs::kSelectFileLastDirectory);
284  base::FilePath new_path;
285  if (!old_path.empty() &&
286      file_manager::util::MigratePathFromOldFormat(profile_,
287                                                   old_path, &new_path)) {
288    profile_->GetPrefs()->SetFilePath(prefs::kSelectFileLastDirectory,
289                                      new_path);
290  }
291
292  // Register 'Downloads' folder for the profile to the file system.
293  const base::FilePath downloads =
294      file_manager::util::GetDownloadsFolderForProfile(profile_);
295  const bool success = RegisterDownloadsMountPoint(profile_, downloads);
296  DCHECK(success);
297
298  DoMountEvent(chromeos::MOUNT_ERROR_NONE,
299               CreateDownloadsVolumeInfo(downloads),
300               kNotRemounting);
301
302  // Subscribe to DriveIntegrationService.
303  if (drive_integration_service_) {
304    drive_integration_service_->AddObserver(this);
305    if (drive_integration_service_->IsMounted()) {
306      DoMountEvent(chromeos::MOUNT_ERROR_NONE,
307                   CreateDriveVolumeInfo(profile_),
308                   kNotRemounting);
309    }
310  }
311
312  // Subscribe to DiskMountManager.
313  disk_mount_manager_->AddObserver(this);
314
315  // Subscribe to FileSystemProviderService and register currently mounted
316  // volumes for the profile.
317  if (file_system_provider_service_) {
318    using chromeos::file_system_provider::ProvidedFileSystemInfo;
319    file_system_provider_service_->AddObserver(this);
320
321    std::vector<ProvidedFileSystemInfo> file_system_info_list =
322        file_system_provider_service_->GetProvidedFileSystemInfoList();
323    for (size_t i = 0; i < file_system_info_list.size(); ++i) {
324      VolumeInfo volume_info =
325          CreateProvidedFileSystemVolumeInfo(file_system_info_list[i]);
326      DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume_info, kNotRemounting);
327    }
328  }
329
330  std::vector<VolumeInfo> archives;
331
332  const chromeos::disks::DiskMountManager::MountPointMap& mount_points =
333      disk_mount_manager_->mount_points();
334  for (chromeos::disks::DiskMountManager::MountPointMap::const_iterator it =
335           mount_points.begin();
336       it != mount_points.end();
337       ++it) {
338    if (it->second.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) {
339      // Archives are mounted after other type of volumes. See below.
340      archives.push_back(CreateVolumeInfoFromMountPointInfo(it->second, NULL));
341      continue;
342    }
343    DoMountEvent(
344        chromeos::MOUNT_ERROR_NONE,
345        CreateVolumeInfoFromMountPointInfo(
346            it->second,
347            disk_mount_manager_->FindDiskBySourcePath(it->second.source_path)),
348            kNotRemounting);
349  }
350
351  // We mount archives only if they are opened from currently mounted volumes.
352  // To check the condition correctly in DoMountEvent, we care the order.
353  std::vector<bool> done(archives.size(), false);
354  for (size_t i = 0; i < archives.size(); ++i) {
355    if (!done[i]) {
356      std::vector<VolumeInfo> chain;
357      done[i] = true;
358      chain.push_back(archives[i]);
359
360      // If archives[i]'s source_path is in another archive, mount it first.
361      for (size_t parent = 0; parent < archives.size(); ++parent) {
362        if (!done[parent] &&
363            archives[parent].mount_path.IsParent(chain.back().source_path)) {
364          done[parent] = true;
365          chain.push_back(archives[parent]);
366          parent = 0;  // Search archives[parent]'s parent from the beginning.
367        }
368      }
369
370      // Mount from the tail of chain.
371      for (size_t i = chain.size(); i > 0; --i)
372        DoMountEvent(chromeos::MOUNT_ERROR_NONE, chain[i - 1], kNotRemounting);
373    }
374  }
375
376  disk_mount_manager_->RequestMountInfoRefresh();
377
378  // Subscribe to Profile Preference change.
379  pref_change_registrar_.Init(profile_->GetPrefs());
380  pref_change_registrar_.Add(
381      prefs::kExternalStorageDisabled,
382      base::Bind(&VolumeManager::OnExternalStorageDisabledChanged,
383                 weak_ptr_factory_.GetWeakPtr()));
384
385  // Subscribe to Privet volume lister.
386  if (CommandLine::ForCurrentProcess()->HasSwitch(
387          switches::kEnablePrivetStorage)) {
388    privet_volume_lister_.reset(new local_discovery::PrivetVolumeLister(
389        base::Bind(&VolumeManager::OnPrivetVolumesAvailable,
390                   weak_ptr_factory_.GetWeakPtr())));
391    privet_volume_lister_->Start();
392  }
393
394  // Subscribe to storage monitor for MTP notifications.
395  const bool disable_mtp =
396      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
397          chromeos::switches::kEnableFileManagerMTP) == "false";
398  if (!disable_mtp && storage_monitor::StorageMonitor::GetInstance()) {
399    storage_monitor::StorageMonitor::GetInstance()->EnsureInitialized(
400        base::Bind(&VolumeManager::OnStorageMonitorInitialized,
401                   weak_ptr_factory_.GetWeakPtr()));
402  }
403}
404
405void VolumeManager::Shutdown() {
406  weak_ptr_factory_.InvalidateWeakPtrs();
407
408  snapshot_manager_.reset();
409  pref_change_registrar_.RemoveAll();
410  disk_mount_manager_->RemoveObserver(this);
411  if (storage_monitor::StorageMonitor::GetInstance())
412    storage_monitor::StorageMonitor::GetInstance()->RemoveObserver(this);
413
414  if (drive_integration_service_)
415    drive_integration_service_->RemoveObserver(this);
416
417  if (file_system_provider_service_)
418    file_system_provider_service_->RemoveObserver(this);
419}
420
421void VolumeManager::AddObserver(VolumeManagerObserver* observer) {
422  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
423  DCHECK(observer);
424  observers_.AddObserver(observer);
425}
426
427void VolumeManager::RemoveObserver(VolumeManagerObserver* observer) {
428  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
429  DCHECK(observer);
430  observers_.RemoveObserver(observer);
431}
432
433std::vector<VolumeInfo> VolumeManager::GetVolumeInfoList() const {
434  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
435
436  std::vector<VolumeInfo> result;
437  for (std::map<std::string, VolumeInfo>::const_iterator iter =
438           mounted_volumes_.begin();
439       iter != mounted_volumes_.end();
440       ++iter) {
441    result.push_back(iter->second);
442  }
443  return result;
444}
445
446bool VolumeManager::FindVolumeInfoById(const std::string& volume_id,
447                                       VolumeInfo* result) const {
448  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
449  DCHECK(result);
450
451  std::map<std::string, VolumeInfo>::const_iterator iter =
452      mounted_volumes_.find(volume_id);
453  if (iter == mounted_volumes_.end())
454    return false;
455  *result = iter->second;
456  return true;
457}
458
459bool VolumeManager::RegisterDownloadsDirectoryForTesting(
460    const base::FilePath& path) {
461  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
462
463  base::FilePath old_path;
464  if (FindDownloadsMountPointPath(profile_, &old_path)) {
465    DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
466                   CreateDownloadsVolumeInfo(old_path));
467  }
468
469  bool success = RegisterDownloadsMountPoint(profile_, path);
470  DoMountEvent(
471      success ? chromeos::MOUNT_ERROR_NONE : chromeos::MOUNT_ERROR_INVALID_PATH,
472      CreateDownloadsVolumeInfo(path),
473      kNotRemounting);
474  return success;
475}
476
477void VolumeManager::AddVolumeInfoForTesting(const base::FilePath& path,
478                                            VolumeType volume_type,
479                                            chromeos::DeviceType device_type) {
480  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
481  DoMountEvent(chromeos::MOUNT_ERROR_NONE,
482               CreateTestingVolumeInfo(path, volume_type, device_type),
483               kNotRemounting);
484}
485
486void VolumeManager::OnFileSystemMounted() {
487  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
488
489  // Raise mount event.
490  // We can pass chromeos::MOUNT_ERROR_NONE even when authentication is failed
491  // or network is unreachable. These two errors will be handled later.
492  VolumeInfo volume_info = CreateDriveVolumeInfo(profile_);
493  DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume_info, kNotRemounting);
494}
495
496void VolumeManager::OnFileSystemBeingUnmounted() {
497  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
498
499  VolumeInfo volume_info = CreateDriveVolumeInfo(profile_);
500  DoUnmountEvent(chromeos::MOUNT_ERROR_NONE, volume_info);
501}
502
503void VolumeManager::OnDiskEvent(
504    chromeos::disks::DiskMountManager::DiskEvent event,
505    const chromeos::disks::DiskMountManager::Disk* disk) {
506  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
507
508  // Disregard hidden devices.
509  if (disk->is_hidden())
510    return;
511
512  switch (event) {
513    case chromeos::disks::DiskMountManager::DISK_ADDED:
514    case chromeos::disks::DiskMountManager::DISK_CHANGED: {
515      if (disk->device_path().empty()) {
516        DVLOG(1) << "Empty system path for " << disk->device_path();
517        return;
518      }
519
520      bool mounting = false;
521      if (disk->mount_path().empty() && disk->has_media() &&
522          !profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
523        // If disk is not mounted yet and it has media and there is no policy
524        // forbidding external storage, give it a try.
525        // Initiate disk mount operation. MountPath auto-detects the filesystem
526        // format if the second argument is empty. The third argument (mount
527        // label) is not used in a disk mount operation.
528        disk_mount_manager_->MountPath(
529            disk->device_path(), std::string(), std::string(),
530            chromeos::MOUNT_TYPE_DEVICE);
531        mounting = true;
532      }
533
534      // Notify to observers.
535      FOR_EACH_OBSERVER(VolumeManagerObserver, observers_,
536                        OnDiskAdded(*disk, mounting));
537      return;
538    }
539
540    case chromeos::disks::DiskMountManager::DISK_REMOVED:
541      // If the disk is already mounted, unmount it.
542      if (!disk->mount_path().empty()) {
543        disk_mount_manager_->UnmountPath(
544            disk->mount_path(),
545            chromeos::UNMOUNT_OPTIONS_LAZY,
546            chromeos::disks::DiskMountManager::UnmountPathCallback());
547      }
548
549      // Notify to observers.
550      FOR_EACH_OBSERVER(VolumeManagerObserver, observers_,
551                        OnDiskRemoved(*disk));
552      return;
553  }
554  NOTREACHED();
555}
556
557void VolumeManager::OnDeviceEvent(
558    chromeos::disks::DiskMountManager::DeviceEvent event,
559    const std::string& device_path) {
560  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
561  DVLOG(1) << "OnDeviceEvent: " << event << ", " << device_path;
562
563  switch (event) {
564    case chromeos::disks::DiskMountManager::DEVICE_ADDED:
565      FOR_EACH_OBSERVER(VolumeManagerObserver, observers_,
566                        OnDeviceAdded(device_path));
567      return;
568    case chromeos::disks::DiskMountManager::DEVICE_REMOVED: {
569      const bool hard_unplugged =
570          mounted_disk_monitor_->DeviceIsHardUnplugged(device_path);
571      FOR_EACH_OBSERVER(VolumeManagerObserver,
572                        observers_,
573                        OnDeviceRemoved(device_path, hard_unplugged));
574      mounted_disk_monitor_->ClearHardUnpluggedFlag(device_path);
575      return;
576    }
577    case chromeos::disks::DiskMountManager::DEVICE_SCANNED:
578      DVLOG(1) << "Ignore SCANNED event: " << device_path;
579      return;
580  }
581  NOTREACHED();
582}
583
584void VolumeManager::OnMountEvent(
585    chromeos::disks::DiskMountManager::MountEvent event,
586    chromeos::MountError error_code,
587    const chromeos::disks::DiskMountManager::MountPointInfo& mount_info) {
588  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
589  DCHECK_NE(chromeos::MOUNT_TYPE_INVALID, mount_info.mount_type);
590
591  if (mount_info.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) {
592    // If the file is not mounted now, tell it to drive file system so that
593    // it can handle file caching correctly.
594    // Note that drive file system knows if the file is managed by drive file
595    // system or not, so here we report all paths.
596    if ((event == chromeos::disks::DiskMountManager::MOUNTING &&
597         error_code != chromeos::MOUNT_ERROR_NONE) ||
598        (event == chromeos::disks::DiskMountManager::UNMOUNTING &&
599         error_code == chromeos::MOUNT_ERROR_NONE)) {
600      drive::FileSystemInterface* file_system =
601          drive::util::GetFileSystemByProfile(profile_);
602      if (file_system) {
603        file_system->MarkCacheFileAsUnmounted(
604            base::FilePath(mount_info.source_path),
605            base::Bind(&drive::util::EmptyFileOperationCallback));
606      }
607    }
608  }
609
610  // Notify a mounting/unmounting event to observers.
611  const chromeos::disks::DiskMountManager::Disk* disk =
612      disk_mount_manager_->FindDiskBySourcePath(mount_info.source_path);
613  VolumeInfo volume_info =
614      CreateVolumeInfoFromMountPointInfo(mount_info, disk);
615  switch (event) {
616    case chromeos::disks::DiskMountManager::MOUNTING: {
617      bool is_remounting =
618          disk && mounted_disk_monitor_->DiskIsRemounting(*disk);
619      DoMountEvent(error_code, volume_info, is_remounting);
620      return;
621    }
622    case chromeos::disks::DiskMountManager::UNMOUNTING:
623      DoUnmountEvent(error_code, volume_info);
624      return;
625  }
626  NOTREACHED();
627}
628
629void VolumeManager::OnFormatEvent(
630    chromeos::disks::DiskMountManager::FormatEvent event,
631    chromeos::FormatError error_code,
632    const std::string& device_path) {
633  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
634  DVLOG(1) << "OnDeviceEvent: " << event << ", " << error_code
635           << ", " << device_path;
636
637  switch (event) {
638    case chromeos::disks::DiskMountManager::FORMAT_STARTED:
639      FOR_EACH_OBSERVER(
640          VolumeManagerObserver, observers_,
641          OnFormatStarted(device_path,
642                          error_code == chromeos::FORMAT_ERROR_NONE));
643      return;
644    case chromeos::disks::DiskMountManager::FORMAT_COMPLETED:
645      if (error_code == chromeos::FORMAT_ERROR_NONE) {
646        // If format is completed successfully, try to mount the device.
647        // MountPath auto-detects filesystem format if second argument is
648        // empty. The third argument (mount label) is not used in a disk mount
649        // operation.
650        disk_mount_manager_->MountPath(
651            device_path, std::string(), std::string(),
652            chromeos::MOUNT_TYPE_DEVICE);
653      }
654
655      FOR_EACH_OBSERVER(
656          VolumeManagerObserver, observers_,
657          OnFormatCompleted(device_path,
658                            error_code == chromeos::FORMAT_ERROR_NONE));
659
660      return;
661  }
662  NOTREACHED();
663}
664
665void VolumeManager::OnProvidedFileSystemMount(
666    const chromeos::file_system_provider::ProvidedFileSystemInfo&
667        file_system_info,
668    base::File::Error error) {
669  VolumeInfo volume_info = CreateProvidedFileSystemVolumeInfo(file_system_info);
670  // TODO(mtomasz): Introduce own type, and avoid using MountError internally,
671  // since it is related to cros disks only.
672  const chromeos::MountError mount_error = error == base::File::FILE_OK
673                                               ? chromeos::MOUNT_ERROR_NONE
674                                               : chromeos::MOUNT_ERROR_UNKNOWN;
675  DoMountEvent(mount_error, volume_info, kNotRemounting);
676}
677
678void VolumeManager::OnProvidedFileSystemUnmount(
679    const chromeos::file_system_provider::ProvidedFileSystemInfo&
680        file_system_info,
681    base::File::Error error) {
682  // TODO(mtomasz): Introduce own type, and avoid using MountError internally,
683  // since it is related to cros disks only.
684  const chromeos::MountError mount_error = error == base::File::FILE_OK
685                                               ? chromeos::MOUNT_ERROR_NONE
686                                               : chromeos::MOUNT_ERROR_UNKNOWN;
687  VolumeInfo volume_info = CreateProvidedFileSystemVolumeInfo(file_system_info);
688  DoUnmountEvent(mount_error, volume_info);
689}
690
691void VolumeManager::OnExternalStorageDisabledChanged() {
692  // If the policy just got disabled we have to unmount every device currently
693  // mounted. The opposite is fine - we can let the user re-plug her device to
694  // make it available.
695  if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
696    // We do not iterate on mount_points directly, because mount_points can
697    // be changed by UnmountPath().
698    // TODO(hidehiko): Is it necessary to unmount mounted archives, too, here?
699    while (!disk_mount_manager_->mount_points().empty()) {
700      std::string mount_path =
701          disk_mount_manager_->mount_points().begin()->second.mount_path;
702      disk_mount_manager_->UnmountPath(
703          mount_path,
704          chromeos::UNMOUNT_OPTIONS_NONE,
705          chromeos::disks::DiskMountManager::UnmountPathCallback());
706    }
707  }
708}
709
710void VolumeManager::OnPrivetVolumesAvailable(
711    const local_discovery::PrivetVolumeLister::VolumeList& volumes) {
712  for (local_discovery::PrivetVolumeLister::VolumeList::const_iterator i =
713           volumes.begin(); i != volumes.end(); i++) {
714    VolumeInfo volume_info = CreatePrivetVolumeInfo(*i);
715    DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume_info, false);
716  }
717}
718
719void VolumeManager::OnRemovableStorageAttached(
720    const storage_monitor::StorageInfo& info) {
721  if (!storage_monitor::StorageInfo::IsMTPDevice(info.device_id()))
722    return;
723  if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled))
724    return;
725
726  const base::FilePath path = base::FilePath::FromUTF8Unsafe(info.location());
727  const std::string fsid = GetMountPointNameForMediaStorage(info);
728  const std::string base_name = base::UTF16ToUTF8(info.model_name());
729
730  // Assign a fresh volume ID based on the volume name.
731  std::string label = base_name;
732  for (int i = 2; mounted_volumes_.count(kMtpVolumeIdPrefix + label); ++i)
733    label = base_name + base::StringPrintf(" (%d)", i);
734
735  bool result =
736      fileapi::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
737          fsid, fileapi::kFileSystemTypeDeviceMediaAsFileStorage,
738          fileapi::FileSystemMountOption(), path);
739  DCHECK(result);
740  content::BrowserThread::PostTask(
741      content::BrowserThread::IO, FROM_HERE, base::Bind(
742          &MTPDeviceMapService::RegisterMTPFileSystem,
743          base::Unretained(MTPDeviceMapService::GetInstance()),
744          info.location(), fsid));
745
746  VolumeInfo volume_info;
747  volume_info.type = VOLUME_TYPE_MTP;
748  volume_info.mount_path = path;
749  volume_info.mount_condition = chromeos::disks::MOUNT_CONDITION_NONE;
750  volume_info.is_parent = true;
751  volume_info.is_read_only = true;
752  volume_info.volume_id = kMtpVolumeIdPrefix + label;
753  volume_info.volume_label = label;
754  volume_info.source_path = path;
755  volume_info.device_type = chromeos::DEVICE_TYPE_MOBILE;
756  DoMountEvent(chromeos::MOUNT_ERROR_NONE, volume_info, false);
757}
758
759void VolumeManager::OnRemovableStorageDetached(
760    const storage_monitor::StorageInfo& info) {
761  if (!storage_monitor::StorageInfo::IsMTPDevice(info.device_id()))
762    return;
763
764  for (std::map<std::string, VolumeInfo>::iterator it =
765           mounted_volumes_.begin(); it != mounted_volumes_.end(); ++it) {
766    if (it->second.source_path.value() == info.location()) {
767      DoUnmountEvent(chromeos::MOUNT_ERROR_NONE, VolumeInfo(it->second));
768
769      const std::string fsid = GetMountPointNameForMediaStorage(info);
770      fileapi::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
771          fsid);
772      content::BrowserThread::PostTask(
773          content::BrowserThread::IO, FROM_HERE, base::Bind(
774              &MTPDeviceMapService::RevokeMTPFileSystem,
775              base::Unretained(MTPDeviceMapService::GetInstance()),
776              fsid));
777      return;
778    }
779  }
780}
781
782void VolumeManager::OnStorageMonitorInitialized() {
783  std::vector<storage_monitor::StorageInfo> storages =
784      storage_monitor::StorageMonitor::GetInstance()->GetAllAvailableStorages();
785  for (size_t i = 0; i < storages.size(); ++i)
786    OnRemovableStorageAttached(storages[i]);
787  storage_monitor::StorageMonitor::GetInstance()->AddObserver(this);
788}
789
790void VolumeManager::DoMountEvent(chromeos::MountError error_code,
791                                 const VolumeInfo& volume_info,
792                                 bool is_remounting) {
793  // Archive files are mounted globally in system. We however don't want to show
794  // archives from profile-specific folders (Drive/Downloads) of other users in
795  // multi-profile session. To this end, we filter out archives not on the
796  // volumes already mounted on this VolumeManager instance.
797  if (volume_info.type == VOLUME_TYPE_MOUNTED_ARCHIVE_FILE) {
798    // Source may be in Drive cache folder under the current profile directory.
799    bool from_current_profile =
800        profile_->GetPath().IsParent(volume_info.source_path);
801    for (std::map<std::string, VolumeInfo>::const_iterator iter =
802             mounted_volumes_.begin();
803         !from_current_profile && iter != mounted_volumes_.end();
804         ++iter) {
805      if (iter->second.mount_path.IsParent(volume_info.source_path))
806        from_current_profile = true;
807    }
808    if (!from_current_profile)
809      return;
810  }
811
812  // Filter out removable disks if forbidden by policy for this profile.
813  if (volume_info.type == VOLUME_TYPE_REMOVABLE_DISK_PARTITION &&
814      profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
815    return;
816  }
817
818  if (error_code == chromeos::MOUNT_ERROR_NONE || volume_info.mount_condition) {
819    mounted_volumes_[volume_info.volume_id] = volume_info;
820
821    if (!is_remounting) {
822      UMA_HISTOGRAM_ENUMERATION("FileBrowser.VolumeType",
823                                volume_info.type,
824                                NUM_VOLUME_TYPE);
825    }
826  }
827
828  FOR_EACH_OBSERVER(VolumeManagerObserver,
829                    observers_,
830                    OnVolumeMounted(error_code, volume_info, is_remounting));
831}
832
833void VolumeManager::DoUnmountEvent(chromeos::MountError error_code,
834                                   const VolumeInfo& volume_info) {
835  if (mounted_volumes_.find(volume_info.volume_id) == mounted_volumes_.end())
836    return;
837  if (error_code == chromeos::MOUNT_ERROR_NONE)
838    mounted_volumes_.erase(volume_info.volume_id);
839
840  FOR_EACH_OBSERVER(VolumeManagerObserver,
841                    observers_,
842                    OnVolumeUnmounted(error_code, volume_info));
843}
844
845}  // namespace file_manager
846