chromium/chrome/browser/ash/file_system_provider/provided_file_system.cc

// Copyright 2014 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_system_provider/provided_file_system.h"

#include <memory>
#include <utility>
#include <vector>

#include "base/files/file.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "chrome/browser/ash/file_system_provider/notification_manager.h"
#include "chrome/browser/ash/file_system_provider/odfs_metrics.h"
#include "chrome/browser/ash/file_system_provider/operation_request_manager.h"
#include "chrome/browser/ash/file_system_provider/operations/abort.h"
#include "chrome/browser/ash/file_system_provider/operations/add_watcher.h"
#include "chrome/browser/ash/file_system_provider/operations/close_file.h"
#include "chrome/browser/ash/file_system_provider/operations/configure.h"
#include "chrome/browser/ash/file_system_provider/operations/copy_entry.h"
#include "chrome/browser/ash/file_system_provider/operations/create_directory.h"
#include "chrome/browser/ash/file_system_provider/operations/create_file.h"
#include "chrome/browser/ash/file_system_provider/operations/delete_entry.h"
#include "chrome/browser/ash/file_system_provider/operations/execute_action.h"
#include "chrome/browser/ash/file_system_provider/operations/get_actions.h"
#include "chrome/browser/ash/file_system_provider/operations/get_metadata.h"
#include "chrome/browser/ash/file_system_provider/operations/move_entry.h"
#include "chrome/browser/ash/file_system_provider/operations/open_file.h"
#include "chrome/browser/ash/file_system_provider/operations/read_directory.h"
#include "chrome/browser/ash/file_system_provider/operations/read_file.h"
#include "chrome/browser/ash/file_system_provider/operations/remove_watcher.h"
#include "chrome/browser/ash/file_system_provider/operations/truncate.h"
#include "chrome/browser/ash/file_system_provider/operations/unmount.h"
#include "chrome/browser/ash/file_system_provider/operations/write_file.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
#include "chrome/browser/ash/file_system_provider/request_dispatcher_impl.h"
#include "chrome/browser/ash/file_system_provider/request_manager.h"
#include "chrome/browser/chromeos/extensions/file_system_provider/service_worker_lifetime_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/file_system_provider.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chromeos/constants/chromeos_features.h"
#include "extensions/browser/event_router.h"

namespace net {
class IOBuffer;
}  // namespace net

namespace ash::file_system_provider {

namespace {

constexpr char kODFSFlushAction[] = "HIDDEN_ONEDRIVE_FLUSH_FILE";

// Timeout in seconds, before a file system operation request is considered as
// stale and hence aborted.
constexpr base::TimeDelta kDefaultOperationTimeout = base::Seconds(10);

// Operation timeout for the ODFS extension.
constexpr base::TimeDelta kODFSOperationTimeout = base::Seconds(30);

extensions::file_system_provider::ServiceWorkerLifetimeManager*
GetServiceWorkerLifetimeManager(Profile* profile) {
  if (!chromeos::features::IsUploadOfficeToCloudEnabled()) {
    return nullptr;
  }
  return extensions::file_system_provider::ServiceWorkerLifetimeManager::Get(
      profile);
}

class ScopedUserInteractionImpl : public ScopedUserInteraction {
 public:
  explicit ScopedUserInteractionImpl(ProvidedFileSystem* file_system)
      : file_system_(file_system->GetWeakPtr()) {
    file_system_->GetRequestManager()->StartUserInteraction();
  }

  ~ScopedUserInteractionImpl() override {
    if (file_system_) {
      file_system_->GetRequestManager()->EndUserInteraction();
    }
  }

