// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/chromeos/file_manager/volume_manager.h" #include "base/basictypes.h" #include "base/bind.h" #include "base/callback.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/memory/singleton.h" #include "base/prefs/pref_service.h" #include "chrome/browser/chromeos/drive/drive_integration_service.h" #include "chrome/browser/chromeos/drive/file_errors.h" #include "chrome/browser/chromeos/drive/file_system_interface.h" #include "chrome/browser/chromeos/drive/file_system_util.h" #include "chrome/browser/chromeos/file_manager/mounted_disk_monitor.h" #include "chrome/browser/chromeos/file_manager/path_util.h" #include "chrome/browser/chromeos/file_manager/volume_manager_factory.h" #include "chrome/browser/chromeos/file_manager/volume_manager_observer.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/pref_names.h" #include "chromeos/dbus/cros_disks_client.h" #include "chromeos/disks/disk_mount_manager.h" #include "content/public/browser/browser_thread.h" #include "webkit/browser/fileapi/external_mount_points.h" namespace file_manager { namespace { // Called on completion of MarkCacheFileAsUnmounted. void OnMarkCacheFileAsUnmounted(drive::FileError error) { // Do nothing. } VolumeType MountTypeToVolumeType( chromeos::MountType type) { switch (type) { case chromeos::MOUNT_TYPE_INVALID: // We don't expect this value, but list here, so that when any value // is added to the enum definition but this is not edited, the compiler // warns it. break; case chromeos::MOUNT_TYPE_DEVICE: return VOLUME_TYPE_REMOVABLE_DISK_PARTITION; case chromeos::MOUNT_TYPE_ARCHIVE: return VOLUME_TYPE_MOUNTED_ARCHIVE_FILE; } NOTREACHED(); return VOLUME_TYPE_DOWNLOADS_DIRECTORY; } // Returns a string representation of the given volume type. std::string VolumeTypeToString(VolumeType type) { switch (type) { case VOLUME_TYPE_GOOGLE_DRIVE: return "drive"; case VOLUME_TYPE_DOWNLOADS_DIRECTORY: return "downloads"; case VOLUME_TYPE_REMOVABLE_DISK_PARTITION: return "removable"; case VOLUME_TYPE_MOUNTED_ARCHIVE_FILE: return "archive"; } NOTREACHED(); return ""; } // Generates a unique volume ID for the given volume info. std::string GenerateVolumeId(const VolumeInfo& volume_info) { // For the same volume type, base names are unique, as mount points are // flat for the same volume type. return (VolumeTypeToString(volume_info.type) + ":" + volume_info.mount_path.BaseName().AsUTF8Unsafe()); } // Returns the VolumeInfo for Drive file system. VolumeInfo CreateDriveVolumeInfo() { const base::FilePath& drive_path = drive::util::GetDriveMountPointPath(); VolumeInfo volume_info; volume_info.type = VOLUME_TYPE_GOOGLE_DRIVE; volume_info.device_type = chromeos::DEVICE_TYPE_UNKNOWN; volume_info.source_path = drive_path; volume_info.mount_path = drive_path; volume_info.mount_condition = chromeos::disks::MOUNT_CONDITION_NONE; volume_info.is_parent = false; volume_info.is_read_only = false; volume_info.volume_id = GenerateVolumeId(volume_info); return volume_info; } VolumeInfo CreateDownloadsVolumeInfo( const base::FilePath& downloads_path) { VolumeInfo volume_info; volume_info.type = VOLUME_TYPE_DOWNLOADS_DIRECTORY; volume_info.device_type = chromeos::DEVICE_TYPE_UNKNOWN; // Keep source_path empty. volume_info.mount_path = downloads_path; volume_info.mount_condition = chromeos::disks::MOUNT_CONDITION_NONE; volume_info.is_parent = false; volume_info.is_read_only = false; volume_info.volume_id = GenerateVolumeId(volume_info); return volume_info; } VolumeInfo CreateVolumeInfoFromMountPointInfo( const chromeos::disks::DiskMountManager::MountPointInfo& mount_point, const chromeos::disks::DiskMountManager::Disk* disk) { VolumeInfo volume_info; volume_info.type = MountTypeToVolumeType(mount_point.mount_type); volume_info.source_path = base::FilePath(mount_point.source_path); volume_info.mount_path = base::FilePath(mount_point.mount_path); volume_info.mount_condition = mount_point.mount_condition; if (disk) { volume_info.device_type = disk->device_type(); volume_info.system_path_prefix = base::FilePath(disk->system_path_prefix()); volume_info.drive_label = disk->drive_label(); volume_info.is_parent = disk->is_parent(); volume_info.is_read_only = disk->is_read_only(); } else { volume_info.device_type = chromeos::DEVICE_TYPE_UNKNOWN; volume_info.is_parent = false; volume_info.is_read_only = false; } volume_info.volume_id = GenerateVolumeId(volume_info); return volume_info; } } // namespace VolumeInfo::VolumeInfo() { } VolumeInfo::~VolumeInfo() { } VolumeManager::VolumeManager( Profile* profile, drive::DriveIntegrationService* drive_integration_service, chromeos::PowerManagerClient* power_manager_client, chromeos::disks::DiskMountManager* disk_mount_manager) : profile_(profile), drive_integration_service_(drive_integration_service), disk_mount_manager_(disk_mount_manager), mounted_disk_monitor_( new MountedDiskMonitor(power_manager_client, disk_mount_manager)) { DCHECK(disk_mount_manager); } VolumeManager::~VolumeManager() { } VolumeManager* VolumeManager::Get(content::BrowserContext* context) { return VolumeManagerFactory::Get(context); } void VolumeManager::Initialize() { // Path to mount user folders have changed several times. We need to migrate // the old preferences on paths to the new format when needed. For the detail, // see the comments in file_manager::util::MigratePathFromOldFormat, // TODO(kinaba): Remove this are several rounds of releases. const char* kPathPreference[] = { prefs::kDownloadDefaultDirectory, prefs::kSelectFileLastDirectory, prefs::kSaveFileDefaultDirectory, }; for (size_t i = 0; i < arraysize(kPathPreference); ++i) { const base::FilePath old_path = profile_->GetPrefs()->GetFilePath(kPathPreference[i]); base::FilePath new_path; if (!old_path.empty() && file_manager::util::MigratePathFromOldFormat(profile_, old_path, &new_path)) { profile_->GetPrefs()->SetFilePath(kPathPreference[i], new_path); } } // Register 'Downloads' folder for the profile to the file system. fileapi::ExternalMountPoints* mount_points = content::BrowserContext::GetMountPoints(profile_); DCHECK(mount_points); const base::FilePath downloads_folder = file_manager::util::GetDownloadsFolderForProfile(profile_); bool success = mount_points->RegisterFileSystem( downloads_folder.BaseName().AsUTF8Unsafe(), fileapi::kFileSystemTypeNativeLocal, downloads_folder); DCHECK(success); // Subscribe to DriveIntegrationService. if (drive_integration_service_) drive_integration_service_->AddObserver(this); // Subscribe to DiskMountManager. disk_mount_manager_->AddObserver(this); disk_mount_manager_->RequestMountInfoRefresh(); // Subscribe to Profile Preference change. pref_change_registrar_.Init(profile_->GetPrefs()); pref_change_registrar_.Add( prefs::kExternalStorageDisabled, base::Bind(&VolumeManager::OnExternalStorageDisabledChanged, base::Unretained(this))); } void VolumeManager::Shutdown() { pref_change_registrar_.RemoveAll(); disk_mount_manager_->RemoveObserver(this); if (drive_integration_service_) drive_integration_service_->RemoveObserver(this); } void VolumeManager::AddObserver(VolumeManagerObserver* observer) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); DCHECK(observer); observers_.AddObserver(observer); } void VolumeManager::RemoveObserver(VolumeManagerObserver* observer) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); DCHECK(observer); observers_.RemoveObserver(observer); } std::vector VolumeManager::GetVolumeInfoList() const { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); std::vector result; // Adds "Drive" volume. if (drive_integration_service_ && drive_integration_service_->IsMounted()) result.push_back(CreateDriveVolumeInfo()); // Adds "Downloads". // Usually, the path of the directory is where we registered in Initialize(), // but in tests, the mount point may be overridden. To take it into account, // here we explicitly retrieves the path from the file API mount points. fileapi::ExternalMountPoints* fileapi_mount_points = content::BrowserContext::GetMountPoints(profile_); DCHECK(fileapi_mount_points); base::FilePath downloads; if (fileapi_mount_points->GetRegisteredPath("Downloads", &downloads)) result.push_back(CreateDownloadsVolumeInfo(downloads)); // Adds disks (both removable disks and zip archives). const chromeos::disks::DiskMountManager::MountPointMap& mount_points = disk_mount_manager_->mount_points(); for (chromeos::disks::DiskMountManager::MountPointMap::const_iterator it = mount_points.begin(); it != mount_points.end(); ++it) { result.push_back(CreateVolumeInfoFromMountPointInfo( it->second, disk_mount_manager_->FindDiskBySourcePath(it->second.source_path))); } return result; } bool VolumeManager::FindVolumeInfoById(const std::string& volume_id, VolumeInfo* result) const { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); DCHECK(result); std::vector info_list = GetVolumeInfoList(); for (size_t i = 0; i < info_list.size(); ++i) { if (info_list[i].volume_id == volume_id) { *result = info_list[i]; return true; } } return false; } void VolumeManager::OnFileSystemMounted() { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); // Raise mount event. // We can pass chromeos::MOUNT_ERROR_NONE even when authentication is failed // or network is unreachable. These two errors will be handled later. VolumeInfo volume_info = CreateDriveVolumeInfo(); FOR_EACH_OBSERVER(VolumeManagerObserver, observers_, OnVolumeMounted(chromeos::MOUNT_ERROR_NONE, volume_info, false)); // Not remounting. } void VolumeManager::OnFileSystemBeingUnmounted() { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); VolumeInfo volume_info = CreateDriveVolumeInfo(); FOR_EACH_OBSERVER( VolumeManagerObserver, observers_, OnVolumeUnmounted(chromeos::MOUNT_ERROR_NONE, volume_info)); } void VolumeManager::OnDiskEvent( chromeos::disks::DiskMountManager::DiskEvent event, const chromeos::disks::DiskMountManager::Disk* disk) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); // Disregard hidden devices. if (disk->is_hidden()) return; switch (event) { case chromeos::disks::DiskMountManager::DISK_ADDED: { if (disk->device_path().empty()) { DVLOG(1) << "Empty system path for " << disk->device_path(); return; } bool mounting = false; if (disk->mount_path().empty() && disk->has_media() && !profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { // If disk is not mounted yet and it has media and there is no policy // forbidding external storage, give it a try. // Initiate disk mount operation. MountPath auto-detects the filesystem // format if the second argument is empty. The third argument (mount // label) is not used in a disk mount operation. disk_mount_manager_->MountPath( disk->device_path(), std::string(), std::string(), chromeos::MOUNT_TYPE_DEVICE); mounting = true; } // Notify to observers. FOR_EACH_OBSERVER(VolumeManagerObserver, observers_, OnDiskAdded(*disk, mounting)); return; } case chromeos::disks::DiskMountManager::DISK_REMOVED: // If the disk is already mounted, unmount it. if (!disk->mount_path().empty()) { disk_mount_manager_->UnmountPath( disk->mount_path(), chromeos::UNMOUNT_OPTIONS_LAZY, chromeos::disks::DiskMountManager::UnmountPathCallback()); } // Notify to observers. FOR_EACH_OBSERVER(VolumeManagerObserver, observers_, OnDiskRemoved(*disk)); return; case chromeos::disks::DiskMountManager::DISK_CHANGED: DVLOG(1) << "Ignore CHANGED event."; return; } NOTREACHED(); } void VolumeManager::OnDeviceEvent( chromeos::disks::DiskMountManager::DeviceEvent event, const std::string& device_path) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); DVLOG(1) << "OnDeviceEvent: " << event << ", " << device_path; switch (event) { case chromeos::disks::DiskMountManager::DEVICE_ADDED: FOR_EACH_OBSERVER(VolumeManagerObserver, observers_, OnDeviceAdded(device_path)); return; case chromeos::disks::DiskMountManager::DEVICE_REMOVED: FOR_EACH_OBSERVER(VolumeManagerObserver, observers_, OnDeviceRemoved(device_path)); return; case chromeos::disks::DiskMountManager::DEVICE_SCANNED: DVLOG(1) << "Ignore SCANNED event: " << device_path; return; } NOTREACHED(); } void VolumeManager::OnMountEvent( chromeos::disks::DiskMountManager::MountEvent event, chromeos::MountError error_code, const chromeos::disks::DiskMountManager::MountPointInfo& mount_info) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); DCHECK_NE(chromeos::MOUNT_TYPE_INVALID, mount_info.mount_type); if (mount_info.mount_type == chromeos::MOUNT_TYPE_ARCHIVE) { // If the file is not mounted now, tell it to drive file system so that // it can handle file caching correctly. // Note that drive file system knows if the file is managed by drive file // system or not, so here we report all paths. if ((event == chromeos::disks::DiskMountManager::MOUNTING && error_code != chromeos::MOUNT_ERROR_NONE) || (event == chromeos::disks::DiskMountManager::UNMOUNTING && error_code == chromeos::MOUNT_ERROR_NONE)) { drive::FileSystemInterface* file_system = drive::util::GetFileSystemByProfile(profile_); if (file_system) { file_system->MarkCacheFileAsUnmounted( base::FilePath(mount_info.source_path), base::Bind(&OnMarkCacheFileAsUnmounted)); } } } // Notify a mounting/unmounting event to observers. const chromeos::disks::DiskMountManager::Disk* disk = disk_mount_manager_->FindDiskBySourcePath(mount_info.source_path); VolumeInfo volume_info = CreateVolumeInfoFromMountPointInfo(mount_info, disk); switch (event) { case chromeos::disks::DiskMountManager::MOUNTING: { bool is_remounting = disk && mounted_disk_monitor_->DiskIsRemounting(*disk); FOR_EACH_OBSERVER( VolumeManagerObserver, observers_, OnVolumeMounted(error_code, volume_info, is_remounting)); return; } case chromeos::disks::DiskMountManager::UNMOUNTING: FOR_EACH_OBSERVER(VolumeManagerObserver, observers_, OnVolumeUnmounted(error_code, volume_info)); return; } NOTREACHED(); } void VolumeManager::OnFormatEvent( chromeos::disks::DiskMountManager::FormatEvent event, chromeos::FormatError error_code, const std::string& device_path) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); DVLOG(1) << "OnDeviceEvent: " << event << ", " << error_code << ", " << device_path; switch (event) { case chromeos::disks::DiskMountManager::FORMAT_STARTED: FOR_EACH_OBSERVER( VolumeManagerObserver, observers_, OnFormatStarted(device_path, error_code == chromeos::FORMAT_ERROR_NONE)); return; case chromeos::disks::DiskMountManager::FORMAT_COMPLETED: if (error_code == chromeos::FORMAT_ERROR_NONE) { // If format is completed successfully, try to mount the device. // MountPath auto-detects filesystem format if second argument is // empty. The third argument (mount label) is not used in a disk mount // operation. disk_mount_manager_->MountPath( device_path, std::string(), std::string(), chromeos::MOUNT_TYPE_DEVICE); } FOR_EACH_OBSERVER( VolumeManagerObserver, observers_, OnFormatCompleted(device_path, error_code == chromeos::FORMAT_ERROR_NONE)); return; } NOTREACHED(); } void VolumeManager::OnExternalStorageDisabledChanged() { // If the policy just got disabled we have to unmount every device currently // mounted. The opposite is fine - we can let the user re-plug her device to // make it available. if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) { // We do not iterate on mount_points directly, because mount_points can // be changed by UnmountPath(). // TODO(hidehiko): Is it necessary to unmount mounted archives, too, here? while (!disk_mount_manager_->mount_points().empty()) { std::string mount_path = disk_mount_manager_->mount_points().begin()->second.mount_path; disk_mount_manager_->UnmountPath( mount_path, chromeos::UNMOUNT_OPTIONS_NONE, chromeos::disks::DiskMountManager::UnmountPathCallback()); } } } } // namespace file_manager