| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/feature_engagement/internal/persistent_availability_store.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/feature_list.h" |
| #include "components/feature_engagement/internal/proto/availability.pb.h" |
| #include "components/feature_engagement/internal/stats.h" |
| #include "components/feature_engagement/public/feature_list.h" |
| #include "components/leveldb_proto/proto_database.h" |
| |
| namespace feature_engagement { |
| |
| namespace { |
| |
| using KeyAvailabilityPair = std::pair<std::string, Availability>; |
| using KeyAvailabilityList = std::vector<KeyAvailabilityPair>; |
| |
| // Corresponds to a UMA suffix "LevelDBOpenResults" in histograms.xml. |
| // Please do not change. |
| const char kDatabaseUMAName[] = "FeatureEngagementTrackerAvailabilityStore"; |
| |
| void OnDBUpdateComplete( |
| std::unique_ptr<leveldb_proto::ProtoDatabase<Availability>> db, |
| PersistentAvailabilityStore::OnLoadedCallback on_loaded_callback, |
| std::unique_ptr<std::map<std::string, uint32_t>> feature_availabilities, |
| bool success) { |
| stats::RecordDbUpdate(success, stats::StoreType::AVAILABILITY_STORE); |
| std::move(on_loaded_callback).Run(success, std::move(feature_availabilities)); |
| } |
| |
| void OnDBLoadComplete( |
| std::unique_ptr<leveldb_proto::ProtoDatabase<Availability>> db, |
| FeatureVector feature_filter, |
| PersistentAvailabilityStore::OnLoadedCallback on_loaded_callback, |
| uint32_t current_day, |
| bool success, |
| std::unique_ptr<std::vector<Availability>> availabilities) { |
| stats::RecordAvailabilityDbLoadEvent(success); |
| if (!success) { |
| std::move(on_loaded_callback) |
| .Run(false, std::make_unique<std::map<std::string, uint32_t>>()); |
| return; |
| } |
| |
| // Create map from feature name to Feature. |
| std::map<std::string, const base::Feature*> feature_mapping; |
| for (const base::Feature* feature : feature_filter) { |
| DCHECK(feature_mapping.find(feature->name) == feature_mapping.end()); |
| feature_mapping[feature->name] = feature; |
| } |
| |
| // Find all availabilities from DB and find out what should be deleted. |
| auto feature_availabilities = |
| std::make_unique<std::map<std::string, uint32_t>>(); |
| auto deletes = std::make_unique<std::vector<std::string>>(); |
| for (auto& availability : *availabilities) { |
| // Check if in |feature_filter|. |
| if (feature_mapping.find(availability.feature_name()) == |
| feature_mapping.end()) { |
| deletes->push_back(availability.feature_name()); |
| continue; |
| } |
| |
| // Check if enabled. |
| const base::Feature* feature = feature_mapping[availability.feature_name()]; |
| if (!base::FeatureList::IsEnabled(*feature)) { |
| deletes->push_back(availability.feature_name()); |
| continue; |
| } |
| |
| // Both in |feature_filter| and is enabled, so keep around. |
| feature_availabilities->insert( |
| std::make_pair(feature->name, availability.day())); |
| DVLOG(2) << "Keeping availability for " << feature->name << " @ " |
| << availability.day(); |
| } |
| |
| // Find features from |feature_filter| that are enabled, but not in DB yet. |
| auto additions = std::make_unique<KeyAvailabilityList>(); |
| for (const base::Feature* feature : feature_filter) { |
| // Check if already in DB. |
| if (feature_availabilities->find(feature->name) != |
| feature_availabilities->end()) |
| continue; |
| |
| // Check if enabled. |
| if (!base::FeatureList::IsEnabled(*feature)) |
| continue; |
| |
| // Both in feature filter, and is enabled, but not in DB, so add to DB. |
| Availability availability; |
| availability.set_feature_name(feature->name); |
| availability.set_day(current_day); |
| additions->push_back({feature->name, std::move(availability)}); |
| // Since it will be written to the DB, also add to the callback result. |
| feature_availabilities->insert({feature->name, current_day}); |
| |
| DVLOG(2) << "Adding availability for " << feature->name << " @ " |
| << current_day; |
| } |
| |
| // Write all changes to the DB. |
| auto* db_ptr = db.get(); |
| db_ptr->UpdateEntries(std::move(additions), std::move(deletes), |
| base::BindOnce(&OnDBUpdateComplete, std::move(db), |
| std::move(on_loaded_callback), |
| std::move(feature_availabilities))); |
| } |
| |
| void OnDBInitComplete( |
| std::unique_ptr<leveldb_proto::ProtoDatabase<Availability>> db, |
| FeatureVector feature_filter, |
| PersistentAvailabilityStore::OnLoadedCallback on_loaded_callback, |
| uint32_t current_day, |
| leveldb_proto::Enums::InitStatus status) { |
| bool success = status == leveldb_proto::Enums::InitStatus::kOK; |
| stats::RecordDbInitEvent(success, stats::StoreType::AVAILABILITY_STORE); |
| |
| if (!success) { |
| std::move(on_loaded_callback) |
| .Run(false, std::make_unique<std::map<std::string, uint32_t>>()); |
| return; |
| } |
| |
| auto* db_ptr = db.get(); |
| db_ptr->LoadEntries(base::BindOnce( |
| &OnDBLoadComplete, std::move(db), std::move(feature_filter), |
| std::move(on_loaded_callback), current_day)); |
| } |
| |
| } // namespace |
| |
| // static |
| void PersistentAvailabilityStore::LoadAndUpdateStore( |
| const base::FilePath& storage_dir, |
| std::unique_ptr<leveldb_proto::ProtoDatabase<Availability>> db, |
| FeatureVector feature_filter, |
| PersistentAvailabilityStore::OnLoadedCallback on_loaded_callback, |
| uint32_t current_day) { |
| auto* db_ptr = db.get(); |
| db_ptr->Init(kDatabaseUMAName, |
| base::BindOnce(&OnDBInitComplete, std::move(db), |
| std::move(feature_filter), |
| std::move(on_loaded_callback), current_day)); |
| } |
| |
| } // namespace feature_engagement |