chromium/chrome/browser/ash/api/tasks/tasks_client_impl.h

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_ASH_API_TASKS_TASKS_CLIENT_IMPL_H_
#define CHROME_BROWSER_ASH_API_TASKS_TASKS_CLIENT_IMPL_H_

#include <map>
#include <memory>
#include <string>
#include <vector>

#include "ash/api/tasks/tasks_client.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/types/expected.h"
#include "google_apis/tasks/tasks_api_requests.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "ui/base/models/list_model.h"

class Profile;

namespace base {
class Time;
}  // namespace base

namespace google_apis {
class RequestSender;
namespace tasks {
class TaskLists;
class Tasks;
}  // namespace tasks
}  // namespace google_apis

namespace ash::api {

struct Task;
struct TaskList;

// Provides implementation for `TasksClient`. Responsible for communication with
// Google Tasks API.
class TasksClientImpl : public TasksClient {
 public:
  // Provides an instance of `google_apis::RequestSender` for the client.
  using CreateRequestSenderCallback =
      base::RepeatingCallback<std::unique_ptr<google_apis::RequestSender>(
          const std::vector<std::string>& scopes,
          const net::NetworkTrafficAnnotationTag& traffic_annotation_tag)>;

  TasksClientImpl(
      Profile* profile,
      const CreateRequestSenderCallback& create_request_sender_callback,
      net::NetworkTrafficAnnotationTag traffic_annotation_tag);
  TasksClientImpl(const TasksClientImpl&) = delete;
  TasksClientImpl& operator=(const TasksClientImpl&) = delete;
  ~TasksClientImpl() override;

  // TasksClient:
  bool IsDisabledByAdmin() const override;
  const ui::ListModel<api::TaskList>* GetCachedTaskLists() override;
  void GetTaskLists(bool force_fetch,
                    TasksClient::GetTaskListsCallback callback) override;
  const ui::ListModel<api::Task>* GetCachedTasksInTaskList(
      const std::string& task_list_id) override;
  void GetTasks(const std::string& task_list_id,
                bool force_fetch,
                TasksClient::GetTasksCallback callback) override;
  void MarkAsCompleted(const std::string& task_list_id,
                       const std::string& task_id,
                       bool completed) override;
  void AddTask(const std::string& task_list_id,
               const std::string& title,
               TasksClient::OnTaskSavedCallback callback) override;
  void UpdateTask(const std::string& task_list_id,
                  const std::string& task_id,
                  const std::string& title,
                  bool completed,
                  TasksClient::OnTaskSavedCallback callback) override;
  void InvalidateCache() override;
  std::optional<base::Time> GetTasksLastUpdateTime(
      const std::string& task_list_id) const override;
  void OnGlanceablesBubbleClosed(base::OnceClosure callback) override;

  using TaskListsRequestCallback =
      base::RepeatingCallback<void(const std::string& page_token)>;
  void set_task_lists_request_callback_for_testing(
      const TaskListsRequestCallback& callback) {
    task_lists_request_callback_ = callback;
  }

  using TasksRequestCallback =
      base::RepeatingCallback<void(const std::string& task_list_id,
                                   const std::string& page_token)>;
  void set_tasks_request_callback_for_testing(
      const TasksRequestCallback& callback) {
    tasks_request_callback_ = callback;
  }

 private:
  enum class FetchStatus { kNotFresh, kRefreshing, kFresh };

  // A structure that keeps track of fetch status and list of pending
  // callbacks for a task lists fetch request.
  struct TaskListsFetchState {
    TaskListsFetchState();
    ~TaskListsFetchState();

    FetchStatus status = FetchStatus::kNotFresh;
    // Callbacks to be called when all task lists get fetched using tasks API.
    // Should be non-empty if a task lists fetch is in progress.
    std::vector<TasksClient::GetTaskListsCallback> callbacks;
  };

  // A structure that keeps track of fetch status and list of pending callbacks
  // for a single tasks in a task list fetch request.
  struct TasksFetchState {
    TasksFetchState();
    ~TasksFetchState();

    FetchStatus status = FetchStatus::kNotFresh;

    // The last time that the tasks were fetched and the status became kFresh.
    std::optional<base::Time> last_updated_time = std::nullopt;

    // Callbacks to be called when all tasks in a task list get fetched using
    // tasks API.
    // Should be non-empty if a tasks fetch for the target task list is in
    // progress.
    std::vector<TasksClient::GetTasksCallback> callbacks;
  };

  // Fetches one page of task lists data.
  // `page_token`  - token specifying the result page to return, comes from the
  //                 previous fetch request. Use an empty string to fetch the
  //                 first page.
  // `page_number` - 1-based page number of this fetch request. Used for UMA
  //                 to track the total number of pages needed to fetch.
  // `accumulated_raw_task_lists` - The list of task lists accumulated from
  //                                different task lists pages.
  void FetchTaskListsPage(
      const std::string& page_token,
      int page_number,
      std::vector<std::unique_ptr<google_apis::tasks::TaskList>>
          accumulated_raw_task_lists);

  // Callback for `FetchTaskListsPage()`. Transforms fetched items to
  // ash-friendly types. If `next_page_token()` in the `result` is not empty -
  // calls another `FetchTaskListsPage()`, otherwise runs
  // `RunGetTaskListsCallbacks()`.
  void OnTaskListsPageFetched(
      const base::Time& request_start_time,
      int page_number,
      std::vector<std::unique_ptr<google_apis::tasks::TaskList>>
          accumulated_raw_task_lists,
      base::expected<std::unique_ptr<google_apis::tasks::TaskLists>,
                     google_apis::ApiErrorCode> result);

