chromium/chrome/browser/ash/file_system_provider/request_manager.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/request_manager.h"

#include "base/files/file.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"

namespace ash::file_system_provider {
namespace {

}  // namespace

RequestManager::RequestManager(
    Profile* profile,
    NotificationManagerInterface* notification_manager,
    base::TimeDelta timeout)
    : profile_(profile),
      notification_manager_(notification_manager),
      next_id_(1),
      timeout_(timeout) {}

RequestManager::~RequestManager() {
  // Abort all of the active requests.
  auto it = requests_.begin();
  while (it != requests_.end()) {
    const int request_id = it->first;
    ++it;
    RejectRequestInternal(request_id, RequestValue(),
                          base::File::FILE_ERROR_ABORT,
                          OperationCompletion::kAbortedInternally);
  }

  DCHECK_EQ(0u, requests_.size());
}

int RequestManager::CreateRequest(RequestType type,
                                  std::unique_ptr<HandlerInterface> handler) {
  // The request id is unique per request manager, so per service, thereof
  // per profile.
  int request_id = next_id_++;

  // If cycled the int, then signal an error.
  if (requests_.find(request_id) != requests_.end())
    return 0;

  TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("file_system_provider",
                                    "RequestManager::Request",
                                    TRACE_ID_LOCAL(request_id), "type", type);

  std::unique_ptr<Request> request = std::make_unique<Request>();
  request->handler = std::move(handler);
  requests_[request_id] = std::move(request);
  ResetTimer(request_id);

  for (auto& observer : observers_)
    observer.OnRequestCreated(request_id, type);

  // Execute the request implementation. In case of an execution failure,
  // unregister and return 0. This may often happen, eg. if the providing
  // extension is not listening for the request event being sent.
  // In such case, we should abort as soon as possible.
  if (!requests_[request_id]->handler->Execute(request_id)) {
    DestroyRequest(request_id, OperationCompletion::kCompletedNormally);
    return 0;
  }

  for (auto& observer : observers_)
    observer.OnRequestExecuted(request_id);

  return request_id;
}

base::File::Error RequestManager::FulfillRequest(int request_id,
                                                 const RequestValue& response,
                                                 bool has_more) {
  auto request_it = requests_.find(request_id);
  if (request_it == requests_.end())
    return base::File::FILE_ERROR_NOT_FOUND;

  for (auto& observer : observers_)
    observer.OnRequestFulfilled(request_id, response, has_more);

  const Request& request = *request_it->second;
  request.handler->OnSuccess(request_id, response, has_more);

  if (!has_more) {
    DestroyRequest(request_id, request.shown_unresponsive_notification
                                   ? OperationCompletion::kCompletedAfterWarning
                                   : OperationCompletion::kCompletedNormally);
  } else {
    if (notification_manager_)
      notification_manager_->HideUnresponsiveNotification(request_id);
    ResetTimer(request_id);
  }

  return base::File::FILE_OK;
}

base::File::Error RequestManager::RejectRequest(int request_id,
                                                const RequestValue& response,
                                                base::File::Error error) {
  auto request_it = requests_.find(request_id);
  if (request_it == requests_.end()) {
    return base::File::FILE_ERROR_NOT_FOUND;
  }
  const Request& request = *request_it->second;
  return RejectRequestInternal(request_id, response, error,
                               request.shown_unresponsive_notification
                                   ? OperationCompletion::kCompletedAfterWarning
                                   : OperationCompletion::kCompletedNormally);
}

void RequestManager::SetTimeoutForTesting(const base::TimeDelta& timeout) {
  timeout_ = timeout;
}

std::vector<int> RequestManager::GetActiveRequestIds() const {
  std::vector<int> result;

  for (const auto& request : requests_) {
    result.push_back(request.first);
  }

  return result;
}

void RequestManager::AddObserver(Observer* observer) {
  DCHECK(observer);
  observers_.AddObserver(observer);
}

void RequestManager::RemoveObserver(Observer* observer) {
  DCHECK(observer);
  observers_.RemoveObserver(observer);
}

RequestManager::Request::Request() = default;

RequestManager::Request::~Request() = default;

void RequestManager::OnRequestTimeout(int request_id) {
  for (auto& observer : observers_)
    observer.OnRequestTimedOut(request_id);

  if (!notification_manager_) {
    RejectRequestInternal(request_id, RequestValue(),
                          base::File::FILE_ERROR_ABORT,
                          OperationCompletion::kAbortedInternally);
    return;
  }

  auto request_it = requests_.find(request_id);
  if (request_it == requests_.end()) {
    return;
  }

  request_it->second->shown_unresponsive_notification = true;
  notification_manager_->ShowUnresponsiveNotification(
      request_id,
      base::BindOnce(&RequestManager::OnUnresponsiveNotificationResult,
                     weak_ptr_factory_.GetWeakPtr(), request_id));
}

void RequestManager::OnUnresponsiveNotificationResult(
    int request_id,
    NotificationManagerInterface::NotificationResult result) {
  auto request_it = requests_.find(request_id);
  if (request_it == requests_.end())
    return;

  if (result == NotificationManagerInterface::CONTINUE) {
    ResetTimer(request_id);
    return;
  }

  RejectRequestInternal(request_id, RequestValue(),
                        base::File::FILE_ERROR_ABORT,
                        OperationCompletion::kAbortedFromNotification);
}

void RequestManager::ResetTimer(int request_id) {
  auto request_it = requests_.find(request_id);
  if (request_it == requests_.end())
    return;

  request_it->second->timeout_timer.Start(
      FROM_HERE, timeout_,
      base::BindOnce(&RequestManager::OnRequestTimeout,
                     weak_ptr_factory_.GetWeakPtr(), request_id));
}

base::File::Error RequestManager::RejectRequestInternal(
    int request_id,
    const RequestValue& response,
    base::File::Error error,
    OperationCompletion completion) {
  auto request_it = requests_.find(request_id);
  if (request_it == requests_.end()) {
    return base::File::FILE_ERROR_NOT_FOUND;
  }

  if (error == base::File::FILE_ERROR_ABORT) {
    request_it->second->handler->OnAbort(request_id);
  }

  for (auto& observer : observers_) {
    observer.OnRequestRejected(request_id, response, error);
  }
  request_it->second->handler->OnError(request_id, response, error);
  DestroyRequest(request_id, completion);

  return base::File::FILE_OK;
}

void RequestManager::DestroyRequest(int request_id,
                                    OperationCompletion completion) {
  auto request_it = requests_.find(request_id);
  if (request_it == requests_.end())
    return;

  requests_.erase(request_it);

  if (notification_manager_)
    notification_manager_->HideUnresponsiveNotification(request_id);

  for (auto& observer : observers_)
    observer.OnRequestDestroyed(request_id, completion);

  TRACE_EVENT_NESTABLE_ASYNC_END0("file_system_provider",
                                  "RequestManager::Request",
                                  TRACE_ID_LOCAL(request_id));
}

}  // namespace ash::file_system_provider