910e62b5创建于 1月15日历史提交
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_COMMERCE_CORE_SUBSCRIPTIONS_SUBSCRIPTIONS_MANAGER_H_
#define COMPONENTS_COMMERCE_CORE_SUBSCRIPTIONS_SUBSCRIPTIONS_MANAGER_H_

#include <queue>
#include <string>
#include <unordered_map>

#include "base/check.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "components/commerce/core/account_checker.h"
#include "components/commerce/core/proto/commerce_subscription_db_content.pb.h"
#include "components/session_proto_db/session_proto_storage.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/primary_account_change_event.h"

namespace network {
class SharedURLLoaderFactory;
}  // namespace network

namespace commerce {

class SubscriptionsObserver;
class SubscriptionsServerProxy;
class SubscriptionsStorage;
enum class SubscriptionType;
struct CommerceSubscription;

extern const char kTrackResultHistogramName[];
extern const char kUntrackResultHistogramName[];

// Possible result status of a product (un)tracking request. This enum needs to
// match the values in enums.xml.
enum class SubscriptionsRequestStatus {
  // Subscriptions successfully added or removed on server.
  kSuccess = 0,
  // Server failed to parse the request.
  kServerParseError = 1,
  // Server successfully parsed the request, but failed afterwards.
  kServerInternalError = 2,
  // Local storage failed to load, create, or delete subscriptions.
  kStorageError = 3,
  // If the last sync with server failed, we just drop this request.
  kLastSyncFailed = 4,
  // The passed in argument is invalid.
  kInvalidArgument = 5,
  // The request was lost somewhere unknown and never came back. This is used
  // for monitoring purpose only and should never happen if the subscriptions
  // work correctly.
  kLost = 6,
  // No action taken because the product is already tracked/untracked on the
  // server.
  kNoOp = 7,

  // This enum must be last and is only used for histograms.
  kMaxValue = kNoOp
};

using SubscriptionsRequestCallback =
    base::OnceCallback<void(SubscriptionsRequestStatus)>;

class SubscriptionsManager : public signin::IdentityManager::Observer {
 public:
  SubscriptionsManager(
      signin::IdentityManager* identity_manager,
      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
      SessionProtoStorage<
          commerce_subscription_db::CommerceSubscriptionContentProto>*
          subscription_proto_db,
      AccountChecker* account_checker,
      signin::ConsentLevel consent_level);
  // Used for tests. The passed in objects are ordinarily created with
  // parameters from the non-test constructor.
  SubscriptionsManager(signin::IdentityManager* identity_manager,
                       std::unique_ptr<SubscriptionsServerProxy> server_proxy,
                       std::unique_ptr<SubscriptionsStorage> storage,
                       AccountChecker* account_checker);
  SubscriptionsManager(const SubscriptionsManager&) = delete;
  SubscriptionsManager& operator=(const SubscriptionsManager&) = delete;
  ~SubscriptionsManager() override;

  void Subscribe(
      std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
      base::OnceCallback<void(bool)> callback);

  void Unsubscribe(
      std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
      base::OnceCallback<void(bool)> callback);

  // Check if a |subscription| exists in the local database.
  void IsSubscribed(CommerceSubscription subscription,
                    base::OnceCallback<void(bool)> callback);

  // Checks if a subscription exists from the in-memory cache. Use of the the
  // callback-based version |IsSubscribed| is preferred. Information provided
  // by this API is not guaranteed to be correct as it doesn't query the
  // backend.
  bool IsSubscribedFromCache(const CommerceSubscription& subscription);

  // Get all subscriptions that match the provided |type|.
  void GetAllSubscriptions(
      SubscriptionType type,
      base::OnceCallback<void(std::vector<CommerceSubscription>)> callback);

  // On bookmark meta info change, we check its |last_subscription_change_time|
  // against last time we sync server subscriptions with local cache. If the
  // latter one is older, the local cache is outdated and we need to fetch the
  // newest subscriptions from server. This is mainly used to keep local
  // subscriptions up to date when users operate on multiple devices.
  virtual void CheckTimestampOnBookmarkChange(
      int64_t bookmark_subscription_change_time);

  void AddObserver(SubscriptionsObserver* observer);
  void RemoveObserver(SubscriptionsObserver* observer);

  // For tests only, return last_sync_succeeded_.
  bool GetLastSyncSucceededForTesting();

  // For tests only, return last_sync_time_;
  int64_t GetLastSyncTimeForTesting();

  // For tests only, set has_request_running_.
  void SetHasRequestRunningForTesting(bool has_request_running);

  // For tests only, return whether there are any pending requests.
  bool HasPendingRequestsForTesting();

  void SetLastRequestStartedTimeForTesting(base::Time time);

 protected:
  // Default constructor for testing.
  SubscriptionsManager();

 private:
  enum class AsyncOperation {
    kSync = 0,
    kSubscribe = 1,
    kUnsubscribe = 2,
    kLookupOne = 3,
    kGetAll = 4,
    kCheckOnBookmarkChange = 5,
  };

  struct Request {
    Request(AsyncOperation operation, base::OnceCallback<void()> callback);
    Request(const Request&) = delete;
    Request& operator=(const Request&) = delete;
    Request(Request&&);
    Request& operator=(Request&&) = default;
    ~Request();

