Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mapsme/omim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIlya Zverev <zverik@textual.ru>2016-11-24 20:27:51 +0300
committerIlya Zverev <zverik@textual.ru>2016-11-30 18:55:40 +0300
commit153e4f2b8c0ea07b90b3f72455f201754577e751 (patch)
tree9c5809357572f6964dde7a9aa39c66eb8b12e3c4 /indexer
parent70871724edcb0a50dde550512310fda1a370566f (diff)
[banners] Add banners infrastructure
Diffstat (limited to 'indexer')
-rw-r--r--indexer/CMakeLists.txt2
-rw-r--r--indexer/banners.cpp224
-rw-r--r--indexer/banners.hpp59
-rw-r--r--indexer/feature_meta.cpp3
-rw-r--r--indexer/feature_meta.hpp1
-rw-r--r--indexer/indexer.pro2
-rw-r--r--indexer/indexer_tests/banners_test.cpp70
-rw-r--r--indexer/indexer_tests/indexer_tests.pro1
-rw-r--r--indexer/map_object.hpp1
9 files changed, 363 insertions, 0 deletions
diff --git a/indexer/CMakeLists.txt b/indexer/CMakeLists.txt
index e239721de0..3ae176faf9 100644
--- a/indexer/CMakeLists.txt
+++ b/indexer/CMakeLists.txt
@@ -6,6 +6,8 @@ set(
SRC
altitude_loader.cpp
altitude_loader.hpp
+ banners.cpp
+ banners.hpp
categories_holder_loader.cpp
categories_holder.cpp
categories_holder.hpp
diff --git a/indexer/banners.cpp b/indexer/banners.cpp
new file mode 100644
index 0000000000..509d069f4b
--- /dev/null
+++ b/indexer/banners.cpp
@@ -0,0 +1,224 @@
+#include "indexer/banners.hpp"
+#include "indexer/classificator.hpp"
+#include "indexer/feature.hpp"
+
+#include "platform/platform.hpp"
+
+#include "coding/reader_streambuf.hpp"
+
+#include "base/stl_add.hpp"
+#include "base/string_utils.hpp"
+
+#include "defines.hpp"
+
+namespace
+{
+time_t constexpr kEternity = 0;
+
+// Convert ISO date to unix time.
+bool StringToTimestamp(string const & s, time_t & result)
+{
+ istringstream is(s);
+ tm time;
+ is >> get_time(&time, "%F");
+ CHECK(!is.fail(), ("Wrong date format:", s, "(expecting YYYY-MM-DD)"));
+
+ time.tm_sec = time.tm_min = time.tm_hour = 0;
+
+ time_t timestamp = mktime(&time);
+ if (timestamp < 0)
+ return false;
+
+ result = timestamp;
+ return true;
+}
+} // namespace
+
+namespace banner
+{
+string Banner::GetProperty(string const & name) const
+{
+ if (name == "lang")
+ {
+ return "ru"; // TODO(@zverik): this variable, {mwmlang}, {country} etc.
+ }
+ else
+ {
+ auto const property = m_properties.find(name);
+ if (property != m_properties.end())
+ return property->second;
+ }
+ return {};
+}
+
+Banner::Banner(string const & id) : m_id(id)
+{
+ m_messageBase = "banner_" + id;
+ m_iconName = m_messageBase + ".png";
+ m_defaultUrl = "";
+ m_activeAfter = time(nullptr);
+ m_activeBefore = kEternity;
+}
+
+bool Banner::IsActive() const
+{
+ if (IsEmpty())
+ return false;
+ time_t const now = time(nullptr);
+ return now >= m_activeAfter && (m_activeBefore == kEternity || now < m_activeBefore);
+}
+
+void Banner::SetProperty(string const & name, string const & value)
+{
+ if (name == "messages")
+ {
+ m_messageBase = value;
+ }
+ else if (name == "icon")
+ {
+ m_iconName = value;
+ }
+ else if (name == "url")
+ {
+ CHECK(strings::StartsWith(value, "http://") || strings::StartsWith(value, "https://"),
+ ("URL without a protocol for banner", m_id));
+ m_defaultUrl = value;
+ }
+ else if (name == "start")
+ {
+ CHECK(StringToTimestamp(value, m_activeAfter), ("Wrong start date", value, "for banner", m_id));
+ }
+ else if (name == "end")
+ {
+ CHECK(StringToTimestamp(value, m_activeBefore), ("Wrong end date", value, "for banner", m_id));
+ m_activeBefore += 24 * 60 * 60; // Add a day so we don't miss one
+ }
+ else
+ {
+ m_properties.emplace(make_pair(name, value));
+ }
+}
+
+string Banner::GetFormattedUrl(string const & url) const
+{
+ string baseUrl = url.empty() ? m_defaultUrl : url;
+ auto start = baseUrl.find('{');
+ while (start != string::npos)
+ {
+ auto end = baseUrl.find('}', start + 1);
+ if (end == string::npos)
+ break;
+ string value = GetProperty(baseUrl.substr(start + 1, end - start - 1));
+ if (!value.empty())
+ {
+ baseUrl.replace(start, end - start + 1, value);
+ end -= end - start + 1 - value.length();
+ }
+ start = baseUrl.find('{', end + 1);
+ }
+ return baseUrl;
+}
+
+void BannerSet::ReadBanners(istream & s)
+{
+ m_banners.clear();
+
+ Banner banner;
+ string type;
+ int lineNumber = 1;
+ for (string line; getline(s, line); ++lineNumber)
+ {
+ strings::Trim(line);
+ if (line.empty() || line.front() == '#')
+ continue;
+
+ auto const equals = line.find('=');
+ if (equals == string::npos)
+ {
+ // Section header, should be in square brackets.
+ CHECK(line.front() == '[' && line.back() == ']', ("Unknown syntax at line", lineNumber));
+ strings::Trim(line, " \t[]");
+ CHECK(!line.empty(), ("Empty banner ID at line", lineNumber));
+ if (!banner.IsEmpty())
+ Add(banner, type);
+ banner = Banner(line);
+ type = "sponsored-banner-" + line;
+ }
+ else
+ {
+ // Variable definition, must be inside a section.
+ CHECK(!banner.IsEmpty(), ("Variable definition outside a section at line", lineNumber));
+ string name = line.substr(0, equals);
+ string value = line.substr(equals + 1);
+ strings::Trim(name);
+ CHECK(!name.empty(), ("Empty variable name at line", lineNumber));
+ strings::Trim(value);
+ if (name == "type")
+ type = value;
+ else
+ banner.SetProperty(name, value);
+ }
+ }
+ if (!banner.IsEmpty())
+ Add(banner, type);
+}
+
+void BannerSet::Add(Banner const & banner, string const & type)
+{
+ vector<string> v;
+ strings::Tokenize(type, "-", MakeBackInsertFunctor(v));
+ uint32_t const ctype = classif().GetTypeByPathSafe(v);
+ if (ctype == 0)
+ {
+ LOG(LWARNING, ("Missing type", type, "for a banner"));
+ }
+ else
+ {
+ CHECK(m_banners.find(ctype) == m_banners.end(), ("Duplicate banner type", type));
+ m_banners.emplace(make_pair(ctype, banner));
+ }
+}
+
+bool BannerSet::HasBannerForType(uint32_t type) const
+{
+ return m_banners.find(type) != m_banners.end();
+}
+
+Banner const & BannerSet::GetBannerForType(uint32_t type) const
+{
+ auto const result = m_banners.find(type);
+ CHECK(result != m_banners.end(), ("GetBannerForType() for absent banner"));
+ return result->second;
+}
+
+bool BannerSet::HasBannerForFeature(FeatureType const & ft) const
+{
+ bool result = false;
+ ft.ForEachType([this, &result](uint32_t type)
+ {
+ if (!result && HasBannerForType(type))
+ result = true;
+ });
+ return result;
+}
+
+Banner const & BannerSet::GetBannerForFeature(FeatureType const & ft) const
+{
+ vector<uint32_t> types;
+ ft.ForEachType([this, &types](uint32_t type)
+ {
+ if (types.empty() && HasBannerForType(type))
+ types.push_back(type);
+ });
+ CHECK(!types.empty(), ("No banners for the feature", ft));
+ return GetBannerForType(types.front());
+}
+
+void BannerSet::LoadBanners()
+{
+ auto reader = GetPlatform().GetReader(BANNERS_FILE);
+ ReaderStreamBuf buffer(move(reader));
+ istream s(&buffer);
+ ReadBanners(s);
+}
+} // namespace banner
diff --git a/indexer/banners.hpp b/indexer/banners.hpp
new file mode 100644
index 0000000000..9fb280e49a
--- /dev/null
+++ b/indexer/banners.hpp
@@ -0,0 +1,59 @@
+#pragma once
+
+#include "std/ctime.hpp"
+#include "std/iostream.hpp"
+#include "std/string.hpp"
+#include "std/unordered_map.hpp"
+
+class FeatureType;
+
+namespace banner
+{
+class Banner
+{
+public:
+ Banner() = default;
+ explicit Banner(string const & id);
+
+ bool IsEmpty() const { return m_id.empty(); }
+ string GetMessageBase() const { return m_messageBase; }
+ string GetIconName() const { return m_iconName; }
+ string GetDefaultUrl() const { return m_defaultUrl; }
+ bool IsActive() const;
+
+ /// Replaces inline variables in the URL, uses the default banner URL if url is not specified.
+ string GetFormattedUrl(string const & url = {}) const;
+
+ /// Usually called from BannerSet.
+ void SetProperty(string const & name, string const & value);
+
+private:
+ string m_id;
+ string m_messageBase;
+ string m_iconName;
+ string m_defaultUrl;
+ time_t m_activeAfter;
+ time_t m_activeBefore;
+ unordered_map<string, string> m_properties;
+
+ string GetProperty(string const & name) const;
+};
+
+class BannerSet
+{
+public:
+ void LoadBanners();
+ void ReadBanners(istream & s);
+
+ bool HasBannerForType(uint32_t type) const;
+ Banner const & GetBannerForType(uint32_t type) const;
+
+ bool HasBannerForFeature(FeatureType const & ft) const;
+ Banner const & GetBannerForFeature(FeatureType const & ft) const;
+
+private:
+ unordered_map<uint32_t, Banner> m_banners;
+
+ void Add(Banner const & banner, string const & type);
+};
+}
diff --git a/indexer/feature_meta.cpp b/indexer/feature_meta.cpp
index 73e9451b2d..c1d34820fa 100644
--- a/indexer/feature_meta.cpp
+++ b/indexer/feature_meta.cpp
@@ -94,6 +94,8 @@ bool Metadata::TypeFromString(string const & k, Metadata::EType & outType)
outType = Metadata::FMD_PRICE_RATE;
else if (k == "rating:sponsored")
outType = Metadata::FMD_RATING;
+ else if (k == "banner_url")
+ outType = Metadata::FMD_BANNER_URL;
else
return false;
@@ -176,6 +178,7 @@ string DebugPrint(feature::Metadata::EType type)
case Metadata::FMD_SPONSORED_ID: return "ref:sponsored";
case Metadata::FMD_PRICE_RATE: return "price_rate";
case Metadata::FMD_RATING: return "rating:sponsored";
+ case Metadata::FMD_BANNER_URL: return "banner_url";
case Metadata::FMD_TEST_ID: return "test_id";
case Metadata::FMD_COUNT: CHECK(false, ("FMD_COUNT can not be used as a type."));
};
diff --git a/indexer/feature_meta.hpp b/indexer/feature_meta.hpp
index 85cede6dc5..9b2a6862bd 100644
--- a/indexer/feature_meta.hpp
+++ b/indexer/feature_meta.hpp
@@ -126,6 +126,7 @@ public:
FMD_SPONSORED_ID = 24,
FMD_PRICE_RATE = 25,
FMD_RATING = 26,
+ FMD_BANNER_URL = 27,
FMD_COUNT
};
diff --git a/indexer/indexer.pro b/indexer/indexer.pro
index 57a5db455a..9b68c979fb 100644
--- a/indexer/indexer.pro
+++ b/indexer/indexer.pro
@@ -11,6 +11,7 @@ include($$ROOT_DIR/common.pri)
SOURCES += \
altitude_loader.cpp \
+ banners.cpp \
categories_holder.cpp \
categories_holder_loader.cpp \
categories_index.cpp \
@@ -64,6 +65,7 @@ SOURCES += \
HEADERS += \
altitude_loader.hpp \
+ banners.hpp \
categories_holder.hpp \
categories_index.hpp \
cell_coverer.hpp \
diff --git a/indexer/indexer_tests/banners_test.cpp b/indexer/indexer_tests/banners_test.cpp
new file mode 100644
index 0000000000..8fc773eeac
--- /dev/null
+++ b/indexer/indexer_tests/banners_test.cpp
@@ -0,0 +1,70 @@
+#include "testing/testing.hpp"
+
+#include "indexer/banners.hpp"
+#include "indexer/classificator.hpp"
+#include "indexer/classificator_loader.hpp"
+
+#include "std/iostream.hpp"
+
+using namespace banner;
+
+UNIT_TEST(Banners_Load)
+{
+ char const kBanners[] =
+ "# comment\n"
+ "[abc]\n"
+ "icon = test.png\n"
+ "start=2016-07-14\n"
+ "type= shop-clothes \n"
+ "\n"
+ "[error]\n"
+ "[re_123]\n"
+ "type=shop-shoes\n"
+ "url=http://{aux}.com\n"
+ " \t aux=\t\ttest \n"
+ "[future]\n"
+ "type=shop-wine\n"
+ "start=2028-01-01\n"
+ "end=2028-12-31\n"
+ "[final]\n"
+ "type=shop-pet\n"
+ "start=2016-07-13\n"
+ "end=2016-07-14\n"
+ "\t";
+
+ classificator::Load();
+ Classificator & c = classif();
+
+ BannerSet bs;
+ istringstream is(kBanners);
+ bs.ReadBanners(is);
+
+ TEST(bs.HasBannerForType(c.GetTypeByPath({"shop", "clothes"})), ());
+ Banner const & bannerAbc = bs.GetBannerForType(c.GetTypeByPath({"shop", "clothes"}));
+ TEST(!bannerAbc.IsEmpty(), ());
+ TEST_EQUAL(bannerAbc.GetIconName(), "test.png", ());
+ TEST_EQUAL(bannerAbc.GetMessageBase(), "banner_abc", ());
+ TEST_EQUAL(bannerAbc.GetDefaultUrl(), "", ());
+ TEST_EQUAL(bannerAbc.GetFormattedUrl("http://example.com"), "http://example.com", ());
+ TEST_EQUAL(bannerAbc.GetFormattedUrl(), "", ());
+ TEST(bannerAbc.IsActive(), ());
+
+ TEST(bs.HasBannerForType(c.GetTypeByPath({"shop", "shoes"})), ());
+ Banner const & bannerRe = bs.GetBannerForType(c.GetTypeByPath({"shop", "shoes"}));
+ TEST(!bannerRe.IsEmpty(), ());
+ TEST(bannerRe.IsActive(), ());
+ TEST_EQUAL(bannerRe.GetIconName(), "banner_re_123.png", ());
+ TEST_EQUAL(bannerRe.GetFormattedUrl(), "http://test.com", ());
+ TEST_EQUAL(bannerRe.GetFormattedUrl("http://ex.ru/{aux}?var={v}"), "http://ex.ru/test?var={v}", ());
+
+ TEST(bs.HasBannerForType(c.GetTypeByPath({"shop", "wine"})), ());
+ Banner const & bannerFuture = bs.GetBannerForType(c.GetTypeByPath({"shop", "wine"}));
+ TEST(!bannerFuture.IsEmpty(), ());
+ TEST(!bannerFuture.IsActive(), ());
+
+ TEST(bs.HasBannerForType(c.GetTypeByPath({"shop", "pet"})), ());
+ Banner const & bannerFinal = bs.GetBannerForType(c.GetTypeByPath({"shop", "pet"}));
+ TEST(!bannerFinal.IsEmpty(), ());
+ TEST(!bannerFinal.IsActive(), ());
+ TEST_EQUAL(bannerFinal.GetFormattedUrl("http://{aux}.ru"), "http://{aux}.ru", ());
+}
diff --git a/indexer/indexer_tests/indexer_tests.pro b/indexer/indexer_tests/indexer_tests.pro
index 1ff165f0ac..79b5de4b4a 100644
--- a/indexer/indexer_tests/indexer_tests.pro
+++ b/indexer/indexer_tests/indexer_tests.pro
@@ -28,6 +28,7 @@ HEADERS += \
SOURCES += \
../../testing/testingmain.cpp \
+ banners_test.cpp \
categories_test.cpp \
cell_coverer_test.cpp \
cell_id_test.cpp \
diff --git a/indexer/map_object.hpp b/indexer/map_object.hpp
index e296e668b9..23c408b6ab 100644
--- a/indexer/map_object.hpp
+++ b/indexer/map_object.hpp
@@ -146,6 +146,7 @@ vector<Props> MetadataToProps(vector<T> const & metadata)
case Metadata::FMD_SPONSORED_ID:
case Metadata::FMD_PRICE_RATE:
case Metadata::FMD_RATING:
+ case Metadata::FMD_BANNER_URL:
case Metadata::FMD_TEST_ID:
case Metadata::FMD_COUNT:
break;