chromium/chrome/browser/ash/extensions/file_manager/private_api_util.cc

// 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/extensions/file_manager/private_api_util.h"

#include <stddef.h>
#include <memory>
#include <string>
#include <utility>

#include "ash/constants/ash_features.h"
#include "base/files/file_error_or.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/location.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/drive/file_system_util.h"
#include "chrome/browser/ash/extensions/file_manager/event_router.h"
#include "chrome/browser/ash/extensions/file_manager/event_router_factory.h"
#include "chrome/browser/ash/file_manager/app_id.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_manager/filesystem_api_util.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/ash/file_manager/snapshot_manager.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/fileapi/external_file_url_util.h"
#include "chrome/browser/ash/fileapi/file_system_backend.h"
#include "chrome/browser/ash/guest_os/public/guest_os_mount_provider.h"
#include "chrome/browser/ash/guest_os/public/guest_os_mount_provider_registry.h"
#include "chrome/browser/ash/guest_os/public/guest_os_service.h"
#include "chrome/browser/ash/guest_os/public/types.h"
#include "chrome/browser/ash/policy/skyvault/policy_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/file_manager_private.h"
#include "chromeos/ash/components/drivefs/drivefs_pinning_manager.h"
#include "chromeos/ash/components/drivefs/drivefs_util.h"
#include "chromeos/ash/components/drivefs/mojom/drivefs.mojom.h"
#include "components/drive/drive_api_util.h"
#include "components/drive/drive_pref_names.h"
#include "components/drive/file_errors.h"
#include "components/drive/file_system_core_util.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_security_policy.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "storage/browser/file_system/external_mount_points.h"
#include "storage/browser/file_system/file_system_context.h"
#include "storage/browser/file_system/file_system_url.h"
#include "storage/common/file_system/file_system_info.h"
#include "ui/shell_dialogs/selected_file_info.h"
#include "url/gurl.h"

