[core] Implement products configuration #9695

Merged
root merged 3 commits from add-products-config into master 2024-11-25 15:32:01 +00:00
16 changed files with 584 additions and 45 deletions

View file

@ -78,7 +78,7 @@
#define WORLD_COASTS_FILE_NAME "WorldCoasts"
#define SETTINGS_FILE_NAME "settings.ini"
#define MARKETING_SETTINGS_FILE_NAME "marketing_settings.ini"
#define PRODUCTS_SETTINGS_FILE_NAME "products_settings.json"
#define SEARCH_CATEGORIES_FILE_NAME "categories.txt"
#define SEARCH_CUISINE_CATEGORIES_FILE_NAME "categories_cuisines.txt"

View file

@ -149,7 +149,7 @@ NSString * const kUDFileLoggingEnabledKey = @"FileLoggingEnabledKey";
+ (NSString *)donateUrl
{
std::string url;
return settings::Get("DonateUrl", url) ? @(url.c_str()) : nil;
return settings::Get(settings::kDonateUrl, url) ? @(url.c_str()) : nil;
}
+ (BOOL)isNY

View file

@ -102,6 +102,14 @@ std::string_view constexpr kTranslitMode = "TransliterationMode";
std::string_view constexpr kPreferredGraphicsAPI = "PreferredGraphicsAPI";
std::string_view constexpr kShowDebugInfo = "DebugInfo";
std::string_view constexpr kScreenViewport = "ScreenClipRect";
std::string_view constexpr kPlacePageProductsPopupCloseTime = "PlacePageProductsPopupCloseTime";
std::string_view constexpr kPlacePageProductsPopupCloseReason = "PlacePageProductsPopupCloseReason";
std::string_view constexpr kPlacePageSelectedProduct = "PlacePageSelectedProduct";
std::string_view constexpr kProductsPopupCloseReasonCloseStr = "close";
std::string_view constexpr kProductsPopupCloseReasonRemindLaterStr = "remind_later";
std::string_view constexpr kProductsPopupCloseReasonAlreadyDonatedStr = "already_donated";
std::string_view constexpr kProductsPopupCloseReasonSelectProductStr = "select_product";
auto constexpr kLargeFontsScaleFactor = 1.6;
size_t constexpr kMaxTrafficCacheSizeBytes = 64 /* Mb */ * 1024 * 1024;
@ -3313,3 +3321,97 @@ void Framework::OnPowerSchemeChanged(power_management::Scheme const actualScheme
if (actualScheme == power_management::Scheme::EconomyMaximum && GetTrafficManager().IsEnabled())
GetTrafficManager().SetEnabled(false);
}
bool Framework::ShouldShowProducts() const
{
auto const connectionStatus = Platform::ConnectionStatus();
if (connectionStatus == Platform::EConnectionType::CONNECTION_NONE)
return false;
std::string donateUrl;
if (!settings::Get(settings::kDonateUrl, donateUrl)) // donation is disabled
return false;
if (!m_usageStats.IsLoyalUser())
return false;
if (!storage::IsPointCoveredByDownloadedMaps(GetCurrentPlacePageInfo().GetMercator(), m_storage, *m_infoGetter))
return false;
uint64_t popupCloseTime;
std::string productCloseReason;
if (!settings::Get(kPlacePageProductsPopupCloseTime, popupCloseTime) ||
!settings::Get(kPlacePageProductsPopupCloseReason, productCloseReason))
return true; // The popup was never closed.
auto const now = base::SecondsSinceEpoch();
auto const timeout = GetTimeoutForReason(FromString(productCloseReason));
bool const timeoutExpired = popupCloseTime + timeout < now;
vng commented 2024-11-22 11:48:16 +00:00 (Migrated from github.com)
Review

Let's take out the function:

bool UsageStats::IsLoyalUser() const
{
#ifdef DEBUG
  uint32_t constexpr kTotalForegroundTimeout = 30;
  uint32_t constexpr kSessionsCount = 3;
#else
  uint32_t constexpr kTotalForegroundTimeout = 60 * 15; // 15 min
  uint32_t constexpr kSessionsCount = 5;
#endif

  return m_sessionsCount >= kMinSessionsCount && m_totalForegroundTime >= kTotalForegroundTimeout;
}

And call it before IsPointCoveredByDownloadedMaps

Let's take out the function: ``` bool UsageStats::IsLoyalUser() const { #ifdef DEBUG uint32_t constexpr kTotalForegroundTimeout = 30; uint32_t constexpr kSessionsCount = 3; #else uint32_t constexpr kTotalForegroundTimeout = 60 * 15; // 15 min uint32_t constexpr kSessionsCount = 5; #endif return m_sessionsCount >= kMinSessionsCount && m_totalForegroundTime >= kTotalForegroundTimeout; } ``` And call it before IsPointCoveredByDownloadedMaps
Review

Nice idea! Thanks!

Nice idea! Thanks!
if (timeoutExpired)
return true;
return false;
}
std::optional<products::ProductsConfig> Framework::GetProductsConfiguration() const
{
if (!ShouldShowProducts())
return nullopt;
return products::GetProductsConfiguration();
}
void Framework::DidCloseProductsPopup(ProductsPopupCloseReason reason) const
{
vng commented 2024-11-20 10:47:33 +00:00 (Migrated from github.com)
Review

ProductSettings::Instance().Get()

We can store actual already parsed instance do avoid parsing every time we show PP:

class ProductSettings
{
public:
  ProductSettings()
  {
     lock_guard guard(m_mutex);
     FileReader r(...);
     r.Read(json);
     m_config = Parse(json);
  }
  void Update(std::string const & json)
  {
    lock_guard guard(m_mutex);
    FileWriter w(...);
    w.Write(json);
    m_config = Parse(json);
  }
  ProductConfig Get()
  {
    lock_guard guard(m_mutex);
    return m_config;
  }
};

ProductSettings::Instance().Get() We can store actual already parsed instance do avoid _parsing_ every time we show PP: ``` class ProductSettings { public: ProductSettings() { lock_guard guard(m_mutex); FileReader r(...); r.Read(json); m_config = Parse(json); } void Update(std::string const & json) { lock_guard guard(m_mutex); FileWriter w(...); w.Write(json); m_config = Parse(json); } ProductConfig Get() { lock_guard guard(m_mutex); return m_config; } }; ```
Review

Fixed! Thanks

Fixed! Thanks
settings::Set(kPlacePageProductsPopupCloseTime, base::SecondsSinceEpoch());
settings::Set(kPlacePageProductsPopupCloseReason, std::string(ToString(reason)));
}
void Framework::DidSelectProduct(products::ProductsConfig::Product const & product) const
{
settings::Set(kPlacePageSelectedProduct, product.GetTitle());
}
uint32_t Framework::GetTimeoutForReason(ProductsPopupCloseReason reason) const
{
#ifdef DEBUG
uint32_t constexpr kPopupCloseTimeout = 10;
uint32_t constexpr kProductSelectTimeout = 20;
uint32_t constexpr kRemindMeLaterTimeout = 5;
#else
uint32_t constexpr kPopupCloseTimeout = 60 * 60 * 24 * 30; // 30 days
Review

why do we need to store the exact donation option a user has chosen?

seems like its not used anywhere

why do we need to store the exact donation option a user has chosen? seems like its not used anywhere
Review

btw I assume a user could change the donation option (amount, recurring or one-time) after the button press? i.e. while on the stripe page already?

btw I assume a user could change the donation option (amount, recurring or one-time) after the button press? i.e. while on the stripe page already?
vng commented 2024-11-22 23:23:31 +00:00 (Migrated from github.com)
Review

Yes, he can do anything after the donate page, but we save what button prompted him to go.

Yes, he can do anything after the donate page, but we save what button prompted him to go.
uint32_t constexpr kProductSelectTimeout = 60 * 60 * 24 * 180; // 180 days
uint32_t constexpr kRemindMeLaterTimeout = 60 * 60 * 24 * 3; // 3 days
#endif
switch (reason)
{
case ProductsPopupCloseReason::Close: return kPopupCloseTimeout;
case ProductsPopupCloseReason::RemindLater: return kRemindMeLaterTimeout;
case ProductsPopupCloseReason::AlreadyDonated: return kProductSelectTimeout;
case ProductsPopupCloseReason::SelectProduct: return kProductSelectTimeout;
}
ASSERT(false, ("Unknown reason"));
return kPopupCloseTimeout;
}
std::string_view Framework::ToString(ProductsPopupCloseReason reason) const
{
switch (reason)
{
case ProductsPopupCloseReason::Close: return kProductsPopupCloseReasonCloseStr;
case ProductsPopupCloseReason::RemindLater: return kProductsPopupCloseReasonRemindLaterStr;
case ProductsPopupCloseReason::AlreadyDonated: return kProductsPopupCloseReasonAlreadyDonatedStr;
case ProductsPopupCloseReason::SelectProduct: return kProductsPopupCloseReasonSelectProductStr;
}
ASSERT(false, ("Unknown reason"));
return kProductsPopupCloseReasonCloseStr;
}
Framework::ProductsPopupCloseReason Framework::FromString(std::string const & str) const
{
if (str == kProductsPopupCloseReasonCloseStr) return ProductsPopupCloseReason::Close;
if (str == kProductsPopupCloseReasonRemindLaterStr) return ProductsPopupCloseReason::RemindLater;
if (str == kProductsPopupCloseReasonAlreadyDonatedStr) return ProductsPopupCloseReason::AlreadyDonated;
if (str == kProductsPopupCloseReasonSelectProductStr) return ProductsPopupCloseReason::SelectProduct;
ASSERT(false, ("Incorrect reason string:", str));
return ProductsPopupCloseReason::Close;
}

