| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/file_manager/volume.h" |
| |
| #include <string_view> |
| |
| #include "ash/constants/ash_features.h" |
| #include "base/strings/strcat.h" |
| #include "chrome/browser/ash/arc/fileapi/arc_documents_provider_util.h" |
| #include "chrome/browser/ash/arc/fileapi/arc_media_view_util.h" |
| #include "chrome/browser/ash/file_manager/path_util.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/chromeos/strings/grit/ui_chromeos_strings.h" |
| |
| namespace file_manager { |
| namespace { |
| |
| using l10n_util::GetStringUTF8; |
| const char kMtpVolumeIdPrefix[] = "mtp:"; |
| |
| VolumeType MountTypeToVolumeType(ash::MountType type) { |
| switch (type) { |
| case ash::MountType::kInvalid: |
| // A zip mount with an invalid path will return type kInvalid. We can use |
| // a default VolumeType in this case. |
| return VOLUME_TYPE_DOWNLOADS_DIRECTORY; |
| case ash::MountType::kDevice: |
| return VOLUME_TYPE_REMOVABLE_DISK_PARTITION; |
| case ash::MountType::kArchive: |
| return VOLUME_TYPE_MOUNTED_ARCHIVE_FILE; |
| case ash::MountType::kNetworkStorage: |
| // Network storage mounts are handled by their mounters so |
| // MountType::kNetworkStorage should never need to be handled |
| // here. |
| break; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| // Returns a string representation of the given volume type. |
| std::string_view VolumeTypeToString(const 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 VOLUME_TYPE_SMB: |
| return "smb"; |
| case VOLUME_TYPE_SYSTEM_INTERNAL: |
| return "system_internal"; |
| case VOLUME_TYPE_GUEST_OS: |
| return "guest_os"; |
| case NUM_VOLUME_TYPE: |
| break; |
| } |
| |
| NOTREACHED() << "Unexpected VolumeType value " |
| << static_cast<std::underlying_type_t<VolumeType>>(type); |
| } |
| |
| // 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 base::StrCat({VolumeTypeToString(volume.type()), ":", |
| volume.mount_path().BaseName().AsUTF8Unsafe()}); |
| } |
| |
| // Returns the localized label for a given media view. |
| std::string MediaViewRootIdToLabel(std::string_view root_id) { |
| if (root_id == arc::kAudioRootId) { |
| return GetStringUTF8(IDS_FILE_BROWSER_MEDIA_VIEW_AUDIO_ROOT_LABEL); |
| } |
| |
| if (root_id == arc::kImagesRootId) { |
| return GetStringUTF8(IDS_FILE_BROWSER_MEDIA_VIEW_IMAGES_ROOT_LABEL); |
| } |
| |
| if (root_id == arc::kVideosRootId) { |
| return GetStringUTF8(IDS_FILE_BROWSER_MEDIA_VIEW_VIDEOS_ROOT_LABEL); |
| } |
| |
| if (root_id == arc::kDocumentsRootId) { |
| return GetStringUTF8(IDS_FILE_BROWSER_MEDIA_VIEW_DOCUMENTS_ROOT_LABEL); |
| } |
| |
| NOTREACHED() << "Unexpected root ID: " << root_id; |
| } |
| |
| } // namespace |
| |
| std::ostream& operator<<(std::ostream& out, const VolumeType type) { |
| return out << VolumeTypeToString(type); |
| } |
| |
| Volume::Volume() = default; |
| Volume::~Volume() = default; |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForDrive(base::FilePath drive_path) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->type_ = VOLUME_TYPE_GOOGLE_DRIVE; |
| volume->source_path_ = drive_path; |
| volume->source_ = SOURCE_NETWORK; |
| volume->mount_path_ = std::move(drive_path); |
| volume->volume_id_ = GenerateVolumeId(*volume); |
| volume->volume_label_ = GetStringUTF8(IDS_FILE_BROWSER_DRIVE_DIRECTORY_LABEL); |
| volume->watchable_ = true; |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForDownloads( |
| base::FilePath downloads_path, |
| base::FilePath optional_fusebox_path, |
| const char* optional_fusebox_volume_label, |
| bool read_only) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->type_ = VOLUME_TYPE_DOWNLOADS_DIRECTORY; |
| // Keep source_path empty. |
| volume->source_ = SOURCE_SYSTEM; |
| volume->mount_path_ = std::move(downloads_path); |
| volume->volume_id_ = GenerateVolumeId(*volume); |
| volume->volume_label_ = GetStringUTF8(IDS_FILE_BROWSER_MY_FILES_ROOT_LABEL); |
| volume->watchable_ = true; |
| volume->is_read_only_ = read_only; |
| |
| if (!optional_fusebox_path.empty()) { |
| // Leaving the type_ as VOLUME_TYPE_DOWNLOADS_DIRECTORY means that, for |
| // some unknown reason, it doesn't show up in the CrOS Files app. Use a |
| // different but arbitrary type instead. |
| // |
| // It doesn't need to be well polished. It's just for debugging FuseBox. We |
| // wouldn't normally need a FuseBox wrapper (exposing to the kernel-level |
| // file system) for something like Downloads that's typically on local disk |
| // (and hence already on the kernel-level file system). |
| volume->type_ = VOLUME_TYPE_MTP; |
| |
| volume->file_system_type_ = util::kFuseBox; |
| volume->mount_path_ = std::move(optional_fusebox_path); |
| volume->volume_id_ = |
| base::StrCat({util::kFuseBox, std::move(volume->volume_id_)}); |
| if (optional_fusebox_volume_label) { |
| volume->volume_label_ = optional_fusebox_volume_label; |
| } |
| } |
| |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForRemovable( |
| const ash::disks::DiskMountManager::MountPoint& mount_point, |
| const ash::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 == ash::MountType::kArchive |
| ? SOURCE_FILE |
| : SOURCE_DEVICE; |
| volume->mount_path_ = base::FilePath(mount_point.mount_path); |
| volume->mount_condition_ = mount_point.mount_error; |
| |
| if (disk) { |
| volume->file_system_type_ = disk->file_system_type(); |
| volume->volume_label_ = disk->device_label(); |
| volume->device_type_ = disk->device_type(); |
| volume->storage_device_path_ = base::FilePath(disk->storage_device_path()); |
| 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->is_read_only_ = |
| (mount_point.mount_type == ash::MountType::kArchive); |
| } |
| volume->volume_id_ = GenerateVolumeId(*volume); |
| volume->watchable_ = true; |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForProvidedFileSystem( |
| const ash::file_system_provider::ProvidedFileSystemInfo& file_system_info, |
| MountContext mount_context, |
| base::FilePath optional_fusebox_path) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| |
| 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_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->icon_set_ = file_system_info.icon_set(); |
| |
| volume->volume_id_ = GenerateVolumeId(*volume); |
| volume->file_system_id_ = file_system_info.file_system_id(); |
| volume->provider_id_ = file_system_info.provider_id(); |
| |
| if (!optional_fusebox_path.empty()) { |
| volume->file_system_type_ = util::kFuseBox; |
| if (ash::features::IsFileManagerFuseBoxDebugEnabled()) { |
| volume->volume_label_.insert(0, "fusebox "); |
| } |
| volume->mount_path_ = std::move(optional_fusebox_path); |
| // Even though the underlying FSP may support watchers, fusebox needs |
| // to implement watchers in order to match the capability of the FSP. |
| // TODO(crbug.com/1353673): Add watcher support to fusebox. |
| volume->watchable_ = false; |
| // "fusebox" prefix the original FSP volume id. |
| volume->volume_id_ = |
| base::StrCat({util::kFuseBox, GenerateVolumeId(*volume)}); |
| } |
| |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForMTP(base::FilePath mount_path, |
| std::string label, |
| bool read_only, |
| bool use_fusebox) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->type_ = VOLUME_TYPE_MTP; |
| volume->mount_path_ = mount_path; |
| volume->is_parent_ = true; |
| volume->is_read_only_ = read_only; |
| volume->volume_id_ = base::StrCat({kMtpVolumeIdPrefix, label}); |
| volume->volume_label_ = std::move(label); |
| volume->source_path_ = std::move(mount_path); |
| volume->source_ = SOURCE_DEVICE; |
| volume->device_type_ = ash::DeviceType::kMobile; |
| |
| // MTP does have watcher support via WatcherManager but it doesn't |
| // seem to work (perhaps something missing in mtpd). |
| volume->watchable_ = false; |
| |
| if (use_fusebox) { |
| volume->file_system_type_ = util::kFuseBox; |
| volume->volume_id_ = |
| base::StrCat({util::kFuseBox, std::move(volume->volume_id_)}); |
| if (ash::features::IsFileManagerFuseBoxDebugEnabled()) { |
| volume->volume_label_.insert(0, "fusebox "); |
| } |
| } |
| |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForMediaView(const std::string& root_id) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->type_ = VOLUME_TYPE_MEDIA_VIEW; |
| volume->source_ = SOURCE_SYSTEM; |
| volume->mount_path_ = arc::GetDocumentsProviderMountPath( |
| arc::kMediaDocumentsProviderAuthority, root_id); |
| volume->volume_label_ = MediaViewRootIdToLabel(root_id); |
| volume->is_read_only_ = false; |
| volume->watchable_ = false; |
| volume->volume_id_ = arc::GetMediaViewVolumeId(root_id); |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForSshfsCrostini( |
| base::FilePath sshfs_mount_path, |
| base::FilePath remote_mount_path) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->type_ = VOLUME_TYPE_CROSTINI; |
| // Keep source_path empty. |
| volume->source_ = SOURCE_SYSTEM; |
| volume->mount_path_ = std::move(sshfs_mount_path); |
| volume->remote_mount_path_ = std::move(remote_mount_path); |
| volume->volume_id_ = GenerateVolumeId(*volume); |
| volume->volume_label_ = |
| GetStringUTF8(IDS_FILE_BROWSER_LINUX_FILES_ROOT_LABEL); |
| volume->watchable_ = true; |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForSftpGuestOs( |
| std::string display_name, |
| base::FilePath sftp_mount_path, |
| base::FilePath remote_mount_path, |
| const guest_os::VmType vm_type) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->type_ = vm_type == guest_os::VmType::ARCVM ? VOLUME_TYPE_ANDROID_FILES |
| : VOLUME_TYPE_GUEST_OS; |
| // Keep source_path empty. |
| volume->source_ = SOURCE_SYSTEM; |
| volume->mount_path_ = std::move(sftp_mount_path); |
| volume->remote_mount_path_ = std::move(remote_mount_path); |
| volume->volume_id_ = GenerateVolumeId(*volume); |
| volume->volume_label_ = std::move(display_name); |
| volume->watchable_ = true; |
| volume->vm_type_ = vm_type; |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForAndroidFiles( |
| base::FilePath mount_path) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->type_ = VOLUME_TYPE_ANDROID_FILES; |
| // Keep source_path empty. |
| volume->source_ = SOURCE_SYSTEM; |
| volume->mount_path_ = std::move(mount_path); |
| volume->volume_id_ = GenerateVolumeId(*volume); |
| volume->volume_label_ = |
| GetStringUTF8(IDS_FILE_BROWSER_ANDROID_FILES_ROOT_LABEL); |
| volume->watchable_ = true; |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForDocumentsProvider( |
| const std::string& authority, |
| const std::string& root_id, |
| const std::string& title, |
| const std::string& summary, |
| const GURL& icon_url, |
| bool read_only, |
| const std::string& optional_fusebox_subdir) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->type_ = VOLUME_TYPE_DOCUMENTS_PROVIDER; |
| // Keep source_path empty. |
| volume->source_ = SOURCE_SYSTEM; |
| volume->mount_path_ = arc::GetDocumentsProviderMountPath(authority, root_id); |
| 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()) { |
| ash::file_system_provider::IconSet icon_set; |
| icon_set.SetIcon(ash::file_system_provider::IconSet::IconSize::SIZE_32x32, |
| icon_url); |
| volume->icon_set_ = icon_set; |
| } |
| |
| if (!optional_fusebox_subdir.empty()) { |
| volume->file_system_type_ = util::kFuseBox; |
| volume->volume_id_.insert(0, util::kFuseBox); |
| volume->mount_path_ = |
| base::FilePath(util::kFuseBoxMediaPath).Append(optional_fusebox_subdir); |
| if (ash::features::IsFileManagerFuseBoxDebugEnabled()) { |
| volume->volume_label_.insert(0, "fusebox "); |
| } |
| } |
| |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForSmb(base::FilePath mount_point, |
| std::string display_name) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->type_ = VOLUME_TYPE_SMB; |
| // Keep source_path empty. |
| volume->source_ = SOURCE_NETWORK; |
| volume->mount_path_ = std::move(mount_point); |
| volume->volume_id_ = GenerateVolumeId(*volume); |
| volume->volume_label_ = std::move(display_name); |
| volume->watchable_ = false; |
| volume->is_read_only_ = false; |
| return volume; |
| } |
| |
| // ShareCache is not visible in the file manager and so this volume does not |
| // represent a real, user-visible Volume. However, shared files can be read |
| // through ImageLoader, which needs a Volume present to be able to read from the |
| // directory. |
| // static |
| std::unique_ptr<Volume> Volume::CreateForShareCache(base::FilePath mount_path) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->type_ = VOLUME_TYPE_SYSTEM_INTERNAL; |
| // Keep source_path empty. |
| volume->source_ = SOURCE_SYSTEM; |
| volume->mount_path_ = std::move(mount_path); |
| volume->volume_id_ = GenerateVolumeId(*volume); |
| volume->watchable_ = false; |
| volume->is_read_only_ = true; |
| volume->hidden_ = true; |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForTesting(base::FilePath path, |
| VolumeType volume_type, |
| ash::DeviceType device_type, |
| bool read_only, |
| base::FilePath device_path, |
| std::string drive_label, |
| std::string file_system_type, |
| bool hidden, |
| bool watchable) { |
| 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_ = std::move(path); |
| volume->storage_device_path_ = std::move(device_path); |
| volume->is_read_only_ = read_only; |
| volume->volume_id_ = GenerateVolumeId(*volume); |
| volume->drive_label_ = std::move(drive_label); |
| volume->file_system_type_ = std::move(file_system_type); |
| volume->hidden_ = hidden; |
| volume->watchable_ = watchable; |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForTesting(base::FilePath device_path, |
| base::FilePath mount_path) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->storage_device_path_ = std::move(device_path); |
| volume->mount_path_ = std::move(mount_path); |
| return volume; |
| } |
| |
| // static |
| std::unique_ptr<Volume> Volume::CreateForTesting( |
| base::FilePath path, |
| VolumeType volume_type, |
| std::optional<guest_os::VmType> vm_type, |
| base::FilePath source_path) { |
| std::unique_ptr<Volume> volume(new Volume()); |
| volume->mount_path_ = std::move(path); |
| volume->type_ = volume_type; |
| volume->vm_type_ = vm_type; |
| volume->volume_id_ = GenerateVolumeId(*volume); |
| volume->source_path_ = std::move(source_path); |
| return volume; |
| } |
| |
| } // namespace file_manager |