namespace file_manager::util {
namespace {

namespace fmp = extensions::api::file_manager_private;

// The struct is used for GetSelectedFileInfo().
struct GetSelectedFileInfoParams {
  GetSelectedFileInfoLocalPathOption local_path_option;
  GetSelectedFileInfoCallback callback;
  std::vector<base::FilePath> file_paths;
  std::vector<ui::SelectedFileInfo> selected_files;
};

// The callback type for GetFileNativeLocalPathFor{Opening,Saving}. It receives
// the resolved local path when successful, and receives empty path for failure.
using LocalPathCallback = base::OnceCallback<void(const base::FilePath&)>;

// Gets a resolved local file path of a non native |path| for file opening.
void GetFileNativeLocalPathForOpening(Profile* profile,
                                      const base::FilePath& path,
                                      LocalPathCallback callback) {
  VolumeManager::Get(profile)->snapshot_manager()->CreateManagedSnapshot(
      path, std::move(callback));
}

// Gets a resolved local file path of a non native |path| for file saving.
void GetFileNativeLocalPathForSaving(Profile* profile,
                                     const base::FilePath& path,
                                     LocalPathCallback callback) {
  // TODO(kinaba): For now, there are no writable non-local volumes.
  NOTREACHED_IN_MIGRATION();
  std::move(callback).Run(base::FilePath());
}

// Forward declarations of helper functions for GetSelectedFileInfo().
void ContinueGetSelectedFileInfo(
    Profile* profile,
    std::unique_ptr<GetSelectedFileInfoParams> params,
    const base::FilePath& local_file_path);

void ContinueGetSelectedFileInfoWithDriveFsMetadata(
    Profile* profile,
    std::unique_ptr<GetSelectedFileInfoParams> params,
    drive::FileError error,
    drivefs::mojom::FileMetadataPtr metadata);

// Part of GetSelectedFileInfo().
void GetSelectedFileInfoInternal(
    Profile* profile,
    std::unique_ptr<GetSelectedFileInfoParams> params) {
  DCHECK(profile);

  for (size_t i = params->selected_files.size(); i < params->file_paths.size();
       ++i) {
    const base::FilePath& file_path = params->file_paths[i];

    if (file_manager::util::IsUnderNonNativeLocalPath(profile, file_path)) {
      // When the caller of the select file dialog wants local file paths, and
      // the selected path does not point to a native local path (e.g., Drive,
      // MTP, or provided file system), we should resolve the path.
      switch (params->local_path_option) {
        case NO_LOCAL_PATH_RESOLUTION: {
          // Pass empty local path.
          params->selected_files.emplace_back(file_path, base::FilePath());

          GURL external_file_url =
              ash::CreateExternalFileURLFromPath(profile, file_path);
          if (!external_file_url.is_empty()) {
            params->selected_files.back().url.emplace(
                std::move(external_file_url));
          }
          break;
        }
        case NEED_LOCAL_PATH_FOR_OPENING: {
          GetFileNativeLocalPathForOpening(
              profile, file_path,
              base::BindOnce(&ContinueGetSelectedFileInfo, profile,
                             std::move(params)));
          return;  // Remaining work is done in ContinueGetSelectedFileInfo.
        }
        case NEED_LOCAL_PATH_FOR_SAVING: {
          GetFileNativeLocalPathForSaving(
              profile, file_path,
              base::BindOnce(&ContinueGetSelectedFileInfo, profile,
                             std::move(params)));
          return;  // Remaining work is done in ContinueGetSelectedFileInfo.
        }
      }
    } else {
      // Hosted docs and encrypted files can only accessed by navigating
      // to their URLs. Get the metadata for the file from DriveFS and
      // if needed populate the |url| field in the SelectedFileInfo.
      auto* integration_service =
          drive::util::GetIntegrationServiceByProfile(profile);
      base::FilePath drive_mount_relative_path;
      if (integration_service && integration_service->GetDriveFsInterface() &&
          integration_service->GetRelativeDrivePath(
              file_path, &drive_mount_relative_path)) {
        integration_service->GetDriveFsInterface()->GetMetadata(
            drive_mount_relative_path,
            base::BindOnce(&ContinueGetSelectedFileInfoWithDriveFsMetadata,
                           profile, std::move(params)));
        return;
      }
      params->selected_files.emplace_back(file_path, file_path);
    }
  }

  // Populate the virtual path for any files on a external mount point. This
  // lets consumers that are capable of using a virtual path use this rather
  // than file_path, which can make certain operations more efficient.
  for (auto& file_info : params->selected_files) {
    auto* external_mount_points =
        storage::ExternalMountPoints::GetSystemInstance();
    base::FilePath virtual_path;
    if (external_mount_points->GetVirtualPath(file_info.file_path,
                                              &virtual_path)) {
      file_info.virtual_path.emplace(std::move(virtual_path));
    } else {
      LOG(ERROR) << "Failed to get external virtual path: "
                 << file_info.file_path;
    }
  }

  std::move(params->callback).Run(params->selected_files);
}

// Part of GetSelectedFileInfo().
void ContinueGetSelectedFileInfo(
    Profile* profile,
    std::unique_ptr<GetSelectedFileInfoParams> params,
    const base::FilePath& local_path) {
  if (local_path.empty()) {
    std::move(params->callback).Run(std::vector<ui::SelectedFileInfo>());
    return;
  }
  const int index = params->selected_files.size();
  const base::FilePath& file_path = params->file_paths[index];
  params->selected_files.emplace_back(file_path, local_path);
  GetSelectedFileInfoInternal(profile, std::move(params));
}

// Part of GetSelectedFileInfo().
void ContinueGetSelectedFileInfoWithDriveFsMetadata(
    Profile* profile,
    std::unique_ptr<GetSelectedFileInfoParams> params,
    drive::FileError error,
    drivefs::mojom::FileMetadataPtr metadata) {
  const int index = params->selected_files.size();
  const auto& path = params->file_paths[index];
  params->selected_files.emplace_back(path, path);
  if (metadata &&
      (drivefs::IsHosted(metadata->type) ||
       drive::util::IsEncryptedMimeType(metadata->content_mime_type)) &&
      !metadata->alternate_url.empty()) {
    params->selected_files.back().url.emplace(
        std::move(metadata->alternate_url));
  }
  GetSelectedFileInfoInternal(profile, std::move(params));
}

std::string GetShareUrlFromAlternateUrl(const GURL& alternate_url) {
  // Set |share_url| to a modified version of |alternate_url| that opens the
  // sharing dialog for files and folders (add ?userstoinvite="" to the URL).
  GURL::Replacements replacements;
  std::string new_query =
      (alternate_url.has_query() ? alternate_url.query() + "&" : "") +
      "userstoinvite=%22%22";
  replacements.SetQueryStr(new_query);

  return alternate_url.ReplaceComponents(replacements).spec();
}

fmp::VmType VmTypeToJs(guest_os::VmType vm_type) {
  switch (vm_type) {
    case guest_os::VmType::TERMINA:
      return fmp::VmType::kTermina;
    case guest_os::VmType::PLUGIN_VM:
      return fmp::VmType::kPluginVm;
    case guest_os::VmType::BOREALIS:
      return fmp::VmType::kBorealis;
    case guest_os::VmType::BRUSCHETTA:
      return fmp::VmType::kBruschetta;
    case guest_os::VmType::ARCVM:
      return fmp::VmType::kArcvm;
    case guest_os::VmType::BAGUETTE:
      // Baguette currently isn't hooked up to file manager
      return fmp::VmType::kNone;
    case guest_os::VmType::UNKNOWN:
    case guest_os::VmType::VmType_INT_MIN_SENTINEL_DO_NOT_USE_:
    case guest_os::VmType::VmType_INT_MAX_SENTINEL_DO_NOT_USE_:
      NOTREACHED_IN_MIGRATION();
      return fmp::VmType::kNone;
  }
}

fmp::BulkPinStage DrivefsPinStageToJs(drivefs::pinning::Stage stage) {
  switch (stage) {
    using enum drivefs::pinning::Stage;
    case kStopped:
      return fmp::BulkPinStage::kStopped;
    case kPausedOffline:
      return fmp::BulkPinStage::kPausedOffline;
    case kPausedBatterySaver:
      return fmp::BulkPinStage::kPausedBatterySaver;
    case kGettingFreeSpace:
      return fmp::BulkPinStage::kGettingFreeSpace;
    case kListingFiles:
      return fmp::BulkPinStage::kListingFiles;
    case kSyncing:
      return fmp::BulkPinStage::kSyncing;
    case kSuccess:
      return fmp::BulkPinStage::kSuccess;
    case kNotEnoughSpace:
      return fmp::BulkPinStage::kNotEnoughSpace;
    case kCannotGetFreeSpace:
      return fmp::BulkPinStage::kCannotGetFreeSpace;
    case kCannotListFiles:
      return fmp::BulkPinStage::kCannotListFiles;
    case kCannotEnableDocsOffline:
      return fmp::BulkPinStage::kCannotEnableDocsOffline;
  }

  NOTREACHED_IN_MIGRATION();
  return fmp::BulkPinStage::kNone;
}

bool IsBulkPinningEnabledForProfile(Profile* profile) {
  if (!profile || !profile->GetPrefs()) {
    return false;
  }
  return profile->GetPrefs()->GetBoolean(
      drive::prefs::kDriveFsBulkPinningEnabled);
}

drivefs::pinning::PinningManager* GetPinningManager(Profile* profile) {
  if (!profile) {
    return nullptr;
  }
  drive::DriveIntegrationService* integration_service =
      drive::DriveIntegrationServiceFactory::FindForProfile(profile);
  if (!integration_service || !integration_service->IsMounted()) {
    return nullptr;
  }

  return integration_service->GetPinningManager();
}

bool IsPathUnderMyDrive(const base::FilePath& relative_path) {
  return base::FilePath("/")
      .Append(drive::util::kDriveMyDriveRootDirName)
      .IsParent(relative_path);
}

// Part of GURLToEntryData().
void GURLToEntryDataOnResolve(
    std::string entry_name,
    const GURL& url,
    base::OnceCallback<void(base::FileErrorOr<fmp::EntryData>)> callback,
    base::File::Error result,
    const storage::FileSystemInfo& file_system_info,
    const base::FilePath& file_path,
    storage::FileSystemContext::ResolvedEntryType type) {
  if (result != base::File::FILE_OK) {
    std::move(callback).Run(base::unexpected(result));
    return;
  }
  fmp::EntryData entry_data;
  switch (type) {
    case storage::FileSystemContext::RESOLVED_ENTRY_FILE:
      entry_data.is_directory = false;
      break;
    case storage::FileSystemContext::RESOLVED_ENTRY_DIRECTORY:
      entry_data.is_directory = true;
      break;
    case storage::FileSystemContext::RESOLVED_ENTRY_NOT_FOUND:
      std::move(callback).Run(
          base::unexpected(base::File::FILE_ERROR_NOT_FOUND));
      return;
  }
  entry_data.name = std::move(entry_name);
  entry_data.entry_url = url.spec();
  entry_data.filesystem.name = file_system_info.name;
  entry_data.filesystem.root_url = file_system_info.root_url.spec();
  std::move(callback).Run(std::move(entry_data));
}

}  // namespace

// Creates an instance and starts the process.
void SingleEntryPropertiesGetterForDriveFs::Start(
    const storage::FileSystemURL& file_system_url,
    Profile* const profile,
    ResultCallback callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  SingleEntryPropertiesGetterForDriveFs* instance =
      new SingleEntryPropertiesGetterForDriveFs(file_system_url, profile,
                                                std::move(callback));
  instance->StartProcess();

  // The instance will be destroyed by itself.
}

SingleEntryPropertiesGetterForDriveFs::SingleEntryPropertiesGetterForDriveFs(
    const storage::FileSystemURL& file_system_url,
    Profile* const profile,
    ResultCallback callback)
    : callback_(std::move(callback)),
      file_system_url_(file_system_url),
      running_profile_(profile),
      properties_(std::make_unique<fmp::EntryProperties>()) {
  DCHECK(callback_);
  DCHECK(profile);
}

SingleEntryPropertiesGetterForDriveFs::
    ~SingleEntryPropertiesGetterForDriveFs() = default;

void SingleEntryPropertiesGetterForDriveFs::StartProcess() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  drive::DriveIntegrationService* integration_service =
      drive::DriveIntegrationServiceFactory::FindForProfile(running_profile_);
  if (!integration_service || !integration_service->IsMounted()) {
    CompleteGetEntryProperties(drive::FILE_ERROR_SERVICE_UNAVAILABLE);
    return;
  }
  if (!integration_service->GetRelativeDrivePath(file_system_url_.path(),
                                                 &relative_path_)) {
    CompleteGetEntryProperties(drive::FILE_ERROR_INVALID_OPERATION);
    return;
  }