View file

@ -47,6 +47,7 @@
#include "platform/location.hpp"
#include "platform/platform.hpp"
#include "platform/distance.hpp"
#include "platform/products.hpp"
#include "routing/router.hpp"
@ -755,4 +756,24 @@ public:
// PowerManager::Subscriber override.
void OnPowerFacilityChanged(power_management::Facility const facility, bool enabled) override;
void OnPowerSchemeChanged(power_management::Scheme const actualScheme) override;
public:
std::optional<products::ProductsConfig> GetProductsConfiguration() const;
enum class ProductsPopupCloseReason
{
Close,
SelectProduct,
AlreadyDonated,
RemindLater
};
void DidCloseProductsPopup(ProductsPopupCloseReason reason) const;
void DidSelectProduct(products::ProductsConfig::Product const & product) const;
private:
bool ShouldShowProducts() const;
uint32_t GetTimeoutForReason(ProductsPopupCloseReason reason) const;
std::string_view ToString(ProductsPopupCloseReason reason) const;
vng commented 2024-11-20 17:27:00 +00:00 (Migrated from github.com)
Review

nit: Move just above the DidCloseProductsPopup function.

nit: Move just above the DidCloseProductsPopup function.
ProductsPopupCloseReason FromString(std::string const & str) const;
};