 private:
  base::WeakPtr<ProvidedFileSystemInterface> file_system_;
};

}  // namespace

AutoUpdater::AutoUpdater(base::OnceClosure update_callback)
    : update_callback_(std::move(update_callback)),
      created_callbacks_(0),
      pending_callbacks_(0) {}

base::OnceClosure AutoUpdater::CreateCallback() {
  ++created_callbacks_;
  ++pending_callbacks_;
  return base::BindOnce(&AutoUpdater::OnPendingCallback, this);
}

void AutoUpdater::OnPendingCallback() {
  DCHECK_LT(0, pending_callbacks_);
  if (--pending_callbacks_ == 0)
    std::move(update_callback_).Run();
}

AutoUpdater::~AutoUpdater() {
  // If no callbacks are created, then we need to invoke updating in the
  // destructor.
  if (!created_callbacks_)
    std::move(update_callback_).Run();
  else if (pending_callbacks_)
    LOG(ERROR) << "Not all callbacks called. This may happen on shutdown.";
}

struct ProvidedFileSystem::AddWatcherInQueueArgs {
  AddWatcherInQueueArgs(
      size_t token,
      const GURL& origin,
      const base::FilePath& entry_path,
      bool recursive,
      bool persistent,
      storage::AsyncFileUtil::StatusCallback callback,
      storage::WatcherManager::NotificationCallback notification_callback)
      : token(token),
        origin(origin),
        entry_path(entry_path),
        recursive(recursive),
        persistent(persistent),
        callback(std::move(callback)),
        notification_callback(std::move(notification_callback)) {}
  ~AddWatcherInQueueArgs() = default;
  AddWatcherInQueueArgs(AddWatcherInQueueArgs&&) = default;

  const size_t token;
  const GURL origin;
  const base::FilePath entry_path;
  const bool recursive;
  const bool persistent;
  storage::AsyncFileUtil::StatusCallback callback;
  const storage::WatcherManager::NotificationCallback notification_callback;
};

struct ProvidedFileSystem::NotifyInQueueArgs {
  NotifyInQueueArgs(
      size_t token,
      const base::FilePath& entry_path,
      bool recursive,
      storage::WatcherManager::ChangeType change_type,
      std::unique_ptr<ProvidedFileSystemObserver::Changes> changes,
      const std::string& tag,
      storage::AsyncFileUtil::StatusCallback callback)
      : token(token),
        entry_path(entry_path),
        recursive(recursive),
        change_type(change_type),
        changes(std::move(changes)),
        tag(tag),
        callback(std::move(callback)) {}

  NotifyInQueueArgs(const NotifyInQueueArgs&) = delete;
  NotifyInQueueArgs& operator=(const NotifyInQueueArgs&) = delete;

  ~NotifyInQueueArgs() = default;