  // Fetches one page of tasks data.
  // `task_list_id`          - task list identifier.
  // `page_token`            - token specifying the result page to return, comes
  //                           from the previous fetch request. Use an empty
  //                           string to fetch the first page.
  // `page_number`           - 1-based page number of this fetch request. Used
  //                           for UMA to track the total number of pages needed
  //                           to fetch.
  // `accumulated_raw_tasks` - in contrast to the task lists conversion logic,
  //                           tasks can't be converted independently on every
  //                           single page response (subtasks could go first,
  //                           but their parent tasks will be on the next page).
  //                           This parameter helps to accumulate all of them
  //                           first and then do the conversion once the last
  //                           page is fetched.
  void FetchTasksPage(const std::string& task_list_id,
                      const std::string& page_token,
                      int page_number,
                      std::vector<std::unique_ptr<google_apis::tasks::Task>>
                          accumulated_raw_tasks);

  // Callback for `FetchTasksPage()`. Transforms fetched items to ash-friendly
  // types. If `next_page_token()` in the `result` is not empty - calls another
  // `FetchTasksPage()`, otherwise runs `RunGetTasksCallbacks()`.
  void OnTasksPageFetched(
      const std::string& task_list_id,
      std::vector<std::unique_ptr<google_apis::tasks::Task>>
          accumulated_raw_tasks,
      const base::Time& request_start_time,
      int page_number,
      base::expected<std::unique_ptr<google_apis::tasks::Tasks>,
                     google_apis::ApiErrorCode> result);

  // Callback for `MarkAsCompleted()` request. Does not removes the task from
  // `tasks_in_task_lists_` as it will be cleared by
  // `OnGlanceablesBubbleClosed`.
  void OnMarkedAsCompleted(
      const base::Time& request_start_time,
      base::RepeatingClosure on_done,
      base::expected<std::unique_ptr<google_apis::tasks::Task>,
                     google_apis::ApiErrorCode> result);

  // Done callback for `AddTask()` request.
  // `task_list_id`       - id of the task list used in the request.
  // `request_start_time` - start time of the request to measure latency.
  // `callback`           - done callback passed from `AddTask`.
  // `result`             - newly created task or HTTP error.
  void OnTaskAdded(const std::string& task_list_id,
                   const base::Time& request_start_time,
                   TasksClient::OnTaskSavedCallback callback,
                   base::expected<std::unique_ptr<google_apis::tasks::Task>,
                                  google_apis::ApiErrorCode> result);

  // Done callback for `UpdateTask()` request.
  // `task_list_id`       - id of the task list used in the request.
  // `request_start_time` - start time of the request to measure latency.
  // `callback`           - done callback passed from `UpdateTask`.
  // `result`             - updated task or HTTP error.
  void OnTaskUpdated(const std::string& task_list_id,
                     const base::Time& request_start_time,
                     TasksClient::OnTaskSavedCallback callback,
                     base::expected<std::unique_ptr<google_apis::tasks::Task>,
                                    google_apis::ApiErrorCode> result);

  // To be called when requests to get user's task lists complete.
  // It sets the task lists fetch status to `final_fetch_status`, and runs all
  // pending callbacks in `task_lists_fetch_state_`. `http_error` will have a
  // value from the Google Tasks API request.
  void RunGetTaskListsCallbacks(
      FetchStatus final_fetch_status,
      std::optional<google_apis::ApiErrorCode> http_error,
      std::vector<std::unique_ptr<google_apis::tasks::TaskList>>
          accumulated_raw_task_lists);

  // To be called when requests to get tasks in the task list identified by
  // `task_list_id` complete. It sets fetch status for the task list fetch to
  // `final_fetch_status`, and runs all pending callbacks for the task list
  // (kept in `tasks_fetch_state_` map). The callbacks are run with `tasks`.
  void RunGetTasksCallbacks(
      const std::string& task_list_id,
      FetchStatus final_fetch_status,
      std::optional<google_apis::ApiErrorCode> http_error,
      std::vector<std::unique_ptr<google_apis::tasks::Task>>
          accumulated_raw_tasks);

  // A map of `task_list_id` to a set of `task_id` that are pending to be
  // completed.
  std::map<std::string, std::set<std::string>> pending_completed_tasks_;

  // Returns lazily initialized `request_sender_`.
  google_apis::RequestSender* GetRequestSender();

  // The profile for which this instance was created.
  const raw_ptr<Profile> profile_;

  // Callback passed from `GlanceablesKeyedService` that creates
  // `request_sender_`.
  const CreateRequestSenderCallback create_request_sender_callback_;

  // Helper class that sends requests, handles retries and authentication.
  std::unique_ptr<google_apis::RequestSender> request_sender_;

  // The current fetch state for the users task lists.
  TaskListsFetchState task_lists_fetch_state_;

  // All available task lists.
  ui::ListModel<TaskList> task_lists_;

  // All available tasks grouped by task list id.
  std::map<std::string, ui::ListModel<Task>> tasks_in_task_lists_;

  // Map that contains fetch states for tasks requests from different task
  // lists. Mapped by the task list id.
  std::map<std::string, std::unique_ptr<TasksFetchState>> tasks_fetch_state_;

  // Stub tasks list model that can be used to return an empty task list to
  // `GetTasks()` requests.
  ui::ListModel<Task> stub_task_list_;

  // Callbacks invoked whenever a tasks API request is made. Used primarily
  // in tests.
  TaskListsRequestCallback task_lists_request_callback_;
  TasksRequestCallback tasks_request_callback_;

  const net::NetworkTrafficAnnotationTag traffic_annotation_tag_;

  base::WeakPtrFactory<TasksClientImpl> weak_factory_{this};
};

}  // namespace ash::api

#endif  // CHROME_BROWSER_ASH_API_TASKS_TASKS_CLIENT_IMPL_H_