  auto* drivefs_interface = integration_service->GetDriveFsInterface();
  if (!drivefs_interface) {
    CompleteGetEntryProperties(drive::FILE_ERROR_SERVICE_UNAVAILABLE);
    return;
  }

  file_manager::EventRouter* event_router =
      file_manager::EventRouterFactory::GetForProfile(running_profile_);
  if (event_router) {
    drivefs::SyncState sync_state =
        event_router->GetDriveSyncStateForPath(file_system_url_.path());
    properties_->progress = sync_state.progress;
    switch (sync_state.status) {
      using enum drivefs::SyncStatus;
      case kQueued:
        properties_->sync_status = fmp::SyncStatus::kQueued;
        break;
      case kInProgress:
        properties_->sync_status = fmp::SyncStatus::kInProgress;
        break;
      case kError:
        properties_->sync_status = fmp::SyncStatus::kError;
        break;
      default:
        properties_->sync_status = fmp::SyncStatus::kNotFound;
        break;
    }
  }

  drivefs_interface->GetMetadata(
      relative_path_,
      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
          base::BindOnce(&SingleEntryPropertiesGetterForDriveFs::OnGetFileInfo,
                         weak_ptr_factory_.GetWeakPtr()),
          drive::FILE_ERROR_SERVICE_UNAVAILABLE, nullptr));
}