View file

@ -53,6 +53,8 @@ set(SRC
secure_storage.hpp
servers_list.cpp
servers_list.hpp
products.cpp
products.hpp
settings.cpp
settings.hpp
socket.hpp

View file

@ -14,6 +14,8 @@ set(SRC
location_test.cpp
measurement_tests.cpp
platform_test.cpp
meta_config_tests.cpp
products_tests.cpp
utm_mgrs_utils_tests.cpp
)

View file

@ -0,0 +1,110 @@
#include "testing/testing.hpp"
#include "platform/servers_list.hpp"
#include "platform/products.hpp"
#include "cppjansson/cppjansson.hpp"
using namespace downloader;
UNIT_TEST(MetaConfig_JSONParser_OldFormat)
{
std::string oldFormatJson = R"(["http://url1", "http://url2", "http://url3"])";
auto result = ParseMetaConfig(oldFormatJson);
TEST(result.has_value(), ());
TEST_EQUAL(result->m_serversList.size(), 3, ());
TEST_EQUAL(result->m_serversList[0], "http://url1", ());
TEST_EQUAL(result->m_serversList[1], "http://url2", ());
TEST_EQUAL(result->m_serversList[2], "http://url3", ());
TEST(result->m_settings.empty(), ());
TEST(result->m_productsConfig.empty(), ());
}
UNIT_TEST(MetaConfig_JSONParser_InvalidJSON)
{
std::string invalidJson = R"({"servers": ["http://url1", "http://url2")";
auto result = ParseMetaConfig(invalidJson);
TEST(!result.has_value(), ());
}
UNIT_TEST(MetaConfig_JSONParser_EmptyServersList)
{
std::string emptyServersJson = R"({"servers": []})";
auto result = ParseMetaConfig(emptyServersJson);
TEST(!result.has_value(), ());
}
UNIT_TEST(MetaConfig_JSONParser_NewFormatWithoutProducts)
{
std::string newFormatJson = R"({
"servers": ["http://url1", "http://url2"],
"settings": {
"key1": "value1",
"key2": "value2"
}
})";
auto result = ParseMetaConfig(newFormatJson);
TEST(result.has_value(), ());
TEST_EQUAL(result->m_serversList.size(), 2, ());
TEST_EQUAL(result->m_serversList[0], "http://url1", ());
TEST_EQUAL(result->m_serversList[1], "http://url2", ());
TEST_EQUAL(result->m_settings.size(), 2, ());
TEST_EQUAL(result->m_settings["key1"], "value1", ());
TEST_EQUAL(result->m_settings["key2"], "value2", ());
TEST(result->m_productsConfig.empty(), ());
}
UNIT_TEST(MetaConfig_JSONParser_NewFormatWithProducts)
{
std::string newFormatJson = R"({
"servers": ["http://url1", "http://url2"],
"settings": {
"key1": "value1",
"key2": "value2"
},
"productsConfig": {
"placePagePrompt": "prompt1",
"aboutScreenPrompt": "prompt2",
"products": [
{
"title": "Product 1",
"link": "http://product1"
},
{
"title": "Product 2",
"link": "http://product2"
}
]
}
})";
auto result = ParseMetaConfig(newFormatJson);
TEST(result.has_value(), ());
TEST_EQUAL(result->m_serversList.size(), 2, ());
TEST_EQUAL(result->m_serversList[0], "http://url1", ());
TEST_EQUAL(result->m_serversList[1], "http://url2", ());
TEST_EQUAL(result->m_settings.size(), 2, ());
TEST_EQUAL(result->m_settings["key1"], "value1", ());
TEST_EQUAL(result->m_settings["key2"], "value2", ());
TEST(!result->m_productsConfig.empty(), ());
auto const productsConfigResult = products::ProductsConfig::Parse(result->m_productsConfig);
TEST(productsConfigResult.has_value(), ());
auto const productsConfig = productsConfigResult.value();
TEST_EQUAL(productsConfig.GetPlacePagePrompt(), "prompt1", ());
TEST(productsConfig.HasProducts(), ());
auto const products = productsConfig.GetProducts();
TEST_EQUAL(products.size(), 2, ());
}
UNIT_TEST(MetaConfig_JSONParser_MissingServersKey)
{
std::string missingServersJson = R"({
"settings": {
"key1": "value1"
}
})";
auto result = ParseMetaConfig(missingServersJson);
TEST(!result.has_value(), ("JSON shouldn't be parsed without 'servers' key"));
}

