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:
authorigrechuhin <i.grechuhin@gmail.com>2016-06-01 15:05:27 +0300
committerigrechuhin <i.grechuhin@gmail.com>2016-06-01 15:05:27 +0300
commit4ded724a1ab01730e5324b6cdc587ba6c335ecbc (patch)
tree3d2e9ba9a97d5b3a1263d193a951f6ed9eeab4be /editor
parenta15e1ca1f08123c31bb48f2e217c468931d91717 (diff)
parent0ae019f82dc2d4e30a1bbf3ea924d07d22621a52 (diff)
Merge pull request #3387 from mgsergio/get-user-stats-from-server
[editor] Get user stats from server
Diffstat (limited to 'editor')
-rw-r--r--editor/editor_tests/user_stats_test.cpp53
-rw-r--r--editor/user_stats.cpp172
-rw-r--r--editor/user_stats.hpp64
3 files changed, 258 insertions, 31 deletions
diff --git a/editor/editor_tests/user_stats_test.cpp b/editor/editor_tests/user_stats_test.cpp
index db68bb7712..d1e43b1ff1 100644
--- a/editor/editor_tests/user_stats_test.cpp
+++ b/editor/editor_tests/user_stats_test.cpp
@@ -2,19 +2,56 @@
#include "editor/user_stats.hpp"
+#include "platform/platform_tests_support/writable_dir_changer.hpp"
+
namespace editor
{
namespace
{
-UNIT_TEST(UserStats_Smoke)
+auto constexpr kEditorTestDir = "editor-tests";
+auto constexpr kUserName = "Vladimir BI";
+
+UNIT_TEST(UserStatsLoader_Smoke)
{
- // This user made only two changes and the possibility of further changes is very low.
- UserStats userStats("Vladimir BI");
- TEST(userStats.GetUpdateStatus(), ());
- TEST(userStats.IsChangesCountInitialized(), ());
- TEST(userStats.IsRankInitialized(), ());
- TEST_EQUAL(userStats.GetChangesCount(), 2, ());
- TEST_GREATER_OR_EQUAL(userStats.GetRank(), 5800, ());
+ WritableDirChanger wdc(kEditorTestDir, WritableDirChanger::SettingsDirPolicy::UseWritableDir);
+
+ {
+ UserStatsLoader statsLoader;
+ TEST(!statsLoader.GetStats(kUserName), ());
+ }
+
+ {
+ // This user made only two changes and the possibility of further changes is very low.
+ UserStatsLoader statsLoader;
+
+ statsLoader.Update(kUserName);
+ auto const userStats = statsLoader.GetStats(kUserName);
+
+ TEST(userStats, ());
+ int32_t rank, changesCount;
+ TEST(userStats.GetRank(rank), ());
+ TEST(userStats.GetChangesCount(changesCount), ());
+
+ TEST_GREATER_OR_EQUAL(rank, 5800, ());
+ TEST_EQUAL(changesCount, 2, ());
+ }
+
+ // This test checks if user stats info was stored in setting.
+ // NOTE: there Update function is not called.
+ {
+ UserStatsLoader statsLoader;
+
+ TEST_EQUAL(statsLoader.GetUserName(), kUserName, ());
+ auto const userStats = statsLoader.GetStats(kUserName);
+
+ TEST(userStats, ());
+ int32_t rank, changesCount;
+ TEST(userStats.GetRank(rank), ());
+ TEST(userStats.GetChangesCount(changesCount), ());
+
+ TEST_GREATER_OR_EQUAL(rank, 5800, ());
+ TEST_EQUAL(changesCount, 2, ());
+ }
}
} // namespace
} // namespace editor
diff --git a/editor/user_stats.cpp b/editor/user_stats.cpp
index 76788e0e75..629f4543a9 100644
--- a/editor/user_stats.cpp
+++ b/editor/user_stats.cpp
@@ -1,42 +1,94 @@
#include "editor/user_stats.hpp"
+#include "platform/platform.hpp"
+#include "platform/settings.hpp"
+
#include "coding/url_encode.hpp"
#include "base/logging.hpp"
+#include "base/timer.hpp"
+
+#include "std/thread.hpp"
#include "3party/Alohalytics/src/http_client.h"
#include "3party/pugixml/src/pugixml.hpp"
using TRequest = alohalytics::HTTPClientPlatformWrapper;
-
namespace
{
string const kUserStatsUrl = "http://py.osmz.ru/mmwatch/user?format=xml";
-auto constexpr kUninitialized = -1;
+int32_t constexpr kUninitialized = -1;
+
+auto constexpr kSettingsUserName = "LastLoggedUser";
+auto constexpr kSettingsRating = "UserEditorRating";
+auto constexpr kSettingsChangesCount = "UserEditorChangesCount";
+auto constexpr kSettingsLastUpdate = "UserEditorLastUpdate";
+
+auto constexpr kSecondsInHour = 60 * 60;
} // namespace
namespace editor
{
-UserStats::UserStats(string const & userName)
- : m_userName(userName), m_changesCount(kUninitialized), m_rank(kUninitialized)
+// UserStat ----------------------------------------------------------------------------------------
+
+UserStats::UserStats()
+ : m_changesCount(kUninitialized), m_rank(kUninitialized)
+ , m_updateTime(my::SecondsSinceEpochToTimeT(0)), m_valid(false)
+{
+}
+
+UserStats::UserStats(time_t const updateTime, uint32_t const rating,
+ uint32_t const changesCount, string const & levelUpFeat)
+ : m_changesCount(changesCount), m_rank(rating)
+ , m_updateTime(updateTime), m_levelUpRequiredFeat(levelUpFeat)
+ , m_valid(true)
+{
+}
+
+bool UserStats::GetChangesCount(int32_t & changesCount) const
+{
+ if (m_changesCount == kUninitialized)
+ return false;
+ changesCount = m_changesCount;
+ return true;
+}
+
+bool UserStats::GetRank(int32_t & rank) const
{
- m_updateStatus = Update();
+ if (m_rank == kUninitialized)
+ return false;
+ rank = m_rank;
+ return true;
}
-bool UserStats::IsChangesCountInitialized() const
+bool UserStats::GetLevelUpRequiredFeat(string & levelUpFeat)
{
- return m_changesCount != kUninitialized;
+ if (m_levelUpRequiredFeat.empty())
+ return false;
+ levelUpFeat = m_levelUpRequiredFeat;
+ return true;
}
-bool UserStats::IsRankInitialized() const
+// UserStatsLoader ---------------------------------------------------------------------------------
+
+UserStatsLoader::UserStatsLoader()
+ : m_lastUpdate(my::SecondsSinceEpochToTimeT(0))
{
- return m_rank != kUninitialized;
+ if (!LoadFromSettings())
+ LOG(LINFO, ("There is no cached user stats info in settings"));
+ else
+ LOG(LINFO, ("User stats info was loaded successfully"));
}
-bool UserStats::Update()
+bool UserStatsLoader::Update(string const & userName)
{
- auto const url = kUserStatsUrl + "&name=" + UrlEncode(m_userName);
+ {
+ lock_guard<mutex> g(m_mutex);
+ m_userName = userName;
+ }
+
+ auto const url = kUserStatsUrl + "&name=" + UrlEncode(userName);
TRequest request(url);
if (!request.RunHTTPRequest())
@@ -60,9 +112,103 @@ bool UserStats::Update()
return false;
}
- m_changesCount = document.select_node("mmwatch/edits/@value").attribute().as_int(kUninitialized);
- m_rank = document.select_node("mmwatch/rank/@value").attribute().as_int(kUninitialized);
+ auto changesCount = document.select_node("mmwatch/edits/@value").attribute().as_int(-1);
+ auto rank = document.select_node("mmwatch/rank/@value").attribute().as_int(-1);
+ auto levelUpFeat = document.select_node("mmwatch/levelUpFeat/@value").attribute().as_string();
+
+ lock_guard<mutex> g(m_mutex);
+ if (m_userName != userName)
+ return false;
+
+ m_lastUpdate = time(nullptr);
+ m_userStats = UserStats(m_lastUpdate, rank, changesCount, levelUpFeat);
+ SaveToSettings();
+
+ return true;
+}
+
+void UserStatsLoader::Update(string const & userName, TOnUpdateCallback fn)
+{
+ auto nothingToUpdate = false;
+ {
+ lock_guard<mutex> g(m_mutex);
+ nothingToUpdate = m_userStats && m_userName == userName && m_userStats &&
+ difftime(m_lastUpdate, time(nullptr)) < kSecondsInHour;
+ }
+
+ if (nothingToUpdate)
+ {
+ GetPlatform().RunOnGuiThread(fn);
+ return;
+ }
+
+ thread([this, userName, fn] {
+ if (Update(userName))
+ GetPlatform().RunOnGuiThread(fn);
+ }).detach();
+}
+
+void UserStatsLoader::DropStats(string const & userName)
+{
+ lock_guard<mutex> g(m_mutex);
+ if (m_userName != userName)
+ return;
+ m_userStats = {};
+ DropSettings();
+}
+
+UserStats UserStatsLoader::GetStats(string const & userName) const
+{
+ lock_guard<mutex> g(m_mutex);
+ if (m_userName == userName)
+ return m_userStats;
+ return {};
+}
+
+string UserStatsLoader::GetUserName() const
+{
+ lock_guard<mutex> g(m_mutex);
+ return m_userName;
+}
+bool UserStatsLoader::LoadFromSettings()
+{
+ uint32_t rating, changesCount;
+ uint64_t lastUpdate;
+ if (!settings::Get(kSettingsUserName, m_userName) ||
+ !settings::Get(kSettingsChangesCount, changesCount) ||
+ !settings::Get(kSettingsRating, rating) ||
+ !settings::Get(kSettingsLastUpdate, lastUpdate))
+ {
+ return false;
+ }
+
+ m_lastUpdate = my::SecondsSinceEpochToTimeT(lastUpdate);
+ m_userStats = UserStats(m_lastUpdate, rating, changesCount, "");
return true;
}
+
+void UserStatsLoader::SaveToSettings()
+{
+ if (!m_userStats)
+ return;
+
+ settings::Set(kSettingsUserName, m_userName);
+ int32_t rank;
+ if (m_userStats.GetRank(rank))
+ settings::Set(kSettingsRating, rank);
+ int32_t changesCount;
+ if (m_userStats.GetChangesCount(changesCount))
+ settings::Set(kSettingsChangesCount, changesCount);
+ settings::Set(kSettingsLastUpdate, my::TimeTToSecondsSinceEpoch(m_lastUpdate));
+ // Do not save m_requiredLevelUpFeat for it becomes obsolete very fast.
+}
+
+void UserStatsLoader::DropSettings()
+{
+ settings::Delete(kSettingsUserName);
+ settings::Delete(kSettingsRating);
+ settings::Delete(kSettingsChangesCount);
+ settings::Delete(kSettingsLastUpdate);
+}
} // namespace editor
diff --git a/editor/user_stats.hpp b/editor/user_stats.hpp
index 38f714bdcd..200330ec23 100644
--- a/editor/user_stats.hpp
+++ b/editor/user_stats.hpp
@@ -1,6 +1,9 @@
#pragma once
#include "std/cstdint.hpp"
+#include "std/ctime.hpp"
+#include "std/function.hpp"
+#include "std/mutex.hpp"
#include "std/string.hpp"
namespace editor
@@ -8,23 +11,64 @@ namespace editor
class UserStats
{
public:
- explicit UserStats(string const & userName);
+ UserStats();
+ UserStats(time_t const updateTime, uint32_t const rating,
+ uint32_t const changesCount, string const & levelUpFeat);
- bool IsChangesCountInitialized() const;
- bool IsRankInitialized() const;
+ bool IsValid() const { return m_valid; }
- int32_t GetChangesCount() const { return m_changesCount; }
- int32_t GetRank() const { return m_rank; }
+ operator bool() const { return IsValid(); }
- bool GetUpdateStatus() const { return m_updateStatus; }
- bool Update();
+ bool GetChangesCount(int32_t & changesCount) const;
+ bool GetRank(int32_t & rank) const;
+ bool GetLevelUpRequiredFeat(string & levelUpFeat);
+
+ time_t GetLastUpdate() const { return m_updateTime; }
private:
- string m_userName;
int32_t m_changesCount;
int32_t m_rank;
+ time_t m_updateTime;
+ /// A very doubtful field representing what a user must commit to have a better rank.
+ string m_levelUpRequiredFeat;
+ bool m_valid;
+};
+
+class UserStatsLoader
+{
+public:
+ using TOnUpdateCallback = function<void()>;
+
+ UserStatsLoader();
+
+ /// Synchronously sends request to the server. Updates stats and returns true on success.
+ bool Update(string const & userName);
+
+ /// Launch the update process if stats are too old.
+ /// The process posts fn to a gui thread on success.
+ void Update(string const & userName, TOnUpdateCallback fn);
+
+ /// Resets internal state and removes records from settings.
+ void DropStats(string const & userName);
+
+ /// Atomically returns stats if userName is still actual.
+ UserStats GetStats(string const & userName) const;
+
+ /// Debug only.
+ string GetUserName() const;
+
+private:
+ /// Not thread-safe, but called only in constructor.
+ bool LoadFromSettings();
+ /// Not thread-safe, use synchonization.
+ void SaveToSettings();
+ void DropSettings();
+
+ string m_userName;
+
+ time_t m_lastUpdate;
+ mutable mutex m_mutex;
- /// True if last update was successful.
- bool m_updateStatus;
+ UserStats m_userStats;
};
} // namespace editor