void SingleEntryPropertiesGetterForDriveFs::OnGetFileInfo(
    drive::FileError error,
    drivefs::mojom::FileMetadataPtr metadata) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (!metadata) {
    CompleteGetEntryProperties(error);
    return;
  }

  properties_->size = metadata->size;
  properties_->present = metadata->available_offline;
  properties_->dirty = metadata->dirty;
  // Dirty files have unsynced changes hence will eventually get queued for
  // syncing. Let's make sure we report them as queued as soon as possible.
  if (metadata->dirty) {
    properties_->sync_status = file_manager_private::SyncStatus::kQueued;
  }
  properties_->hosted = drivefs::IsHosted(metadata->type);

  properties_->available_offline =
      metadata->available_offline ||
      drive::util::IsEncryptedMimeType(metadata->content_mime_type) ||
      *properties_->hosted;
  properties_->available_when_metered =
      metadata->available_offline || *properties_->hosted;
  properties_->pinned = metadata->pinned;

  if (drive::util::IsDriveFsBulkPinningAvailable(running_profile_)) {
    properties_->available_offline =
        (drivefs::IsHosted(metadata->type) &&
         !drive::util::IsPinnableGDocMimeType(metadata->content_mime_type))
            ? false
            : metadata->available_offline;
    properties_->available_when_metered = properties_->available_offline;
    properties_->pinned = metadata->pinned;

    if (IsBulkPinningEnabledForProfile(running_profile_) &&
        IsPathUnderMyDrive(relative_path_)) {
      drivefs::pinning::PinningManager* const pinning_manager =
          GetPinningManager(running_profile_);

      const auto stable_id =
          drivefs::pinning::PinningManager::Id(metadata->stable_id);
      if (properties_->sync_status ==
              file_manager_private::SyncStatus::kNotFound &&
          pinning_manager->IsTrackedAndUnpinned(stable_id)) {
        // The `PinningManager` maintains a list of 200 items that it pins, if
        // the item is not within these 200 items it will eventually be pinned,
        // but does not enter into a queued state just yet. This ensures the
        // queued state is reflected for items that will be pinned but haven't
        // called `SetPinned` yet.
        properties_->sync_status = file_manager_private::SyncStatus::kQueued;
      }

      if (drive::util::IsPinnableGDocMimeType(metadata->content_mime_type)) {
        // When bulk pinning is enabled, hosted files should reflect the pinned
        // state as their available offline state.
        properties_->pinned = properties_->available_offline;
      }
    }
  }

  properties_->shared = metadata->shared;
  properties_->starred = metadata->starred;

  if (metadata->modification_time != base::Time()) {
    properties_->modification_time =
        metadata->modification_time.InMillisecondsFSinceUnixEpoch();
  }
  if (metadata->last_viewed_by_me_time != base::Time()) {
    properties_->modification_by_me_time =
        metadata->last_viewed_by_me_time.InMillisecondsFSinceUnixEpoch();
  }
  if (!metadata->content_mime_type.empty()) {
    properties_->content_mime_type = metadata->content_mime_type;
  }
  if (!metadata->custom_icon_url.empty()) {
    properties_->custom_icon_url = std::move(metadata->custom_icon_url);
  }
  if (!metadata->alternate_url.empty()) {
    properties_->alternate_url = std::move(metadata->alternate_url);
    properties_->share_url =
        GetShareUrlFromAlternateUrl(GURL(*properties_->alternate_url));
  }
  if (metadata->image_metadata) {
    properties_->image_height = metadata->image_metadata->height;
    properties_->image_width = metadata->image_metadata->width;
    properties_->image_rotation = metadata->image_metadata->rotation;
  }

  properties_->can_delete = metadata->capabilities->can_delete;
  properties_->can_rename = metadata->capabilities->can_rename;
  properties_->can_add_children = metadata->capabilities->can_add_children;

  // Only set the |can_copy| capability for hosted documents; for other files,
  // we must have read access, so |can_copy| is implicitly true.
  properties_->can_copy =
      !*properties_->hosted || metadata->capabilities->can_copy;
  properties_->can_share = metadata->capabilities->can_share;

  properties_->can_pin =
      metadata->can_pin == drivefs::mojom::FileMetadata::CanPinStatus::kOk;

  if (drivefs::IsAFile(metadata->type)) {
    properties_->thumbnail_url =
        base::StrCat({"drivefs:", file_system_url_.ToGURL().spec()});
    properties_->cropped_thumbnail_url = *properties_->thumbnail_url;
  }

  if (metadata->folder_feature) {
    properties_->is_machine_root = metadata->folder_feature->is_machine_root;
    properties_->is_external_media =
        metadata->folder_feature->is_external_media;
    properties_->is_arbitrary_sync_folder =
        metadata->folder_feature->is_arbitrary_sync_folder;
  }

  if (metadata->shortcut_details) {
    properties_->shortcut =
        (metadata->shortcut_details->target_lookup_status !=
         drivefs::mojom::ShortcutDetails::LookupStatus::kUnknown);
  } else {
    properties_->shortcut = false;
  }

  CompleteGetEntryProperties(drive::FILE_ERROR_OK);
}

