chromium/chrome/browser/ui/ash/picker/picker_file_suggester.cc

// Copyright 2024 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/picker/picker_file_suggester.h"

#include <optional>
#include <string>
#include <utility>

#include "base/barrier_callback.h"
#include "base/time/time.h"
#include "chrome/browser/ash/app_list/search/files/file_title.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/fileapi/recent_file.h"
#include "chrome/browser/ash/fileapi/recent_model.h"
#include "chrome/browser/ash/fileapi/recent_model_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/file_manager_private.h"
#include "content/public/browser/storage_partition.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_types.h"

namespace {

using LocalFile = PickerFileSuggester::LocalFile;
using DriveFile = PickerFileSuggester::DriveFile;
namespace fmp = extensions::api::file_manager_private;

constexpr base::TimeDelta kMaxFileRecencyDelta = base::Days(30);
constexpr base::TimeDelta kScanTimeout = base::Seconds(1);

storage::FileSystemContext* GetFileSystemContextForProfile(Profile* profile) {
  content::StoragePartition* storage = profile->GetDefaultStoragePartition();
  return storage->GetFileSystemContext();
}

void GetRecentFiles(Profile* profile,
                    ash::RecentSource::FileType file_type,
                    fmp::VolumeType volume_type,
                    size_t max_files,
                    ash::RecentModel::GetRecentFilesCallback callback) {
  const scoped_refptr<storage::FileSystemContext> file_system_context =
      GetFileSystemContextForProfile(profile);
  if (!file_system_context) {
    return;
  }

  ash::RecentModel* model = ash::RecentModelFactory::GetForProfile(profile);
  if (!model) {
    return;
  }
  ash::RecentModelOptions options;
  options.now_delta = kMaxFileRecencyDelta;
  options.file_type = file_type;
  options.scan_timeout = kScanTimeout;
  options.max_files = max_files;
  options.source_specs = {
      ash::RecentSourceSpec{.volume_type = volume_type},
  };

  model->GetRecentFiles(file_system_context.get(), GURL(), /*query=*/"",
                        options, std::move(callback));
}

void GetDriveFileMetadata(
    drive::DriveIntegrationService* drive_integration,
    const ash::RecentFile& file,
    base::OnceCallback<void(std::optional<DriveFile>)> callback) {
  const storage::FileSystemURL& url = file.url();
  if (url.type() != storage::kFileSystemTypeDriveFs) {
    std::move(callback).Run(std::nullopt);
    return;
  }

  const base::FilePath& path = url.path();
  drive_integration->GetMetadata(
      path, base::BindOnce(
                [](const base::FilePath& path, drive::FileError error,
                   drivefs::mojom::FileMetadataPtr metadata)
                    -> std::optional<DriveFile> {
                  if (error != drive::FILE_ERROR_OK) {
                    return std::nullopt;
                  }
                  return DriveFile(metadata->item_id,
                                   app_list::GetFileTitle(path), path,
                                   GURL(metadata->alternate_url));
                },
                path)
                .Then(std::move(callback)));
}

std::vector<DriveFile> FilterDriveFiles(
    std::vector<std::optional<DriveFile>> files) {
  std::vector<DriveFile> filtered;
  filtered.reserve(files.size());
  for (std::optional<DriveFile>& file : files) {
    if (file.has_value()) {
      filtered.push_back(std::move(*file));
    }
  }
  filtered.shrink_to_fit();
  return filtered;
}

}  // namespace

PickerFileSuggester::DriveFile::DriveFile(std::optional<std::string> id,
                                          std::u16string title,
                                          base::FilePath local_path,
                                          GURL url)
    : id(std::move(id)),
      title(std::move(title)),
      local_path(std::move(local_path)),
      url(std::move(url)) {}

PickerFileSuggester::DriveFile::~DriveFile() = default;

PickerFileSuggester::DriveFile::DriveFile(const DriveFile&) = default;
PickerFileSuggester::DriveFile::DriveFile(DriveFile&&) = default;
PickerFileSuggester::DriveFile& PickerFileSuggester::DriveFile::operator=(
    const DriveFile&) = default;
PickerFileSuggester::DriveFile& PickerFileSuggester::DriveFile::operator=(
    DriveFile&&) = default;

PickerFileSuggester::PickerFileSuggester(Profile* profile)
    : profile_(profile) {}

PickerFileSuggester::~PickerFileSuggester() = default;

void PickerFileSuggester::GetRecentLocalImages(
    size_t max_files,
    RecentLocalImagesCallback callback) {
  GetRecentFiles(
      profile_, ash::RecentSource::FileType::kImage,
      fmp::VolumeType::kDownloads, max_files,
      base::BindOnce(&PickerFileSuggester::OnGetRecentLocalImages,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void PickerFileSuggester::GetRecentDriveFiles(
    size_t max_files,
    RecentDriveFilesCallback callback) {
  GetRecentFiles(
      profile_, ash::RecentSource::FileType::kAll, fmp::VolumeType::kDrive,
      max_files,
      base::BindOnce(&PickerFileSuggester::OnGetRecentDriveFiles,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void PickerFileSuggester::OnGetRecentLocalImages(
    RecentLocalImagesCallback callback,
    const std::vector<ash::RecentFile>& recent_files) {
  std::vector<LocalFile> files;
  files.reserve(recent_files.size());
  for (const ash::RecentFile& recent_file : recent_files) {
    const base::FilePath& path = recent_file.url().path();
    files.push_back({.title = app_list::GetFileTitle(path), .path = path});
  }
  std::move(callback).Run(std::move(files));
}

void PickerFileSuggester::OnGetRecentDriveFiles(
    RecentDriveFilesCallback callback,
    const std::vector<ash::RecentFile>& recent_files) {
  drive::DriveIntegrationService* drive_integration =
      drive::DriveIntegrationServiceFactory::FindForProfile(profile_);
  if (!drive_integration) {
    std::move(callback).Run({});
  }

  auto barrier_callback = base::BarrierCallback<std::optional<DriveFile>>(
      /*num_callbacks=*/recent_files.size(),
      /*done_callback=*/base::BindOnce(FilterDriveFiles)
          .Then(std::move(callback)));

  for (const ash::RecentFile& recent_file : recent_files) {
    GetDriveFileMetadata(drive_integration, recent_file, barrier_callback);
  }
}