View file

@ -0,0 +1,107 @@
#include "testing/testing.hpp"
#include "platform/products.hpp"
#include "cppjansson/cppjansson.hpp"
using namespace products;
UNIT_TEST(ProductsConfig_ValidConfig)
{
std::string jsonStr = R"({
"placePagePrompt": "prompt1",
"products": [
{
"title": "Product 1",
"link": "http://product1"
},
{
"title": "Product 2",
"link": "http://product2"
}
]
})";
auto const result = ProductsConfig::Parse(jsonStr);
TEST(result.has_value(), ());
auto const productsConfig = result.value();
TEST_EQUAL(productsConfig.GetPlacePagePrompt(), "prompt1", ());
auto const products = productsConfig.GetProducts();
TEST_EQUAL(products.size(), 2, ());
TEST_EQUAL(products[0].GetTitle(), "Product 1", ());
TEST_EQUAL(products[0].GetLink(), "http://product1", ());
TEST_EQUAL(products[1].GetTitle(), "Product 2", ());
TEST_EQUAL(products[1].GetLink(), "http://product2", ());
}
UNIT_TEST(ProductsConfig_EmptyPrompts)
{
std::string jsonStr = R"({
"aboutScreenPrompt": "",
"products": [
{
"title": "Product 1",
"link": "http://product1"
},
{
"title": "Product 2",
"link": "http://product2"
}
]
})";
auto const result = ProductsConfig::Parse(jsonStr);
TEST(result.has_value(), ());
auto const productsConfig = result.value();
TEST_EQUAL(productsConfig.GetPlacePagePrompt(), "", ());
TEST_EQUAL(productsConfig.GetProducts().size(), 2, ());
}
UNIT_TEST(ProductsConfig_InvalidProduct)
{
std::string jsonStr = R"({
"placePagePrompt": "prompt1",
"products": [
{
"title": "Product 1"
},
{
"title": "Product 2",
"link": "http://product2"
}
]
})";
auto const result = ProductsConfig::Parse(jsonStr);
TEST(result.has_value(), ());
auto const productsConfig = result.value();
TEST_EQUAL(productsConfig.GetPlacePagePrompt(), "prompt1", ());
auto const products = productsConfig.GetProducts();
TEST_EQUAL(products.size(), 1, ());
TEST_EQUAL(products[0].GetTitle(), "Product 2", ());
TEST_EQUAL(products[0].GetLink(), "http://product2", ());
}
UNIT_TEST(ProductsConfig_EmptyProducts)
{
std::string jsonStr = R"({
"placePagePrompt": "prompt1",
"products": []
})";
auto const result = ProductsConfig::Parse(jsonStr);
TEST(!result.has_value(), ());
}
UNIT_TEST(ProductsConfig_MissedProductsField)
{
std::string jsonStr = R"({
"placePagePrompt": "prompt1"
})";
auto const result = ProductsConfig::Parse(jsonStr);
TEST(!result.has_value(), ());
}

118
platform/products.cpp Normal file
View file