void SingleEntryPropertiesGetterForDriveFs::CompleteGetEntryProperties(
    drive::FileError error) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK(callback_);

  std::move(callback_).Run(std::move(properties_),
                           drive::FileErrorToBaseFileError(error));
  content::GetUIThreadTaskRunner({})->DeleteSoon(FROM_HERE, this);
}

void FillIconSet(fmp::IconSet* output,
                 const ash::file_system_provider::IconSet& input) {
  DCHECK(output);
  using ash::file_system_provider::IconSet;
  if (input.HasIcon(IconSet::IconSize::SIZE_16x16)) {
    output->icon16x16_url = input.GetIcon(IconSet::IconSize::SIZE_16x16).spec();
  }
  if (input.HasIcon(IconSet::IconSize::SIZE_32x32)) {
    output->icon32x32_url = input.GetIcon(IconSet::IconSize::SIZE_32x32).spec();
  }
}

void VolumeToVolumeMetadata(Profile* profile,
                            const Volume& volume,
                            fmp::VolumeMetadata* volume_metadata) {
  DCHECK(volume_metadata);

  volume_metadata->volume_id = volume.volume_id();

  // TODO(kinaba): fill appropriate information once multi-profile support is
  // implemented.
  volume_metadata->profile.display_name = profile->GetProfileUserName();
  volume_metadata->profile.is_current_profile = true;

  if (!volume.source_path().empty()) {
    volume_metadata->source_path = volume.source_path().AsUTF8Unsafe();
  }
  if (!volume.remote_mount_path().empty()) {
    volume_metadata->remote_mount_path = volume.remote_mount_path().value();
  }

  switch (volume.source()) {
    case SOURCE_FILE:
      volume_metadata->source = fmp::Source::kFile;
      break;
    case SOURCE_DEVICE:
      volume_metadata->source = fmp::Source::kDevice;
      volume_metadata->is_read_only_removable_device =
          volume.is_read_only_removable_device();
      break;
    case SOURCE_NETWORK:
      volume_metadata->source = fmp::Source::kNetwork;
      break;
    case SOURCE_SYSTEM:
      volume_metadata->source = fmp::Source::kSystem;
      break;
  }

  volume_metadata->configurable = volume.configurable();
  volume_metadata->watchable = volume.watchable();

  if (volume.type() == VOLUME_TYPE_PROVIDED) {
    volume_metadata->provider_id = volume.provider_id().ToString();
    volume_metadata->file_system_id = volume.file_system_id();
  }

  FillIconSet(&volume_metadata->icon_set, volume.icon_set());

  volume_metadata->volume_label = volume.volume_label();
  volume_metadata->disk_file_system_type = volume.file_system_type();
  volume_metadata->drive_label = volume.drive_label();

  switch (volume.type()) {
    case VOLUME_TYPE_GOOGLE_DRIVE:
      volume_metadata->volume_type = fmp::VolumeType::kDrive;
      break;
    case VOLUME_TYPE_DOWNLOADS_DIRECTORY:
      volume_metadata->volume_type = fmp::VolumeType::kDownloads;
      break;
    case VOLUME_TYPE_REMOVABLE_DISK_PARTITION:
      volume_metadata->volume_type = fmp::VolumeType::kRemovable;
      break;
    case VOLUME_TYPE_MOUNTED_ARCHIVE_FILE:
      volume_metadata->volume_type = fmp::VolumeType::kArchive;
      break;
    case VOLUME_TYPE_PROVIDED:
      volume_metadata->volume_type = fmp::VolumeType::kProvided;
      break;
    case VOLUME_TYPE_MTP:
      volume_metadata->volume_type = fmp::VolumeType::kMtp;
      break;
    case VOLUME_TYPE_MEDIA_VIEW:
      volume_metadata->volume_type = fmp::VolumeType::kMediaView;
      break;
    case VOLUME_TYPE_CROSTINI:
      volume_metadata->volume_type = fmp::VolumeType::kCrostini;
      break;
    case VOLUME_TYPE_ANDROID_FILES:
      volume_metadata->volume_type = fmp::VolumeType::kAndroidFiles;
      break;
    case VOLUME_TYPE_DOCUMENTS_PROVIDER:
      volume_metadata->volume_type = fmp::VolumeType::kDocumentsProvider;
      break;
    case VOLUME_TYPE_TESTING:
      volume_metadata->volume_type = fmp::VolumeType::kTesting;
      break;
    case VOLUME_TYPE_SMB:
      volume_metadata->volume_type = fmp::VolumeType::kSmb;
      break;
    case VOLUME_TYPE_SYSTEM_INTERNAL:
      volume_metadata->volume_type = fmp::VolumeType::kSystemInternal;
      break;
    case VOLUME_TYPE_GUEST_OS:
      volume_metadata->volume_type = fmp::VolumeType::kGuestOs;
      break;
    case NUM_VOLUME_TYPE:
      NOTREACHED_IN_MIGRATION();
      break;
  }

  // Fill device_type iff the volume is removable partition.
  if (volume.type() == VOLUME_TYPE_REMOVABLE_DISK_PARTITION) {
    switch (volume.device_type()) {
      case ash::DeviceType::kUnknown:
        volume_metadata->device_type = fmp::DeviceType::kUnknown;
        break;
      case ash::DeviceType::kUSB:
        volume_metadata->device_type = fmp::DeviceType::kUsb;
        break;
      case ash::DeviceType::kSD:
        volume_metadata->device_type = fmp::DeviceType::kSd;
        break;
      case ash::DeviceType::kOpticalDisc:
      case ash::DeviceType::kDVD:
        volume_metadata->device_type = fmp::DeviceType::kOptical;
        break;
      case ash::DeviceType::kMobile:
        volume_metadata->device_type = fmp::DeviceType::kMobile;
        break;
    }
    volume_metadata->device_path = volume.storage_device_path().AsUTF8Unsafe();
    volume_metadata->is_parent_device = volume.is_parent();
  } else {
    volume_metadata->device_type = fmp::DeviceType::kNone;
  }

  volume_metadata->is_read_only = volume.is_read_only();
  volume_metadata->has_media = volume.has_media();
  volume_metadata->hidden = volume.hidden();

  switch (volume.mount_condition()) {
    default:
      LOG(ERROR) << "Unexpected mount condition: " << volume.mount_condition();
      [[fallthrough]];
    case ash::MountError::kSuccess:
      volume_metadata->mount_condition = fmp::MountError::kNone;
      break;
    case ash::MountError::kUnknownFilesystem:
      volume_metadata->mount_condition = fmp::MountError::kUnknownFilesystem;
      break;
    case ash::MountError::kUnsupportedFilesystem:
      volume_metadata->mount_condition =
          fmp::MountError::kUnsupportedFilesystem;
      break;
  }

  // If the context is known, then pass it.
  switch (volume.mount_context()) {
    case MOUNT_CONTEXT_USER:
      volume_metadata->mount_context = fmp::MountContext::kUser;
      break;
    case MOUNT_CONTEXT_AUTO:
      volume_metadata->mount_context = fmp::MountContext::kAuto;
      break;
    case MOUNT_CONTEXT_UNKNOWN:
      break;
  }

  if (volume.vm_type()) {
    volume_metadata->vm_type = VmTypeToJs(*volume.vm_type());
  }
}

