blob: a634566d8d885109b8e1803f88aed4e28b3388de [file] [log] [blame]
// 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 <stddef.h>
#include <stdint.h>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "chrome/browser/chromeos/arc/arc_util.h"
#include "chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root_map.h"
#include "chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.h"
#include "chrome/browser/chromeos/arc/fileapi/arc_file_system_operation_runner.h"
#include "chrome/browser/chromeos/arc/fileapi/arc_media_view_util.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/chromeos/drive/drive_integration_service.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/chromeos/file_manager/snapshot_manager.h"
#include "chrome/browser/chromeos/file_manager/volume_manager_factory.h"
#include "chrome/browser/chromeos/file_manager/volume_manager_observer.h"
#include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chromeos/constants/chromeos_switches.h"
#include "chromeos/disks/disk.h"
#include "chromeos/disks/disk_mount_manager.h"
#include "components/drive/chromeos/file_system_interface.h"
#include "components/prefs/pref_service.h"
#include "components/storage_monitor/storage_monitor.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "services/device/public/mojom/mtp_manager.mojom.h"
#include "services/device/public/mojom/mtp_storage_info.mojom.h"
#include "storage/browser/fileapi/external_mount_points.h"
namespace file_manager {
namespace {
const uint32_t kAccessCapabilityReadWrite = 0;
const uint32_t kFilesystemTypeGenericHierarchical = 2;
const char kFileManagerMTPMountNamePrefix[] = "fileman-mtp-";
const char kMtpVolumeIdPrefix[] = "mtp:";
const char kRootPath[] = "/";
// Registers |path| as the "Downloads" folder to the FileSystem API backend.
// If another folder is already mounted. It revokes and overrides the old one.
bool RegisterDownloadsMountPoint(Profile* profile, const base::FilePath& path) {
// Although we show only profile's own "Downloads" folder in the Files app,
// in the backend we need to mount all profile's download directory globally.
// Otherwise, the Files app cannot support cross-profile file copies, etc.
// For this reason, we need to register to the global GetSystemInstance().
const std::string mount_point_name =
file_manager::util::GetDownloadsMountPointName(profile);
storage::ExternalMountPoints* const mount_points =
storage::ExternalMountPoints::GetSystemInstance();
// In some tests we want to override existing Downloads mount point, so we
// first revoke the existing mount point (if any).
mount_points->RevokeFileSystem(mount_point_name);
return mount_points->RegisterFileSystem(mount_point_name,
storage::kFileSystemTypeNativeLocal,
storage::FileSystemMountOption(),
path);
}
// Returns true if the "Play files" root should be shown based on the current
// flag settings (chrome://flags/#android-files-in-files-app).
bool IsShowAndroidFilesEnabled() {
return !base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kHideAndroidFilesInFilesApp);
}
// Registers a mount point for Android files to ExternalMountPoints.
bool RegisterAndroidFilesMountPoint() {
storage::ExternalMountPoints* const mount_points =
storage::ExternalMountPoints::GetSystemInstance();
return mount_points->RegisterFileSystem(
file_manager::util::GetAndroidFilesMountPointName(),
storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(),
base::FilePath(util::kAndroidFilesPath));
}
// Finds the path register as the "Downloads" folder to FileSystem API backend.
// Returns false if it is not registered.
bool FindDownloadsMountPointPath(Profile* profile, base::FilePath* path) {
const std::string mount_point_name =
util::GetDownloadsMountPointName(profile);
storage::ExternalMountPoints* const mount_points =
storage::ExternalMountPoints::GetSystemInstance();
return mount_points->GetRegisteredPath(mount_point_name, path);
}
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;
case chromeos::MOUNT_TYPE_NETWORK_STORAGE:
// Network storage mounts are handled by their mounters so
// MOUNT_TYPE_NETWORK_STORAGE should never need to be handled here.
break;
}
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";
case VOLUME_TYPE_PROVIDED:
return "provided";
case VOLUME_TYPE_MTP:
return "mtp";
case VOLUME_TYPE_MEDIA_VIEW:
return "media_view";
case VOLUME_TYPE_ANDROID_FILES:
return "android_files";
case VOLUME_TYPE_DOCUMENTS_PROVIDER:
return "documents_provider";
case VOLUME_TYPE_TESTING:
return "testing";
case VOLUME_TYPE_CROSTINI:
return "crostini";
case NUM_VOLUME_TYPE:
break;
}
NOTREACHED();
return "";
}
// Generates a unique volume ID for the given volume info.
std::string GenerateVolumeId(const Volume& volume) {
// For the same volume type, base names are unique, as mount points are
// flat for the same volume type.
return (VolumeTypeToString(volume.type()) + ":" +
volume.mount_path().BaseName().AsUTF8Unsafe());
}
std::string GetMountPointNameForMediaStorage(
const storage_monitor::StorageInfo& info) {
std::string name(kFileManagerMTPMountNamePrefix);
name += info.device_id();
return name;
}
chromeos::MountAccessMode GetExternalStorageAccessMode(const Profile* profile) {
return profile->GetPrefs()->GetBoolean(prefs::kExternalStorageReadOnly)
? chromeos::MOUNT_ACCESS_MODE_READ_ONLY
: chromeos::MOUNT_ACCESS_MODE_READ_WRITE;
}
void RecordDownloadsDiskUsageStats(base::FilePath downloads_path) {
constexpr int64_t kOneMiB = 1024 * 1024;
// For now assume a maximum bucket size of 512GB, which exceeds all current
// chromeOS hard disk sizes.
constexpr int64_t k512GiBInMiB = 512 * 1024;
int64_t download_directory_size_in_bytes =
base::ComputeDirectorySize(downloads_path);
base::UmaHistogramCustomCounts(
"FileBrowser.Downloads.DirectorySizeMiB",
static_cast<int>(download_directory_size_in_bytes / kOneMiB), 1,
k512GiBInMiB, 100);
int64_t total_disk_space_in_bytes =
base::SysInfo::AmountOfTotalDiskSpace(downloads_path);
// total_disk_space_in_bytes can be -1 on error.
if (total_disk_space_in_bytes > 0) {
int percentage_space_used = std::lround(
(download_directory_size_in_bytes * 100.0) / total_disk_space_in_bytes);
base::UmaHistogramPercentage(
"FileBrowser.Downloads.DirectoryPercentageOfDiskUsage",
percentage_space_used);
}
}
} // namespace
Volume::Volume()
: source_(SOURCE_FILE),
type_(VOLUME_TYPE_GOOGLE_DRIVE),
device_type_(chromeos::DEVICE_TYPE_UNKNOWN),
mount_condition_(chromeos::disks::MOUNT_CONDITION_NONE),
mount_context_(MOUNT_CONTEXT_UNKNOWN),
is_parent_(false),
is_read_only_(false),
is_read_only_removable_device_(false),
has_media_(false),
configurable_(false),
watchable_(false) {
}
Volume::~Volume() = default;
// static
std::unique_ptr<Volume> Volume::CreateForDrive(
const base::FilePath& drive_path) {
std::unique_ptr<Volume> volume(new Volume());
volume->type_ = VOLUME_TYPE_GOOGLE_DRIVE;
volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN;
volume->source_path_ = drive_path;
volume->source_ = SOURCE_NETWORK;
volume->mount_path_ = drive_path;
volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE;
volume->volume_id_ = GenerateVolumeId(*volume);
volume->watchable_ = true;
return volume;
}
// static
std::unique_ptr<Volume> Volume::CreateForDownloads(
const base::FilePath& downloads_path) {
std::unique_ptr<Volume> volume(new Volume());
volume->type_ = VOLUME_TYPE_DOWNLOADS_DIRECTORY;
volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN;
// Keep source_path empty.
volume->source_ = SOURCE_SYSTEM;
volume->mount_path_ = downloads_path;
volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE;
volume->volume_id_ = GenerateVolumeId(*volume);
volume->watchable_ = true;
return volume;
}
// static
std::unique_ptr<Volume> Volume::CreateForRemovable(
const chromeos::disks::DiskMountManager::MountPointInfo& mount_point,
const chromeos::disks::Disk* disk) {
std::unique_ptr<Volume> volume(new Volume());
volume->type_ = MountTypeToVolumeType(mount_point.mount_type);
volume->source_path_ = base::FilePath(mount_point.source_path);
volume->source_ = mount_point.mount_type == chromeos::MOUNT_TYPE_ARCHIVE
? SOURCE_FILE
: SOURCE_DEVICE;
volume->mount_path_ = base::FilePath(mount_point.mount_path);
volume->mount_condition_ = mount_point.mount_condition;
if (disk) {
volume->file_system_type_ = disk->file_system_type();
volume->volume_label_ = disk->device_label();
volume->device_type_ = disk->device_type();
volume->system_path_prefix_ = base::FilePath(disk->system_path_prefix());
volume->is_parent_ = disk->is_parent();
volume->is_read_only_ = disk->is_read_only();
volume->is_read_only_removable_device_ = disk->is_read_only_hardware();
volume->has_media_ = disk->has_media();
volume->drive_label_ = disk->drive_label();
} else {
volume->volume_label_ = volume->mount_path().BaseName().AsUTF8Unsafe();
volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN;
volume->is_read_only_ =
(mount_point.mount_type == chromeos::MOUNT_TYPE_ARCHIVE);
}
volume->volume_id_ = GenerateVolumeId(*volume);
volume->watchable_ = true;
return volume;
}
// static
std::unique_ptr<Volume> Volume::CreateForProvidedFileSystem(
const chromeos::file_system_provider::ProvidedFileSystemInfo&
file_system_info,
MountContext mount_context) {
std::unique_ptr<Volume> volume(new Volume());
volume->file_system_id_ = file_system_info.file_system_id();
volume->provider_id_ = file_system_info.provider_id();
switch (file_system_info.source()) {
case extensions::SOURCE_FILE:
volume->source_ = SOURCE_FILE;
break;
case extensions::SOURCE_DEVICE:
volume->source_ = SOURCE_DEVICE;
break;
case extensions::SOURCE_NETWORK:
volume->source_ = SOURCE_NETWORK;
break;
}
volume->volume_label_ = file_system_info.display_name();
volume->type_ = VOLUME_TYPE_PROVIDED;
volume->mount_path_ = file_system_info.mount_path();
volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE;
volume->mount_context_ = mount_context;
volume->is_parent_ = true;
volume->is_read_only_ = !file_system_info.writable();
volume->configurable_ = file_system_info.configurable();
volume->watchable_ = file_system_info.watchable();
volume->volume_id_ = GenerateVolumeId(*volume);
volume->icon_set_ = file_system_info.icon_set();
return volume;
}
// static
std::unique_ptr<Volume> Volume::CreateForMTP(const base::FilePath& mount_path,
const std::string& label,
bool read_only) {
std::unique_ptr<Volume> volume(new Volume());
volume->type_ = VOLUME_TYPE_MTP;
volume->mount_path_ = mount_path;
volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE;
volume->is_parent_ = true;
volume->is_read_only_ = read_only;
volume->volume_id_ = kMtpVolumeIdPrefix + label;
volume->volume_label_ = label;
volume->source_path_ = mount_path;
volume->source_ = SOURCE_DEVICE;
volume->device_type_ = chromeos::DEVICE_TYPE_MOBILE;
return volume;
}
// static
std::unique_ptr<Volume> Volume::CreateForMediaView(
const std::string& root_document_id) {
std::unique_ptr<Volume> volume(new Volume());
volume->type_ = VOLUME_TYPE_MEDIA_VIEW;
volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN;
volume->source_ = SOURCE_SYSTEM;
volume->mount_path_ = arc::GetDocumentsProviderMountPath(
arc::kMediaDocumentsProviderAuthority, root_document_id);
volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE;
volume->volume_label_ = root_document_id;
volume->is_read_only_ = true;
volume->watchable_ = false;
volume->volume_id_ = arc::GetMediaViewVolumeId(root_document_id);
return volume;
}
// static
std::unique_ptr<Volume> Volume::CreateForSshfsCrostini(
const base::FilePath& sshfs_mount_path) {
std::unique_ptr<Volume> volume(new Volume());
volume->type_ = VOLUME_TYPE_CROSTINI;
volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN;
// Keep source_path empty.
volume->source_ = SOURCE_SYSTEM;
volume->mount_path_ = sshfs_mount_path;
volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE;
volume->volume_id_ = GenerateVolumeId(*volume);
volume->watchable_ = false;
return volume;
}
// static
std::unique_ptr<Volume> Volume::CreateForAndroidFiles(
const base::FilePath& mount_path) {
std::unique_ptr<Volume> volume(new Volume());
volume->type_ = VOLUME_TYPE_ANDROID_FILES;
volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN;
// Keep source_path empty.
volume->source_ = SOURCE_SYSTEM;
volume->mount_path_ = mount_path;
volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE;
volume->volume_id_ = GenerateVolumeId(*volume);
volume->watchable_ = true;
return volume;
}
// static
std::unique_ptr<Volume> Volume::CreateForDocumentsProvider(
const std::string& authority,
const std::string& root_id,
const std::string& document_id,
const std::string& title,
const std::string& summary,
const GURL& icon_url,
bool read_only) {
std::unique_ptr<Volume> volume(new Volume());
volume->type_ = VOLUME_TYPE_DOCUMENTS_PROVIDER;
volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN;
// Keep source_path empty.
volume->source_ = SOURCE_SYSTEM;
volume->mount_path_ =
arc::GetDocumentsProviderMountPath(authority, document_id);
volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE;
volume->volume_label_ = title;
volume->is_read_only_ = read_only;
volume->watchable_ = false;
volume->volume_id_ = arc::GetDocumentsProviderVolumeId(authority, root_id);
if (!icon_url.is_empty()) {
chromeos::file_system_provider::IconSet icon_set;
icon_set.SetIcon(
chromeos::file_system_provider::IconSet::IconSize::SIZE_32x32,
icon_url);
volume->icon_set_ = icon_set;
}
return volume;
}
// static
std::unique_ptr<Volume> Volume::CreateForTesting(
const base::FilePath& path,
VolumeType volume_type,
chromeos::DeviceType device_type,
bool read_only,
const base::FilePath& device_path,
const std::string& drive_label) {
std::unique_ptr<Volume> volume(new Volume());
volume->type_ = volume_type;
volume->device_type_ = device_type;
// Keep source_path empty.
volume->source_ = SOURCE_DEVICE;
volume->mount_path_ = path;
volume->system_path_prefix_ = device_path;
volume->mount_condition_ = chromeos::disks::MOUNT_CONDITION_NONE;
volume->is_read_only_ = read_only;
volume->volume_id_ = GenerateVolumeId(*volume);
volume->drive_label_ = drive_label;
if (volume_type == VOLUME_TYPE_REMOVABLE_DISK_PARTITION) {
volume->file_system_type_ = "ext4";
}
return volume;
}
// static
std::unique_ptr<Volume> Volume::CreateForTesting(
const base::FilePath& device_path,
const base::FilePath& mount_path) {
std::unique_ptr<Volume> volume(new Volume());
volume->system_path_prefix_ = device_path;
volume->mount_path_ = mount_path;
return volume;
}
VolumeManager::VolumeManager(
Profile* profile,
drive::DriveIntegrationService* drive_integration_service,
chromeos::PowerManagerClient* power_manager_client,
chromeos::disks::DiskMountManager* disk_mount_manager,
chromeos::file_system_provider::Service* file_system_provider_service,
GetMtpStorageInfoCallback get_mtp_storage_info_callback)
: profile_(profile),
drive_integration_service_(drive_integration_service),
disk_mount_manager_(disk_mount_manager),
file_system_provider_service_(file_system_provider_service),
get_mtp_storage_info_callback_(get_mtp_storage_info_callback),
snapshot_manager_(new SnapshotManager(profile_)),
documents_provider_root_manager_(
std::make_unique<DocumentsProviderRootManager>(
profile_,
arc::ArcFileSystemOperationRunner::GetForBrowserContext(
profile_))),
weak_ptr_factory_(this) {
DCHECK(disk_mount_manager);
}
VolumeManager::~VolumeManager() = default;
VolumeManager* VolumeManager::Get(content::BrowserContext* context) {
return VolumeManagerFactory::Get(context);
}
void VolumeManager::Initialize() {
// If in the Sign in profile pr the lock screen app profile, skip mounting
// and listening for mount events.
if (chromeos::ProfileHelper::IsSigninProfile(profile_) ||
chromeos::ProfileHelper::IsLockScreenAppProfile(profile_)) {
return;
}
const base::FilePath localVolume =
file_manager::util::GetMyFilesFolderForProfile(profile_);
const bool success = RegisterDownloadsMountPoint(profile_, localVolume);
DCHECK(success);
DoMountEvent(chromeos::MOUNT_ERROR_NONE,
Volume::CreateForDownloads(localVolume));
// Asyncrhonously record the disk usage for the downloads path
base::PostTaskWithTraits(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN,
base::TaskPriority::BEST_EFFORT},
base::BindOnce(&RecordDownloadsDiskUsageStats, std::move(localVolume)));
// Subscribe to DriveIntegrationService.
drive_integration_service_->AddObserver(this);
if (drive_integration_service_->IsMounted()) {
DoMountEvent(chromeos::MOUNT_ERROR_NONE,
Volume::CreateForDrive(GetDriveMountPointPath()));
}
// Subscribe to DiskMountManager.
disk_mount_manager_->AddObserver(this);
disk_mount_manager_->EnsureMountInfoRefreshed(
base::BindOnce(&VolumeManager::OnDiskMountManagerRefreshed,
weak_ptr_factory_.GetWeakPtr()),
false /* force */);
// Subscribe to ARC DocumentsProvider events about roots.
documents_provider_root_manager_->AddObserver(this);
// Subscribe to FileSystemProviderService and register currently mounted
// volumes for the profile.
if (file_system_provider_service_) {
using chromeos::file_system_provider::ProvidedFileSystemInfo;
file_system_provider_service_->AddObserver(this);
std::vector<ProvidedFileSystemInfo> file_system_info_list =
file_system_provider_service_->GetProvidedFileSystemInfoList();
for (size_t i = 0; i < file_system_info_list.size(); ++i) {
std::unique_ptr<Volume> volume = Volume::CreateForProvidedFileSystem(
file_system_info_list[i], MOUNT_CONTEXT_AUTO);
DoMountEvent(chromeos::MOUNT_ERROR_NONE, std::move(volume));
}
}
// Subscribe to Profile Preference change.
pref_change_registrar_.Init(profile_->GetPrefs());
pref_change_registrar_.Add(
prefs::kExternalStorageDisabled,
base::Bind(&VolumeManager::OnExternalStorageDisabledChanged,
weak_ptr_factory_.GetWeakPtr()));
pref_change_registrar_.Add(
prefs::kExternalStorageReadOnly,
base::Bind(&VolumeManager::OnExternalStorageReadOnlyChanged,
weak_ptr_factory_.GetWeakPtr()));
// Subscribe to storage monitor for MTP notifications.
if (storage_monitor::StorageMonitor::GetInstance()) {
storage_monitor::StorageMonitor::GetInstance()->EnsureInitialized(
base::Bind(&VolumeManager::OnStorageMonitorInitialized,
weak_ptr_factory_.GetWeakPtr()));
}
// Subscribe to ARC file system events.
if (base::FeatureList::IsEnabled(arc::kMediaViewFeature) &&
arc::IsArcAllowedForProfile(profile_)) {
// Registers a mount point for Android files only when the flag is enabled.
if (IsShowAndroidFilesEnabled())
RegisterAndroidFilesMountPoint();
arc::ArcSessionManager::Get()->AddObserver(this);
OnArcPlayStoreEnabledChanged(
arc::IsArcPlayStoreEnabledForProfile(profile_));
}
}
void VolumeManager::Shutdown() {
weak_ptr_factory_.InvalidateWeakPtrs();
snapshot_manager_.reset();
pref_change_registrar_.RemoveAll();
disk_mount_manager_->RemoveObserver(this);
documents_provider_root_manager_->RemoveObserver(this);
documents_provider_root_manager_.reset();
if (storage_monitor::StorageMonitor::GetInstance())
storage_monitor::StorageMonitor::GetInstance()->RemoveObserver(this);
if (drive_integration_service_)
drive_integration_service_->RemoveObserver(this);
if (file_system_provider_service_)
file_system_provider_service_->RemoveObserver(this);
// Unsubscribe from ARC file system events.
if (base::FeatureList::IsEnabled(arc::kMediaViewFeature) &&
arc::IsArcAllowedForProfile(profile_)) {
auto* session_manager = arc::ArcSessionManager::Get();
// TODO(crbug.com/672829): We need nullptr check here because
// ArcSessionManager may or may not be alive at this point.
if (session_manager)
session_manager->RemoveObserver(this);
}
}
void VolumeManager::AddObserver(VolumeManagerObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(observer);
observers_.AddObserver(observer);
}
void VolumeManager::RemoveObserver(VolumeManagerObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(observer);
observers_.RemoveObserver(observer);
}
std::vector<base::WeakPtr<Volume>> VolumeManager::GetVolumeList() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::vector<base::WeakPtr<Volume>> result;
result.reserve(mounted_volumes_.size());
for (const auto& pair : mounted_volumes_) {
result.push_back(pair.second->AsWeakPtr());
}
return result;
}
base::WeakPtr<Volume> VolumeManager::FindVolumeById(
const std::string& volume_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const auto it = mounted_volumes_.find(volume_id);
if (it != mounted_volumes_.end())
return it->second->AsWeakPtr();
return base::WeakPtr<Volume>();
}
void VolumeManager::AddSshfsCrostiniVolume(
const base::FilePath& sshfs_mount_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::unique_ptr<Volume> volume =
Volume::CreateForSshfsCrostini(sshfs_mount_path);
// Ignore if volume already exists.
if (mounted_volumes_.find(volume->volume_id()) != mounted_volumes_.end())
return;
DoMountEvent(chromeos::MOUNT_ERROR_NONE, std::move(volume));
// Listen for crostini container shutdown and remove volume.
crostini::CrostiniManager::GetForProfile(profile_)
->AddShutdownContainerCallback(
crostini::kCrostiniDefaultVmName,
crostini::kCrostiniDefaultContainerName,
base::BindOnce(&VolumeManager::RemoveSshfsCrostiniVolume,
weak_ptr_factory_.GetWeakPtr(), sshfs_mount_path));
}
void VolumeManager::RemoveSshfsCrostiniVolume(
const base::FilePath& sshfs_mount_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Call DiskMountManager first since DoUnmountEvent deletes Volume.
disk_mount_manager_->UnmountPath(
sshfs_mount_path.value(), chromeos::UNMOUNT_OPTIONS_NONE,
chromeos::disks::DiskMountManager::UnmountPathCallback());
DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
*Volume::CreateForSshfsCrostini(sshfs_mount_path));
}
bool VolumeManager::RegisterAndroidFilesDirectoryForTesting(
const base::FilePath& path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
bool result =
storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
file_manager::util::GetAndroidFilesMountPointName(),
storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(),
path);
DCHECK(result);
DoMountEvent(chromeos::MOUNT_ERROR_NONE, Volume::CreateForAndroidFiles(path));
return true;
}
bool VolumeManager::RegisterMediaViewForTesting(
const std::string& root_document_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DoMountEvent(chromeos::MOUNT_ERROR_NONE,
Volume::CreateForMediaView(root_document_id));
return true;
}
bool VolumeManager::RemoveAndroidFilesDirectoryForTesting(
const base::FilePath& path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
*Volume::CreateForAndroidFiles(path));
return true;
}
void VolumeManager::RemoveDownloadsDirectoryForTesting() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::FilePath path;
if (FindDownloadsMountPointPath(profile_, &path)) {
DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
*Volume::CreateForDownloads(path));
}
}
bool VolumeManager::RegisterDownloadsDirectoryForTesting(
const base::FilePath& path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::FilePath old_path;
if (FindDownloadsMountPointPath(profile_, &old_path)) {
DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
*Volume::CreateForDownloads(old_path));
}
bool success = RegisterDownloadsMountPoint(profile_, path);
DoMountEvent(
success ? chromeos::MOUNT_ERROR_NONE : chromeos::MOUNT_ERROR_INVALID_PATH,
Volume::CreateForDownloads(path));
return success;
}
bool VolumeManager::RegisterCrostiniDirectoryForTesting(
const base::FilePath& path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
bool success =
storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
file_manager::util::GetCrostiniMountPointName(profile_),
storage::kFileSystemTypeNativeLocal, storage::FileSystemMountOption(),
path);
DoMountEvent(
success ? chromeos::MOUNT_ERROR_NONE : chromeos::MOUNT_ERROR_INVALID_PATH,
Volume::CreateForSshfsCrostini(path));
return true;
}
void VolumeManager::AddVolumeForTesting(const base::FilePath& path,
VolumeType volume_type,
chromeos::DeviceType device_type,
bool read_only,
const base::FilePath& device_path,
const std::string& drive_label) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DoMountEvent(chromeos::MOUNT_ERROR_NONE,
Volume::CreateForTesting(path, volume_type, device_type,
read_only, device_path, drive_label));
}
void VolumeManager::AddVolumeForTesting(std::unique_ptr<Volume> volume) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DoMountEvent(chromeos::MOUNT_ERROR_NONE, std::move(volume));
}
void VolumeManager::RemoveVolumeForTesting(const base::FilePath& path,
VolumeType volume_type,
chromeos::DeviceType device_type,
bool read_only,
const base::FilePath& device_path,
const std::string& drive_label) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DoUnmountEvent(
chromeos::MOUNT_ERROR_NONE,
*Volume::CreateForTesting(path, volume_type, device_type, read_only,
device_path, drive_label));
}
void VolumeManager::OnFileSystemMounted() {
DCHECK_CURRENTLY_ON(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.
DoMountEvent(chromeos::MOUNT_ERROR_NONE,
Volume::CreateForDrive(GetDriveMountPointPath()));
}
void VolumeManager::OnFileSystemBeingUnmounted() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
*Volume::CreateForDrive(GetDriveMountPointPath()));
}
void VolumeManager::OnAutoMountableDiskEvent(
chromeos::disks::DiskMountManager::DiskEvent event,
const chromeos::disks::Disk& disk) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Disregard hidden devices.
if (disk.is_hidden())
return;
switch (event) {
case chromeos::disks::DiskMountManager::DISK_ADDED:
case chromeos::disks::DiskMountManager::DISK_CHANGED: {
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)) {
// TODO(crbug.com/774890): Remove |mount_label| when the issue gets
// resolved. Currently we suggest a mount point name, because in case
// when disk's name contains '#', content will not load in Files App.
std::string mount_label = disk.device_label();
std::replace(mount_label.begin(), mount_label.end(), '#', '_');
// 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(),
mount_label, {},
chromeos::MOUNT_TYPE_DEVICE,
GetExternalStorageAccessMode(profile_));
mounting = true;
}
// Notify to observers.
for (auto& observer : observers_)
observer.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 (auto& observer : observers_)
observer.OnDiskRemoved(disk);
return;
}
NOTREACHED();
}
void VolumeManager::OnDeviceEvent(
chromeos::disks::DiskMountManager::DeviceEvent event,
const std::string& device_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DVLOG(1) << "OnDeviceEvent: " << event << ", " << device_path;
switch (event) {
case chromeos::disks::DiskMountManager::DEVICE_ADDED:
for (auto& observer : observers_)
observer.OnDeviceAdded(device_path);
return;
case chromeos::disks::DiskMountManager::DEVICE_REMOVED: {
for (auto& observer : observers_)
observer.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_CURRENTLY_ON(content::BrowserThread::UI);
switch (mount_info.mount_type) {
case 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* const file_system =
drive::util::GetFileSystemByProfile(profile_);
if (file_system) {
file_system->MarkCacheFileAsUnmounted(
base::FilePath(mount_info.source_path), base::DoNothing());
}
}
FALLTHROUGH;
}
case chromeos::MOUNT_TYPE_DEVICE: {
// Notify a mounting/unmounting event to observers.
const chromeos::disks::Disk* const disk =
disk_mount_manager_->FindDiskBySourcePath(mount_info.source_path);
std::unique_ptr<Volume> volume =
Volume::CreateForRemovable(mount_info, disk);
switch (event) {
case chromeos::disks::DiskMountManager::MOUNTING: {
DoMountEvent(error_code, std::move(volume));
return;
}
case chromeos::disks::DiskMountManager::UNMOUNTING:
DoUnmountEvent(error_code, *volume);
return;
}
NOTREACHED();
}
// Network storage is responsible for doing its own mounting.
case chromeos::MOUNT_TYPE_NETWORK_STORAGE: {
break;
}
case chromeos::MOUNT_TYPE_INVALID: {
NOTREACHED();
break;
}
}
}
void VolumeManager::OnFormatEvent(
chromeos::disks::DiskMountManager::FormatEvent event,
chromeos::FormatError error_code,
const std::string& device_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DVLOG(1) << "OnDeviceEvent: " << event << ", " << error_code
<< ", " << device_path;
switch (event) {
case chromeos::disks::DiskMountManager::FORMAT_STARTED:
for (auto& observer : observers_) {
observer.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,
GetExternalStorageAccessMode(profile_));
}
for (auto& observer : observers_) {
observer.OnFormatCompleted(device_path,
error_code == chromeos::FORMAT_ERROR_NONE);
}
return;
}
NOTREACHED();
}
void VolumeManager::OnRenameEvent(
chromeos::disks::DiskMountManager::RenameEvent event,
chromeos::RenameError error_code,
const std::string& device_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DVLOG(1) << "OnDeviceEvent: " << event << ", " << error_code << ", "
<< device_path;
switch (event) {
case chromeos::disks::DiskMountManager::RENAME_STARTED:
for (auto& observer : observers_) {
observer.OnRenameStarted(device_path,
error_code == chromeos::RENAME_ERROR_NONE);
}
return;
case chromeos::disks::DiskMountManager::RENAME_COMPLETED:
// Find previous mount point label if it exists
std::string mount_label = "";
auto disk_map_iter = disk_mount_manager_->disks().find(device_path);
if (disk_map_iter != disk_mount_manager_->disks().end() &&
!disk_map_iter->second->base_mount_path().empty()) {
mount_label = base::FilePath(disk_map_iter->second->base_mount_path())
.BaseName()
.AsUTF8Unsafe();
}
// Try to mount the device. MountPath auto-detects filesystem format if
// second argument is empty. Third argument is a mount point name of the
// disk when it was first time mounted (to preserve mount point regardless
// of the volume name).
disk_mount_manager_->MountPath(device_path, std::string(), mount_label,
{}, chromeos::MOUNT_TYPE_DEVICE,
GetExternalStorageAccessMode(profile_));
bool successfully_renamed = error_code == chromeos::RENAME_ERROR_NONE;
for (auto& observer : observers_)
observer.OnRenameCompleted(device_path, successfully_renamed);
return;
}
NOTREACHED();
}
void VolumeManager::OnProvidedFileSystemMount(
const chromeos::file_system_provider::ProvidedFileSystemInfo&
file_system_info,
chromeos::file_system_provider::MountContext context,
base::File::Error error) {
MountContext volume_context = MOUNT_CONTEXT_UNKNOWN;
switch (context) {
case chromeos::file_system_provider::MOUNT_CONTEXT_USER:
volume_context = MOUNT_CONTEXT_USER;
break;
case chromeos::file_system_provider::MOUNT_CONTEXT_RESTORE:
volume_context = MOUNT_CONTEXT_AUTO;
break;
}
std::unique_ptr<Volume> volume =
Volume::CreateForProvidedFileSystem(file_system_info, volume_context);
// TODO(mtomasz): Introduce own type, and avoid using MountError internally,
// since it is related to cros disks only.
chromeos::MountError mount_error;
switch (error) {
case base::File::FILE_OK:
mount_error = chromeos::MOUNT_ERROR_NONE;
break;
case base::File::FILE_ERROR_EXISTS:
mount_error = chromeos::MOUNT_ERROR_PATH_ALREADY_MOUNTED;
break;
default:
mount_error = chromeos::MOUNT_ERROR_UNKNOWN;
break;
}
DoMountEvent(mount_error, std::move(volume));
}
void VolumeManager::OnProvidedFileSystemUnmount(
const chromeos::file_system_provider::ProvidedFileSystemInfo&
file_system_info,
base::File::Error error) {
// TODO(mtomasz): Introduce own type, and avoid using MountError internally,
// since it is related to cros disks only.
const chromeos::MountError mount_error = error == base::File::FILE_OK
? chromeos::MOUNT_ERROR_NONE
: chromeos::MOUNT_ERROR_UNKNOWN;
std::unique_ptr<Volume> volume = Volume::CreateForProvidedFileSystem(
file_system_info, MOUNT_CONTEXT_UNKNOWN);
DoUnmountEvent(mount_error, *volume);
}
void VolumeManager::OnExternalStorageDisabledChangedUnmountCallback(
std::vector<std::string> remaining_mount_paths,
chromeos::MountError error_code) {
LOG_IF(ERROR, error_code != chromeos::MOUNT_ERROR_NONE)
<< "Unmount on ExternalStorageDisabled policy change failed: "
<< error_code;
while (!remaining_mount_paths.empty()) {
std::string mount_path = remaining_mount_paths.back();
remaining_mount_paths.pop_back();
if (!base::ContainsKey(disk_mount_manager_->mount_points(), mount_path)) {
// The mount point could have already been removed for another reason
// (i.e. the disk was removed by the user).
continue;
}
disk_mount_manager_->UnmountPath(
mount_path, chromeos::UNMOUNT_OPTIONS_NONE,
base::BindOnce(
&VolumeManager::OnExternalStorageDisabledChangedUnmountCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(remaining_mount_paths)));
return;
}
}
void VolumeManager::OnArcPlayStoreEnabledChanged(bool enabled) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(base::FeatureList::IsEnabled(arc::kMediaViewFeature));
DCHECK(arc::IsArcAllowedForProfile(profile_));
if (enabled == arc_volumes_mounted_)
return;
if (enabled) {
DoMountEvent(chromeos::MOUNT_ERROR_NONE,
Volume::CreateForMediaView(arc::kImagesRootDocumentId));
DoMountEvent(chromeos::MOUNT_ERROR_NONE,
Volume::CreateForMediaView(arc::kVideosRootDocumentId));
DoMountEvent(chromeos::MOUNT_ERROR_NONE,
Volume::CreateForMediaView(arc::kAudioRootDocumentId));
if (IsShowAndroidFilesEnabled()) {
DoMountEvent(chromeos::MOUNT_ERROR_NONE,
Volume::CreateForAndroidFiles(
base::FilePath(util::kAndroidFilesPath)));
}
} else {
DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
*Volume::CreateForMediaView(arc::kImagesRootDocumentId));
DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
*Volume::CreateForMediaView(arc::kVideosRootDocumentId));
DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
*Volume::CreateForMediaView(arc::kAudioRootDocumentId));
if (IsShowAndroidFilesEnabled()) {
DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
*Volume::CreateForAndroidFiles(
base::FilePath(util::kAndroidFilesPath)));
}
}
documents_provider_root_manager_->SetEnabled(enabled);
arc_volumes_mounted_ = enabled;
}
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 their 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(). Also, a failing unmount shouldn't be retried
// indefinitely. So make a set of all the mount points that should be
// unmounted (all external media mounts), and iterate through them.
std::vector<std::string> remaining_mount_paths;
for (auto& mount_point : disk_mount_manager_->mount_points()) {
if (mount_point.second.mount_type == chromeos::MOUNT_TYPE_DEVICE) {
remaining_mount_paths.push_back(mount_point.first);
}
}
if (remaining_mount_paths.empty()) {
return;
}
std::string mount_path = remaining_mount_paths.back();
remaining_mount_paths.pop_back();
disk_mount_manager_->UnmountPath(
mount_path, chromeos::UNMOUNT_OPTIONS_NONE,
base::BindOnce(
&VolumeManager::OnExternalStorageDisabledChangedUnmountCallback,
weak_ptr_factory_.GetWeakPtr(), std::move(remaining_mount_paths)));
}
}
void VolumeManager::OnExternalStorageReadOnlyChanged() {
disk_mount_manager_->RemountAllRemovableDrives(
GetExternalStorageAccessMode(profile_));
}
void VolumeManager::OnRemovableStorageAttached(
const storage_monitor::StorageInfo& info) {
if (!storage_monitor::StorageInfo::IsMTPDevice(info.device_id()))
return;
if (profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled))
return;
// Resolve mtp storage name and get MtpStorageInfo.
std::string storage_name;
base::RemoveChars(info.location(), kRootPath, &storage_name);
DCHECK(!storage_name.empty());
if (get_mtp_storage_info_callback_.is_null()) {
storage_monitor::StorageMonitor::GetInstance()
->media_transfer_protocol_manager()
->GetStorageInfo(storage_name,
base::BindOnce(&VolumeManager::DoAttachMtpStorage,
weak_ptr_factory_.GetWeakPtr(), info));
} else {
get_mtp_storage_info_callback_.Run(
storage_name, base::BindOnce(&VolumeManager::DoAttachMtpStorage,
weak_ptr_factory_.GetWeakPtr(), info));
}
}
void VolumeManager::DoAttachMtpStorage(
const storage_monitor::StorageInfo& info,
device::mojom::MtpStorageInfoPtr mtp_storage_info) {
if (!mtp_storage_info) {
// |mtp_storage_info| can be null. e.g. As OnRemovableStorageAttached and
// DoAttachMtpStorage are called asynchronously, there can be a race
// condition where the storage has been already removed in
// MediaTransferProtocolManager at the time when this method is called.
return;
}
// Mtp write is enabled only when the device is writable, supports generic
// hierarchical file system, and writing to external storage devices is not
// prohibited by the preference.
const bool read_only =
base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kDisableMtpWriteSupport) ||
mtp_storage_info->access_capability != kAccessCapabilityReadWrite ||
mtp_storage_info->filesystem_type !=
kFilesystemTypeGenericHierarchical ||
GetExternalStorageAccessMode(profile_) ==
chromeos::MOUNT_ACCESS_MODE_READ_ONLY;
const base::FilePath path = base::FilePath::FromUTF8Unsafe(info.location());
const std::string fsid = GetMountPointNameForMediaStorage(info);
const std::string base_name = base::UTF16ToUTF8(info.model_name());
// Assign a fresh volume ID based on the volume name.
std::string label = base_name;
for (int i = 2; mounted_volumes_.count(kMtpVolumeIdPrefix + label); ++i)
label = base_name + base::StringPrintf(" (%d)", i);
bool result =
storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
fsid, storage::kFileSystemTypeDeviceMediaAsFileStorage,
storage::FileSystemMountOption(), path);
DCHECK(result);
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&MTPDeviceMapService::RegisterMTPFileSystem,
base::Unretained(MTPDeviceMapService::GetInstance()),
info.location(), fsid, read_only));
std::unique_ptr<Volume> volume = Volume::CreateForMTP(path, label, read_only);
DoMountEvent(chromeos::MOUNT_ERROR_NONE, std::move(volume));
}
void VolumeManager::OnRemovableStorageDetached(
const storage_monitor::StorageInfo& info) {
if (!storage_monitor::StorageInfo::IsMTPDevice(info.device_id()))
return;
for (const auto& mounted_volume : mounted_volumes_) {
if (mounted_volume.second->source_path().value() == info.location()) {
DoUnmountEvent(chromeos::MOUNT_ERROR_NONE, *mounted_volume.second.get());
const std::string fsid = GetMountPointNameForMediaStorage(info);
storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(fsid);
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::IO},
base::BindOnce(&MTPDeviceMapService::RevokeMTPFileSystem,
base::Unretained(MTPDeviceMapService::GetInstance()),
fsid));
return;
}
}
}
void VolumeManager::OnDocumentsProviderRootAdded(
const std::string& authority,
const std::string& root_id,
const std::string& document_id,
const std::string& title,
const std::string& summary,
const GURL& icon_url,
bool read_only,
const std::vector<std::string>& mime_types) {
arc::ArcDocumentsProviderRootMap::GetForArcBrowserContext()->RegisterRoot(
authority, document_id, root_id, mime_types);
DoMountEvent(
chromeos::MOUNT_ERROR_NONE,
Volume::CreateForDocumentsProvider(authority, root_id, document_id, title,
summary, icon_url, read_only));
}
void VolumeManager::OnDocumentsProviderRootRemoved(
const std::string& authority,
const std::string& root_id,
const std::string& document_id) {
DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
*Volume::CreateForDocumentsProvider(
authority, root_id, std::string(), std::string(),
std::string(), GURL(), false));
arc::ArcDocumentsProviderRootMap::GetForArcBrowserContext()->UnregisterRoot(
authority, document_id);
}
void VolumeManager::OnDiskMountManagerRefreshed(bool success) {
if (!success) {
LOG(ERROR) << "Failed to refresh disk mount manager";
return;
}
std::vector<std::unique_ptr<Volume>> archives;
const chromeos::disks::DiskMountManager::MountPointMap& mount_points =
disk_mount_manager_->mount_points();
for (const auto& mount_point : mount_points) {
switch (mount_point.second.mount_type) {
case chromeos::MOUNT_TYPE_ARCHIVE: {
// Archives are mounted after other types of volume. See below.
archives.push_back(
Volume::CreateForRemovable(mount_point.second, nullptr));
break;
}
case chromeos::MOUNT_TYPE_DEVICE: {
DoMountEvent(
chromeos::MOUNT_ERROR_NONE,
Volume::CreateForRemovable(
mount_point.second, disk_mount_manager_->FindDiskBySourcePath(
mount_point.second.source_path)));
break;
}
case chromeos::MOUNT_TYPE_NETWORK_STORAGE: {
break;
}
case chromeos::MOUNT_TYPE_INVALID: {
NOTREACHED();
}
}
}
// We mount archives only if they are opened from currently mounted volumes.
// To check the condition correctly in DoMountEvent, we care about the order.
std::vector<bool> done(archives.size(), false);
for (size_t i = 0; i < archives.size(); ++i) {
if (done[i])
continue;
std::vector<std::unique_ptr<Volume>> chain;
// done[x] = true means archives[x] is null and that volume is in |chain|.
done[i] = true;
chain.push_back(std::move(archives[i]));
// If archives[i]'s source_path is in another archive, mount it first.
for (size_t parent = i + 1; parent < archives.size(); ++parent) {
if (!done[parent] &&
archives[parent]->mount_path().IsParent(
chain.back()->source_path())) {
// done[parent] started false, so archives[parent] is non-null.
done[parent] = true;
chain.push_back(std::move(archives[parent]));
parent = i + 1; // Search archives[parent]'s parent from the beginning.
}
}
// Mount from the tail of chain.
for (size_t i = chain.size(); i > 0; --i) {
DoMountEvent(chromeos::MOUNT_ERROR_NONE, std::move(chain[i - 1]));
}
}
}
void VolumeManager::OnStorageMonitorInitialized() {
std::vector<storage_monitor::StorageInfo> storages =
storage_monitor::StorageMonitor::GetInstance()->GetAllAvailableStorages();
for (size_t i = 0; i < storages.size(); ++i)
OnRemovableStorageAttached(storages[i]);
storage_monitor::StorageMonitor::GetInstance()->AddObserver(this);
}
void VolumeManager::DoMountEvent(chromeos::MountError error_code,
std::unique_ptr<Volume> volume) {
// Archive files are mounted globally in system. We however don't want to show
// archives from profile-specific folders (Drive/Downloads) of other users in
// multi-profile session. To this end, we filter out archives not on the
// volumes already mounted on this VolumeManager instance.
if (volume->type() == VOLUME_TYPE_MOUNTED_ARCHIVE_FILE) {
// Source may be in Drive cache folder under the current profile directory.
bool from_current_profile =
profile_->GetPath().IsParent(volume->source_path());
for (const auto& mounted_volume : mounted_volumes_) {
if (mounted_volume.second->mount_path().IsParent(volume->source_path())) {
from_current_profile = true;
break;
}
}
if (!from_current_profile)
return;
}
// Filter out removable disks if forbidden by policy for this profile.
if (volume->type() == VOLUME_TYPE_REMOVABLE_DISK_PARTITION &&
profile_->GetPrefs()->GetBoolean(prefs::kExternalStorageDisabled)) {
return;
}
Volume* raw_volume = volume.get();
if (error_code == chromeos::MOUNT_ERROR_NONE || volume->mount_condition()) {
mounted_volumes_[volume->volume_id()] = std::move(volume);
UMA_HISTOGRAM_ENUMERATION("FileBrowser.VolumeType", raw_volume->type(),
NUM_VOLUME_TYPE);
}
for (auto& observer : observers_)
observer.OnVolumeMounted(error_code, *raw_volume);
}
void VolumeManager::DoUnmountEvent(chromeos::MountError error_code,
const Volume& volume) {
auto iter = mounted_volumes_.find(volume.volume_id());
if (iter == mounted_volumes_.end())
return;
std::unique_ptr<Volume> volume_ref;
if (error_code == chromeos::MOUNT_ERROR_NONE) {
// It is important to hold a reference to the removed Volume from
// |mounted_volumes_|, because OnVolumeMounted() will access it.
volume_ref = std::move(iter->second);
mounted_volumes_.erase(iter);
}
for (auto& observer : observers_)
observer.OnVolumeUnmounted(error_code, volume);
}
base::FilePath VolumeManager::GetDriveMountPointPath() const {
return drive_integration_service_->GetMountPointPath();
}
} // namespace file_manager