chromium/chrome/browser/ui/ash/holding_space/holding_space_util.cc

// Copyright 2020 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/ui/ash/holding_space/holding_space_util.h"

#include "ash/public/cpp/holding_space/holding_space_constants.h"
#include "ash/public/cpp/holding_space/holding_space_util.h"
#include "ash/public/cpp/image_util.h"
#include "base/barrier_closure.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/file_manager/app_id.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ui/ash/thumbnail_loader/thumbnail_loader.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/user.h"
#include "storage/browser/file_system/file_system_context.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "url/gurl.h"

namespace ash {
namespace holding_space_util {
namespace {

// Helpers ---------------------------------------------------------------------

HoldingSpaceFile::FileSystemType ToHoldingSpaceFileSystemType(
    storage::FileSystemType file_system_type) {
  switch (file_system_type) {
    case storage::FileSystemType::kFileSystemTypeArcContent:
      return HoldingSpaceFile::FileSystemType::kArcContent;
    case storage::FileSystemType::kFileSystemTypeArcDocumentsProvider:
      return HoldingSpaceFile::FileSystemType::kArcDocumentsProvider;
    case storage::FileSystemType::kFileSystemTypeDeviceMedia:
      return HoldingSpaceFile::FileSystemType::kDeviceMedia;
    case storage::FileSystemType::kFileSystemTypeDeviceMediaAsFileStorage:
      return HoldingSpaceFile::FileSystemType::kDeviceMediaAsFileStorage;
    case storage::FileSystemType::kFileSystemTypeDragged:
      return HoldingSpaceFile::FileSystemType::kDragged;
    case storage::FileSystemType::kFileSystemTypeDriveFs:
      return HoldingSpaceFile::FileSystemType::kDriveFs;
    case storage::FileSystemType::kFileSystemTypeExternal:
      return HoldingSpaceFile::FileSystemType::kExternal;
    case storage::FileSystemType::kFileSystemTypeForTransientFile:
      return HoldingSpaceFile::FileSystemType::kForTransientFile;
    case storage::FileSystemType::kFileSystemTypeFuseBox:
      return HoldingSpaceFile::FileSystemType::kFuseBox;
    case storage::FileSystemType::kFileSystemTypeIsolated:
      return HoldingSpaceFile::FileSystemType::kIsolated;
    case storage::FileSystemType::kFileSystemTypeLocal:
      return HoldingSpaceFile::FileSystemType::kLocal;
    case storage::FileSystemType::kFileSystemTypeLocalForPlatformApp:
      return HoldingSpaceFile::FileSystemType::kLocalForPlatformApp;
    case storage::FileSystemType::kFileSystemTypeLocalMedia:
      return HoldingSpaceFile::FileSystemType::kLocalMedia;
    case storage::FileSystemType::kFileSystemTypePersistent:
      return HoldingSpaceFile::FileSystemType::kPersistent;
    case storage::FileSystemType::kFileSystemTypeProvided:
      return HoldingSpaceFile::FileSystemType::kProvided;
    case storage::FileSystemType::kFileSystemTypeSmbFs:
      return HoldingSpaceFile::FileSystemType::kSmbFs;
    case storage::FileSystemType::kFileSystemTypeSyncable:
      return HoldingSpaceFile::FileSystemType::kSyncable;
    case storage::FileSystemType::kFileSystemTypeSyncableForInternalSync:
      return HoldingSpaceFile::FileSystemType::kSyncableForInternalSync;
    case storage::FileSystemType::kFileSystemTypeTemporary:
      return HoldingSpaceFile::FileSystemType::kTemporary;
    case storage::FileSystemType::kFileSystemTypeTest:
      return HoldingSpaceFile::FileSystemType::kTest;
    case storage::FileSystemType::kFileSystemTypeUnknown:
      return HoldingSpaceFile::FileSystemType::kUnknown;
    case storage::FileSystemType::kFileSystemInternalTypeEnumStart:
    case storage::FileSystemType::kFileSystemInternalTypeEnumEnd:
      NOTREACHED();
  }
}

}  // namespace

// ValidityRequirement ---------------------------------------------------------

ValidityRequirement::ValidityRequirement() = default;
ValidityRequirement::ValidityRequirement(const ValidityRequirement&) = default;
ValidityRequirement::ValidityRequirement(ValidityRequirement&& other) = default;

// Utilities -------------------------------------------------------------------

bool ShouldSkipPathCheck(Profile* profile, const base::FilePath& path) {
  // Drive FS may be in the middle of restarting, so if it is enabled but not
  // mounted, assume any files in drive are valid.
  const auto* drive_integration_service =
      drive::DriveIntegrationServiceFactory::FindForProfile(profile);
  return drive_integration_service && drive_integration_service->is_enabled() &&
         !drive_integration_service->IsMounted() &&
         drive_integration_service->GetMountPointPath().IsParent(path);
}

void FilePathValid(Profile* profile,
                   FilePathWithValidityRequirement file_path_with_requirement,
                   FilePathValidCallback callback) {
  auto* user = ProfileHelper::Get()->GetUserByProfile(profile);
  file_manager::util::GetMetadataForPath(
      file_manager::util::GetFileManagerFileSystemContext(profile),
      file_path_with_requirement.first,
      // NOTE: Provided file systems DCHECK if no metadata field is requested.
      // TODO(http://b/274011452): Investigate if provided file systems should
      //                           be supported by holding space.
      // TODO(http://b/274011722): Investigate if we can remove time based
      //                           validation of items in holding space.
      {storage::FileSystemOperation::GetMetadataField::kLastModified},
      base::BindOnce(
          [](FilePathValidCallback callback,
             FilePathWithValidityRequirement file_path_with_requirement,
             AccountId account_id, base::File::Error result,
             const base::File::Info& file_info) {
            Profile* profile =
                ProfileHelper::Get()->GetProfileByAccountId(account_id);
            if (profile && ShouldSkipPathCheck(
                               profile, file_path_with_requirement.first)) {
              std::move(callback).Run(true);
              return;
            }

            bool valid = true;
            const ValidityRequirement& requirement =
                file_path_with_requirement.second;
            if (requirement.must_exist)
              valid = result == base::File::Error::FILE_OK;
            if (valid && requirement.must_be_newer_than) {
              valid =
                  file_info.creation_time >
                  base::Time::Now() - requirement.must_be_newer_than.value();
            }
            std::move(callback).Run(valid);
          },
          std::move(callback), file_path_with_requirement,
          user ? user->GetAccountId() : EmptyAccountId()));
}

void PartitionFilePathsByExistence(
    Profile* profile,
    FilePathList file_paths,
    PartitionFilePathsByExistenceCallback callback) {
  FilePathsWithValidityRequirements file_paths_with_requirements;
  for (const auto& file_path : file_paths)
    file_paths_with_requirements.push_back({file_path, /*requirements=*/{}});
  PartitionFilePathsByValidity(profile, file_paths_with_requirements,
                               std::move(callback));
}

void PartitionFilePathsByValidity(
    Profile* profile,
    FilePathsWithValidityRequirements file_paths_with_requirements,
    PartitionFilePathsByValidityCallback callback) {
  if (file_paths_with_requirements.empty()) {
    std::move(callback).Run(/*valid_file_paths=*/{},
                            /*invalid_file_paths=*/{});
    return;
  }

  auto valid_file_paths = std::make_unique<FilePathList>();
  auto invalid_file_paths = std::make_unique<FilePathList>();

  auto* valid_file_paths_ptr = valid_file_paths.get();
  auto* invalid_file_paths_ptr = invalid_file_paths.get();

  FilePathList file_paths;
  for (const auto& file_path_with_requirement : file_paths_with_requirements)
    file_paths.push_back(file_path_with_requirement.first);

  // This `barrier_closure` will be run after verifying the existence of all
  // `file_paths`. It is expected that both `valid_file_paths` and
  // `invalid_file_paths` will have been populated by the time of
  // invocation.
  base::RepeatingClosure barrier_closure = base::BarrierClosure(
      file_paths_with_requirements.size(),
      base::BindOnce(
          [](FilePathList sorted_file_paths,
             std::unique_ptr<FilePathList> valid_file_paths,
             std::unique_ptr<FilePathList> invalid_file_paths,
             PartitionFilePathsByValidityCallback callback) {
            // We need to sort our partitioned vectors to match the original
            // order that was provided at call time. This is necessary as the
            // original order may have been lost due to race conditions when
            // checking for file path existence.
            auto sort = [&sorted_file_paths](FilePathList* file_paths) {
              FilePathList temp_file_paths;
              temp_file_paths.swap(*file_paths);
              for (const auto& file_path : sorted_file_paths) {
                if (base::Contains(temp_file_paths, file_path))
                  file_paths->push_back(file_path);
              }
            };
            sort(valid_file_paths.get());
            sort(invalid_file_paths.get());

            // Ownership of the partitioned vectors is passed to `callback`.
            std::move(callback).Run(std::move(*valid_file_paths),
                                    std::move(*invalid_file_paths));
          },
          /*sorted_file_paths=*/file_paths, std::move(valid_file_paths),
          std::move(invalid_file_paths), std::move(callback)));

  // Verify existence of each `file_path`. Upon successful check of existence,
  // each `file_path` should be pushed into either `valid_file_paths` or
  // `invalid_file_paths` as appropriate.
  for (const auto& file_path_with_requirement : file_paths_with_requirements) {
    FilePathValid(
        profile, file_path_with_requirement,
        base::BindOnce(
            [](base::FilePath file_path, FilePathList* valid_file_paths,
               FilePathList* invalid_file_paths,
               base::RepeatingClosure barrier_closure, bool exists) {
              if (exists)
                valid_file_paths->push_back(file_path);
              else
                invalid_file_paths->push_back(file_path);
              barrier_closure.Run();
            },
            file_path_with_requirement.first,
            base::Unretained(valid_file_paths_ptr),
            base::Unretained(invalid_file_paths_ptr), barrier_closure));
  }
}

HoldingSpaceFile::FileSystemType ResolveFileSystemType(
    Profile* profile,
    const GURL& file_system_url) {
  return ToHoldingSpaceFileSystemType(
      file_manager::util::GetFileManagerFileSystemContext(profile)
          ->CrackURLInFirstPartyContext(file_system_url)
          .type());
}

GURL ResolveFileSystemUrl(Profile* profile, const base::FilePath& file_path) {
  GURL file_system_url;
  if (!file_manager::util::ConvertAbsoluteFilePathToFileSystemUrl(
          profile, file_path, file_manager::util::GetFileManagerURL(),
          &file_system_url)) {
    VLOG(2) << "Unable to convert file path to File System URL.";
  }
  return file_system_url;
}

std::unique_ptr<HoldingSpaceImage> ResolveImage(
    ThumbnailLoader* thumbnail_loader,
    HoldingSpaceItem::Type type,
    const base::FilePath& file_path) {
  return ResolveImageWithPlaceholderImageSkiaResolver(
      thumbnail_loader,
      /*placeholder_image_skia_resolver=*/base::NullCallback(), type,
      file_path);
}

std::unique_ptr<HoldingSpaceImage> ResolveImageWithPlaceholderImageSkiaResolver(
    ThumbnailLoader* thumbnail_loader,
    HoldingSpaceImage::PlaceholderImageSkiaResolver
        placeholder_image_skia_resolver,
    HoldingSpaceItem::Type type,
    const base::FilePath& file_path) {
  return std::make_unique<HoldingSpaceImage>(
      GetMaxImageSizeForType(type), file_path,
      /*async_bitmap_resolver=*/
      base::BindRepeating(
          [](const base::WeakPtr<ThumbnailLoader>& thumbnail_loader,
             const base::FilePath& file_path, const gfx::Size& size,
             HoldingSpaceImage::BitmapCallback callback) {
            if (thumbnail_loader)
              thumbnail_loader->Load({file_path, size}, std::move(callback));
          },
          thumbnail_loader->GetWeakPtr()),
      /*placeholder_image_skia_resolver=*/
      base::BindRepeating(
          [](HoldingSpaceImage::PlaceholderImageSkiaResolver
                 placeholder_image_skia_resolver,
             const base::FilePath& file_path, const gfx::Size& size,
             const std::optional<bool>& dark_background,
             const std::optional<bool>& is_folder) {
            // When the initial placeholder is being created during
            // construction, `dark_background` and `is_folder` will be absent.
            // In that case, don't show a placeholder to minimize jank.
            if (!dark_background.has_value() && !is_folder.has_value())
              return image_util::CreateEmptyImage(size);
            // If an explicit `placeholder_image_skia_resolver` has been
            // specified, use it to create the appropriate placeholder image.
            if (!placeholder_image_skia_resolver.is_null()) {
              return placeholder_image_skia_resolver.Run(
                  file_path, size, dark_background, is_folder);
            }
            // Otherwise, fallback to default behavior which is to create an
            // image corresponding to the file type of the associated backing
            // file.
            return HoldingSpaceImage::
                CreateDefaultPlaceholderImageSkiaResolver()
                    .Run(file_path, size, dark_background, is_folder);
          },
          placeholder_image_skia_resolver));
}

}  // namespace holding_space_util
}  // namespace ash