base::FilePath GetLocalPathFromURL(
    scoped_refptr<storage::FileSystemContext> file_system_context,
    const GURL& url) {
  const storage::FileSystemURL filesystem_url(
      file_system_context->CrackURLInFirstPartyContext(url));
  base::FilePath path;
  if (!ash::FileSystemBackend::CanHandleURL(filesystem_url)) {
    return base::FilePath();
  }
  return filesystem_url.path();
}

void GetSelectedFileInfo(Profile* profile,
                         std::vector<base::FilePath> local_paths,
                         GetSelectedFileInfoLocalPathOption local_path_option,
                         GetSelectedFileInfoCallback callback) {
  std::unique_ptr<GetSelectedFileInfoParams> params =
      std::make_unique<GetSelectedFileInfoParams>();
  params->local_path_option = local_path_option;
  params->callback = std::move(callback);
  params->file_paths = std::move(local_paths);

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&GetSelectedFileInfoInternal, profile, std::move(params)));
}

drive::EventLogger* GetLogger(Profile* profile) {
  if (!profile) {
    return nullptr;
  }
  drive::DriveIntegrationService* service =
      drive::DriveIntegrationServiceFactory::FindForProfile(profile);
  return service ? service->GetLogger() : nullptr;
}

std::vector<fmp::MountableGuest> CreateMountableGuestList(Profile* profile) {
  auto* service = guest_os::GuestOsService::GetForProfile(profile);
  if (!service) {
    return {};
  }

  bool local_user_files_allowed =
      policy::local_user_files::LocalUserFilesAllowed();

  auto* registry = service->MountProviderRegistry();
  std::vector<fmp::MountableGuest> guests;
  for (const auto id : registry->List()) {
    fmp::MountableGuest guest;
    auto* provider = registry->Get(id);
    if (!local_user_files_allowed &&
        provider->vm_type() == guest_os::VmType::ARCVM) {
      continue;
    }
    guest.id = id;
    guest.display_name = provider->DisplayName();
    guest.vm_type = VmTypeToJs(provider->vm_type());
    guests.push_back(std::move(guest));
  }
  return guests;
}