  const size_t token;
  const base::FilePath entry_path;
  const bool recursive;
  const storage::WatcherManager::ChangeType change_type;
  const std::unique_ptr<ProvidedFileSystemObserver::Changes> changes;
  const std::string tag;
  storage::AsyncFileUtil::StatusCallback callback;
};

ProvidedFileSystem::ProvidedFileSystem(
    Profile* profile,
    const ProvidedFileSystemInfo& file_system_info)
    : profile_(profile),
      event_router_(extensions::EventRouter::Get(profile)),  // May be NULL.
      file_system_info_(file_system_info),
      notification_manager_(
          new NotificationManager(profile_, file_system_info_)),
      watcher_queue_(1) {
  DCHECK_EQ(ProviderId::EXTENSION, file_system_info.provider_id().GetType());
  request_dispatcher_ = std::make_unique<RequestDispatcherImpl>(
      file_system_info_.provider_id().GetExtensionId(), event_router_,
      base::BindRepeating(&ProvidedFileSystem::OnLacrosOperationForwarded,
                          weak_ptr_factory_.GetWeakPtr()),
      GetServiceWorkerLifetimeManager(profile_));
  const ProviderId& provider_id = file_system_info_.provider_id();
  if (chromeos::features::IsUploadOfficeToCloudEnabled() &&
      provider_id.GetExtensionId() == extension_misc::kODFSExtensionId) {
    odfs_metrics_ = std::make_unique<ODFSMetrics>();
  }
  ConstructRequestManager();
}

ProvidedFileSystem::~ProvidedFileSystem() {
  const std::vector<int> request_ids = request_manager_->GetActiveRequestIds();
  for (int request_id : request_ids) {
    Abort(request_id);
  }
}

void ProvidedFileSystem::SetEventRouterForTesting(
    extensions::EventRouter* event_router) {
  event_router_ = event_router;
  request_dispatcher_ = std::make_unique<RequestDispatcherImpl>(
      file_system_info_.provider_id().GetExtensionId(), event_router_,
      base::BindRepeating(&ProvidedFileSystem::OnLacrosOperationForwarded,
                          weak_ptr_factory_.GetWeakPtr()),
      GetServiceWorkerLifetimeManager(profile_));
}

void ProvidedFileSystem::SetNotificationManagerForTesting(
    std::unique_ptr<NotificationManagerInterface> notification_manager) {
  notification_manager_ = std::move(notification_manager);
  ConstructRequestManager();
}

AbortCallback ProvidedFileSystem::RequestUnmount(
    storage::AsyncFileUtil::StatusCallback callback) {
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kUnmount, std::make_unique<operations::Unmount>(
                                 request_dispatcher_.get(), file_system_info_,
                                 std::move(split_callback.first)));
  if (!request_id) {
    std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::GetMetadata(const base::FilePath& entry_path,
                                              MetadataFieldMask fields,
                                              GetMetadataCallback callback) {
  // Create two callbacks of which only one will be called because
  // RequestManager::CreateRequest() is guaranteed not to call |callback| if it
  // signals an error (by returning request_id == 0).
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kGetMetadata,
      std::make_unique<operations::GetMetadata>(
          request_dispatcher_.get(), file_system_info_, entry_path, fields,
          std::move(split_callback.first)));
  if (!request_id) {
    std::move(split_callback.second)
        .Run(base::WrapUnique<EntryMetadata>(nullptr),
             base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::GetActions(
    const std::vector<base::FilePath>& entry_paths,
    GetActionsCallback callback) {
  // Create two callbacks of which only one will be called because
  // RequestManager::CreateRequest() is guaranteed not to call |callback| if it
  // signals an error (by returning request_id == 0).
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kGetActions,
      std::make_unique<operations::GetActions>(
          request_dispatcher_.get(), file_system_info_, entry_paths,
          std::move(split_callback.first)));
  if (!request_id) {
    // If the provider doesn't listen for GetActions requests, treat it as
    // having no actions.
    std::move(split_callback.second).Run(Actions(), base::File::FILE_OK);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::ExecuteAction(
    const std::vector<base::FilePath>& entry_paths,
    const std::string& action_id,
    storage::AsyncFileUtil::StatusCallback callback) {
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kExecuteAction,
      std::make_unique<operations::ExecuteAction>(
          request_dispatcher_.get(), file_system_info_, entry_paths, action_id,
          std::move(split_callback.first)));
  if (!request_id) {
    std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::ReadDirectory(
    const base::FilePath& directory_path,
    storage::AsyncFileUtil::ReadDirectoryCallback callback) {
  const int request_id = request_manager_->CreateRequest(
      RequestType::kReadDirectory,
      std::make_unique<operations::ReadDirectory>(request_dispatcher_.get(),
                                                  file_system_info_,
                                                  directory_path, callback));
  if (!request_id) {
    callback.Run(base::File::FILE_ERROR_SECURITY,
                 storage::AsyncFileUtil::EntryList(),
                 /*has_more=*/false);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::ReadFile(int file_handle,
                                           net::IOBuffer* buffer,
                                           int64_t offset,
                                           int length,
                                           ReadChunkReceivedCallback callback) {
  TRACE_EVENT1(
      "file_system_provider", "ProvidedFileSystem::ReadFile", "length", length);
  const int request_id = request_manager_->CreateRequest(
      RequestType::kReadFile,
      std::make_unique<operations::ReadFile>(request_dispatcher_.get(),
                                             file_system_info_, file_handle,
                                             buffer, offset, length, callback));
  if (!request_id) {
    callback.Run(/*chunk_length=*/0,
                 /*has_more=*/false, base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::OpenFile(const base::FilePath& file_path,
                                           OpenFileMode mode,
                                           OpenFileCallback callback) {
  // Create two callbacks of which only one will be called because
  // RequestManager::CreateRequest() is guaranteed not to call |callback| if it
  // signals an error (by returning request_id == 0).
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kOpenFile,
      std::make_unique<operations::OpenFile>(
          request_dispatcher_.get(), file_system_info_, file_path, mode,
          base::BindOnce(&ProvidedFileSystem::OnOpenFileCompleted,
                         weak_ptr_factory_.GetWeakPtr(), file_path, mode,
                         std::move(split_callback.first))));
  if (!request_id) {
    std::move(split_callback.second)
        .Run(/*file_handle=*/0, base::File::FILE_ERROR_SECURITY,
             /*cloud_file_info=*/nullptr);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::CloseFile(
    int file_handle,
    storage::AsyncFileUtil::StatusCallback callback) {
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kCloseFile,
      std::make_unique<operations::CloseFile>(
          request_dispatcher_.get(), file_system_info_, file_handle,
          base::BindOnce(&ProvidedFileSystem::OnCloseFileCompleted,
                         weak_ptr_factory_.GetWeakPtr(), file_handle,
                         std::move(split_callback.first))));
  if (!request_id) {
    std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::CreateDirectory(
    const base::FilePath& directory_path,
    bool recursive,
    storage::AsyncFileUtil::StatusCallback callback) {
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kCreateDirectory,
      std::make_unique<operations::CreateDirectory>(
          request_dispatcher_.get(), file_system_info_, directory_path,
          recursive, std::move(split_callback.first)));
  if (!request_id) {
    std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::DeleteEntry(
    const base::FilePath& entry_path,
    bool recursive,
    storage::AsyncFileUtil::StatusCallback callback) {
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kDeleteEntry,
      std::make_unique<operations::DeleteEntry>(
          request_dispatcher_.get(), file_system_info_, entry_path, recursive,
          std::move(split_callback.first)));
  if (!request_id) {
    std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::CreateFile(
    const base::FilePath& file_path,
    storage::AsyncFileUtil::StatusCallback callback) {
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kCreateFile,
      std::make_unique<operations::CreateFile>(
          request_dispatcher_.get(), file_system_info_, file_path,
          std::move(split_callback.first)));
  if (!request_id) {
    std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::CopyEntry(
    const base::FilePath& source_path,
    const base::FilePath& target_path,
    storage::AsyncFileUtil::StatusCallback callback) {
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kCopyEntry,
      std::make_unique<operations::CopyEntry>(
          request_dispatcher_.get(), file_system_info_, source_path,
          target_path, std::move(split_callback.first)));
  if (!request_id) {
    std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::WriteFile(
    int file_handle,
    net::IOBuffer* buffer,
    int64_t offset,
    int length,
    storage::AsyncFileUtil::StatusCallback callback) {
  TRACE_EVENT1("file_system_provider",
               "ProvidedFileSystem::WriteFile",
               "length",
               length);
  if (length < 0) {
    std::move(callback).Run(base::File::FILE_ERROR_INVALID_OPERATION);
    return AbortCallback();
  }
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kWriteFile,
      std::make_unique<operations::WriteFile>(
          request_dispatcher_.get(), file_system_info_, file_handle,
          base::WrapRefCounted(buffer), offset, static_cast<size_t>(length),
          std::move(split_callback.first)));
  if (!request_id) {
    std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::FlushFile(
    int file_handle,
    storage::AsyncFileUtil::StatusCallback callback) {
  if (!chromeos::features::IsUploadOfficeToCloudEnabled()) {
    std::move(callback).Run(base::File::FILE_OK);
    return AbortCallback();
  }
  const auto& provider_id = file_system_info_.provider_id();
  bool is_odfs =
      provider_id.GetType() == ProviderId::EXTENSION &&
      provider_id.GetExtensionId() == extension_misc::kODFSExtensionId;
  if (!is_odfs) {
    // No-op for non-ODFS providers for backwards compatibility.
    std::move(callback).Run(base::File::FILE_OK);
    return AbortCallback();
  }

  // Flush for ODFS: run a custom action by path.
  auto it = opened_files_.find(file_handle);
  if (it == opened_files_.end()) {
    std::move(callback).Run(base::File::FILE_ERROR_INVALID_OPERATION);
    return AbortCallback();
  }
  const OpenedFile& opened_file = it->second;
  return ExecuteAction({opened_file.file_path}, kODFSFlushAction,
                       std::move(callback));
}

AbortCallback ProvidedFileSystem::MoveEntry(
    const base::FilePath& source_path,
    const base::FilePath& target_path,
    storage::AsyncFileUtil::StatusCallback callback) {
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kMoveEntry,
      std::make_unique<operations::MoveEntry>(
          request_dispatcher_.get(), file_system_info_, source_path,
          target_path, std::move(split_callback.first)));
  if (!request_id) {
    std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::Truncate(
    const base::FilePath& file_path,
    int64_t length,
    storage::AsyncFileUtil::StatusCallback callback) {
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kTruncate,
      std::make_unique<operations::Truncate>(
          request_dispatcher_.get(), file_system_info_, file_path, length,
          std::move(split_callback.first)));
  if (!request_id) {
    std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
    return AbortCallback();
  }

  return base::BindOnce(&ProvidedFileSystem::Abort,
                        weak_ptr_factory_.GetWeakPtr(), request_id);
}

AbortCallback ProvidedFileSystem::AddWatcher(
    const GURL& origin,
    const base::FilePath& entry_path,
    bool recursive,
    bool persistent,
    storage::AsyncFileUtil::StatusCallback callback,
    storage::WatcherManager::NotificationCallback notification_callback) {
  const size_t token = watcher_queue_.NewToken();
  watcher_queue_.Enqueue(
      token,
      base::BindOnce(&ProvidedFileSystem::AddWatcherInQueue,
                     base::Unretained(this),  // Outlived by the queue.
                     AddWatcherInQueueArgs(token, origin, entry_path, recursive,
                                           persistent, std::move(callback),
                                           std::move(notification_callback))));
  return AbortCallback();
}

void ProvidedFileSystem::RemoveWatcher(
    const GURL& origin,
    const base::FilePath& entry_path,
    bool recursive,
    storage::AsyncFileUtil::StatusCallback callback) {
  const size_t token = watcher_queue_.NewToken();
  watcher_queue_.Enqueue(
      token, base::BindOnce(&ProvidedFileSystem::RemoveWatcherInQueue,
                            base::Unretained(this),  // Outlived by the queue.
                            token, origin, entry_path, recursive,
                            std::move(callback)));
}

const ProvidedFileSystemInfo& ProvidedFileSystem::GetFileSystemInfo() const {
  return file_system_info_;
}

OperationRequestManager* ProvidedFileSystem::GetRequestManager() {
  return request_manager_.get();
}

Watchers* ProvidedFileSystem::GetWatchers() {
  return &watchers_;
}

const OpenedFiles& ProvidedFileSystem::GetOpenedFiles() const {
  return opened_files_;
}

void ProvidedFileSystem::AddObserver(ProvidedFileSystemObserver* observer) {
  DCHECK(observer);
  observers_.AddObserver(observer);
}

void ProvidedFileSystem::RemoveObserver(ProvidedFileSystemObserver* observer) {
  DCHECK(observer);
  observers_.RemoveObserver(observer);
}

void ProvidedFileSystem::Notify(
    const base::FilePath& entry_path,
    bool recursive,
    storage::WatcherManager::ChangeType change_type,
    std::unique_ptr<ProvidedFileSystemObserver::Changes> changes,
    const std::string& tag,
    storage::AsyncFileUtil::StatusCallback callback) {
  const size_t token = watcher_queue_.NewToken();
  watcher_queue_.Enqueue(
      token, base::BindOnce(&ProvidedFileSystem::NotifyInQueue,
                            base::Unretained(this),  // Outlived by the queue.
                            std::make_unique<NotifyInQueueArgs>(
                                token, entry_path, recursive, change_type,
                                std::move(changes), tag, std::move(callback))));
}

void ProvidedFileSystem::Configure(
    storage::AsyncFileUtil::StatusCallback callback) {
  auto split_callback = base::SplitOnceCallback(std::move(callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kConfigure, std::make_unique<operations::Configure>(
                                   request_dispatcher_.get(), file_system_info_,
                                   std::move(split_callback.first)));
  if (!request_id)
    std::move(split_callback.second).Run(base::File::FILE_ERROR_SECURITY);
}

void ProvidedFileSystem::Abort(int operation_request_id) {
  if (!request_manager_->CreateRequest(
          RequestType::kAbort,
          std::make_unique<operations::Abort>(
              request_dispatcher_.get(), file_system_info_,
              operation_request_id,
              base::BindOnce(&ProvidedFileSystem::OnAbortCompleted,
                             weak_ptr_factory_.GetWeakPtr(),
                             operation_request_id)))) {
    // If the aborting event is not handled, then the operation should simply
    // be not aborted. Instead we'll wait until it completes.
    LOG(ERROR) << "Failed to create an abort request.";
  }
}

void ProvidedFileSystem::OnAbortCompleted(int operation_request_id,
                                          base::File::Error result) {
  if (result != base::File::FILE_OK) {
    // If an error in aborting happens, then do not abort the request in the
    // request manager, as the operation is supposed to complete. The only case
    // it wouldn't complete is if there is a bug in the extension code, and
    // the extension never calls the callback. We consiously *do not* handle
    // bugs in extensions here.
    return;
  }
  request_manager_->RejectRequest(operation_request_id, RequestValue(),
                                  base::File::FILE_ERROR_ABORT);
}

AbortCallback ProvidedFileSystem::AddWatcherInQueue(
    AddWatcherInQueueArgs args) {
  if (args.persistent && (!file_system_info_.supports_notify_tag() ||
                          !args.notification_callback.is_null())) {
    OnAddWatcherInQueueCompleted(args.token, args.entry_path, args.recursive,
                                 Subscriber(), std::move(args.callback),
                                 base::File::FILE_ERROR_INVALID_OPERATION);
    return AbortCallback();
  }

  // Create a candidate subscriber. This could be done in OnAddWatcherCompleted,
  // but base::Bind supports only up to 7 arguments.
  Subscriber subscriber;
  subscriber.origin = args.origin;
  subscriber.persistent = args.persistent;
  subscriber.notification_callback = args.notification_callback;

  const WatcherKey key(args.entry_path, args.recursive);
  const Watchers::const_iterator it = watchers_.find(key);
  if (it != watchers_.end()) {
    const bool exists = it->second.subscribers.find(args.origin) !=
                        it->second.subscribers.end();
    OnAddWatcherInQueueCompleted(
        args.token, args.entry_path, args.recursive, subscriber,
        std::move(args.callback),
        exists ? base::File::FILE_ERROR_EXISTS : base::File::FILE_OK);
    return AbortCallback();
  }

  auto split_callback = base::SplitOnceCallback(std::move(args.callback));
  const int request_id = request_manager_->CreateRequest(
      RequestType::kAddWatcher,
      std::make_unique<operations::AddWatcher>(
          request_dispatcher_.get(), file_system_info_, args.entry_path,
          args.recursive,
          base::BindOnce(&ProvidedFileSystem::OnAddWatcherInQueueCompleted,
                         weak_ptr_factory_.GetWeakPtr(), args.token,
                         args.entry_path, args.recursive, subscriber,
                         std::move(split_callback.first))));

  if (!request_id) {
    OnAddWatcherInQueueCompleted(args.token, args.entry_path, args.recursive,
                                 subscriber, std::move(split_callback.second),
                                 base::File::FILE_ERROR_SECURITY);
  }

  return AbortCallback();
}

AbortCallback ProvidedFileSystem::RemoveWatcherInQueue(
    size_t token,
    const GURL& origin,
    const base::FilePath& entry_path,
    bool recursive,
    storage::AsyncFileUtil::StatusCallback callback) {
  const WatcherKey key(entry_path, recursive);
  const Watchers::iterator it = watchers_.find(key);
  if (it == watchers_.end() ||
      it->second.subscribers.find(origin) == it->second.subscribers.end()) {
    OnRemoveWatcherInQueueCompleted(token, origin, key, std::move(callback),
                                    /*extension_response=*/false,
                                    base::File::FILE_ERROR_NOT_FOUND);
    return AbortCallback();
  }

  // If there are other subscribers, then do not remove the observer, but simply
  // return a success.
  if (it->second.subscribers.size() > 1) {
    OnRemoveWatcherInQueueCompleted(token, origin, key, std::move(callback),
                                    /*extension_response=*/false,
                                    base::File::FILE_OK);
    return AbortCallback();
  }

  // Otherwise, emit an event, and remove the watcher.
  request_manager_->CreateRequest(
      RequestType::kRemoveWatcher,
      std::make_unique<operations::RemoveWatcher>(
          request_dispatcher_.get(), file_system_info_, entry_path, recursive,
          base::BindOnce(&ProvidedFileSystem::OnRemoveWatcherInQueueCompleted,
                         weak_ptr_factory_.GetWeakPtr(), token, origin, key,
                         std::move(callback), /*extension_response=*/true)));

  return AbortCallback();
}

AbortCallback ProvidedFileSystem::NotifyInQueue(
    std::unique_ptr<NotifyInQueueArgs> args) {
  const WatcherKey key(args->entry_path, args->recursive);
  const auto& watcher_it = watchers_.find(key);
  if (watcher_it == watchers_.end()) {
    OnNotifyInQueueCompleted(std::move(args), base::File::FILE_ERROR_NOT_FOUND);
    return AbortCallback();
  }

  // The tag must be provided if and only if it's explicitly supported.
  if (file_system_info_.supports_notify_tag() == args->tag.empty()) {
    OnNotifyInQueueCompleted(std::move(args),
                             base::File::FILE_ERROR_INVALID_OPERATION);
    return AbortCallback();
  }

  // It's illegal to provide a tag which is not unique.
  if (!args->tag.empty() && args->tag == watcher_it->second.last_tag) {
    OnNotifyInQueueCompleted(std::move(args),
                             base::File::FILE_ERROR_INVALID_OPERATION);
    return AbortCallback();
  }

  // The object is owned by AutoUpdated, so the reference is valid as long as
  // callbacks created with AutoUpdater::CreateCallback().
  const ProvidedFileSystemObserver::Changes& changes_ref = *args->changes.get();
  const storage::WatcherManager::ChangeType change_type = args->change_type;

  scoped_refptr<AutoUpdater> auto_updater(new AutoUpdater(base::BindOnce(
      &ProvidedFileSystem::OnNotifyInQueueCompleted,
      weak_ptr_factory_.GetWeakPtr(), std::move(args), base::File::FILE_OK)));

  // Call all notification callbacks (if any).
  for (const auto& subscriber_it : watcher_it->second.subscribers) {
    const storage::WatcherManager::NotificationCallback& notification_callback =
        subscriber_it.second.notification_callback;
    if (!notification_callback.is_null())
      notification_callback.Run(change_type);
  }

  // Notify all observers.
  for (auto& observer : observers_) {
    observer.OnWatcherChanged(file_system_info_, watcher_it->second,
                              change_type, changes_ref,
                              auto_updater->CreateCallback());
  }

  return AbortCallback();
}

base::WeakPtr<ProvidedFileSystemInterface> ProvidedFileSystem::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

std::unique_ptr<ScopedUserInteraction>
ProvidedFileSystem::StartUserInteraction() {
  return std::make_unique<ScopedUserInteractionImpl>(this);
}

void ProvidedFileSystem::OnAddWatcherInQueueCompleted(
    size_t token,
    const base::FilePath& entry_path,
    bool recursive,
    const Subscriber& subscriber,
    storage::AsyncFileUtil::StatusCallback callback,
    base::File::Error result) {
  if (result != base::File::FILE_OK) {
    std::move(callback).Run(result);
    watcher_queue_.Complete(token);
    return;
  }

  const WatcherKey key(entry_path, recursive);
  const Watchers::iterator it = watchers_.find(key);
  if (it != watchers_.end()) {
    std::move(callback).Run(base::File::FILE_OK);
    watcher_queue_.Complete(token);
    return;
  }

  Watcher* const watcher = &watchers_[key];
  watcher->entry_path = entry_path;
  watcher->recursive = recursive;
  watcher->subscribers[subscriber.origin] = subscriber;

  for (auto& observer : observers_)
    observer.OnWatcherListChanged(file_system_info_, watchers_);

  std::move(callback).Run(base::File::FILE_OK);
  watcher_queue_.Complete(token);
}

void ProvidedFileSystem::OnRemoveWatcherInQueueCompleted(
    size_t token,
    const GURL& origin,
    const WatcherKey& key,
    storage::AsyncFileUtil::StatusCallback callback,
    bool extension_response,
    base::File::Error result) {
  if (!extension_response && result != base::File::FILE_OK) {
    watcher_queue_.Complete(token);
    std::move(callback).Run(result);
    return;
  }

  // Even if the extension returns an error, the callback is called with base::
  // File::FILE_OK.
  const auto it = watchers_.find(key);
  DCHECK(it != watchers_.end());
  DCHECK(it->second.subscribers.find(origin) != it->second.subscribers.end());

  it->second.subscribers.erase(origin);

  // If there are no more subscribers, then remove the watcher.
  if (it->second.subscribers.empty())
    watchers_.erase(it);

  for (auto& observer : observers_) {
    observer.OnWatcherListChanged(file_system_info_, watchers_);
  }

  std::move(callback).Run(base::File::FILE_OK);
  watcher_queue_.Complete(token);
}

void ProvidedFileSystem::OnNotifyInQueueCompleted(
    std::unique_ptr<NotifyInQueueArgs> args,
    base::File::Error result) {
  if (result != base::File::FILE_OK) {
    std::move(args->callback).Run(result);
    watcher_queue_.Complete(args->token);
    return;
  }

  // Check if the entry is still watched.
  const WatcherKey key(args->entry_path, args->recursive);
  const Watchers::iterator it = watchers_.find(key);
  if (it == watchers_.end()) {
    std::move(args->callback).Run(base::File::FILE_ERROR_NOT_FOUND);
    watcher_queue_.Complete(args->token);
    return;
  }

  it->second.last_tag = args->tag;

  for (auto& observer : observers_)
    observer.OnWatcherTagUpdated(file_system_info_, it->second);

  // If the watched entry is deleted, then remove the watcher.
  if (args->change_type == storage::WatcherManager::DELETED) {
    // Make a copy, since the |it| iterator will get invalidated on the last
    // subscriber.
    Subscribers subscribers = it->second.subscribers;
    for (const auto& subscriber_it : subscribers) {
      RemoveWatcher(subscriber_it.second.origin, args->entry_path,
                    args->recursive, base::DoNothing());
    }
  }

  std::move(args->callback).Run(base::File::FILE_OK);
  watcher_queue_.Complete(args->token);
}

void ProvidedFileSystem::OnOpenFileCompleted(
    const base::FilePath& file_path,
    OpenFileMode mode,
    OpenFileCallback callback,
    int file_handle,
    base::File::Error result,
    std::unique_ptr<EntryMetadata> metadata) {
  if (result != base::File::FILE_OK) {
    std::move(callback).Run(file_handle, result, std::move(metadata));
    return;
  }

  opened_files_[file_handle] = OpenedFile(file_path, mode);
  std::move(callback).Run(file_handle, base::File::FILE_OK,
                          std::move(metadata));
}

void ProvidedFileSystem::OnCloseFileCompleted(
    int file_handle,
    storage::AsyncFileUtil::StatusCallback callback,
    base::File::Error result) {
  // Closing files is final. Even if an error happened, we remove it from the
  // list of opened files.
  opened_files_.erase(file_handle);
  std::move(callback).Run(result);
}

void ProvidedFileSystem::OnLacrosOperationForwarded(int request_id,
                                                    base::File::Error error) {
  request_manager_->RejectRequest(request_id, RequestValue(), error);
}

void ProvidedFileSystem::ConstructRequestManager() {
  const extensions::ExtensionId& extension_id =
      file_system_info_.provider_id().GetExtensionId();
  base::TimeDelta operation_timeout = kDefaultOperationTimeout;
  if (chromeos::features::IsUploadOfficeToCloudEnabled() &&
      extension_id == extension_misc::kODFSExtensionId) {
    // Longer timeout for ODFS.
    operation_timeout = kODFSOperationTimeout;
  }

  request_manager_ = std::make_unique<OperationRequestManager>(
      profile_, extension_id, notification_manager_.get(), operation_timeout);

  if (chromeos::features::IsUploadOfficeToCloudEnabled() &&
      extension_id == extension_misc::kODFSExtensionId) {
    request_manager_->AddObserver(odfs_metrics_.get());
  }
}

}  // namespace ash::file_system_provider