chromium/chrome/browser/ash/file_manager/file_tasks_notifier.cc

// Copyright 2019 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/file_tasks_notifier.h"

#include <memory>
#include <utility>

#include "base/barrier_closure.h"
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/drive/drive_integration_service.h"
#include "chrome/browser/ash/file_manager/file_tasks_notifier_factory.h"
#include "chrome/browser/ash/file_manager/file_tasks_observer.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/profiles/profile.h"
#include "components/download/public/common/download_item.h"
#include "content/public/browser/network_service_instance.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "services/network/public/cpp/network_connection_tracker.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_types.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "ui/shell_dialogs/selected_file_info.h"

namespace file_manager {
namespace file_tasks {
namespace {

bool IsSupportedFileSystemType(storage::FileSystemType type) {
  switch (type) {
    case storage::kFileSystemTypeLocal:
    case storage::kFileSystemTypeDriveFs:
      return true;
    default:
      return false;
  }
}

void ReturnQueryResults(
    std::unique_ptr<std::vector<FileTasksNotifier::FileAvailability>> results,
    base::OnceCallback<void(std::vector<FileTasksNotifier::FileAvailability>)>
        callback) {
  std::move(callback).Run(std::move(*results));
}

}  // namespace

struct FileTasksNotifier::PendingFileAvailabilityTask {
  storage::FileSystemURL url;
  raw_ptr<FileTasksNotifier::FileAvailability, DanglingUntriaged> output;
  base::OnceClosure done;
};

FileTasksNotifier::FileTasksNotifier(Profile* profile)
    : profile_(profile),
      download_notifier_(profile_->GetDownloadManager(), this) {}

FileTasksNotifier::~FileTasksNotifier() = default;

// static
FileTasksNotifier* FileTasksNotifier::GetForProfile(Profile* profile) {
  return FileTasksNotifierFactory::GetInstance()->GetForProfile(profile);
}

void FileTasksNotifier::AddObserver(FileTasksObserver* observer) {
  observers_.AddObserver(observer);
}

void FileTasksNotifier::RemoveObserver(FileTasksObserver* observer) {
  observers_.RemoveObserver(observer);
}

void FileTasksNotifier::QueryFileAvailability(
    const std::vector<base::FilePath>& paths,
    base::OnceCallback<void(std::vector<FileAvailability>)> callback) {
  const auto* mount_points = storage::ExternalMountPoints::GetSystemInstance();
  std::vector<FileAvailability> results(paths.size(),
                                        FileAvailability::kUnknown);

  std::vector<PendingFileAvailabilityTask> tasks;
  for (size_t i = 0; i < paths.size(); ++i) {
    base::FilePath virtual_path;
    if (!mount_points->GetVirtualPath(paths[i], &virtual_path)) {
      continue;
    }
    auto url = mount_points->CreateCrackedFileSystemURL(
        blink::StorageKey(), storage::kFileSystemTypeExternal, virtual_path);
    if (!url.is_valid() || !IsSupportedFileSystemType(url.type())) {
      results[i] = FileAvailability::kGone;
      continue;
    }
    tasks.push_back({url, &results[i]});
  }
  if (tasks.empty()) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, base::BindOnce(std::move(callback), std::move(results)));
    return;
  }

  auto results_owner = std::make_unique<std::vector<FileAvailability>>();
  std::swap(results, *results_owner);
  auto closure = base::BarrierClosure(
      tasks.size(),
      base::BindOnce(&ReturnQueryResults, std::move(results_owner),
                     std::move(callback)));
  for (auto& task : tasks) {
    task.done = closure;
    GetFileAvailability(std::move(task));
  }
}

void FileTasksNotifier::OnDownloadUpdated(content::DownloadManager* manager,
                                          download::DownloadItem* item) {
  if (item->IsTransient() ||
      item->GetState() != download::DownloadItem::DownloadState::COMPLETE ||
      item->GetDownloadCreationType() ==
          download::DownloadItem::DownloadCreationType::TYPE_HISTORY_IMPORT) {
    return;
  }
  NotifyObservers({item->GetTargetFilePath()},
                  FileTasksObserver::OpenType::kDownload);
}

void FileTasksNotifier::NotifyFileTasks(
    const std::vector<storage::FileSystemURL>& file_urls) {
  std::vector<base::FilePath> paths;
  for (const auto& url : file_urls) {
    if (IsSupportedFileSystemType(url.type())) {
      paths.push_back(url.path());
    }
  }
  NotifyObservers(paths, FileTasksObserver::OpenType::kLaunch);
}

