chromium/ios/chrome/browser/variations/model/ios_chrome_variations_seed_store.mm

// 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.

#import "ios/chrome/browser/variations/model/ios_chrome_variations_seed_store.h"

#import "base/no_destructor.h"
#import "components/variations/seed_response.h"
#import "ios/chrome/browser/variations/model/ios_chrome_variations_seed_store+fetcher.h"
#import "ios/chrome/browser/variations/model/ios_chrome_variations_seed_store+testing.h"

namespace {

using ::variations::SeedApplicationStage;
using ::variations::SeedResponse;

// Seed application stages in the order of sequence they should happen.
const SeedApplicationStage kSeedApplicationStages[] = {
    SeedApplicationStage::kNoSeed, SeedApplicationStage::kSeedStored,
    SeedApplicationStage::kSeedImported, SeedApplicationStage::kSeedApplied};

// Convenience getter and setter for the seed application stage. If
// `increment=true`, increment the stage before returning; if
// `reset_for_testing=true`, the seed application stage would be reset to
// `kNoSeed`.
//
// NOTE:`reset_for_testing` has precedence over `increment`, and setting this
// parameter explicitly should ONLY be done for testing purpose.
SeedApplicationStage SeedApplicationStageAccessor(
    bool increment,
    bool reset_for_testing = false) {
  // Index of the current seed application stage in `kSeedApplicationStages`.
  static int g_shared_seed_application_stage_idx = 0;

  if (reset_for_testing) {
    g_shared_seed_application_stage_idx = 0;
  } else if (increment) {
    g_shared_seed_application_stage_idx++;
  }
  CHECK_LE(g_shared_seed_application_stage_idx,
           static_cast<int>(SeedApplicationStage::kMaxValue));
  return kSeedApplicationStages[g_shared_seed_application_stage_idx];
}

// Getter for seed application stage extracted for readability purpose.
SeedApplicationStage GetSeedApplicationStage() {
  return SeedApplicationStageAccessor(/*increment=*/false);
}

// Increment the seed application stage if the current stage equals `stage`,
// otherwise do nothing; extracted for readability purpose.
void IncrementSeedApplicationStageIfAt(SeedApplicationStage stage) {
  if (GetSeedApplicationStage() == stage) {
    SeedApplicationStageAccessor(/*increment=*/true);
  }
}

// Swaps `seed` with the shared seed.
std::unique_ptr<SeedResponse> SwapSeedWithSharedSeed(
    std::unique_ptr<SeedResponse> seed) {
  // The source of truth of the entire app for the stored seed.
  static base::NoDestructor<std::unique_ptr<SeedResponse>> g_shared_seed;
  std::swap(*g_shared_seed, seed);
  return seed;
}

}  // namespace

@implementation IOSChromeVariationsSeedStore

#pragma mark - Public

+ (std::unique_ptr<SeedResponse>)popSeed {
  IncrementSeedApplicationStageIfAt(SeedApplicationStage::kSeedStored);
  // The old global seed is returned after being replaced
  // with nullptr.
  return SwapSeedWithSharedSeed(nullptr);
}

+ (void)notifySeedApplication {
  // If the seed has never been imported by variations service, the applied seed
  // is highly unlikely to be the seed that was in the seed store. Skip
  // incrementing.
  IncrementSeedApplicationStageIfAt(SeedApplicationStage::kSeedImported);
}

+ (SeedApplicationStage)seedApplicationStage {
  return GetSeedApplicationStage();
}

#pragma mark - Private Setter (exposed to fetcher only)

// Updates the shared seed response.
+ (void)updateSharedSeed:(std::unique_ptr<SeedResponse>)seed {
  // The old global seed is returned and destroyed when
  // the returned object goes out of scope.
  SwapSeedWithSharedSeed(std::move(seed));
  IncrementSeedApplicationStageIfAt(SeedApplicationStage::kNoSeed);
}

#pragma mark - Testing only

+ (void)resetForTesting {
  SwapSeedWithSharedSeed(nullptr);
  SeedApplicationStageAccessor(false, /*reset_for_testing=*/true);
}

@end