@ -0,0 +1,118 @@
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
#include "platform/products.hpp"
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
#include "platform/platform.hpp"
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
#include "base/logging.hpp"
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
#include "base/assert.hpp"
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
#include "base/string_utils.hpp"
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
#include "defines.hpp"
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
#include "coding/file_writer.hpp"
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
#include "cppjansson/cppjansson.hpp"
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
namespace products {
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
char const kPlacePagePrompt[] = "placePagePrompt";
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
char const kProducts[] = "products";
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
char const kProductTitle[] = "title";
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
char const kProductLink[] = "link";
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
std::string GetProductsFilePath()
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
return GetPlatform().SettingsPathForFile(PRODUCTS_SETTINGS_FILE_NAME);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
ProductsSettings::ProductsSettings()
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
std::lock_guard guard(m_mutex);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
auto const path = GetProductsFilePath();
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
if (Platform::IsFileExistsByFullPath(path))
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
try
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
std::string outValue;
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
auto dataReader = GetPlatform().GetReader(path);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
dataReader->ReadAsString(outValue);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
m_productsConfig = ProductsConfig::Parse(outValue);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
catch (std::exception const & ex)
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
LOG(LERROR, ("Error reading ProductsConfig file.", ex.what()));
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
LOG(LWARNING, ("ProductsConfig file not found."));
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
ProductsSettings & ProductsSettings::Instance()
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
static ProductsSettings instance;
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
return instance;
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
std::optional<ProductsConfig> ProductsSettings::Get()
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
std::lock_guard guard(m_mutex);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
return m_productsConfig;
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
void ProductsSettings::Update(std::string const & jsonStr)
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
std::lock_guard guard(m_mutex);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
if (jsonStr.empty())
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
FileWriter::DeleteFileX(GetProductsFilePath());
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
else
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
try
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
FileWriter file(GetProductsFilePath());
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
file.Write(jsonStr.data(), jsonStr.size());
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
m_productsConfig = ProductsConfig::Parse(jsonStr);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
catch (std::exception const & ex)
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
LOG(LERROR, ("Error writing ProductsConfig file.", ex.what()));
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
std::optional<ProductsConfig> ProductsConfig::Parse(std::string const & jsonStr)
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
const base::Json root(jsonStr.c_str());
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
auto const json = root.get();
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
auto const productsObj = json_object_get(json, kProducts);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
if (!json_is_object(json) || !productsObj || !json_is_array(productsObj))
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
LOG(LWARNING, ("Failed to parse ProductsConfig:", jsonStr));
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
return std::nullopt;
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
ProductsConfig config;
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
auto const placePagePrompt = json_object_get(json, kPlacePagePrompt);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
if (placePagePrompt && json_is_string(placePagePrompt))
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
config.m_placePagePrompt = json_string_value(placePagePrompt);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
for (size_t i = 0; i < json_array_size(productsObj); ++i)
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
json_t * product = json_array_get(productsObj, i);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
if (!product || !json_is_object(product))
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
LOG(LWARNING, ("Failed to parse Product:", jsonStr));
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
continue;
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
json_t * title = json_object_get(product, kProductTitle);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
json_t * link = json_object_get(product, kProductLink);
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
if (title && link && json_is_string(title) && json_is_string(link))
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
config.m_products.push_back({json_string_value(title), json_string_value(link)});
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
else
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
LOG(LWARNING, ("Failed to parse Product:", jsonStr));
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
if (config.m_products.empty())
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
{
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
LOG(LWARNING, ("Products list is empty"));
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
return std::nullopt;
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
return config;
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
}
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string
} // namespace products
vng commented 2024-11-20 17:25:08 +00:00 (Migrated from github.com)
Review