void FileTasksNotifier::NotifyFileDialogSelection(
    const std::vector<ui::SelectedFileInfo>& files,
    bool for_open) {
  std::vector<base::FilePath> paths;
  for (const auto& file : files) {
    paths.push_back(file.file_path);
  }
  NotifyObservers(paths, for_open ? FileTasksObserver::OpenType::kOpen
                                  : FileTasksObserver::OpenType::kSaveAs);
}

void FileTasksNotifier::NotifyObservers(
    const std::vector<base::FilePath>& paths,
    FileTasksObserver::OpenType open_type) {
  std::vector<FileTasksObserver::FileOpenEvent> opens;
  for (const auto& path : paths) {
    if (util::GetMyFilesFolderForProfile(profile_).DirName().IsParent(path) ||
        base::FilePath("/run/arc/sdcard/write/emulated/0").IsParent(path) ||
        base::FilePath("/media/fuse").IsParent(path)) {
      opens.push_back({path, open_type});
    }
  }
  if (opens.empty()) {
    return;
  }
  for (auto& observer : observers_) {
    observer.OnFilesOpened(opens);
  }
}

void FileTasksNotifier::GetFileAvailability(PendingFileAvailabilityTask task) {
  if (task.url.type() != storage::kFileSystemTypeDriveFs) {
    base::FilePath path = std::move(task.url.path());
    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE, {base::MayBlock()},
        base::BindOnce(&base::PathExists, std::move(path)),
        base::BindOnce(&FileTasksNotifier::ForwardQueryResult,
                       std::move(task)));
    return;
  }
  if (!GetDriveFsInterface()) {
    *task.output = FileTasksNotifier::FileAvailability::kUnknown;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(task.done));
    return;
  }
  base::FilePath drive_path;
  if (!GetRelativeDrivePath(task.url.path(), &drive_path)) {
    *task.output = FileTasksNotifier::FileAvailability::kGone;
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(task.done));
    return;
  }
  GetDriveFsInterface()->GetMetadata(
      drive_path,
      mojo::WrapCallbackWithDefaultInvokeIfNotRun(
          base::BindOnce(&FileTasksNotifier::ForwardDriveFsQueryResult,
                         std::move(task), IsOffline()),
          drive::FILE_ERROR_SERVICE_UNAVAILABLE, nullptr));
}

// static
void FileTasksNotifier::ForwardQueryResult(PendingFileAvailabilityTask task,
                                           bool exists) {
  *task.output = exists ? FileTasksNotifier::FileAvailability::kOk
                        : FileTasksNotifier::FileAvailability::kGone;
  std::move(task.done).Run();
}

// static
void FileTasksNotifier::ForwardDriveFsQueryResult(
    PendingFileAvailabilityTask task,
    bool is_offline,
    drive::FileError error,
    drivefs::mojom::FileMetadataPtr metadata) {
  if (error == drive::FILE_ERROR_NOT_FOUND) {
    *task.output = FileTasksNotifier::FileAvailability::kGone;
  } else if (error != drive::FILE_ERROR_OK) {
    *task.output = FileTasksNotifier::FileAvailability::kUnknown;
  } else {
    *task.output =
        metadata->available_offline || !is_offline
            ? FileTasksNotifier::FileAvailability::kOk
            : FileTasksNotifier::FileAvailability::kTemporarilyUnavailable;
  }
  std::move(task.done).Run();
}

drivefs::mojom::DriveFs* FileTasksNotifier::GetDriveFsInterface() {
  drive::DriveIntegrationService* integration_service =
      drive::DriveIntegrationServiceFactory::FindForProfile(profile_);
  if (!integration_service || !integration_service->IsMounted()) {
    return nullptr;
  }
  return integration_service->GetDriveFsInterface();
}

bool FileTasksNotifier::GetRelativeDrivePath(
    const base::FilePath& path,
    base::FilePath* drive_relative_path) {
  return drive::DriveIntegrationServiceFactory::FindForProfile(profile_)
      ->GetRelativeDrivePath(path, drive_relative_path);
}

bool FileTasksNotifier::IsOffline() {
  return content::GetNetworkConnectionTracker()->IsOffline();
}

}  // namespace file_tasks
}  // namespace file_manager