    AsyncOperation operation;
    base::OnceCallback<void()> callback;
  };

  // Fetch all backend subscriptions and sync with local storage.
  void SyncSubscriptions();

  // Check if there is any request running. If not, process the next request in
  // the queue.
  void CheckAndProcessRequest();

  // Before adding certain operations (Subscribe, Unsubscribe, LookupOne, and
  // GetAll) to the pending requests, if the last sync with server failed, we
  // should re-try the sync first.
  void SyncIfNeeded();

  // On request completion, mark that no request is running and then check next
  // request. This must be chained to the end of callback in every Request.
  void OnRequestCompletion();

  // In certain operations (Sync, Subscribe, Unsubscribe, and
  // CheckOnBookmarkChange), we may sync local cache with server and need to
  // update |last_sync_succeeded_| and |last_sync_time_|.
  void UpdateSyncStates(bool sync_succeeded);

  void HandleSync();

  void OnSyncStatusFetched(SubscriptionsRequestStatus result);

  void HandleSubscribe(
      std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
      base::OnceCallback<void(bool)> callback);

  void OnSubscribeStatusFetched(
      std::vector<CommerceSubscription> notified_subscriptions,
      base::OnceCallback<void(bool)> callback,
      SubscriptionsRequestStatus result);

  void OnIncomingSubscriptionsFilteredForSubscribe(
      SubscriptionType type,
      SubscriptionsRequestCallback callback,
      std::unique_ptr<std::vector<CommerceSubscription>> unique_subscriptions);

  void HandleUnsubscribe(
      std::unique_ptr<std::vector<CommerceSubscription>> subscriptions,
      base::OnceCallback<void(bool)> callback);

  void OnUnsubscribeStatusFetched(
      std::vector<CommerceSubscription> notified_subscriptions,
      base::OnceCallback<void(bool)> callback,
      SubscriptionsRequestStatus result);

  void OnIncomingSubscriptionsFilteredForUnsubscribe(
      SubscriptionType type,
      SubscriptionsRequestCallback callback,
      std::unique_ptr<std::vector<CommerceSubscription>> unique_subscriptions);

  void GetRemoteSubscriptionsAndUpdateStorage(
      SubscriptionType type,
      SubscriptionsRequestCallback callback);

  void HandleGetSubscriptionsResponse(
      SubscriptionType type,
      SubscriptionsRequestCallback callback,
      SubscriptionsRequestStatus status,
      std::unique_ptr<std::vector<CommerceSubscription>> remote_subscriptions);

  void HandleManageSubscriptionsResponse(
      SubscriptionType type,
      SubscriptionsRequestCallback callback,
      SubscriptionsRequestStatus status,
      std::unique_ptr<std::vector<CommerceSubscription>> remote_subscriptions);

  void HandleLookup(CommerceSubscription subscription,
                    base::OnceCallback<void(bool)> callback);

  void OnLookupResult(base::OnceCallback<void(bool)> callback,
                      bool is_subscribed);

  void HandleGetAll(
      SubscriptionType type,
      base::OnceCallback<void(std::vector<CommerceSubscription>)> callback);

  void OnGetAllResult(
      base::OnceCallback<void(std::vector<CommerceSubscription>)> callback,
      std::unique_ptr<std::vector<CommerceSubscription>> subscriptions);

  void HandleCheckTimestampOnBookmarkChange(
      int64_t bookmark_subscription_change_time);

  void HandleGetSubscriptionsResponseOnBookmarkChange(
      SubscriptionsRequestStatus status,
      std::unique_ptr<std::vector<CommerceSubscription>> remote_subscriptions);

  void OnStorageUpdatedOnBookmarkChange(
      SubscriptionsRequestStatus status,
      std::vector<CommerceSubscription> added_subs,
      std::vector<CommerceSubscription> removed_subs);

  void OnSubscribe(const std::vector<CommerceSubscription>& subscriptions,
                   bool succeeded);
  void OnUnsubscribe(const std::vector<CommerceSubscription>& subscriptions,
                     bool succeeded);

  void OnPrimaryAccountChanged(
      const signin::PrimaryAccountChangeEvent& event_details) override;

  bool HasRequestRunning();

  // Hold coming requests until previous ones have finished to avoid race
  // conditions.
  std::queue<Request> pending_requests_;

  // Whether the last sync with server is successful. If not, all (un)subscribe
  // operations will fail immediately.
  bool last_sync_succeeded_ = false;
  // Last time we successfully synced with server.
  int64_t last_sync_time_{0L};

  // Whether there is any request running.
  bool has_request_running_ = false;

  base::Time last_request_started_time_ = base::Time();

  AsyncOperation last_request_operation_;

  base::ScopedObservation<signin::IdentityManager,
                          signin::IdentityManager::Observer>
      scoped_identity_manager_observation_{this};

  std::unique_ptr<SubscriptionsServerProxy> server_proxy_;

  std::unique_ptr<SubscriptionsStorage> storage_;

  raw_ptr<AccountChecker> account_checker_;

  base::ObserverList<SubscriptionsObserver>::Unchecked observers_;

  base::WeakPtrFactory<SubscriptionsManager> weak_ptr_factory_{this};
};

}  // namespace commerce

#endif  // COMPONENTS_COMMERCE_CORE_SUBSCRIPTIONS_SUBSCRIPTIONS_MANAGER_H_