diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index 7b54713917..34f5d82511 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -1549,7 +1549,18 @@ Java_com_mapswithme_maps_Framework_nativeAuthenticateUser(JNIEnv * env, jclass, jint socialTokenType) { auto const tokenStr = jni::ToNativeString(env, socialToken); - frm()->GetUser().Authenticate(tokenStr, static_cast(socialTokenType)); + auto & user = frm()->GetUser(); + auto s = make_unique(); + s->m_postCallAction = User::Subscriber::Action::RemoveSubscriber; + s->m_onAuthenticate = [](bool success) + { + GetPlatform().RunTask(Platform::Thread::Gui, [success]() + { + //TODO: @alexzatsepin add reaction on auth success/failure, please. + }); + }; + user.AddSubscriber(std::move(s)); + user.Authenticate(tokenStr, static_cast(socialTokenType)); } JNIEXPORT jboolean JNICALL diff --git a/coding/CMakeLists.txt b/coding/CMakeLists.txt index 3020c77966..e2c1e6e003 100644 --- a/coding/CMakeLists.txt +++ b/coding/CMakeLists.txt @@ -68,6 +68,7 @@ set( reader_wrapper.hpp reader_writer_ops.cpp reader_writer_ops.hpp + serdes_json.hpp simple_dense_coding.cpp simple_dense_coding.hpp streams.hpp diff --git a/coding/serdes_json.hpp b/coding/serdes_json.hpp new file mode 100644 index 0000000000..79278eba44 --- /dev/null +++ b/coding/serdes_json.hpp @@ -0,0 +1,207 @@ +#pragma once + +#include "base/exception.hpp" +#include "base/scope_guard.hpp" +#include "base/stl_add.hpp" + +#include "3party/jansson/myjansson.hpp" + +#include +#include +#include +#include +#include + +namespace coding +{ +template +class SerializerJson +{ +public: + explicit SerializerJson(Sink & sink) : m_sink(sink) {} + + virtual ~SerializerJson() + { + std::unique_ptr buffer(json_dumps(m_json.get(), 0)); + m_sink.Write(buffer.get(), strlen(buffer.get())); + } + + void operator()(bool const d, char const * name = nullptr) { ToJSONObject(*m_json, name, d); } + void operator()(uint8_t const d, char const * name = nullptr) { ToJSONObject(*m_json, name, d); } + void operator()(uint32_t const d, char const * name = nullptr) { ToJSONObject(*m_json, name, d); } + void operator()(uint64_t const d, char const * name = nullptr) { ToJSONObject(*m_json, name, d); } + void operator()(int64_t const d, char const * name = nullptr) { ToJSONObject(*m_json, name, d); } + void operator()(double const d, char const * name = nullptr) { ToJSONObject(*m_json, name, d); } + void operator()(std::string const & s, char const * name = nullptr) + { + ToJSONObject(*m_json, name, s); + } + + template + void operator()(std::vector const & vs, char const * name = nullptr) + { + NewScopeWith(my::NewJSONArray(), name, [this, &vs] { + for (auto const & v : vs) + (*this)(v); + }); + } + + template + void operator()(R const & r, char const * name = nullptr) + { + NewScopeWith(my::NewJSONObject(), name, [this, &r] { r.Visit(*this); }); + } + + template + void operator()(std::unique_ptr const & r, char const * name = nullptr) + { + NewScopeWith(my::NewJSONObject(), name, [this, &r] { + CHECK(r, ()); + r->Visit(*this); + }); + } + + template + void operator()(std::shared_ptr const & r, char const * name = nullptr) + { + NewScopeWith(my::NewJSONObject(), name, [this, &r] { + CHECK(r, ()); + r->Visit(*this); + }); + } + +protected: + template + void NewScopeWith(my::JSONPtr json_object, char const * name, Fn && fn) + { + my::JSONPtr safe_json = std::move(m_json); + m_json = std::move(json_object); + + auto rollback = [this, &safe_json, name]() + { + if (safe_json == nullptr) + return; + + if (json_is_array(safe_json)) + json_array_append_new(safe_json.get(), m_json.release()); + else if (json_is_object(safe_json)) + json_object_set_new(safe_json.get(), name, m_json.release()); + + m_json = std::move(safe_json); + }; + MY_SCOPE_GUARD(rollbackJson, rollback); + + fn(); + } + + my::JSONPtr m_json = nullptr; + Sink & m_sink; +}; + +class DeserializerJson +{ +public: + DECLARE_EXCEPTION(Exception, RootException); + + template ::value, + Source>::type * = nullptr> + explicit DeserializerJson(Source & source) + { + std::string src(source.Size(), '\0'); + source.Read(static_cast(&src[0]), source.Size()); + m_jsonObject.ParseFrom(src); + m_json = m_jsonObject.get(); + } + + explicit DeserializerJson(std::string const & source) + : m_jsonObject(source), m_json(m_jsonObject.get()) + { + } + + explicit DeserializerJson(json_t * json) : m_json(json) {} + + template + void FromJsonObjectOrValue(T & s, char const * name) + { + if (name != nullptr) + FromJSONObject(m_json, name, s); + else + FromJSON(m_json, s); + } + + void operator()(bool & d, char const * name = nullptr) { FromJsonObjectOrValue(d, name); } + void operator()(uint8_t & d, char const * name = nullptr) { FromJsonObjectOrValue(d, name); } + void operator()(uint32_t & d, char const * name = nullptr) { FromJsonObjectOrValue(d, name); } + void operator()(uint64_t & d, char const * name = nullptr) { FromJsonObjectOrValue(d, name); } + void operator()(int64_t & d, char const * name = nullptr) { FromJsonObjectOrValue(d, name); } + void operator()(double & d, char const * name = nullptr) { FromJsonObjectOrValue(d, name); } + void operator()(std::string & s, char const * name = nullptr) { FromJsonObjectOrValue(s, name); } + + template + void operator()(std::vector & vs, char const * name = nullptr) + { + json_t * context = SaveContext(name); + + if (!json_is_array(m_json)) + MYTHROW(my::Json::Exception, ("The field", name, "must contain a json array.")); + + vs.resize(json_array_size(m_json)); + for (size_t index = 0; index < vs.size(); ++index) + { + json_t * context = SaveContext(); + m_json = json_array_get(context, index); + (*this)(vs[index]); + RestoreContext(context); + } + + RestoreContext(context); + } + + template + void operator()(R & r, char const * name = nullptr) + { + json_t * context = SaveContext(name); + r.Visit(*this); + RestoreContext(context); + } + + template + void operator()(std::unique_ptr & r, char const * name = nullptr) + { + json_t * context = SaveContext(name); + if (!r) + r = my::make_unique(); + r->Visit(*this); + RestoreContext(context); + } + + template + void operator()(std::shared_ptr & r, char const * name = nullptr) + { + json_t * context = SaveContext(name); + if (!r) + r = std::make_shared(); + r->Visit(*this); + RestoreContext(context); + } + +protected: + json_t * SaveContext(char const * name = nullptr) + { + json_t * context = m_json; + if (name) + m_json = my::GetJSONObligatoryField(context, name); + return context; + } + + void RestoreContext(json_t * context) + { + if (context) + m_json = context; + } + + my::Json m_jsonObject; + json_t * m_json = nullptr; +}; +} // namespace coding diff --git a/iphone/Maps/UI/Authorization/MWMAuthorizationViewModel.mm b/iphone/Maps/UI/Authorization/MWMAuthorizationViewModel.mm index b4865e56bf..4637a09fa7 100644 --- a/iphone/Maps/UI/Authorization/MWMAuthorizationViewModel.mm +++ b/iphone/Maps/UI/Authorization/MWMAuthorizationViewModel.mm @@ -4,6 +4,8 @@ #include "Framework.h" +#include + @implementation MWMAuthorizationViewModel + (BOOL)isAuthenticated @@ -37,6 +39,15 @@ case MWMSocialTokenTypeGoogle: socialTokenType = User::SocialTokenType::Google; break; case MWMSocialTokenTypeFacebook: socialTokenType = User::SocialTokenType::Facebook; break; } + + auto s = std::make_unique(); + s->m_postCallAction = User::Subscriber::Action::RemoveSubscriber; + s->m_onAuthenticate = [](bool success) + { + //TODO: @igrechuhin add reaction on auth success/failure, please. + // Warning! Callback can be called on not UI Thread. + }; + user.AddSubscriber(std::move(s)); user.Authenticate(token.UTF8String, socialTokenType); } diff --git a/map/CMakeLists.txt b/map/CMakeLists.txt index 3764581220..b2f82e76db 100644 --- a/map/CMakeLists.txt +++ b/map/CMakeLists.txt @@ -27,6 +27,8 @@ set( bookmarks_search_params.hpp chart_generator.cpp chart_generator.hpp + cloud.cpp + cloud.hpp discovery/discovery_client_params.hpp discovery/discovery_manager.cpp discovery/discovery_manager.hpp diff --git a/map/cloud.cpp b/map/cloud.cpp new file mode 100644 index 0000000000..939f6e08d5 --- /dev/null +++ b/map/cloud.cpp @@ -0,0 +1,554 @@ +#include "map/cloud.hpp" + +#include "coding/file_name_utils.hpp" +#include "coding/file_reader.hpp" +#include "coding/file_writer.hpp" +#include "coding/internal/file_data.hpp" +#include "coding/zip_creator.hpp" + +#include "platform/http_client.hpp" +#include "platform/platform.hpp" +#include "platform/settings.hpp" +#include "platform/http_uploader.hpp" + +#include "base/assert.hpp" +#include "base/logging.hpp" + +#include +#include +#include + +#define STAGE_CLOUD_SERVER +#include "private.h" + +using namespace std::chrono; + +namespace +{ +uint32_t constexpr kUploadTaskTimeoutInSeconds = 1; +uint64_t constexpr kUpdateTimeoutInHours = 24; +uint32_t constexpr kRetryMaxAttempts = 3; +uint32_t constexpr kRetryTimeoutInSeconds = 5; +uint32_t constexpr kRetryDegradationFactor = 2; + +uint64_t constexpr kMaxWwanUploadingSizeInBytes = 10 * 1024; // 10Kb + +std::string const kServerUrl = CLOUD_URL; +std::string const kCloudServerVersion = "v1"; +std::string const kCloudServerUploadMethod = "upload_url"; + +std::string GetIndexFilePath(std::string const & indexName) +{ + return my::JoinPath(GetPlatform().SettingsDir(), indexName); +} + +std::string BuildUploadingUrl(std::string const & serverPathName) +{ + if (kServerUrl.empty()) + return {}; + + std::ostringstream ss; + ss << kServerUrl << "/" + << kCloudServerVersion << "/" + << serverPathName << "/" + << kCloudServerUploadMethod << "/"; + return ss.str(); +} + +std::string BuildAuthenticationToken(std::string const & accessToken) +{ + return "Bearer " + accessToken; +} +} // namespace + +Cloud::Cloud(CloudParams && params) + : m_params(std::move(params)) +{ + ASSERT(!m_params.m_indexName.empty(), ()); + ASSERT(!m_params.m_serverPathName.empty(), ()); + ASSERT(!m_params.m_settingsParamName.empty(), ()); + ASSERT(!m_params.m_zipExtension.empty(), ()); + + int stateValue; + if (!settings::Get(m_params.m_settingsParamName, stateValue)) + { + stateValue = static_cast(State::Unknown); + settings::Set(m_params.m_settingsParamName, stateValue); + } + + m_state = static_cast(stateValue); +} + +Cloud::~Cloud() +{ + std::lock_guard lock(m_mutex); + SaveIndexImpl(); +} + +void Cloud::SetInvalidTokenHandler(InvalidTokenHandler && onInvalidToken) +{ + std::lock_guard lock(m_mutex); + m_onInvalidToken = std::move(onInvalidToken); +} + +void Cloud::SetSynchronizationHandlers(SynchronizationStartedHandler && onSynchronizationStarted, + SynchronizationFinishedHandler && onSynchronizationFinished) +{ + std::lock_guard lock(m_mutex); + m_onSynchronizationStarted = std::move(onSynchronizationStarted); + m_onSynchronizationFinished = std::move(onSynchronizationFinished); +} + +void Cloud::SetState(State state) +{ + std::lock_guard lock(m_mutex); + + if (m_state == state) + return; + + m_state = state; + settings::Set(m_params.m_settingsParamName, static_cast(m_state)); + + switch (m_state) + { + case State::Enabled: + GetPlatform().RunTask(Platform::Thread::File, [this]() { LoadIndex(); }); + break; + + case State::Disabled: + // Delete index file and clear memory. + my::DeleteFileX(m_params.m_indexName); + m_files.clear(); + m_index = Index(); + break; + + case State::Unknown: ASSERT(false, ("Unknown state can't be set up")); break; + } +} + +Cloud::State Cloud::GetState() const +{ + std::lock_guard lock(m_mutex); + return m_state; +} + +void Cloud::Init(std::vector const & filePaths) +{ + std::lock_guard lock(m_mutex); + m_files.insert(filePaths.cbegin(), filePaths.end()); + + if (m_state != State::Enabled) + return; + + GetPlatform().RunTask(Platform::Thread::File, [this]() { LoadIndex(); }); +} + +void Cloud::MarkModified(std::string const & filePath) +{ + std::lock_guard lock(m_mutex); + if (m_state != State::Enabled) + return; + + m_files.insert(filePath); + MarkModifiedImpl(filePath, false /* checkSize */); +} + +std::unique_ptr Cloud::GetUserSubscriber() +{ + auto s = std::make_unique(); + s->m_onChangeToken = [this](std::string const & token) + { + SetAccessToken(token); + ScheduleUploading(); + }; + return s; +} + +void Cloud::LoadIndex() +{ + auto const indexFilePath = GetIndexFilePath(m_params.m_indexName); + if (GetPlatform().IsFileExistsByFullPath(indexFilePath)) + ReadIndex(indexFilePath); + + UpdateIndex(); + + ScheduleUploading(); +} + +void Cloud::ReadIndex(std::string const & indexFilePath) +{ + // Read index file. + std::string data; + try + { + FileReader r(indexFilePath); + r.ReadAsString(data); + } + catch (FileReader::Exception const & exception) + { + data.clear(); + LOG(LWARNING, ("Exception while reading file:", indexFilePath, + "reason:", exception.what())); + } + + // Parse index file. + if (!data.empty()) + { + try + { + Index index; + coding::DeserializerJson deserializer(data); + deserializer(index); + + std::lock_guard lock(m_mutex); + std::swap(m_index, index); + } + catch (my::Json::Exception const & exception) + { + LOG(LWARNING, ("Exception while parsing file:", indexFilePath, + "reason:", exception.what())); + } + } +} + +void Cloud::UpdateIndex() +{ + std::lock_guard lock(m_mutex); + + // Now we process files ONLY if update time is out. + auto const h = static_cast( + duration_cast(system_clock::now().time_since_epoch()).count()); + if (h >= m_index.m_lastUpdateInHours + kUpdateTimeoutInHours) + { + for (auto const & path : m_files) + MarkModifiedImpl(path, true /* checkSize */); + m_index.m_lastUpdateInHours = h; + m_index.m_isOutdated = true; + + SaveIndexImpl(); + } +} + +uint64_t Cloud::CalculateUploadingSizeImpl() const +{ + uint64_t sz = 0; + for (auto const & entry : m_index.m_entries) + sz += entry->m_sizeInBytes; + return sz; +} + +void Cloud::SortEntriesBeforeUploadingImpl() +{ + std::sort(m_index.m_entries.begin(), m_index.m_entries.end(), + [](EntryPtr const & lhs, EntryPtr const & rhs) + { + return lhs->m_sizeInBytes < rhs->m_sizeInBytes; + }); +} + +void Cloud::MarkModifiedImpl(std::string const & filePath, bool checkSize) +{ + uint64_t fileSize = 0; + if (!my::GetFileSize(filePath, fileSize)) + return; + + auto entryPtr = GetEntryImpl(filePath); + if (entryPtr) + { + entryPtr->m_isOutdated = checkSize ? (entryPtr->m_sizeInBytes != fileSize) : true; + entryPtr->m_sizeInBytes = fileSize; + } + else + { + m_index.m_entries.emplace_back( + std::make_shared(filePath, fileSize, true /* m_isOutdated */)); + } +} + +Cloud::EntryPtr Cloud::GetEntryImpl(std::string const & filePath) const +{ + auto it = std::find_if(m_index.m_entries.begin(), m_index.m_entries.end(), + [filePath](EntryPtr ptr) { return ptr->m_name == filePath; }); + if (it != m_index.m_entries.end()) + return *it; + return nullptr; +} + +void Cloud::SaveIndexImpl() const +{ + if (m_state != State::Enabled) + return; + + std::string jsonData; + { + using Sink = MemWriter; + Sink sink(jsonData); + coding::SerializerJson serializer(sink); + serializer(m_index); + } + + auto const indexFilePath = GetIndexFilePath(m_params.m_indexName); + try + { + FileWriter w(indexFilePath); + w.Write(jsonData.c_str(), jsonData.length()); + } + catch (FileWriter::Exception const & exception) + { + LOG(LERROR, ("Exception while writing file:", indexFilePath, + "reason:", exception.what())); + } +} + +void Cloud::ScheduleUploading() +{ + { + std::lock_guard lock(m_mutex); + if (m_state != State::Enabled || !m_index.m_isOutdated || m_accessToken.empty() || + m_uploadingStarted) + { + return; + } + + auto const status = GetPlatform().ConnectionStatus(); + auto const totalUploadingSize = CalculateUploadingSizeImpl(); + if (status == Platform::EConnectionType::CONNECTION_NONE || + (status == Platform::EConnectionType::CONNECTION_WWAN && + totalUploadingSize > kMaxWwanUploadingSizeInBytes)) + { + return; + } + + SortEntriesBeforeUploadingImpl(); + m_uploadingStarted = true; + } + + if (m_onSynchronizationStarted != nullptr) + m_onSynchronizationStarted(); + + auto entry = FindOutdatedEntry(); + if (entry != nullptr) + ScheduleUploadingTask(entry, kUploadTaskTimeoutInSeconds, 0 /* attemptIndex */); + else + FinishUploading(SynchronizationResult::Success, {}); +} + +void Cloud::ScheduleUploadingTask(EntryPtr const & entry, uint32_t timeout, + uint32_t attemptIndex) +{ + GetPlatform().RunDelayedTask(Platform::Thread::Network, seconds(timeout), + [this, entry, timeout, attemptIndex]() + { + ASSERT(m_state == State::Enabled, ()); + ASSERT(!m_accessToken.empty(), ()); + ASSERT(m_uploadingStarted, ()); + ASSERT(entry->m_isOutdated, ()); + + auto const uploadingUrl = BuildUploadingUrl(m_params.m_serverPathName); + if (uploadingUrl.empty()) + { + FinishUploading(SynchronizationResult::NetworkError, "Empty uploading url"); + return; + } + + // Prepare file to uploading. + bool needDeleteFileAfterUploading = false; + auto const uploadedName = PrepareFileToUploading(entry->m_name, needDeleteFileAfterUploading); + auto deleteAfterUploading = [needDeleteFileAfterUploading, uploadedName]() { + if (needDeleteFileAfterUploading) + my::DeleteFileX(uploadedName); + }; + MY_SCOPE_GUARD(deleteAfterUploadingGuard, deleteAfterUploading); + + if (uploadedName.empty()) + { + FinishUploading(SynchronizationResult::DiskError, {}); + return; + } + + // Request uploading. + auto const result = RequestUploading(uploadingUrl, uploadedName); + if (result.m_requestResult.m_status == RequestStatus::NetworkError) + { + // Retry uploading request up to kRetryMaxAttempts times. + if (attemptIndex + 1 == kRetryMaxAttempts) + { + FinishUploading(SynchronizationResult::NetworkError, result.m_requestResult.m_error); + return; + } + + auto const retryTimeout = attemptIndex == 0 ? kRetryTimeoutInSeconds + : timeout * kRetryDegradationFactor; + ScheduleUploadingTask(entry, retryTimeout, attemptIndex + 1); + return; + } + else if (result.m_requestResult.m_status == RequestStatus::Forbidden) + { + // Finish uploading and nofity about invalid access token. + if (m_onInvalidToken != nullptr) + m_onInvalidToken(); + + FinishUploading(SynchronizationResult::AuthError, result.m_requestResult.m_error); + return; + } + + // Execute uploading. + auto const executeResult = ExecuteUploading(result.m_response, uploadedName); + if (executeResult.m_status != RequestStatus::Ok) + { + FinishUploading(SynchronizationResult::NetworkError, executeResult.m_error); + return; + } + + // Mark entry as not outdated. + { + std::lock_guard lock(m_mutex); + entry->m_isOutdated = false; + SaveIndexImpl(); + } + + // Schedule next uploading task. + auto nextEntry = FindOutdatedEntry(); + if (nextEntry != nullptr) + ScheduleUploadingTask(nextEntry, kUploadTaskTimeoutInSeconds, 0 /* attemptIndex */); + else + FinishUploading(SynchronizationResult::Success, {}); + }); +} + +std::string Cloud::PrepareFileToUploading(std::string const & filePath, + bool & needDeleteAfterUploading) +{ + needDeleteAfterUploading = false; + auto ext = my::GetFileExtension(filePath); + strings::AsciiToLower(ext); + if (ext == m_params.m_zipExtension) + return filePath; + + std::string name = filePath; + my::GetNameFromFullPath(name); + my::GetNameWithoutExt(name); + auto const zipPath = my::JoinFoldersToPath(GetPlatform().TmpDir(), name + m_params.m_zipExtension); + + if (CreateZipFromPathDeflatedAndDefaultCompression(filePath, zipPath)) + { + needDeleteAfterUploading = true; + return zipPath; + } + return {}; +} + +Cloud::UploadingResult Cloud::RequestUploading(std::string const & uploadingUrl, + std::string const & filePath) const +{ + static std::string const kApplicationJson = "application/json"; + + UploadingResult result; + + platform::HttpClient request(uploadingUrl); + request.SetRawHeader("Accept", kApplicationJson); + request.SetRawHeader("Authorization", BuildAuthenticationToken(m_accessToken)); + + std::string jsonBody; + { + UploadingRequestData data; + data.m_alohaId = GetPlatform().UniqueClientId(); + data.m_deviceName = GetPlatform().DeviceName(); + data.m_fileName = filePath; + my::GetNameFromFullPath(data.m_fileName); + + using Sink = MemWriter; + Sink sink(jsonBody); + coding::SerializerJson serializer(sink); + serializer(data); + } + request.SetBodyData(std::move(jsonBody), kApplicationJson); + + if (request.RunHttpRequest() && !request.WasRedirected()) + { + int const resultCode = request.ErrorCode(); + bool const isSuccessfulCode = (resultCode == 200 || resultCode == 201); + if (isSuccessfulCode) + { + result.m_requestResult = {RequestStatus::Ok, {}}; + coding::DeserializerJson des(request.ServerResponse()); + des(result.m_response); + return result; + } + + if (resultCode == 403) + { + LOG(LWARNING, ("Access denied for", uploadingUrl)); + result.m_requestResult = {RequestStatus::Forbidden, request.ServerResponse()}; + return result; + } + } + + result.m_requestResult = {RequestStatus::NetworkError, request.ServerResponse()}; + return result; +} + +Cloud::RequestResult Cloud::ExecuteUploading(UploadingResponseData const & responseData, + std::string const & filePath) +{ + ASSERT(!responseData.m_url.empty(), ()); + ASSERT(!responseData.m_method.empty(), ()); + + platform::HttpUploader request; + request.SetUrl(responseData.m_url); + request.SetMethod(responseData.m_method); + std::map params; + for (auto const & f : responseData.m_fields) + { + ASSERT_EQUAL(f.size(), 2, ()); + params.insert(std::make_pair(f[0], f[1])); + } + request.SetParams(params); + request.SetFilePath(filePath); + + auto const result = request.Upload(); + if (result.m_httpCode == 200 || result.m_httpCode == 201) + return {RequestStatus::Ok, {}}; + + auto const errorStr = strings::to_string(result.m_httpCode) + " " + result.m_description; + if (result.m_httpCode == 403) + return {RequestStatus::Forbidden, errorStr}; + + return {RequestStatus::NetworkError, errorStr}; +} + +Cloud::EntryPtr Cloud::FindOutdatedEntry() const +{ + std::lock_guard lock(m_mutex); + for (auto const & entry : m_index.m_entries) + { + if (entry->m_isOutdated) + return entry; + } + return nullptr; +} + +void Cloud::FinishUploading(SynchronizationResult result, std::string const & errorStr) +{ + { + std::lock_guard lock(m_mutex); + m_index.m_isOutdated = (result != SynchronizationResult::Success); + if (result == SynchronizationResult::Success) + { + m_index.m_lastSyncTimestamp = static_cast( + duration_cast(system_clock::now().time_since_epoch()).count()); + } + m_uploadingStarted = false; + SaveIndexImpl(); + } + + + if (m_onSynchronizationFinished != nullptr) + m_onSynchronizationFinished(result, errorStr); +} + +void Cloud::SetAccessToken(std::string const & token) +{ + std::lock_guard lock(m_mutex); + m_accessToken = token; +} diff --git a/map/cloud.hpp b/map/cloud.hpp new file mode 100644 index 0000000000..def9aa7f02 --- /dev/null +++ b/map/cloud.hpp @@ -0,0 +1,210 @@ +#pragma once + +#include "map/user.hpp" + +#include "coding/serdes_json.hpp" + +#include "base/assert.hpp" +#include "base/visitor.hpp" + +#include +#include +#include +#include +#include +#include + +class Cloud +{ +public: + struct Entry + { + Entry() = default; + Entry(std::string const & name, uint64_t sizeInBytes, bool isOutdated) + : m_name(name) + , m_sizeInBytes(sizeInBytes) + , m_isOutdated(isOutdated) + {} + + bool operator==(Entry const & entry) const + { + return m_name == entry.m_name && m_sizeInBytes == entry.m_sizeInBytes && + m_isOutdated == entry.m_isOutdated; + } + + bool operator!=(Entry const & entry) const + { + return !operator==(entry); + } + + std::string m_name; + uint64_t m_sizeInBytes = 0; + bool m_isOutdated = false; + + DECLARE_VISITOR_AND_DEBUG_PRINT(Entry, visitor(m_name, "name"), + visitor(m_sizeInBytes, "sizeInBytes"), + visitor(m_isOutdated, "isOutdated")) + }; + + using EntryPtr = std::shared_ptr; + + struct Index + { + std::vector m_entries; + uint64_t m_lastUpdateInHours = 0; + bool m_isOutdated = false; + uint64_t m_lastSyncTimestamp = 0; + + DECLARE_VISITOR_AND_DEBUG_PRINT(Index, visitor(m_entries, "entries"), + visitor(m_lastUpdateInHours, "lastUpdateInHours"), + visitor(m_isOutdated, "isOutdated"), + visitor(m_lastSyncTimestamp, "lastSyncTimestamp")) + }; + + struct UploadingRequestData + { + std::string m_alohaId; + std::string m_deviceName; + std::string m_fileName; + + DECLARE_VISITOR_AND_DEBUG_PRINT(UploadingRequestData, visitor(m_alohaId, "aloha_id"), + visitor(m_deviceName, "device_name"), + visitor(m_fileName, "file_name")) + }; + + struct UploadingResponseData + { + std::string m_url; + std::vector> m_fields; + std::string m_method; + + DECLARE_VISITOR_AND_DEBUG_PRINT(UploadingResponseData, visitor(m_url, "url"), + visitor(m_fields, "fields"), + visitor(m_method, "method")) + }; + + enum class RequestStatus + { + Ok, + Forbidden, + NetworkError + }; + + struct RequestResult + { + RequestResult() = default; + RequestResult(RequestStatus status, std::string const & error) + : m_status(status) + , m_error(error) + {} + + RequestStatus m_status = RequestStatus::Ok; + std::string m_error; + }; + + struct UploadingResult + { + RequestResult m_requestResult; + UploadingResponseData m_response; + }; + + enum class State + { + // User never enabled or disabled synchronization via cloud. It is a default state. + Unknown = 0, + // User explicitly disabled synchronization via cloud. + Disabled = 1, + // User explicitly enabled synchronization via cloud. + Enabled = 2 + }; + + enum class SynchronizationResult + { + // Synchronization was finished successfully. + Success = 0, + // Synchronization was interrupted by an authentication error. + AuthError = 1, + // Synchronization was interrupted by a network error. + NetworkError = 2, + // Synchronization was interrupted by a disk error. + DiskError = 3 + }; + + using InvalidTokenHandler = std::function; + using SynchronizationStartedHandler = std::function; + using SynchronizationFinishedHandler = std::function; + + struct CloudParams + { + CloudParams() = default; + CloudParams(std::string && indexName, std::string && serverPathName, + std::string && settingsParamName, std::string && zipExtension) + : m_indexName(std::move(indexName)) + , m_serverPathName(std::move(serverPathName)) + , m_settingsParamName(std::move(settingsParamName)) + , m_zipExtension(std::move(zipExtension)) + {} + + // Name of file in which cloud stores metadata. + std::string m_indexName; + // Part of path to the cloud server. + std::string m_serverPathName; + // Name of parameter to store cloud's state in settings. + std::string m_settingsParamName; + // Extension of zipped file. The first character must be '.' + std::string m_zipExtension; + }; + + Cloud(CloudParams && params); + ~Cloud(); + + void SetInvalidTokenHandler(InvalidTokenHandler && onInvalidToken); + void SetSynchronizationHandlers(SynchronizationStartedHandler && onSynchronizationStarted, + SynchronizationFinishedHandler && onSynchronizationFinished); + + void SetState(State state); + State GetState() const; + + void Init(std::vector const & filePaths); + void MarkModified(std::string const & filePath); + + std::unique_ptr GetUserSubscriber(); + +private: + void LoadIndex(); + void ReadIndex(std::string const & indexFilePath); + void UpdateIndex(); + void SaveIndexImpl() const; + + EntryPtr GetEntryImpl(std::string const & filePath) const; + void MarkModifiedImpl(std::string const & filePath, bool checkSize); + + uint64_t CalculateUploadingSizeImpl() const; + void SortEntriesBeforeUploadingImpl(); + void ScheduleUploading(); + void ScheduleUploadingTask(EntryPtr const & entry, uint32_t timeout, + uint32_t attemptIndex); + EntryPtr FindOutdatedEntry() const; + void FinishUploading(SynchronizationResult result, std::string const & errorStr); + void SetAccessToken(std::string const & token); + + std::string PrepareFileToUploading(std::string const & filePath, + bool & needDeleteAfterUploading); + + UploadingResult RequestUploading(std::string const & uploadingUrl, + std::string const & filePath) const; + RequestResult ExecuteUploading(UploadingResponseData const & responseData, + std::string const & filePath); + + CloudParams const m_params; + InvalidTokenHandler m_onInvalidToken; + SynchronizationStartedHandler m_onSynchronizationStarted; + SynchronizationFinishedHandler m_onSynchronizationFinished; + State m_state; + Index m_index; + std::string m_accessToken; + std::set m_files; + bool m_uploadingStarted = false; + mutable std::mutex m_mutex; +}; diff --git a/map/framework.cpp b/map/framework.cpp index afd4cdfea4..46e0e58614 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -1,6 +1,7 @@ #include "map/framework.hpp" #include "map/benchmark_tools.hpp" #include "map/chart_generator.hpp" +#include "map/cloud.hpp" #include "map/displayed_categories_modifiers.hpp" #include "map/everywhere_search_params.hpp" #include "map/ge0_parser.hpp" @@ -438,6 +439,27 @@ Framework::Framework(FrameworkParams const & params) m_routingManager.SetBookmarkManager(m_bmManager.get()); m_searchMarks.SetBookmarkManager(m_bmManager.get()); + //TODO: move into refactored BookmarkManager + m_bookmarkCloud = make_unique(Cloud::CloudParams("bmc.json", "bookmarks", "BookmarkCloudParam", ".kmz")); + m_bookmarkCloud->SetInvalidTokenHandler([this] { m_user.ResetAccessToken(); }); + m_bookmarkCloud->SetSynchronizationHandlers([]() + { + alohalytics::Stats::Instance().LogEvent("Bookmarks_sync_started"); + }, [](Cloud::SynchronizationResult result, std::string const & errorStr) + { + if (result == Cloud::SynchronizationResult::Success) + { + alohalytics::Stats::Instance().LogEvent("Bookmarks_sync_success"); + } + else + { + std::string const typeStr = (result == Cloud::SynchronizationResult::DiskError) ? "disk" : "network"; + alohalytics::TStringMap details {{"type", typeStr}, {"error", errorStr}}; + alohalytics::Stats::Instance().LogEvent("Bookmarks_sync_error", details); + } + }); + m_user.AddSubscriber(m_bookmarkCloud->GetUserSubscriber()); + InitCityFinder(); InitDiscoveryManager(); InitTaxiEngine(); @@ -501,6 +523,11 @@ Framework::~Framework() m_trafficManager.Teardown(); DestroyDrapeEngine(); m_model.SetOnMapDeregisteredCallback(nullptr); + + //TODO: move into refactored BookmarkManager + m_bookmarkCloud->SetInvalidTokenHandler(nullptr); + + m_user.ClearSubscribers(); } booking::Api * Framework::GetBookingApi(platform::NetworkPolicy const & policy) diff --git a/map/framework.hpp b/map/framework.hpp index 785ea2f7e6..7ef06f128f 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -107,6 +107,8 @@ namespace ads class Engine; } +class Cloud; + /// Uncomment line to make fixed position settings and /// build version for screenshots. //#define FIXED_LOCATION @@ -188,6 +190,9 @@ protected: location::TMyPositionModeChanged m_myPositionListener; unique_ptr m_bmManager; + //TODO: move into refactored BookmarkManager + unique_ptr m_bookmarkCloud; + SearchMarks m_searchMarks; unique_ptr m_bookingApi = make_unique(); diff --git a/map/map_tests/CMakeLists.txt b/map/map_tests/CMakeLists.txt index 49ba5101fc..6c66b0322b 100644 --- a/map/map_tests/CMakeLists.txt +++ b/map/map_tests/CMakeLists.txt @@ -9,6 +9,7 @@ set( booking_filter_test.cpp bookmarks_test.cpp chart_generator_tests.cpp + cloud_tests.cpp feature_getters_tests.cpp ge0_parser_tests.cpp geourl_test.cpp diff --git a/map/map_tests/cloud_tests.cpp b/map/map_tests/cloud_tests.cpp new file mode 100644 index 0000000000..a21f4603de --- /dev/null +++ b/map/map_tests/cloud_tests.cpp @@ -0,0 +1,59 @@ +#include "testing/testing.hpp" + +#include "map/cloud.hpp" + +#include "coding/serdes_json.hpp" +#include "coding/writer.hpp" + +#include +#include +#include +#include + +using namespace std::chrono; + +namespace +{ +bool AreEqualEntries(std::vector const & entries1, + std::vector const & entries2) +{ + if (entries1.size() != entries2.size()) + return false; + + for (size_t i = 0; i < entries1.size(); ++i) + { + if (*entries1[i] != *entries2[i]) + return false; + } + + return true; +} +} // namespace + +UNIT_TEST(Cloud_SerDes) +{ + Cloud::Index index; + index.m_entries.emplace_back(std::make_shared("bm1.kml", 100, false)); + index.m_entries.emplace_back(std::make_shared("bm2.kml", 50, true)); + auto const h = duration_cast(system_clock::now().time_since_epoch()).count(); + index.m_lastUpdateInHours = static_cast(h); + index.m_isOutdated = true; + + std::string data; + { + using Sink = MemWriter; + Sink sink(data); + coding::SerializerJson ser(sink); + ser(index); + } + + Cloud::Index indexDes; + { + coding::DeserializerJson des(data); + des(indexDes); + } + + TEST_EQUAL(index.m_isOutdated, indexDes.m_isOutdated, ()); + TEST_EQUAL(index.m_lastUpdateInHours, indexDes.m_lastUpdateInHours, ()); + TEST(AreEqualEntries(index.m_entries, indexDes.m_entries), ()); +} diff --git a/map/user.cpp b/map/user.cpp index 8df0fc2809..b9b52c761d 100644 --- a/map/user.cpp +++ b/map/user.cpp @@ -6,6 +6,7 @@ #include "coding/url_encode.hpp" #include "base/logging.hpp" +#include "base/stl_helpers.hpp" #include "base/string_utils.hpp" #include "3party/Alohalytics/src/alohalytics.h" @@ -135,6 +136,8 @@ void User::Init() if (GetPlatform().GetSecureStorage().Load(kMapsMeTokenKey, token)) m_accessToken = token; + NotifySubscribersImpl(); + std::string reviewIds; if (GetPlatform().GetSecureStorage().Load(kReviewIdsKey, reviewIds)) { @@ -153,6 +156,7 @@ void User::ResetAccessToken() std::lock_guard lock(m_mutex); m_accessToken.clear(); GetPlatform().GetSecureStorage().Remove(kMapsMeTokenKey); + NotifySubscribersImpl(); } void User::UpdateUserDetails() @@ -188,6 +192,7 @@ void User::SetAccessToken(std::string const & accessToken) m_accessToken = accessToken; GetPlatform().GetSecureStorage().Save(kMapsMeTokenKey, m_accessToken); RequestUserDetails(); + NotifySubscribersImpl(); } void User::Authenticate(std::string const & socialToken, SocialTokenType socialTokenType) @@ -199,25 +204,82 @@ void User::Authenticate(std::string const & socialToken, SocialTokenType socialT return; } - { - std::lock_guard lock(m_mutex); - if (m_authenticationInProgress) - return; - m_authenticationInProgress = true; - } + if (!StartAuthentication()) + return; GetPlatform().RunTask(Platform::Thread::Network, [this, url]() { Request(url, nullptr, [this](std::string const & response) { SetAccessToken(ParseAccessToken(response)); - - std::lock_guard lock(m_mutex); - m_authenticationInProgress = false; + FinishAuthentication(!m_accessToken.empty()); + }, [this](int code) + { + FinishAuthentication(false /* success */); }); }); } +bool User::StartAuthentication() +{ + std::lock_guard lock(m_mutex); + if (m_authenticationInProgress) + return false; + m_authenticationInProgress = true; + return true; +} + +void User::FinishAuthentication(bool success) +{ + std::lock_guard lock(m_mutex); + m_authenticationInProgress = false; + + for (auto & s : m_subscribers) + { + if (s->m_onAuthenticate != nullptr) + { + s->m_onAuthenticate(success); + if (s->m_postCallAction == Subscriber::Action::RemoveSubscriber) + s.reset(); + } + } + ClearSubscribersImpl(); +} + +void User::AddSubscriber(std::unique_ptr && subscriber) +{ + std::lock_guard lock(m_mutex); + + subscriber->m_onChangeToken(m_accessToken); + if (subscriber->m_postCallAction == Subscriber::Action::RemoveSubscriber) + m_subscribers.push_back(std::move(subscriber)); +} + +void User::ClearSubscribers() +{ + std::lock_guard lock(m_mutex); + m_subscribers.clear(); +} + +void User::NotifySubscribersImpl() +{ + for (auto & s : m_subscribers) + { + if (s->m_onChangeToken != nullptr) + { + s->m_onChangeToken(m_accessToken); + if (s->m_postCallAction == Subscriber::Action::RemoveSubscriber) + s.reset(); + } + } + ClearSubscribersImpl(); +} + +void User::ClearSubscribersImpl() +{ + my::EraseIf(m_subscribers, [](auto const & s) { return s == nullptr; }); +} + void User::RequestUserDetails() { std::string const url = UserDetailsUrl(); diff --git a/map/user.hpp b/map/user.hpp index 8d6304df96..04639142f6 100644 --- a/map/user.hpp +++ b/map/user.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -20,12 +21,28 @@ public: // m_reviewIds must be sorted. std::vector m_reviewIds; }; + enum SocialTokenType { Facebook, Google }; + struct Subscriber + { + enum class Action + { + DoNothing, + RemoveSubscriber + }; + using AuthenticateHandler = std::function; + using ChangeTokenHandler = std::function; + + Action m_postCallAction = Action::DoNothing; + AuthenticateHandler m_onAuthenticate; + ChangeTokenHandler m_onChangeToken; + }; + using BuildRequestHandler = std::function; using SuccessHandler = std::function; using ErrorHandler = std::function; @@ -37,6 +54,9 @@ public: void ResetAccessToken(); void UpdateUserDetails(); + void AddSubscriber(std::unique_ptr && subscriber); + void ClearSubscribers(); + std::string GetAccessToken() const; Details GetDetails() const; @@ -54,8 +74,15 @@ private: SuccessHandler const & onSuccess, ErrorHandler const & onError, uint8_t attemptIndex, uint32_t waitingTimeInSeconds); + void NotifySubscribersImpl(); + void ClearSubscribersImpl(); + + bool StartAuthentication(); + void FinishAuthentication(bool success); + std::string m_accessToken; mutable std::mutex m_mutex; bool m_authenticationInProgress = false; Details m_details; + std::vector> m_subscribers; }; diff --git a/xcode/base/base.xcodeproj/project.pbxproj b/xcode/base/base.xcodeproj/project.pbxproj index 89260c7c8e..3b98cba339 100644 --- a/xcode/base/base.xcodeproj/project.pbxproj +++ b/xcode/base/base.xcodeproj/project.pbxproj @@ -350,35 +350,21 @@ 675341791A3F57BF00A0A8C3 /* base */ = { isa = PBXGroup; children = ( - 39BC0FCF1FD057F900B6C276 /* control_flow.hpp */, - 56DE23031FCD8AB4008FEFD5 /* osm_id.cpp */, - 56DE23021FCD8AB3008FEFD5 /* osm_id.hpp */, - 3D3731FC1F9A445400D2121B /* url_helpers.cpp */, - 3D3731FD1F9A445500D2121B /* url_helpers.hpp */, - 3D74EF0E1F8B902B0081202C /* bwt.cpp */, - 3D74EF0F1F8B902C0081202C /* bwt.hpp */, - 3D74EF0D1F8B902B0081202C /* move_to_front.cpp */, - 3D74EF0A1F8B902A0081202C /* move_to_front.hpp */, - 3D74EF0B1F8B902B0081202C /* suffix_array.cpp */, - 3D74EF0C1F8B902B0081202C /* visitor.hpp */, - 3D78157A1F3D89EC0068B6AC /* waiter.hpp */, - 3D7815711F3A145F0068B6AC /* task_loop.hpp */, - F6F8E3C61EF846CE00F2DE8F /* worker_thread.cpp */, - F6F8E3C71EF846CE00F2DE8F /* worker_thread.hpp */, - 56B1A0711E69DE4D00395022 /* random.cpp */, - 56B1A0721E69DE4D00395022 /* random.hpp */, - 56B1A0731E69DE4D00395022 /* small_set.hpp */, 675341851A3F57E400A0A8C3 /* array_adapters.hpp */, 675341861A3F57E400A0A8C3 /* assert.hpp */, 675341871A3F57E400A0A8C3 /* base.cpp */, 675341881A3F57E400A0A8C3 /* base.hpp */, 675341891A3F57E400A0A8C3 /* bits.hpp */, 6753418A1A3F57E400A0A8C3 /* buffer_vector.hpp */, + 3D74EF0E1F8B902B0081202C /* bwt.cpp */, + 3D74EF0F1F8B902C0081202C /* bwt.hpp */, 6753418B1A3F57E400A0A8C3 /* cache.hpp */, 672DD4B01E04255F0078E13C /* cancellable.hpp */, + 67C79B9E1E2929DB00C40034 /* checked_cast.hpp */, 672DD4B11E04255F0078E13C /* collection_cast.hpp */, 672DD4B31E04255F0078E13C /* condition.cpp */, 672DD4B41E04255F0078E13C /* condition.hpp */, + 39BC0FCF1FD057F900B6C276 /* control_flow.hpp */, 67A609AC1C88642E001E641A /* deferred_task.cpp */, 67A609AD1C88642E001E641A /* deferred_task.hpp */, 3446C66C1DDCA96300146687 /* dfa_helpers.hpp */, @@ -397,10 +383,16 @@ 6753419E1A3F57E400A0A8C3 /* math.hpp */, 6753419F1A3F57E400A0A8C3 /* matrix.hpp */, 672DD4B51E04255F0078E13C /* mem_trie.hpp */, + 3D74EF0D1F8B902B0081202C /* move_to_front.cpp */, + 3D74EF0A1F8B902A0081202C /* move_to_front.hpp */, 675341A11A3F57E400A0A8C3 /* mutex.hpp */, 672DD4B61E04255F0078E13C /* newtype.hpp */, 675341A21A3F57E400A0A8C3 /* normalize_unicode.cpp */, 672DD4B71E04255F0078E13C /* observer_list.hpp */, + 56DE23031FCD8AB4008FEFD5 /* osm_id.cpp */, + 56DE23021FCD8AB3008FEFD5 /* osm_id.hpp */, + 56B1A0711E69DE4D00395022 /* random.cpp */, + 56B1A0721E69DE4D00395022 /* random.hpp */, 672DD4B81E0425600078E13C /* range_iterator.hpp */, 672DD4B91E0425600078E13C /* ref_counted.hpp */, 675341AA1A3F57E400A0A8C3 /* rolling_hash.hpp */, @@ -408,6 +400,7 @@ 675341B01A3F57E400A0A8C3 /* set_operations.hpp */, 675341B11A3F57E400A0A8C3 /* shared_buffer_manager.cpp */, 675341B21A3F57E400A0A8C3 /* shared_buffer_manager.hpp */, + 56B1A0731E69DE4D00395022 /* small_set.hpp */, 675341B41A3F57E400A0A8C3 /* src_point.cpp */, 675341B51A3F57E400A0A8C3 /* src_point.hpp */, 675341B61A3F57E400A0A8C3 /* stats.hpp */, @@ -421,8 +414,10 @@ 675341BD1A3F57E400A0A8C3 /* string_utils.hpp */, 675341BE1A3F57E400A0A8C3 /* strings_bundle.cpp */, 675341BF1A3F57E400A0A8C3 /* strings_bundle.hpp */, + 3D74EF0B1F8B902B0081202C /* suffix_array.cpp */, 670E39421C46C76900E9C0A6 /* sunrise_sunset.cpp */, 670E39431C46C76900E9C0A6 /* sunrise_sunset.hpp */, + 3D7815711F3A145F0068B6AC /* task_loop.hpp */, 67B52B5E1AD3C84E00664C17 /* thread_checker.cpp */, 67B52B5F1AD3C84E00664C17 /* thread_checker.hpp */, 675341C11A3F57E400A0A8C3 /* thread_pool.cpp */, @@ -438,7 +433,12 @@ 675341CA1A3F57E400A0A8C3 /* timer.hpp */, 3446C66F1DDCA96300146687 /* uni_string_dfa.cpp */, 3446C6701DDCA96300146687 /* uni_string_dfa.hpp */, - 67C79B9E1E2929DB00C40034 /* checked_cast.hpp */, + 3D3731FC1F9A445400D2121B /* url_helpers.cpp */, + 3D3731FD1F9A445500D2121B /* url_helpers.hpp */, + 3D74EF0C1F8B902B0081202C /* visitor.hpp */, + 3D78157A1F3D89EC0068B6AC /* waiter.hpp */, + F6F8E3C61EF846CE00F2DE8F /* worker_thread.cpp */, + F6F8E3C71EF846CE00F2DE8F /* worker_thread.hpp */, ); name = base; path = ../../base; diff --git a/xcode/coding/coding.xcodeproj/project.pbxproj b/xcode/coding/coding.xcodeproj/project.pbxproj index e06d4b16f5..ba6f8e8ad6 100644 --- a/xcode/coding/coding.xcodeproj/project.pbxproj +++ b/xcode/coding/coding.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 3D489BC31D3D21AE0052AA38 /* elias_coder_test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3D489BB71D3D217E0052AA38 /* elias_coder_test.cpp */; }; 3D74EF211F8F55740081202C /* csv_reader.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3D74EF1F1F8F55740081202C /* csv_reader.hpp */; }; 3D74EF221F8F55740081202C /* csv_reader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3D74EF201F8F55740081202C /* csv_reader.cpp */; }; + 454523B4202AEB21009275C1 /* serdes_json.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 454523B3202AEB21009275C1 /* serdes_json.hpp */; }; 45C108B41E9CFE69000FE1F6 /* point_to_integer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45C108B21E9CFE69000FE1F6 /* point_to_integer.cpp */; }; 45C108B51E9CFE69000FE1F6 /* point_to_integer.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 45C108B31E9CFE69000FE1F6 /* point_to_integer.hpp */; }; 45C108B81E9CFE7B000FE1F6 /* point_to_integer_test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45C108B61E9CFE78000FE1F6 /* point_to_integer_test.cpp */; }; @@ -183,6 +184,7 @@ 3D489BBA1D3D217E0052AA38 /* succinct_mapper_test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = succinct_mapper_test.cpp; sourceTree = ""; }; 3D74EF1F1F8F55740081202C /* csv_reader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = csv_reader.hpp; sourceTree = ""; }; 3D74EF201F8F55740081202C /* csv_reader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = csv_reader.cpp; sourceTree = ""; }; + 454523B3202AEB21009275C1 /* serdes_json.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = serdes_json.hpp; sourceTree = ""; }; 45C108B21E9CFE69000FE1F6 /* point_to_integer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = point_to_integer.cpp; sourceTree = ""; }; 45C108B31E9CFE69000FE1F6 /* point_to_integer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = point_to_integer.hpp; sourceTree = ""; }; 45C108B61E9CFE78000FE1F6 /* point_to_integer_test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = point_to_integer_test.cpp; sourceTree = ""; }; @@ -416,40 +418,23 @@ 6753421D1A3F586300A0A8C3 /* coding */ = { isa = PBXGroup; children = ( - 39B2B9801FB4694300AB85A1 /* memory_region.hpp */, - 39B2B97E1FB4693B00AB85A1 /* elias_coder.hpp */, - 39B2B97C1FB4693400AB85A1 /* bwt_coder.hpp */, - 39B2B97A1FB4692D00AB85A1 /* text_storage.hpp */, - 3D74EF201F8F55740081202C /* csv_reader.cpp */, - 3D74EF1F1F8F55740081202C /* csv_reader.hpp */, - 45C108B21E9CFE69000FE1F6 /* point_to_integer.cpp */, - 45C108B31E9CFE69000FE1F6 /* point_to_integer.hpp */, - BB537C5D1E8490120074D9D3 /* transliteration.cpp */, - BB537C5E1E8490120074D9D3 /* transliteration.hpp */, - 34A129D11DF99E43001B4531 /* zlib.cpp */, - 34A129D21DF99E43001B4531 /* zlib.hpp */, - 675E889A1DB7B0D000F8EBDA /* traffic.cpp */, - 675E889B1DB7B0D000F8EBDA /* traffic.hpp */, - 347F33311C4540F0009758CC /* compressed_bit_vector.cpp */, - 347F33321C4540F0009758CC /* compressed_bit_vector.hpp */, - 347F33331C4540F0009758CC /* fixed_bits_ddvector.hpp */, - 347F33341C4540F0009758CC /* simple_dense_coding.cpp */, - 347F33351C4540F0009758CC /* simple_dense_coding.hpp */, - 347F33361C4540F0009758CC /* succinct_mapper.hpp */, - 670D04B21B0BA9050013A7AC /* internal */, - 394917221BAC3C2F002A8C4F /* huffman.cpp */, - 394917231BAC3C2F002A8C4F /* huffman.hpp */, 6753422B1A3F588B00A0A8C3 /* base64.cpp */, 6753422C1A3F588B00A0A8C3 /* base64.hpp */, 6753422F1A3F588B00A0A8C3 /* bit_streams.hpp */, 675342341A3F588B00A0A8C3 /* buffer_reader.hpp */, + 39B2B97C1FB4693400AB85A1 /* bwt_coder.hpp */, 675342351A3F588B00A0A8C3 /* byte_stream.hpp */, 675342381A3F588B00A0A8C3 /* coder_util.hpp */, 675342391A3F588B00A0A8C3 /* coder.hpp */, + 347F33311C4540F0009758CC /* compressed_bit_vector.cpp */, + 347F33321C4540F0009758CC /* compressed_bit_vector.hpp */, 6753423E1A3F588B00A0A8C3 /* constants.hpp */, + 3D74EF201F8F55740081202C /* csv_reader.cpp */, + 3D74EF1F1F8F55740081202C /* csv_reader.hpp */, 6753423F1A3F588B00A0A8C3 /* dd_vector.hpp */, 675342401A3F588B00A0A8C3 /* diff_patch_common.hpp */, 675342411A3F588B00A0A8C3 /* diff.hpp */, + 39B2B97E1FB4693B00AB85A1 /* elias_coder.hpp */, 675342421A3F588B00A0A8C3 /* endianness.hpp */, 675342431A3F588B00A0A8C3 /* file_container.cpp */, 675342441A3F588B00A0A8C3 /* file_container.hpp */, @@ -460,14 +445,21 @@ 6753424A1A3F588B00A0A8C3 /* file_sort.hpp */, 6753424C1A3F588B00A0A8C3 /* file_writer.cpp */, 6753424D1A3F588B00A0A8C3 /* file_writer.hpp */, + 347F33331C4540F0009758CC /* fixed_bits_ddvector.hpp */, 675342501A3F588B00A0A8C3 /* hex.cpp */, 675342511A3F588B00A0A8C3 /* hex.hpp */, + 394917221BAC3C2F002A8C4F /* huffman.cpp */, + 394917231BAC3C2F002A8C4F /* huffman.hpp */, + 670D04B21B0BA9050013A7AC /* internal */, 675342571A3F588B00A0A8C3 /* matrix_traversal.hpp */, + 39B2B9801FB4694300AB85A1 /* memory_region.hpp */, 675342581A3F588B00A0A8C3 /* mmap_reader.cpp */, 675342591A3F588B00A0A8C3 /* mmap_reader.hpp */, 6753425A1A3F588B00A0A8C3 /* multilang_utf8_string.cpp */, 6753425B1A3F588B00A0A8C3 /* multilang_utf8_string.hpp */, 6753425C1A3F588B00A0A8C3 /* parse_xml.hpp */, + 45C108B21E9CFE69000FE1F6 /* point_to_integer.cpp */, + 45C108B31E9CFE69000FE1F6 /* point_to_integer.hpp */, 6753425D1A3F588B00A0A8C3 /* polymorph_reader.hpp */, 6753425E1A3F588B00A0A8C3 /* read_write_utils.hpp */, 6753425F1A3F588B00A0A8C3 /* reader_cache.hpp */, @@ -478,9 +470,18 @@ 675342641A3F588B00A0A8C3 /* reader_writer_ops.hpp */, 675342651A3F588B00A0A8C3 /* reader.cpp */, 675342661A3F588B00A0A8C3 /* reader.hpp */, + 454523B3202AEB21009275C1 /* serdes_json.hpp */, + 347F33341C4540F0009758CC /* simple_dense_coding.cpp */, + 347F33351C4540F0009758CC /* simple_dense_coding.hpp */, 675342691A3F588B00A0A8C3 /* streams_common.hpp */, 6753426A1A3F588B00A0A8C3 /* streams_sink.hpp */, 6753426B1A3F588B00A0A8C3 /* streams.hpp */, + 347F33361C4540F0009758CC /* succinct_mapper.hpp */, + 39B2B97A1FB4692D00AB85A1 /* text_storage.hpp */, + 675E889A1DB7B0D000F8EBDA /* traffic.cpp */, + 675E889B1DB7B0D000F8EBDA /* traffic.hpp */, + BB537C5D1E8490120074D9D3 /* transliteration.cpp */, + BB537C5E1E8490120074D9D3 /* transliteration.hpp */, 675342701A3F588B00A0A8C3 /* uri.cpp */, 675342711A3F588B00A0A8C3 /* uri.hpp */, 675342721A3F588B00A0A8C3 /* url_encode.hpp */, @@ -497,6 +498,8 @@ 6753427D1A3F588C00A0A8C3 /* zip_creator.hpp */, 6753427E1A3F588C00A0A8C3 /* zip_reader.cpp */, 6753427F1A3F588C00A0A8C3 /* zip_reader.hpp */, + 34A129D11DF99E43001B4531 /* zlib.cpp */, + 34A129D21DF99E43001B4531 /* zlib.hpp */, ); name = coding; path = ../../coding; @@ -546,6 +549,7 @@ 675342991A3F588C00A0A8C3 /* endianness.hpp in Headers */, 347F333C1C4540F0009758CC /* succinct_mapper.hpp in Headers */, 675342951A3F588C00A0A8C3 /* constants.hpp in Headers */, + 454523B4202AEB21009275C1 /* serdes_json.hpp in Headers */, 675342B71A3F588C00A0A8C3 /* reader_streambuf.hpp in Headers */, 675342CF1A3F588C00A0A8C3 /* write_to_sink.hpp in Headers */, 675342C11A3F588C00A0A8C3 /* streams.hpp in Headers */, diff --git a/xcode/map/map.xcodeproj/project.pbxproj b/xcode/map/map.xcodeproj/project.pbxproj index 7f89b8e12b..0541c05697 100644 --- a/xcode/map/map.xcodeproj/project.pbxproj +++ b/xcode/map/map.xcodeproj/project.pbxproj @@ -37,6 +37,9 @@ 3D4E99A51FB4A6410025B48C /* booking_filter.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3D4E99A11FB4A6410025B48C /* booking_filter.hpp */; }; 3D74ABBE1EA76F1D0063A898 /* local_ads_supported_types.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3D74ABBD1EA76F1D0063A898 /* local_ads_supported_types.cpp */; }; 45201E931CE4AC90008A4842 /* api_mark_point.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45201E921CE4AC90008A4842 /* api_mark_point.cpp */; }; + 454523A9202A0068009275C1 /* cloud.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 454523A7202A0067009275C1 /* cloud.cpp */; }; + 454523AA202A0068009275C1 /* cloud.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 454523A8202A0067009275C1 /* cloud.hpp */; }; + 454523AD202A00C3009275C1 /* cloud_tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 454523AB202A00B9009275C1 /* cloud_tests.cpp */; }; 454649F11F2728CE00EF4064 /* local_ads_mark.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 454649EF1F2728CE00EF4064 /* local_ads_mark.cpp */; }; 454649F21F2728CE00EF4064 /* local_ads_mark.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 454649F01F2728CE00EF4064 /* local_ads_mark.hpp */; }; 45580ABE1E2CBD5E00CD535D /* benchmark_tools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 45580ABC1E2CBD5E00CD535D /* benchmark_tools.cpp */; }; @@ -194,6 +197,9 @@ 3D4E99A11FB4A6410025B48C /* booking_filter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = booking_filter.hpp; sourceTree = ""; }; 3D74ABBD1EA76F1D0063A898 /* local_ads_supported_types.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = local_ads_supported_types.cpp; sourceTree = ""; }; 45201E921CE4AC90008A4842 /* api_mark_point.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = api_mark_point.cpp; sourceTree = ""; }; + 454523A7202A0067009275C1 /* cloud.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = cloud.cpp; sourceTree = ""; }; + 454523A8202A0067009275C1 /* cloud.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = cloud.hpp; sourceTree = ""; }; + 454523AB202A00B9009275C1 /* cloud_tests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = cloud_tests.cpp; sourceTree = ""; }; 454649EF1F2728CE00EF4064 /* local_ads_mark.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = local_ads_mark.cpp; sourceTree = ""; }; 454649F01F2728CE00EF4064 /* local_ads_mark.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = local_ads_mark.hpp; sourceTree = ""; }; 45580ABC1E2CBD5E00CD535D /* benchmark_tools.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = benchmark_tools.cpp; sourceTree = ""; }; @@ -406,19 +412,20 @@ 674A29CA1B26FCC0001A525C /* map_tests */ = { isa = PBXGroup; children = ( - BB421D6A1E8C0026005BFA4D /* transliteration_test.cpp */, 679624A01D1017C200AE4E3C /* address_tests.cpp */, + 674A29CB1B26FCFE001A525C /* bookmarks_test.cpp */, + 454523AB202A00B9009275C1 /* cloud_tests.cpp */, 679624A11D1017C200AE4E3C /* feature_getters_tests.cpp */, + 674A29CC1B26FCFE001A525C /* ge0_parser_tests.cpp */, + 674A29CD1B26FCFE001A525C /* geourl_test.cpp */, 679624A21D1017C200AE4E3C /* gps_track_collection_test.cpp */, 679624A31D1017C200AE4E3C /* gps_track_storage_test.cpp */, 679624A41D1017C200AE4E3C /* gps_track_test.cpp */, - 679624A51D1017C200AE4E3C /* mwm_set_test.cpp */, - 674A29EE1B26FD5F001A525C /* testingmain.cpp */, - 674A29CB1B26FCFE001A525C /* bookmarks_test.cpp */, - 674A29CC1B26FCFE001A525C /* ge0_parser_tests.cpp */, - 674A29CD1B26FCFE001A525C /* geourl_test.cpp */, 674A29CE1B26FCFE001A525C /* kmz_unarchive_test.cpp */, + 679624A51D1017C200AE4E3C /* mwm_set_test.cpp */, 674A29CF1B26FCFE001A525C /* mwm_url_tests.cpp */, + 674A29EE1B26FD5F001A525C /* testingmain.cpp */, + BB421D6A1E8C0026005BFA4D /* transliteration_test.cpp */, 674A2A351B27011A001A525C /* working_time_tests.cpp */, ); name = map_tests; @@ -501,25 +508,26 @@ 675345BD1A4054AD00A0A8C3 /* map */ = { isa = PBXGroup; children = ( - 0831F23B200E53600034C365 /* bookmarks_search_params.hpp */, - BB4E5F201FCC663700A77250 /* transit */, - F6FC3CB11FC323420001D929 /* discovery */, - 3D4E999F1FB4A6400025B48C /* booking_filter_cache.cpp */, - 3D4E999E1FB4A6400025B48C /* booking_filter_cache.hpp */, - 3D4E99A01FB4A6410025B48C /* booking_filter.cpp */, - 3D4E99A11FB4A6410025B48C /* booking_filter.hpp */, 675345CB1A4054E800A0A8C3 /* address_finder.cpp */, 45201E921CE4AC90008A4842 /* api_mark_point.cpp */, 34921F611BFA0A6900737D6E /* api_mark_point.hpp */, 45580ABC1E2CBD5E00CD535D /* benchmark_tools.cpp */, 45580ABD1E2CBD5E00CD535D /* benchmark_tools.hpp */, 3D4E99841FB469DD0025B48C /* booking_filter_availability_params.hpp */, + 3D4E999F1FB4A6400025B48C /* booking_filter_cache.cpp */, + 3D4E999E1FB4A6400025B48C /* booking_filter_cache.hpp */, + 3D4E99A01FB4A6410025B48C /* booking_filter.cpp */, + 3D4E99A11FB4A6410025B48C /* booking_filter.hpp */, 675345D91A4054E800A0A8C3 /* bookmark_manager.cpp */, 675345DA1A4054E800A0A8C3 /* bookmark_manager.hpp */, 675345DB1A4054E800A0A8C3 /* bookmark.cpp */, 675345DC1A4054E800A0A8C3 /* bookmark.hpp */, + 0831F23B200E53600034C365 /* bookmarks_search_params.hpp */, 348AB57A1D7EE0C6009F8301 /* chart_generator.cpp */, 348AB57B1D7EE0C6009F8301 /* chart_generator.hpp */, + 454523A7202A0067009275C1 /* cloud.cpp */, + 454523A8202A0067009275C1 /* cloud.hpp */, + F6FC3CB11FC323420001D929 /* discovery */, 342D83381D5233E8000D8AEA /* displacement_mode_manager.cpp */, 342D83391D5233E8000D8AEA /* displacement_mode_manager.hpp */, 3D47B2C51F20EF06000828D2 /* displayed_categories_modifiers.cpp */, @@ -570,6 +578,7 @@ 6753462D1A4054E800A0A8C3 /* track.hpp */, 347B60741DD9926D0050FA24 /* traffic_manager.cpp */, 347B60751DD9926D0050FA24 /* traffic_manager.hpp */, + BB4E5F201FCC663700A77250 /* transit */, 6753462E1A4054E800A0A8C3 /* user_mark_container.cpp */, 6753462F1A4054E800A0A8C3 /* user_mark_container.hpp */, 674C385F1BFF3095000D603B /* user_mark.cpp */, @@ -629,6 +638,7 @@ 6753464B1A4054E800A0A8C3 /* bookmark.hpp in Headers */, 3D47B2941F054BC5000828D2 /* taxi_delegate.hpp in Headers */, 3D47B2C81F20EF06000828D2 /* displayed_categories_modifiers.hpp in Headers */, + 454523AA202A0068009275C1 /* cloud.hpp in Headers */, BB4E5F271FCC664A00A77250 /* transit_reader.hpp in Headers */, 348AB57D1D7EE0C6009F8301 /* chart_generator.hpp in Headers */, 45A2D9D61F7556EB003310A0 /* user.hpp in Headers */, @@ -740,6 +750,7 @@ 67F183771BD5045700AB1840 /* ge0_parser_tests.cpp in Sources */, 679624B21D1017DB00AE4E3C /* mwm_set_test.cpp in Sources */, 67F183781BD5045700AB1840 /* geourl_test.cpp in Sources */, + 454523AD202A00C3009275C1 /* cloud_tests.cpp in Sources */, 679624AD1D1017DB00AE4E3C /* address_tests.cpp in Sources */, 67F183791BD5045700AB1840 /* kmz_unarchive_test.cpp in Sources */, 679624AE1D1017DB00AE4E3C /* feature_getters_tests.cpp in Sources */, @@ -771,6 +782,7 @@ 3D4E99A31FB4A6410025B48C /* booking_filter_cache.cpp in Sources */, 6753469B1A4054E800A0A8C3 /* track.cpp in Sources */, 675346621A4054E800A0A8C3 /* feature_vec_model.cpp in Sources */, + 454523A9202A0068009275C1 /* cloud.cpp in Sources */, 6753469D1A4054E800A0A8C3 /* user_mark_container.cpp in Sources */, 674C38621BFF3095000D603B /* user_mark.cpp in Sources */, 675346641A4054E800A0A8C3 /* framework.cpp in Sources */,