nit: { from new string

nit: { from new string

64
platform/products.hpp Normal file
View file

@ -0,0 +1,64 @@
#pragma once
#include <string>
#include <vector>
#include <optional>
vng commented 2024-11-20 17:30:27 +00:00 (Migrated from github.com)
Review

#include <mutex>

```#include <mutex>```
#include <mutex>
namespace products {
struct ProductsConfig
{
struct Product
{
private:
std::string m_title;
std::string m_link;
public:
Product(std::string const & title, std::string const & link)
: m_title(title), m_link(link)
{}
std::string const & GetTitle() const { return m_title; }
std::string const & GetLink() const { return m_link; }
};
private:
std::string m_placePagePrompt;
std::vector<Product> m_products;
public:
std::string const GetPlacePagePrompt() const { return m_placePagePrompt; }
std::vector<Product> const & GetProducts() const { return m_products; }
bool HasProducts() const { return !m_products.empty(); }
static std::optional<ProductsConfig> Parse(std::string const & jsonStr);
};
class ProductsSettings
{
private:
ProductsSettings();
std::optional<ProductsConfig> m_productsConfig;
mutable std::mutex m_mutex;
public:
static ProductsSettings & Instance();
void Update(std::string const & jsonStr);
std::optional<ProductsConfig> Get();
};
inline void Update(std::string const & jsonStr)
{
ProductsSettings::Instance().Update(jsonStr);
}
inline std::optional<ProductsConfig> GetProductsConfiguration()
{
return ProductsSettings::Instance().Get();
}
} // namespace products

View file

@ -10,8 +10,13 @@
namespace downloader
{
std::optional<MetaConfig> ParseMetaConfig(std::string const & jsonStr)
{
char const kSettings[] = "settings";
char const kServers[] = "servers";
char const kProductsConfig[] = "productsConfig";
MetaConfig outMetaConfig;
try
{
@ -28,7 +33,7 @@ std::optional<MetaConfig> ParseMetaConfig(std::string const & jsonStr)
// }
// }
json_t * settings = json_object_get(root.get(), "settings");
json_t * settings = json_object_get(root.get(), kSettings);
const char * key;
const json_t * value;
json_object_foreach(settings, key, value)
@ -38,7 +43,13 @@ std::optional<MetaConfig> ParseMetaConfig(std::string const & jsonStr)
outMetaConfig.m_settings[key] = valueStr;
}
vng commented 2024-11-20 10:39:32 +00:00 (Migrated from github.com)
Review

We are doing unnecessary transformations here:

  • m_productsConfig should be a simple string
  • outMetaConfig.m_productsConfig = json_dumps(product, JSON_ENCODE_ANY)
  • thus avoiding servers_list dependency from products
We are doing unnecessary transformations here: - m_productsConfig should be a simple string - outMetaConfig.m_productsConfig = json_dumps(product, JSON_ENCODE_ANY) - thus avoiding servers_list dependency from products
Review

Fixed! Thanks

Fixed! Thanks
servers = json_object_get(root.get(), "servers");
servers = json_object_get(root.get(), kServers);
auto const productsConfig = json_object_get(root.get(), kProductsConfig);
if (productsConfig)
outMetaConfig.m_productsConfig = json_dumps(productsConfig, JSON_ENCODE_ANY);
else
LOG(LINFO, ("No ProductsConfig in meta configuration"));
}
else
{

View file

@ -14,6 +14,7 @@ struct MetaConfig
ServersList m_serversList;
using SettingsMap = std::map<std::string, std::string>;
SettingsMap m_settings;
std::string m_productsConfig;
};
std::optional<MetaConfig> ParseMetaConfig(std::string const & jsonStr);

View file

@ -23,6 +23,7 @@ using namespace std;
std::string_view kMeasurementUnits = "Units";
vng commented 2024-11-20 19:17:00 +00:00 (Migrated from github.com)
Review

Probably last 2 options are needed only in framework.cpp, no?

Probably last 2 options are needed only in framework.cpp, no?
Review

Yes. But I suppose that it is better to store all the setting keys in settings to avoid accidental key duplication and unnecessary string literal usage. It will guarantee key uniqueness

Yes. But I suppose that it is better to store all the setting keys in `settings` to avoid accidental key duplication and unnecessary string literal usage. It will guarantee key uniqueness
vng commented 2024-11-21 11:34:31 +00:00 (Migrated from github.com)
Review

We already have a dozen keys declared in framework.cpp
From my perspective, the more local scope they have - the better.

We already have a dozen keys declared in framework.cpp From my perspective, the more local scope they have - the better.
std::string_view kMapLanguageCode = "MapLanguageCode";
std::string_view kDeveloperMode = "DeveloperMode";
std::string_view kDonateUrl = "DonateUrl";
StringStorage::StringStorage() : StringStorageBase(GetPlatform().SettingsPathForFile(SETTINGS_FILE_NAME)) {}
@ -425,18 +426,16 @@ void UsageStats::EnterBackground()
m_ss.SetValue(m_sessions, ToString(m_sessionsCount));
}
vng commented 2024-11-20 18:28:00 +00:00 (Migrated from github.com)
Review

Sorry, I think that it is a valid situation:

else if (m_enterForegroundTime > 0)
  return m_enterForegroundTime;
else
{
  LOG(LERROR, ("Failed to get first launch time"));
  return TimeSinceEpoch();
}
Sorry, I think that it is a valid situation: ``` else if (m_enterForegroundTime > 0) return m_enterForegroundTime; else { LOG(LERROR, ("Failed to get first launch time")); return TimeSinceEpoch(); } ```
Review

Thx!

Thx!
} // namespace settings
/*
namespace marketing
bool UsageStats::IsLoyalUser() const
{
Settings::Settings() : platform::StringStorageBase(GetPlatform().SettingsPathForFile(MARKETING_SETTINGS_FILE_NAME)) {}
// static
Settings & Settings::Instance()
{
static Settings instance;
return instance;
#ifdef DEBUG
uint32_t constexpr kMinTotalForegroundTimeout = 30;
uint32_t constexpr kMinSessionsCount = 3;
#else
uint32_t constexpr kMinTotalForegroundTimeout = 60 * 30; // 30 min
uint32_t constexpr kMinSessionsCount = 5;
#endif
return m_sessionsCount >= kMinSessionsCount && m_totalForegroundTime >= kMinTotalForegroundTimeout;
}
} // namespace marketing
*/
} // namespace settings

