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
path: root/editor
diff options
context:
space:
mode:
authorArsentiy Milchakov <a.milchakov@corp.mail.ru>2016-08-31 18:27:16 +0300
committerArsentiy Milchakov <a.milchakov@corp.mail.ru>2016-08-31 18:27:16 +0300
commit97f9878607fb15f406d3b52b2f06ca8ecbe4915a (patch)
tree9853b6922f09488092c8cbb0b396bb3b9bcfb0b2 /editor
parent39103b30cf1fcb94801d6725f60897c85df45304 (diff)
editor web config
Diffstat (limited to 'editor')
-rw-r--r--editor/config_loader.cpp176
-rw-r--r--editor/config_loader.hpp73
-rw-r--r--editor/editor.pro2
-rw-r--r--editor/editor_config.cpp19
-rw-r--r--editor/editor_config.hpp33
-rw-r--r--editor/editor_tests/config_loader_test.cpp62
-rw-r--r--editor/editor_tests/editor_config_test.cpp11
-rw-r--r--editor/editor_tests/editor_tests.pro4
8 files changed, 350 insertions, 30 deletions
diff --git a/editor/config_loader.cpp b/editor/config_loader.cpp
new file mode 100644
index 0000000000..305910a4ed
--- /dev/null
+++ b/editor/config_loader.cpp
@@ -0,0 +1,176 @@
+#include "editor/config_loader.hpp"
+#include "editor/editor_config.hpp"
+
+#include "platform/platform.hpp"
+
+#include "coding/internal/file_data.hpp"
+#include "coding/reader.hpp"
+
+#include "std/atomic.hpp"
+#include "std/fstream.hpp"
+#include "std/iterator.hpp"
+
+#include "3party/Alohalytics/src/http_client.h"
+#include "3party/pugixml/src/pugixml.hpp"
+
+namespace
+{
+using alohalytics::HTTPClientPlatformWrapper;
+
+auto const kConfigFileName = "editor.config";
+auto const kHashFileName = "editor.config.hash";
+
+auto const kSynchroTimeout = hours(4);
+auto const kRemoteHashUrl = "http://osmz.ru/mwm/editor.config.date";
+auto const kRemoteConfigUrl = "http://osmz.ru/mwm/editor.config";
+
+string GetConfigFilePath() { return GetPlatform().WritablePathForFile(kConfigFileName); }
+string GetHashFilePath() { return GetPlatform().WritablePathForFile(kHashFileName); }
+
+string RunSimpleHttpRequest(string const & url)
+{
+ HTTPClientPlatformWrapper request(url);
+ auto const result = request.RunHTTPRequest();
+
+ if (result && !request.was_redirected() && request.error_code() == 200) // 200 - http status OK
+ {
+ return request.server_response();
+ }
+
+ return {};
+}
+} // namespace
+
+namespace editor
+{
+void Waiter::Interrupt()
+{
+ {
+ lock_guard<mutex> lock(m_mutex);
+ m_interrupted = true;
+ }
+
+ m_event.notify_all();
+}
+
+ConfigLoader::ConfigLoader(EditorConfigWrapper & config) : m_config(config)
+{
+ pugi::xml_document doc;
+ LoadFromLocal(doc);
+ ResetConfig(doc);
+ m_loaderThread = thread(&ConfigLoader::LoadFromServer, this);
+}
+
+ConfigLoader::~ConfigLoader()
+{
+ m_waiter.Interrupt();
+ m_loaderThread.join();
+}
+
+void ConfigLoader::LoadFromServer()
+{
+ auto const saveAndReload = [this](pugi::xml_document const & doc)
+ {
+ if (doc.empty())
+ return false;
+
+ auto const filePath = GetConfigFilePath();
+ auto const result =
+ my::WriteToTempAndRenameToFile(filePath, [&doc](string const & fileName)
+ {
+ return doc.save_file(fileName.c_str(), " ");
+ });
+
+ if (!result)
+ return false;
+
+ ResetConfig(doc);
+ return true;
+ };
+
+ auto const hash = LoadHash(GetHashFilePath());
+
+ try
+ {
+ do
+ {
+ auto const remoteHash = GetRemoteHash();
+ if (remoteHash.empty() || hash == remoteHash)
+ continue;
+
+ pugi::xml_document doc;
+ GetRemoteConfig(doc);
+
+ if (saveAndReload(doc))
+ SaveHash(remoteHash, GetHashFilePath());
+
+ } while (m_waiter.Wait(kSynchroTimeout));
+ }
+ catch (RootException const & ex)
+ {
+ LOG(LERROR, (ex.Msg()));
+ }
+}
+
+void ConfigLoader::ResetConfig(pugi::xml_document const & doc)
+{
+ auto config = make_shared<EditorConfig>();
+ config->SetConfig(doc);
+ m_config.Set(config);
+}
+
+//static
+void ConfigLoader::LoadFromLocal(pugi::xml_document & doc)
+{
+ string content;
+ auto const reader = GetPlatform().GetReader(kConfigFileName);
+ reader->ReadAsString(content);
+
+ if (!doc.load_buffer(content.data(), content.size()))
+ MYTHROW(ConfigLoadError, ("Can't parse config"));
+}
+
+// static
+string ConfigLoader::GetRemoteHash()
+{
+ return RunSimpleHttpRequest(kRemoteHashUrl);
+}
+
+// static
+void ConfigLoader::GetRemoteConfig(pugi::xml_document & doc)
+{
+ auto const result = RunSimpleHttpRequest(kRemoteConfigUrl);
+ if (result.empty())
+ return;
+
+ if (!doc.load_string(result.c_str(), pugi::parse_default | pugi::parse_comments))
+ doc.reset();
+}
+
+// static
+bool ConfigLoader::SaveHash(string const & hash, string const & filePath)
+{
+ auto const result =
+ my::WriteToTempAndRenameToFile(filePath, [&hash](string const & fileName)
+ {
+ ofstream ofs(fileName, ofstream::out);
+ if (!ofs.is_open())
+ return false;
+
+ ofs.write(hash.data(), hash.size());
+ return true;
+ });
+
+ return result;
+}
+
+// static
+string ConfigLoader::LoadHash(string const & filePath)
+{
+ ifstream ifs(filePath, ifstream::in);
+ if (!ifs.is_open())
+ return {};
+
+ return {istreambuf_iterator<char>(ifs), istreambuf_iterator<char>()};
+}
+} // namespace editor
diff --git a/editor/config_loader.hpp b/editor/config_loader.hpp
new file mode 100644
index 0000000000..8c252125db
--- /dev/null
+++ b/editor/config_loader.hpp
@@ -0,0 +1,73 @@
+#pragma once
+
+#include "base/exception.hpp"
+#include "base/logging.hpp"
+
+#include "std/chrono.hpp"
+#include "std/condition_variable.hpp"
+#include "std/mutex.hpp"
+#include "std/shared_ptr.hpp"
+#include "std/string.hpp"
+#include "std/thread.hpp"
+
+namespace pugi
+{
+class xml_document;
+}
+
+namespace editor
+{
+class EditorConfigWrapper;
+
+// Class for multithreaded interruptable waiting.
+class Waiter
+{
+public:
+ template <typename Rep, typename Period>
+ bool Wait(duration<Rep, Period> const & waitDuration)
+ {
+ unique_lock<mutex> lock(m_mutex);
+
+ if (m_interrupted)
+ return false;
+
+ m_event.wait_for(lock, waitDuration, [this]() { return m_interrupted; });
+
+ return true;
+ }
+
+ void Interrupt();
+
+private:
+ bool m_interrupted = false;
+ mutex m_mutex;
+ condition_variable m_event;
+};
+
+DECLARE_EXCEPTION(ConfigLoadError, RootException);
+
+// Class which loads config from local drive, checks hash
+// for config on server and downloads new config if needed.
+class ConfigLoader
+{
+public:
+ explicit ConfigLoader(EditorConfigWrapper & config);
+ ~ConfigLoader();
+
+ // Static methods for production and testing.
+ static void LoadFromLocal(pugi::xml_document & doc);
+ static string GetRemoteHash();
+ static void GetRemoteConfig(pugi::xml_document & doc);
+ static bool SaveHash(string const & hash, string const & filePath);
+ static string LoadHash(string const & filePath);
+
+private:
+ void LoadFromServer();
+ void ResetConfig(pugi::xml_document const & doc);
+
+ EditorConfigWrapper & m_config;
+
+ Waiter m_waiter;
+ thread m_loaderThread;
+};
+} // namespace editor
diff --git a/editor/editor.pro b/editor/editor.pro
index 8f3d5d6fc0..4cf861450c 100644
--- a/editor/editor.pro
+++ b/editor/editor.pro
@@ -10,6 +10,7 @@ include($$ROOT_DIR/common.pri)
SOURCES += \
changeset_wrapper.cpp \
+ config_loader.cpp \
editor_config.cpp \
editor_notes.cpp \
editor_storage.cpp \
@@ -23,6 +24,7 @@ SOURCES += \
HEADERS += \
changeset_wrapper.hpp \
+ config_loader.hpp \
editor_config.hpp \
editor_notes.hpp \
editor_storage.hpp \
diff --git a/editor/editor_config.cpp b/editor/editor_config.cpp
index 9920464136..7dcef62f94 100644
--- a/editor/editor_config.cpp
+++ b/editor/editor_config.cpp
@@ -1,9 +1,5 @@
#include "editor/editor_config.hpp"
-#include "platform/platform.hpp"
-
-#include "coding/reader.hpp"
-
#include "base/stl_helpers.hpp"
#include "std/algorithm.hpp"
@@ -114,12 +110,6 @@ vector<pugi::xml_node> GetPrioritizedTypes(pugi::xml_node const & node)
namespace editor
{
-EditorConfig::EditorConfig(string const & fileName)
- : m_fileName(fileName)
-{
- Reload();
-}
-
bool EditorConfig::GetTypeDescription(vector<string> classificatorTypes,
TypeAggregatedDescription & outDesc) const
{
@@ -152,18 +142,15 @@ bool EditorConfig::GetTypeDescription(vector<string> classificatorTypes,
vector<string> EditorConfig::GetTypesThatCanBeAdded() const
{
auto const xpathResult = m_document.select_nodes("/mapsme/editor/types/type[not(@can_add='no' or @editable='no')]");
+
vector<string> result;
for (auto const xNode : xpathResult)
result.emplace_back(xNode.node().attribute("id").value());
return result;
}
-void EditorConfig::Reload()
+void EditorConfig::SetConfig(pugi::xml_document const & doc)
{
- string content;
- auto const reader = GetPlatform().GetReader(m_fileName);
- reader->ReadAsString(content);
- if (!m_document.load_buffer(content.data(), content.size()))
- MYTHROW(ConfigLoadError, ("Can't parse config"));
+ m_document.reset(doc);
}
} // namespace editor
diff --git a/editor/editor_config.hpp b/editor/editor_config.hpp
index 399f03c10a..f4217e1371 100644
--- a/editor/editor_config.hpp
+++ b/editor/editor_config.hpp
@@ -2,11 +2,8 @@
#include "indexer/feature_meta.hpp"
-#include "base/exception.hpp"
-
-#include "std/set.hpp"
+#include "std/shared_ptr.hpp"
#include "std/string.hpp"
-#include "std/unique_ptr.hpp"
#include "std/vector.hpp"
#include "3party/pugixml/src/pugixml.hpp"
@@ -36,27 +33,39 @@ struct TypeAggregatedDescription
bool m_address = false;
};
-DECLARE_EXCEPTION(ConfigLoadError, RootException);
-
class EditorConfig
{
public:
- EditorConfig(string const & fileName = "editor.config");
+ EditorConfig() = default;
// TODO(mgsergio): Reduce overhead by matching uint32_t types instead of strings.
- bool GetTypeDescription(vector<string> classificatorTypes, TypeAggregatedDescription & outDesc) const;
+ bool GetTypeDescription(vector<string> classificatorTypes,
+ TypeAggregatedDescription & outDesc) const;
vector<string> GetTypesThatCanBeAdded() const;
- bool EditingEnable() const;
-
- void Reload();
+ void SetConfig(pugi::xml_document const & doc);
// TODO(mgsergio): Implement this getter to avoid hard-code in XMLFeature::ApplyPatch.
// It should return [[phone, contact:phone], [website, contact:website, url], ...].
//vector<vector<string>> GetAlternativeFields() const;
private:
- string const m_fileName;
pugi::xml_document m_document;
};
+
+// Class which provides methods for EditorConfig concurrently using.
+class EditorConfigWrapper
+{
+public:
+ EditorConfigWrapper() = default;
+
+ void Set(shared_ptr<EditorConfig> config) { atomic_store(&m_config, config); }
+ shared_ptr<EditorConfig const> Get() const { return atomic_load(&m_config); }
+
+private:
+ shared_ptr<EditorConfig> m_config = make_shared<EditorConfig>();
+
+ // Just in case someone tryes to pass EditorConfigWrapper by value instead of referense.
+ DISALLOW_COPY_AND_MOVE(EditorConfigWrapper);
+};
} // namespace editor
diff --git a/editor/editor_tests/config_loader_test.cpp b/editor/editor_tests/config_loader_test.cpp
new file mode 100644
index 0000000000..9ad4a9faba
--- /dev/null
+++ b/editor/editor_tests/config_loader_test.cpp
@@ -0,0 +1,62 @@
+#include "testing/testing.hpp"
+
+#include "editor/config_loader.hpp"
+#include "editor/editor_config.hpp"
+
+#include "platform/platform_tests_support/scoped_file.hpp"
+
+#include "3party/pugixml/src/pugixml.hpp"
+
+namespace
+{
+using namespace editor;
+
+void CheckGeneralTags(pugi::xml_document const & doc)
+{
+ auto const types = doc.select_nodes("/mapsme/editor/types");
+ TEST(!types.empty(), ());
+ auto const fields = doc.select_nodes("/mapsme/editor/fields");
+ TEST(!fields.empty(), ());
+ auto const preferred_types = doc.select_nodes("/mapsme/editor/preferred_types");
+ TEST(!preferred_types.empty(), ());
+}
+
+UNIT_TEST(ConfigLoader_Base)
+{
+ EditorConfigWrapper config;
+ ConfigLoader loader(config);
+
+ TEST(!config.Get()->GetTypesThatCanBeAdded().empty(), ());
+}
+
+UNIT_TEST(ConfigLoader_GetRemoteHash)
+{
+ auto const hashStr = ConfigLoader::GetRemoteHash();
+ TEST_NOT_EQUAL(hashStr, "", ());
+ TEST_EQUAL(hashStr, ConfigLoader::GetRemoteHash(), ());
+}
+
+UNIT_TEST(ConfigLoader_GetRemoteConfig)
+{
+ pugi::xml_document doc;
+ ConfigLoader::GetRemoteConfig(doc);
+ CheckGeneralTags(doc);
+}
+
+UNIT_TEST(ConfigLoader_SaveLoadHash)
+{
+ platform::tests_support::ScopedFile sf("test.hash");
+ auto const testHash = "12345 678909 87654 321 \n 32";
+
+ ConfigLoader::SaveHash(testHash, sf.GetFullPath());
+ auto const loadedHash = ConfigLoader::LoadHash(sf.GetFullPath());
+ TEST_EQUAL(testHash, loadedHash, ());
+}
+
+UNIT_TEST(ConfigLoader_LoadFromLocal)
+{
+ pugi::xml_document doc;
+ ConfigLoader::LoadFromLocal(doc);
+ CheckGeneralTags(doc);
+}
+} // namespace
diff --git a/editor/editor_tests/editor_config_test.cpp b/editor/editor_tests/editor_config_test.cpp
index 329ea1488f..92faae7725 100644
--- a/editor/editor_tests/editor_config_test.cpp
+++ b/editor/editor_tests/editor_config_test.cpp
@@ -1,5 +1,6 @@
#include "testing/testing.hpp"
+#include "editor/config_loader.hpp"
#include "editor/editor_config.hpp"
#include "base/stl_helpers.hpp"
@@ -20,7 +21,11 @@ UNIT_TEST(EditorConfig_TypeDescription)
feature::Metadata::FMD_EMAIL
};
+ pugi::xml_document doc;
+ ConfigLoader::LoadFromLocal(doc);
+
EditorConfig config;
+ config.SetConfig(doc);
{
editor::TypeAggregatedDescription desc;
@@ -57,9 +62,13 @@ UNIT_TEST(EditorConfig_TypeDescription)
// TODO(mgsergio): Test case with priority="high" when there is one on editor.config.
}
-UNIT_TEST(EditorConfig_GetTypesThatGenBeAdded)
+UNIT_TEST(EditorConfig_GetTypesThatCanBeAdded)
{
+ pugi::xml_document doc;
+ ConfigLoader::LoadFromLocal(doc);
+
EditorConfig config;
+ config.SetConfig(doc);
auto const types = config.GetTypesThatCanBeAdded();
TEST(find(begin(types), end(types), "amenity-cafe") != end(types), ());
diff --git a/editor/editor_tests/editor_tests.pro b/editor/editor_tests/editor_tests.pro
index f4ead74e60..cfb2704eda 100644
--- a/editor/editor_tests/editor_tests.pro
+++ b/editor/editor_tests/editor_tests.pro
@@ -4,7 +4,8 @@ CONFIG -= app_bundle
TEMPLATE = app
ROOT_DIR = ../..
-DEPENDENCIES = editor platform_tests_support platform geometry coding base stats_client opening_hours pugixml oauthcpp
+DEPENDENCIES = editor platform_tests_support platform geometry coding base \
+ stats_client opening_hours pugixml oauthcpp tomcrypt
include($$ROOT_DIR/common.pri)
@@ -14,6 +15,7 @@ HEADERS += \
SOURCES += \
$$ROOT_DIR/testing/testingmain.cpp \
+ config_loader_test.cpp \
editor_config_test.cpp \
editor_notes_test.cpp \
opening_hours_ui_test.cpp \