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.
5 #include "chrome/browser/chromeos/file_manager/volume_manager.h"
7 #include "base/basictypes.h"
9 #include "base/callback.h"
10 #include "base/files/file_path.h"
11 #include "base/logging.h"
12 #include "base/memory/singleton.h"
13 #include "base/prefs/pref_service.h"
14 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
15 #include "chrome/browser/chromeos/drive/file_errors.h"
16 #include "chrome/browser/chromeos/drive/file_system_interface.h"
17 #include "chrome/browser/chromeos/drive/file_system_util.h"
18 #include "chrome/browser/chromeos/file_manager/mounted_disk_monitor.h"
19 #include "chrome/browser/chromeos/file_manager/path_util.h"
20 #include "chrome/browser/chromeos/file_manager/volume_manager_factory.h"
21 #include "chrome/browser/chromeos/file_manager/volume_manager_observer.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/common/pref_names.h"
24 #include "chromeos/dbus/cros_disks_client.h"
25 #include "chromeos/disks/disk_mount_manager.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "webkit/browser/fileapi/external_mount_points.h"
29 namespace file_manager
{
32 // Called on completion of MarkCacheFileAsUnmounted.
33 void OnMarkCacheFileAsUnmounted(drive::FileError error
) {
37 VolumeType
MountTypeToVolumeType(
38 chromeos::MountType type
) {
40 case chromeos::MOUNT_TYPE_INVALID
:
41 // We don't expect this value, but list here, so that when any value
42 // is added to the enum definition but this is not edited, the compiler
45 case chromeos::MOUNT_TYPE_DEVICE
:
46 return VOLUME_TYPE_REMOVABLE_DISK_PARTITION
;
47 case chromeos::MOUNT_TYPE_ARCHIVE
:
48 return VOLUME_TYPE_MOUNTED_ARCHIVE_FILE
;
52 return VOLUME_TYPE_DOWNLOADS_DIRECTORY
;
55 // Returns a string representation of the given volume type.
56 std::string
VolumeTypeToString(VolumeType type
) {
58 case VOLUME_TYPE_GOOGLE_DRIVE
:
60 case VOLUME_TYPE_DOWNLOADS_DIRECTORY
:
62 case VOLUME_TYPE_REMOVABLE_DISK_PARTITION
:
64 case VOLUME_TYPE_MOUNTED_ARCHIVE_FILE
:
71 // Generates a unique volume ID for the given volume info.
72 std::string
GenerateVolumeId(const VolumeInfo
& volume_info
) {
73 // For the same volume type, base names are unique, as mount points are
74 // flat for the same volume type.
75 return (VolumeTypeToString(volume_info
.type
) + ":" +
76 volume_info
.mount_path
.BaseName().AsUTF8Unsafe());
79 // Returns the VolumeInfo for Drive file system.
80 VolumeInfo
CreateDriveVolumeInfo() {
81 const base::FilePath
& drive_path
= drive::util::GetDriveMountPointPath();
83 VolumeInfo volume_info
;
84 volume_info
.type
= VOLUME_TYPE_GOOGLE_DRIVE
;
85 volume_info
.device_type
= chromeos::DEVICE_TYPE_UNKNOWN
;
86 volume_info
.source_path
= drive_path
;
87 volume_info
.mount_path
= drive_path
;
88 volume_info
.mount_condition
= chromeos::disks::MOUNT_CONDITION_NONE
;
89 volume_info
.is_parent
= false;
90 volume_info
.is_read_only
= false;
91 volume_info
.volume_id
= GenerateVolumeId(volume_info
);
95 VolumeInfo
CreateDownloadsVolumeInfo(
96 const base::FilePath
& downloads_path
) {
97 VolumeInfo volume_info
;
98 volume_info
.type
= VOLUME_TYPE_DOWNLOADS_DIRECTORY
;
99 volume_info
.device_type
= chromeos::DEVICE_TYPE_UNKNOWN
;
100 // Keep source_path empty.
101 volume_info
.mount_path
= downloads_path
;
102 volume_info
.mount_condition
= chromeos::disks::MOUNT_CONDITION_NONE
;
103 volume_info
.is_parent
= false;
104 volume_info
.is_read_only
= false;
105 volume_info
.volume_id
= GenerateVolumeId(volume_info
);
109 VolumeInfo
CreateVolumeInfoFromMountPointInfo(
110 const chromeos::disks::DiskMountManager::MountPointInfo
& mount_point
,
111 const chromeos::disks::DiskMountManager::Disk
* disk
) {
112 VolumeInfo volume_info
;
113 volume_info
.type
= MountTypeToVolumeType(mount_point
.mount_type
);
114 volume_info
.source_path
= base::FilePath(mount_point
.source_path
);
115 volume_info
.mount_path
= base::FilePath(mount_point
.mount_path
);
116 volume_info
.mount_condition
= mount_point
.mount_condition
;
118 volume_info
.device_type
= disk
->device_type();
119 volume_info
.system_path_prefix
=
120 base::FilePath(disk
->system_path_prefix());
121 volume_info
.drive_label
= disk
->drive_label();
122 volume_info
.is_parent
= disk
->is_parent();
123 volume_info
.is_read_only
= disk
->is_read_only();
125 volume_info
.device_type
= chromeos::DEVICE_TYPE_UNKNOWN
;
126 volume_info
.is_parent
= false;
127 volume_info
.is_read_only
= false;
129 volume_info
.volume_id
= GenerateVolumeId(volume_info
);
136 VolumeInfo::VolumeInfo() {
139 VolumeInfo::~VolumeInfo() {
142 VolumeManager::VolumeManager(
144 drive::DriveIntegrationService
* drive_integration_service
,
145 chromeos::PowerManagerClient
* power_manager_client
,
146 chromeos::disks::DiskMountManager
* disk_mount_manager
)
148 drive_integration_service_(drive_integration_service
),
149 disk_mount_manager_(disk_mount_manager
),
150 mounted_disk_monitor_(
151 new MountedDiskMonitor(power_manager_client
, disk_mount_manager
)) {
152 DCHECK(disk_mount_manager
);
155 VolumeManager::~VolumeManager() {
158 VolumeManager
* VolumeManager::Get(content::BrowserContext
* context
) {
159 return VolumeManagerFactory::Get(context
);
162 void VolumeManager::Initialize() {
163 // Path to mount user folders have changed several times. We need to migrate
164 // the old preferences on paths to the new format when needed. For the detail,
165 // see the comments in file_manager::util::MigratePathFromOldFormat,
166 // TODO(kinaba): Remove this are several rounds of releases.
167 const char* kPathPreference
[] = {
168 prefs::kDownloadDefaultDirectory
,
169 prefs::kSelectFileLastDirectory
,
170 prefs::kSaveFileDefaultDirectory
,
172 for (size_t i
= 0; i
< arraysize(kPathPreference
); ++i
) {
173 const base::FilePath old_path
=
174 profile_
->GetPrefs()->GetFilePath(kPathPreference
[i
]);
175 base::FilePath new_path
;
176 if (!old_path
.empty() &&
177 file_manager::util::MigratePathFromOldFormat(profile_
,
178 old_path
, &new_path
)) {
179 profile_
->GetPrefs()->SetFilePath(kPathPreference
[i
], new_path
);
183 // Register 'Downloads' folder for the profile to the file system.
184 fileapi::ExternalMountPoints
* mount_points
=
185 content::BrowserContext::GetMountPoints(profile_
);
186 DCHECK(mount_points
);
188 const base::FilePath downloads_folder
=
189 file_manager::util::GetDownloadsFolderForProfile(profile_
);
190 bool success
= mount_points
->RegisterFileSystem(
191 downloads_folder
.BaseName().AsUTF8Unsafe(),
192 fileapi::kFileSystemTypeNativeLocal
,
196 // Subscribe to DriveIntegrationService.
197 if (drive_integration_service_
)
198 drive_integration_service_
->AddObserver(this);
200 // Subscribe to DiskMountManager.
201 disk_mount_manager_
->AddObserver(this);
202 disk_mount_manager_
->RequestMountInfoRefresh();
204 // Subscribe to Profile Preference change.
205 pref_change_registrar_
.Init(profile_
->GetPrefs());
206 pref_change_registrar_
.Add(
207 prefs::kExternalStorageDisabled
,
208 base::Bind(&VolumeManager::OnExternalStorageDisabledChanged
,
209 base::Unretained(this)));
212 void VolumeManager::Shutdown() {
213 pref_change_registrar_
.RemoveAll();
214 disk_mount_manager_
->RemoveObserver(this);
216 if (drive_integration_service_
)
217 drive_integration_service_
->RemoveObserver(this);
220 void VolumeManager::AddObserver(VolumeManagerObserver
* observer
) {
221 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
223 observers_
.AddObserver(observer
);
226 void VolumeManager::RemoveObserver(VolumeManagerObserver
* observer
) {
227 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
229 observers_
.RemoveObserver(observer
);
232 std::vector
<VolumeInfo
> VolumeManager::GetVolumeInfoList() const {
233 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
235 std::vector
<VolumeInfo
> result
;
237 // Adds "Drive" volume.
238 if (drive_integration_service_
&& drive_integration_service_
->IsMounted())
239 result
.push_back(CreateDriveVolumeInfo());
242 // Usually, the path of the directory is where we registered in Initialize(),
243 // but in tests, the mount point may be overridden. To take it into account,
244 // here we explicitly retrieves the path from the file API mount points.
245 fileapi::ExternalMountPoints
* fileapi_mount_points
=
246 content::BrowserContext::GetMountPoints(profile_
);
247 DCHECK(fileapi_mount_points
);
248 base::FilePath downloads
;
249 if (fileapi_mount_points
->GetRegisteredPath("Downloads", &downloads
))
250 result
.push_back(CreateDownloadsVolumeInfo(downloads
));
252 // Adds disks (both removable disks and zip archives).
253 const chromeos::disks::DiskMountManager::MountPointMap
& mount_points
=
254 disk_mount_manager_
->mount_points();
255 for (chromeos::disks::DiskMountManager::MountPointMap::const_iterator it
=
256 mount_points
.begin();
257 it
!= mount_points
.end(); ++it
) {
258 result
.push_back(CreateVolumeInfoFromMountPointInfo(
260 disk_mount_manager_
->FindDiskBySourcePath(it
->second
.source_path
)));
266 bool VolumeManager::FindVolumeInfoById(const std::string
& volume_id
,
267 VolumeInfo
* result
) const {
268 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
271 std::vector
<VolumeInfo
> info_list
= GetVolumeInfoList();
272 for (size_t i
= 0; i
< info_list
.size(); ++i
) {
273 if (info_list
[i
].volume_id
== volume_id
) {
274 *result
= info_list
[i
];
282 void VolumeManager::OnFileSystemMounted() {
283 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
285 // Raise mount event.
286 // We can pass chromeos::MOUNT_ERROR_NONE even when authentication is failed
287 // or network is unreachable. These two errors will be handled later.
288 VolumeInfo volume_info
= CreateDriveVolumeInfo();
289 FOR_EACH_OBSERVER(VolumeManagerObserver
, observers_
,
290 OnVolumeMounted(chromeos::MOUNT_ERROR_NONE
,
292 false)); // Not remounting.
295 void VolumeManager::OnFileSystemBeingUnmounted() {
296 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
298 VolumeInfo volume_info
= CreateDriveVolumeInfo();
300 VolumeManagerObserver
, observers_
,
301 OnVolumeUnmounted(chromeos::MOUNT_ERROR_NONE
, volume_info
));
304 void VolumeManager::OnDiskEvent(
305 chromeos::disks::DiskMountManager::DiskEvent event
,
306 const chromeos::disks::DiskMountManager::Disk
* disk
) {
307 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
309 // Disregard hidden devices.
310 if (disk
->is_hidden())
314 case chromeos::disks::DiskMountManager::DISK_ADDED
: {
315 if (disk
->device_path().empty()) {
316 DVLOG(1) << "Empty system path for " << disk
->device_path();
320 bool mounting
= false;
321 if (disk
->mount_path().empty() && disk
->has_media() &&
322 !profile_
->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled
)) {
323 // If disk is not mounted yet and it has media and there is no policy
324 // forbidding external storage, give it a try.
325 // Initiate disk mount operation. MountPath auto-detects the filesystem
326 // format if the second argument is empty. The third argument (mount
327 // label) is not used in a disk mount operation.
328 disk_mount_manager_
->MountPath(
329 disk
->device_path(), std::string(), std::string(),
330 chromeos::MOUNT_TYPE_DEVICE
);
334 // Notify to observers.
335 FOR_EACH_OBSERVER(VolumeManagerObserver
, observers_
,
336 OnDiskAdded(*disk
, mounting
));
340 case chromeos::disks::DiskMountManager::DISK_REMOVED
:
341 // If the disk is already mounted, unmount it.
342 if (!disk
->mount_path().empty()) {
343 disk_mount_manager_
->UnmountPath(
345 chromeos::UNMOUNT_OPTIONS_LAZY
,
346 chromeos::disks::DiskMountManager::UnmountPathCallback());
349 // Notify to observers.
350 FOR_EACH_OBSERVER(VolumeManagerObserver
, observers_
,
351 OnDiskRemoved(*disk
));
354 case chromeos::disks::DiskMountManager::DISK_CHANGED
:
355 DVLOG(1) << "Ignore CHANGED event.";
361 void VolumeManager::OnDeviceEvent(
362 chromeos::disks::DiskMountManager::DeviceEvent event
,
363 const std::string
& device_path
) {
364 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
365 DVLOG(1) << "OnDeviceEvent: " << event
<< ", " << device_path
;
368 case chromeos::disks::DiskMountManager::DEVICE_ADDED
:
369 FOR_EACH_OBSERVER(VolumeManagerObserver
, observers_
,
370 OnDeviceAdded(device_path
));
372 case chromeos::disks::DiskMountManager::DEVICE_REMOVED
:
373 FOR_EACH_OBSERVER(VolumeManagerObserver
, observers_
,
374 OnDeviceRemoved(device_path
));
376 case chromeos::disks::DiskMountManager::DEVICE_SCANNED
:
377 DVLOG(1) << "Ignore SCANNED event: " << device_path
;
383 void VolumeManager::OnMountEvent(
384 chromeos::disks::DiskMountManager::MountEvent event
,
385 chromeos::MountError error_code
,
386 const chromeos::disks::DiskMountManager::MountPointInfo
& mount_info
) {
387 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
388 DCHECK_NE(chromeos::MOUNT_TYPE_INVALID
, mount_info
.mount_type
);
390 if (mount_info
.mount_type
== chromeos::MOUNT_TYPE_ARCHIVE
) {
391 // If the file is not mounted now, tell it to drive file system so that
392 // it can handle file caching correctly.
393 // Note that drive file system knows if the file is managed by drive file
394 // system or not, so here we report all paths.
395 if ((event
== chromeos::disks::DiskMountManager::MOUNTING
&&
396 error_code
!= chromeos::MOUNT_ERROR_NONE
) ||
397 (event
== chromeos::disks::DiskMountManager::UNMOUNTING
&&
398 error_code
== chromeos::MOUNT_ERROR_NONE
)) {
399 drive::FileSystemInterface
* file_system
=
400 drive::util::GetFileSystemByProfile(profile_
);
402 file_system
->MarkCacheFileAsUnmounted(
403 base::FilePath(mount_info
.source_path
),
404 base::Bind(&OnMarkCacheFileAsUnmounted
));
409 // Notify a mounting/unmounting event to observers.
410 const chromeos::disks::DiskMountManager::Disk
* disk
=
411 disk_mount_manager_
->FindDiskBySourcePath(mount_info
.source_path
);
412 VolumeInfo volume_info
=
413 CreateVolumeInfoFromMountPointInfo(mount_info
, disk
);
415 case chromeos::disks::DiskMountManager::MOUNTING
: {
417 disk
&& mounted_disk_monitor_
->DiskIsRemounting(*disk
);
419 VolumeManagerObserver
, observers_
,
420 OnVolumeMounted(error_code
, volume_info
, is_remounting
));
423 case chromeos::disks::DiskMountManager::UNMOUNTING
:
424 FOR_EACH_OBSERVER(VolumeManagerObserver
, observers_
,
425 OnVolumeUnmounted(error_code
, volume_info
));
431 void VolumeManager::OnFormatEvent(
432 chromeos::disks::DiskMountManager::FormatEvent event
,
433 chromeos::FormatError error_code
,
434 const std::string
& device_path
) {
435 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
436 DVLOG(1) << "OnDeviceEvent: " << event
<< ", " << error_code
437 << ", " << device_path
;
440 case chromeos::disks::DiskMountManager::FORMAT_STARTED
:
442 VolumeManagerObserver
, observers_
,
443 OnFormatStarted(device_path
,
444 error_code
== chromeos::FORMAT_ERROR_NONE
));
446 case chromeos::disks::DiskMountManager::FORMAT_COMPLETED
:
447 if (error_code
== chromeos::FORMAT_ERROR_NONE
) {
448 // If format is completed successfully, try to mount the device.
449 // MountPath auto-detects filesystem format if second argument is
450 // empty. The third argument (mount label) is not used in a disk mount
452 disk_mount_manager_
->MountPath(
453 device_path
, std::string(), std::string(),
454 chromeos::MOUNT_TYPE_DEVICE
);
458 VolumeManagerObserver
, observers_
,
459 OnFormatCompleted(device_path
,
460 error_code
== chromeos::FORMAT_ERROR_NONE
));
467 void VolumeManager::OnExternalStorageDisabledChanged() {
468 // If the policy just got disabled we have to unmount every device currently
469 // mounted. The opposite is fine - we can let the user re-plug her device to
470 // make it available.
471 if (profile_
->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled
)) {
472 // We do not iterate on mount_points directly, because mount_points can
473 // be changed by UnmountPath().
474 // TODO(hidehiko): Is it necessary to unmount mounted archives, too, here?
475 while (!disk_mount_manager_
->mount_points().empty()) {
476 std::string mount_path
=
477 disk_mount_manager_
->mount_points().begin()->second
.mount_path
;
478 LOG(INFO
) << "Unmounting " << mount_path
<< " because of preference.";
479 disk_mount_manager_
->UnmountPath(
481 chromeos::UNMOUNT_OPTIONS_NONE
,
482 chromeos::disks::DiskMountManager::UnmountPathCallback());
487 } // namespace file_manager