View file

@ -12,6 +12,7 @@ namespace settings
extern std::string_view kMeasurementUnits;
extern std::string_view kDeveloperMode;
extern std::string_view kMapLanguageCode;
extern std::string_view kDonateUrl;
template <class T>
bool FromString(std::string const & str, T & outValue);
@ -76,32 +77,8 @@ public:
void EnterForeground();
void EnterBackground();
vng commented 2024-11-20 17:09:01 +00:00 (Migrated from github.com)
Review
std::string str;
uint64_t time;
if (m_ss.GetValue(m_firstLaunch, str) && FromString(str, time))
  return time;
else  // IDK, return current time ..
  return m_enterForegroundTime;
``` std::string str; uint64_t time; if (m_ss.GetValue(m_firstLaunch, str) && FromString(str, time)) return time; else // IDK, return current time .. return m_enterForegroundTime; ```
vng commented 2024-11-20 10:32:37 +00:00 (Migrated from github.com)
Review

first launch or sessions count?

first launch or sessions count?
vng commented 2024-11-20 10:41:41 +00:00 (Migrated from github.com)
Review

Make ProductSettings like singleton and remove these functions from settings. Will use separate ProductSettings::Instance()

Make ProductSettings like singleton and remove these functions from settings. Will use separate ProductSettings::Instance()
Review

Fixed! Thanks

Fixed! Thanks
bool IsLoyalUser() const;
};
} // namespace settings
/*
namespace marketing
{
class Settings : public platform::StringStorageBase
{
public:
template <class Value>
static void Set(std::string const & key, Value const & value)
{
Instance().SetValue(key, settings::ToString(value));
}
template <class Value>
[[nodiscard]] static bool Get(std::string const & key, Value & outValue)
{
std::string strVal;
return Instance().GetValue(key, strVal) && settings::FromString(strVal, outValue);
}
private:
static Settings & Instance();
Settings();
};
} // namespace marketing
*/

View file