bool ToRecentSourceFileType(fmp::FileCategory input_category,
                            ash::RecentSource::FileType* output_type) {
  switch (input_category) {
    using enum ash::RecentSource::FileType;
    case fmp::FileCategory::kNone:
      // The FileCategory is an optional parameter. Thus we convert NONE to All.
      // If the calling code does not specify the restrictions on the category
      // we do not enforce then.
    case fmp::FileCategory::kAll:
      *output_type = kAll;
      return true;
    case fmp::FileCategory::kAudio:
      *output_type = kAudio;
      return true;
    case fmp::FileCategory::kImage:
      *output_type = kImage;
      return true;
    case fmp::FileCategory::kVideo:
      *output_type = kVideo;
      return true;
    case fmp::FileCategory::kDocument:
      *output_type = kDocument;
      return true;
  }

  NOTREACHED_IN_MIGRATION();
  return false;
}

fmp::BulkPinProgress BulkPinProgressToJs(
    const drivefs::pinning::Progress& progress) {
  fmp::BulkPinProgress result;
  result.stage = DrivefsPinStageToJs(progress.stage);
  result.free_space_bytes = progress.free_space;
  result.required_space_bytes = progress.required_space;
  result.bytes_to_pin = progress.bytes_to_pin;
  result.pinned_bytes = progress.pinned_bytes;
  result.files_to_pin = progress.files_to_pin;
  result.listed_files = progress.listed_files;
  result.remaining_seconds = !progress.remaining_time.is_inf()
                                 ? progress.remaining_time.InSecondsF()
                                 : 0;
  result.should_pin = progress.should_pin;
  result.emptied_queue = progress.emptied_queue;
  return result;
}

void GURLToEntryData(
    Profile* profile,
    scoped_refptr<storage::FileSystemContext> file_system_context,
    const GURL& url,
    base::OnceCallback<void(base::FileErrorOr<fmp::EntryData>)> callback) {
  storage::FileSystemURL file_system_url =
      file_system_context->CrackURLInFirstPartyContext(url);
  std::string entry_name = GetDisplayablePath(profile, file_system_url)
                               .value_or(base::FilePath())
                               .BaseName()
                               .value();
  file_system_context->ResolveURL(
      file_system_url,
      base::BindOnce(GURLToEntryDataOnResolve, std::move(entry_name), url,
                     std::move(callback)));
}

}  // namespace file_manager::util