@ -7,6 +7,8 @@
#include "platform/platform.hpp"
#include "platform/servers_list.hpp"
#include "platform/settings.hpp"
#include "platform/products.hpp"
#include "platform/locale.hpp"
#include "coding/url.hpp"
@ -49,7 +51,7 @@ void MapFilesDownloader::RunMetaConfigAsync(std::function<void()> && callback)
{
m_serversList = metaConfig.m_serversList;
settings::Update(metaConfig.m_settings);
products::Update(metaConfig.m_productsConfig);
callback();
// Reset flag to invoke servers list downloading next time if current request has failed.
@ -149,6 +151,12 @@ std::vector<std::string> MapFilesDownloader::MakeUrlList(std::string const & rel
return urls;
}
std::string GetAcceptLanguage()
{
auto const locale = platform::GetCurrentLocale();
return locale.m_language + "-" + locale.m_country;
}
// static
MetaConfig MapFilesDownloader::LoadMetaConfig()
{
@ -160,7 +168,8 @@ MetaConfig MapFilesDownloader::LoadMetaConfig()
{
platform::HttpClient request(metaServerUrl);
request.SetRawHeader("X-OM-DataVersion", std::to_string(m_dataVersion));
request.SetRawHeader("X-OM-AppVersion", GetPlatform().Version());
request.SetRawHeader("X-OM-AppVersion", pl.Version());
request.SetRawHeader("Accept-Language", GetAcceptLanguage());
request.SetTimeout(10.0); // timeout in seconds
request.RunHttpRequest(httpResult);
}

View file

@ -114,6 +114,10 @@
D5B191CF2386C7E4009CD0D6 /* http_uploader_background.hpp in Headers */ = {isa = PBXBuildFile; fileRef = D5B191CE2386C7E4009CD0D6 /* http_uploader_background.hpp */; };
EB60B4DC204C130300E4953B /* network_policy_ios.mm in Sources */ = {isa = PBXBuildFile; fileRef = EB60B4DB204C130300E4953B /* network_policy_ios.mm */; };
EB60B4DE204C175700E4953B /* network_policy_ios.h in Headers */ = {isa = PBXBuildFile; fileRef = EB60B4DD204C175700E4953B /* network_policy_ios.h */; };
ED49D7402CEE438E004AF27E /* products.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED49D73F2CEE438E004AF27E /* products.cpp */; };
ED49D7412CEE438E004AF27E /* products.hpp in Headers */ = {isa = PBXBuildFile; fileRef = ED49D73E2CEE438E004AF27E /* products.hpp */; };
ED49D7442CEE43A4004AF27E /* meta_config_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED49D7422CEE43A4004AF27E /* meta_config_tests.cpp */; };
ED49D7452CEE43A4004AF27E /* products_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED49D7432CEE43A4004AF27E /* products_tests.cpp */; };
ED965B252CD8F72E0049E39E /* duration.hpp in Headers */ = {isa = PBXBuildFile; fileRef = ED965B242CD8F72A0049E39E /* duration.hpp */; };
ED965B272CD8F7810049E39E /* duration.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED965B262CD8F77D0049E39E /* duration.cpp */; };
ED965B472CDA52DB0049E39E /* duration_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED965B462CDA4EC00049E39E /* duration_tests.cpp */; };
@ -256,6 +260,10 @@
D5B191CE2386C7E4009CD0D6 /* http_uploader_background.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = http_uploader_background.hpp; sourceTree = "<group>"; };
EB60B4DB204C130300E4953B /* network_policy_ios.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = network_policy_ios.mm; sourceTree = "<group>"; };
EB60B4DD204C175700E4953B /* network_policy_ios.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = network_policy_ios.h; sourceTree = "<group>"; };
ED49D73E2CEE438E004AF27E /* products.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = products.hpp; sourceTree = "<group>"; };
ED49D73F2CEE438E004AF27E /* products.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = products.cpp; sourceTree = "<group>"; };
ED49D7422CEE43A4004AF27E /* meta_config_tests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = meta_config_tests.cpp; sourceTree = "<group>"; };
ED49D7432CEE43A4004AF27E /* products_tests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = products_tests.cpp; sourceTree = "<group>"; };
ED965B242CD8F72A0049E39E /* duration.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = duration.hpp; sourceTree = "<group>"; };
ED965B262CD8F77D0049E39E /* duration.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = duration.cpp; sourceTree = "<group>"; };
ED965B462CDA4EC00049E39E /* duration_tests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = duration_tests.cpp; sourceTree = "<group>"; };
@ -317,6 +325,8 @@
675340DE1C58C405002CF0D9 /* platform_tests */ = {
isa = PBXGroup;
children = (
ED49D7422CEE43A4004AF27E /* meta_config_tests.cpp */,
ED49D7432CEE43A4004AF27E /* products_tests.cpp */,
ED965B462CDA4EC00049E39E /* duration_tests.cpp */,
168EFCC12A30EB7400F71EE8 /* distance_tests.cpp */,
675341001C58C4C9002CF0D9 /* apk_test.cpp */,
@ -466,6 +476,8 @@
675343A71A3F5D5A00A0A8C3 /* servers_list.hpp */,
675343A81A3F5D5A00A0A8C3 /* settings.cpp */,
675343A91A3F5D5A00A0A8C3 /* settings.hpp */,
ED49D73E2CEE438E004AF27E /* products.hpp */,
ED49D73F2CEE438E004AF27E /* products.cpp */,
34C624BB1DABCCD100510300 /* socket_apple.mm */,
34C624BC1DABCCD100510300 /* socket.hpp */,
F6DF73561EC9EAE700D8BA0B /* string_storage_base.cpp */,
@ -544,6 +556,7 @@
675343CD1A3F5D5A00A0A8C3 /* platform.hpp in Headers */,
6741250F1B4C00CC00A3E828 /* local_country_file.hpp in Headers */,
3D15ACE2214A707900F725D5 /* localization.hpp in Headers */,
ED49D7412CEE438E004AF27E /* products.hpp in Headers */,
D5B191CF2386C7E4009CD0D6 /* http_uploader_background.hpp in Headers */,
1669C8422A30DCD200530AD1 /* distance.hpp in Headers */,
3DE8B98F1DEC3115000E6083 /* network_policy.hpp in Headers */,
@ -699,6 +712,7 @@
ED965B272CD8F7810049E39E /* duration.cpp in Sources */,
EB60B4DC204C130300E4953B /* network_policy_ios.mm in Sources */,
6741250E1B4C00CC00A3E828 /* local_country_file.cpp in Sources */,
ED49D7402CEE438E004AF27E /* products.cpp in Sources */,
670E8C761BB318AB00094197 /* platform_ios.mm in Sources */,
67A2526A1BB40E100063F8A8 /* platform_qt.cpp in Sources */,
56EB1EDE1C6B6E6C0022D831 /* mwm_traits.cpp in Sources */,
@ -737,6 +751,8 @@
buildActionMask = 2147483647;
files = (
6783389E1C6DE59200FD6263 /* platform_test.cpp in Sources */,
ED49D7442CEE43A4004AF27E /* meta_config_tests.cpp in Sources */,
ED49D7452CEE43A4004AF27E /* products_tests.cpp in Sources */,
678338961C6DE59200FD6263 /* apk_test.cpp in Sources */,
678338991C6DE59200FD6263 /* jansson_test.cpp in Sources */,
ED965B482CDA575B0049E39E /* duration.cpp in Sources */,