diff options
author | Yuri Gorshenin <y@maps.me> | 2015-06-16 18:15:09 +0300 |
---|---|---|
committer | Alex Zolotarev <alex@maps.me> | 2015-09-23 02:53:25 +0300 |
commit | 598e71858b63ef4e587efd932db41474f8f516a0 (patch) | |
tree | 19e39c1dd331579432c9b58e5e1e82c750842ac3 | |
parent | 9148053b3aa67aac443ade98e2ed2ae899419095 (diff) |
Storage redesign.
66 files changed, 2489 insertions, 1908 deletions
diff --git a/android/jni/com/mapswithme/maps/DownloadResourcesActivity.cpp b/android/jni/com/mapswithme/maps/DownloadResourcesActivity.cpp index 1b6b3c9067..bd6993716b 100644 --- a/android/jni/com/mapswithme/maps/DownloadResourcesActivity.cpp +++ b/android/jni/com/mapswithme/maps/DownloadResourcesActivity.cpp @@ -3,9 +3,10 @@ #include "defines.hpp" -#include "coding/url_encode.hpp" -#include "coding/reader_streambuf.hpp" +#include "coding/file_name_utils.hpp" #include "coding/internal/file_data.hpp" +#include "coding/reader_streambuf.hpp" +#include "coding/url_encode.hpp" #include "platform/platform.hpp" #include "platform/http_request.hpp" @@ -212,7 +213,11 @@ extern "C" storage::Storage const & storage = g_framework->Storage(); for (size_t i = 0; i < curFile.m_urls.size(); ++i) { - curFile.m_urls[i] = storage.GetFileDownloadUrl(curFile.m_urls[i], curFile.m_fileName); + string baseName = curFile.m_fileName; + my::GetNameWithoutExt(baseName); + storage::TIndex const index = storage.FindIndexByFile(baseName); + + curFile.m_urls[i] = storage.GetFileDownloadUrl(curFile.m_urls[i], index, TMapOptions::EMap); LOG(LDEBUG, (curFile.m_urls[i])); } diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index 957e150873..3187676797 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -23,8 +23,11 @@ #include "geometry/angles.hpp" -#include "platform/measurement_utils.hpp" +#include "platform/country_file.hpp" +#include "platform/local_country_file.hpp" +#include "platform/local_country_file_utils.hpp" #include "platform/location.hpp" +#include "platform/measurement_utils.hpp" #include "platform/platform.hpp" #include "platform/preferred_languages.hpp" @@ -45,6 +48,8 @@ const double DOUBLE_TOUCH_S = SHORT_TOUCH_MS / 1000.0; android::Framework * g_framework = 0; using namespace storage; +using platform::CountryFile; +using platform::LocalCountryFile; namespace { @@ -557,28 +562,26 @@ namespace android ::Platform const & pl = GetPlatform(); - vector<string> v; - m_work.GetMaps(v); + vector<LocalCountryFile> localFiles; + platform::FindAllLocalMaps(localFiles); - for (size_t i = 0; i < v.size(); ++i) + for (LocalCountryFile const & localFile : localFiles) { + CountryFile const countryFile = localFile.GetCountryFile(); // skip World and WorldCoast - if (v[i].find(WORLD_FILE_NAME) == string::npos && - v[i].find(WORLD_COASTS_FILE_NAME) == string::npos) + if (countryFile.GetNameWithoutExt() != WORLD_FILE_NAME && + countryFile.GetNameWithoutExt() != WORLD_COASTS_FILE_NAME) { try { - FilesContainerR cont(pl.GetReader(v[i])); + FilesContainerR cont(pl.GetCountryReader(localFile, TMapOptions::EMap)); if (!cont.IsExist(SEARCH_INDEX_FILE_TAG)) - { - my::GetNameWithoutExt(v[i]); - out.push_back(v[i]); - } + out.push_back(countryFile.GetNameWithoutExt()); } catch (RootException const & ex) { // sdcard can contain dummy _*.mwm files. Supress this errors. - LOG(LWARNING, ("Bad mwm file:", v[i], "Error:", ex.Msg())); + LOG(LWARNING, ("Bad mwm file:", countryFile.GetNameWithoutExt(), "Error:", ex.Msg())); } } } diff --git a/drape_head/drape_surface.cpp b/drape_head/drape_surface.cpp index 76df4004da..c1c431ebfa 100644 --- a/drape_head/drape_surface.cpp +++ b/drape_head/drape_surface.cpp @@ -28,13 +28,13 @@ DrapeSurface::DrapeSurface() ///{ Temporary initialization m_model.InitClassificator(); - Platform::FilesList maps; - Platform & pl = GetPlatform(); - pl.GetFilesByExt(pl.WritableDir(), DATA_FILE_EXTENSION, maps); + // Platform::FilesList maps; + // Platform & pl = GetPlatform(); + // pl.GetFilesByExt(pl.WritableDir(), DATA_FILE_EXTENSION, maps); - for_each(maps.begin(), maps.end(), bind(&model::FeaturesFetcher::RegisterMap, &m_model, _1)); - ///} - /// + // for_each(maps.begin(), maps.end(), bind(&model::FeaturesFetcher::RegisterMap, &m_model, _1)); + // ///} + // /// } DrapeSurface::~DrapeSurface() diff --git a/generator/generator_tests/check_mwms.cpp b/generator/generator_tests/check_mwms.cpp index b4771110ba..1ce869d62c 100644 --- a/generator/generator_tests/check_mwms.cpp +++ b/generator/generator_tests/check_mwms.cpp @@ -5,6 +5,8 @@ #include "indexer/data_header.hpp" #include "indexer/interval_index.hpp" +#include "platform/local_country_file.hpp" +#include "platform/local_country_file_utils.hpp" #include "platform/platform.hpp" #include "base/logging.hpp" @@ -12,25 +14,25 @@ UNIT_TEST(CheckMWM_LoadAll) { - Platform & pl = GetPlatform(); - - Platform::FilesList maps; - pl.GetFilesByExt(pl.WritableDir(), DATA_FILE_EXTENSION, maps); + Platform & platform = GetPlatform(); + vector<platform::LocalCountryFile> localFiles; + platform::FindAllLocalMapsInDirectory(platform.WritableDir(), 0 /* version */, localFiles); model::FeaturesFetcher m; m.InitClassificator(); - for (string const & s : maps) + for (platform::LocalCountryFile const & localFile : localFiles) { + LOG(LINFO, ("Found mwm:", localFile)); try { - pair<MwmSet::MwmLock, bool> const p = m.RegisterMap(s); + pair<MwmSet::MwmLock, bool> const p = m.RegisterMap(localFile); TEST(p.first.IsLocked(), ()); TEST(p.second, ()); } catch (RootException const & ex) { - TEST(false, ("Bad mwm file:", s)); + TEST(false, ("Bad mwm file:", localFile)); } } } diff --git a/generator/routing_generator.cpp b/generator/routing_generator.cpp index 5827019b9b..169e09f2ad 100644 --- a/generator/routing_generator.cpp +++ b/generator/routing_generator.cpp @@ -30,6 +30,9 @@ #include "3party/osrm/osrm-backend/data_structures/query_edge.hpp" #include "3party/osrm/osrm-backend/data_structures/internal_route_result.hpp" +using platform::CountryFile; +using platform::LocalCountryFile; + namespace routing { @@ -223,9 +226,10 @@ void BuildRoutingIndex(string const & baseDir, string const & countryName, strin { classificator::Load(); - string const mwmFile = baseDir + countryName + DATA_FILE_EXTENSION; + CountryFile countryFile(countryName); + LocalCountryFile localFile(baseDir, countryFile, 0 /* version */); Index index; - pair<MwmSet::MwmLock, bool> const p = index.Register(mwmFile); + pair<MwmSet::MwmLock, bool> const p = index.Register(localFile); if (!p.second) { LOG(LCRITICAL, ("MWM file not found")); @@ -235,7 +239,7 @@ void BuildRoutingIndex(string const & baseDir, string const & countryName, strin osrm::NodeDataVectorT nodeData; gen::OsmID2FeatureID osm2ft; - if (!LoadIndexes(mwmFile, osrmFile, nodeData, osm2ft)) + if (!LoadIndexes(localFile.GetPath(TMapOptions::EMap), osrmFile, nodeData, osm2ft)) return; OsrmFtSegMappingBuilder mapping; @@ -366,13 +370,13 @@ void BuildRoutingIndex(string const & baseDir, string const & countryName, strin "Multiple:", multiple, "Equal:", equal)); LOG(LINFO, ("Collect all data into one file...")); - string const fPath = mwmFile + ROUTING_FILE_EXTENSION; + string const fPath = localFile.GetPath(TMapOptions::ECarRouting); FilesContainerW routingCont(fPath /*, FileWriter::OP_APPEND*/); { // Write version for routing file that is equal to correspondent mwm file. - FilesContainerR mwmCont(mwmFile); + FilesContainerR mwmCont(localFile.GetPath(TMapOptions::EMap)); FileWriter w = routingCont.GetWriter(VERSION_FILE_TAG); ReaderSource<ModelReaderPtr> src(mwmCont.GetReader(VERSION_FILE_TAG)); diff --git a/generator/update_generator.cpp b/generator/update_generator.cpp index 9d8cda8d45..2653e281ad 100644 --- a/generator/update_generator.cpp +++ b/generator/update_generator.cpp @@ -43,10 +43,10 @@ namespace update string m_dataDir; Platform::FilesList & m_files; - uint64_t GetFileSize(CountryFile const & cnt, TMapOptions opt) const + uint64_t GetFileSize(platform::CountryFile const & cnt, TMapOptions opt) const { uint64_t sz = 0; - string const fName = cnt.GetFileWithExt(opt); + string const fName = cnt.GetNameWithExt(opt); if (!GetPlatform().GetFileSizeByFullPath(m_dataDir + fName, sz)) { LOG(opt == TMapOptions::EMap ? LCRITICAL : LWARNING, ("File was not found:", fName)); @@ -74,14 +74,14 @@ namespace update { for (size_t i = 0; i < c.Value().m_files.size(); ++i) { - CountryFile & cnt = c.Value().m_files[i]; + platform::CountryFile & cnt = c.Value().m_files[i]; ++m_processedFiles; - cnt.AssignSizes(GetFileSize(cnt, TMapOptions::EMap), - GetFileSize(cnt, TMapOptions::ECarRouting)); + cnt.SetRemoteSizes(GetFileSize(cnt, TMapOptions::EMap), + GetFileSize(cnt, TMapOptions::ECarRouting)); - string const fName = cnt.GetFileWithoutExt() + DATA_FILE_EXTENSION; + string const fName = cnt.GetNameWithExt(TMapOptions::EMap); auto found = find(m_files.begin(), m_files.end(), fName); if (found != m_files.end()) m_files.erase(found); diff --git a/indexer/index.cpp b/indexer/index.cpp index e27814dc74..12d0a02fe7 100644 --- a/indexer/index.cpp +++ b/indexer/index.cpp @@ -6,32 +6,27 @@ #include "coding/file_name_utils.hpp" #include "coding/internal/file_data.hpp" +using platform::CountryFile; +using platform::LocalCountryFile; ////////////////////////////////////////////////////////////////////////////////// // MwmValue implementation ////////////////////////////////////////////////////////////////////////////////// -MwmValue::MwmValue(string const & name) - : m_cont(GetPlatform().GetReader(name)) +MwmValue::MwmValue(LocalCountryFile const & localFile) + : m_cont(GetPlatform().GetCountryReader(localFile, TMapOptions::EMap)), + m_countryFile(localFile.GetCountryFile()) { m_factory.Load(m_cont); } -string MwmValue::GetFileName() const -{ - string s = m_cont.GetFileName(); - my::GetNameFromFullPath(s); - my::GetNameWithoutExt(s); - return s; -} - ////////////////////////////////////////////////////////////////////////////////// // Index implementation ////////////////////////////////////////////////////////////////////////////////// -bool Index::GetVersion(string const & name, MwmInfo & info) const +bool Index::GetVersion(LocalCountryFile const & localFile, MwmInfo & info) const { - MwmValue value(name); + MwmValue value(localFile); feature::DataHeader const & h = value.GetHeader(); if (!h.IsMWMSuitable()) @@ -47,9 +42,9 @@ bool Index::GetVersion(string const & name, MwmInfo & info) const return true; } -MwmSet::TMwmValueBasePtr Index::CreateValue(string const & name) const +MwmSet::TMwmValueBasePtr Index::CreateValue(LocalCountryFile const & localFile) const { - TMwmValueBasePtr p(new MwmValue(name)); + TMwmValueBasePtr p(new MwmValue(localFile)); ASSERT(static_cast<MwmValue &>(*p.get()).GetHeader().IsMWMSuitable(), ()); return p; } @@ -63,124 +58,23 @@ Index::~Index() Cleanup(); } -namespace +pair<MwmSet::MwmLock, bool> Index::RegisterMap(LocalCountryFile const & localFile) { - // Deletes map file denoted by @path and all temporary files related - // to it. - void DeleteMapFiles(string const & path, bool deleteReady) - { - (void)my::DeleteFileX(path); - (void)my::DeleteFileX(path + RESUME_FILE_EXTENSION); - (void)my::DeleteFileX(path + DOWNLOADING_FILE_EXTENSION); - - if (deleteReady) - (void)my::DeleteFileX(path + READY_FILE_EXTENSION); - } - - string GetFullPath(string const & fileName) - { - return GetPlatform().WritablePathForFile(fileName); - } - - // Deletes all files related to @fileName and renames - // @fileName.READY_FILE_EXTENSION to @fileName. - void ReplaceFileWithReady(string const & fileName) - { - string const path = GetFullPath(fileName); - DeleteMapFiles(path, false /* deleteReady */); - CHECK(my::RenameFileX(path + READY_FILE_EXTENSION, path), (path)); - } -} - -pair<MwmSet::MwmLock, bool> Index::RegisterMap(string const & fileName) -{ - if (GetPlatform().IsFileExistsByFullPath(GetFullPath(fileName + READY_FILE_EXTENSION))) - { - pair<MwmSet::MwmLock, UpdateStatus> updateResult = UpdateMap(fileName); - switch (updateResult.second) - { - case UPDATE_STATUS_OK: - return make_pair(move(updateResult.first), true); - case UPDATE_STATUS_BAD_FILE: - return make_pair(move(updateResult.first), false); - case UPDATE_STATUS_UPDATE_DELAYED: - // Not dangerous, but it's strange when adding existing maps. - ASSERT(false, ()); - return make_pair(move(updateResult.first), true); - } - } - - pair<MwmSet::MwmLock, bool> result = Register(fileName); - if (result.second) - m_observers.ForEach(&Observer::OnMapRegistered, fileName); + pair<MwmSet::MwmLock, bool> result = Register(localFile); + if (result.first.IsLocked() && result.second) + m_observers.ForEach(&Observer::OnMapRegistered, localFile); return result; } -bool Index::DeleteMap(string const & fileName) -{ - { - lock_guard<mutex> lock(m_lock); - - if (!DeregisterImpl(fileName)) - return false; - - DeleteMapFiles(GetFullPath(fileName), true /* deleteReady */); - } - m_observers.ForEach(&Observer::OnMapDeleted, fileName); - return true; -} +bool Index::DeregisterMap(CountryFile const & countryFile) { return Deregister(countryFile); } bool Index::AddObserver(Observer & observer) { return m_observers.Add(observer); } bool Index::RemoveObserver(Observer const & observer) { return m_observers.Remove(observer); } -pair<MwmSet::MwmLock, Index::UpdateStatus> Index::UpdateMap(string const & fileName) -{ - pair<MwmSet::MwmLock, UpdateStatus> result; - result.second = UPDATE_STATUS_BAD_FILE; - - { - lock_guard<mutex> lock(m_lock); - - MwmId const id = GetMwmIdByFileNameImpl(fileName); - shared_ptr<MwmInfo> info = id.GetInfo(); - if (id.IsAlive() && info->m_lockCount > 0) - { - info->SetStatus(MwmInfo::STATUS_PENDING_UPDATE); - result.first = GetLock(id); - result.second = UPDATE_STATUS_UPDATE_DELAYED; - } - else - { - ReplaceFileWithReady(fileName); - pair<MwmSet::MwmLock, bool> registerResult = RegisterImpl(fileName); - if (registerResult.second) - { - result.first = move(registerResult.first); - result.second = UPDATE_STATUS_OK; - } - } - } - if (result.second != UPDATE_STATUS_BAD_FILE) - m_observers.ForEach(&Observer::OnMapUpdateIsReady, fileName); - if (result.second == UPDATE_STATUS_OK) - m_observers.ForEach(&Observer::OnMapUpdated, fileName); - return result; -} - -void Index::OnMwmDeleted(shared_ptr<MwmInfo> const & info) -{ - string const & fileName = info->m_fileName; - DeleteMapFiles(fileName, true /* deleteReady */); - m_observers.ForEach(&Observer::OnMapDeleted, fileName); -} - -void Index::OnMwmReadyForUpdate(shared_ptr<MwmInfo> const & info) +void Index::OnMwmDeregistered(LocalCountryFile const & localFile) { - ClearCache(MwmId(info)); - ReplaceFileWithReady(info->m_fileName); - info->SetStatus(MwmInfo::STATUS_UP_TO_DATE); - m_observers.ForEach(&Observer::OnMapUpdated, info->m_fileName); + m_observers.ForEach(&Observer::OnMapDeregistered, localFile); } ////////////////////////////////////////////////////////////////////////////////// @@ -194,11 +88,11 @@ Index::FeaturesLoaderGuard::FeaturesLoaderGuard(Index const & parent, MwmId id) { } -string Index::FeaturesLoaderGuard::GetFileName() const +string Index::FeaturesLoaderGuard::GetCountryFileName() const { if (!m_lock.IsLocked()) return string(); - return m_lock.GetValue<MwmValue>()->GetFileName(); + return m_lock.GetValue<MwmValue>()->GetCountryFile().GetNameWithoutExt(); } bool Index::FeaturesLoaderGuard::IsWorld() const diff --git a/indexer/index.hpp b/indexer/index.hpp index c058a0f11b..ba5e031cf4 100644 --- a/indexer/index.hpp +++ b/indexer/index.hpp @@ -24,33 +24,25 @@ class MwmValue : public MwmSet::MwmValueBase public: FilesContainerR m_cont; IndexFactory m_factory; + platform::CountryFile const m_countryFile; - explicit MwmValue(string const & name); + explicit MwmValue(platform::LocalCountryFile const & localFile); inline feature::DataHeader const & GetHeader() const { return m_factory.GetHeader(); } inline version::MwmVersion const & GetMwmVersion() const { return m_factory.GetMwmVersion(); } - /// @return MWM file name without extension. - string GetFileName() const; + inline platform::CountryFile const & GetCountryFile() const { return m_countryFile; } }; class Index : public MwmSet { protected: // MwmSet overrides: - bool GetVersion(string const & name, MwmInfo & info) const override; - TMwmValueBasePtr CreateValue(string const & name) const override; - void OnMwmDeleted(shared_ptr<MwmInfo> const & info) override; - void OnMwmReadyForUpdate(shared_ptr<MwmInfo> const & info) override; + bool GetVersion(platform::LocalCountryFile const & localFile, MwmInfo & info) const override; + TMwmValueBasePtr CreateValue(platform::LocalCountryFile const & localFile) const override; + void OnMwmDeregistered(platform::LocalCountryFile const & localFile) override; public: - enum UpdateStatus - { - UPDATE_STATUS_OK, - UPDATE_STATUS_BAD_FILE, - UPDATE_STATUS_UPDATE_DELAYED - }; - /// An Observer interface to MwmSet. Note that these functions can /// be called from *ANY* thread because most signals are sent when /// some thread releases its MwmLock, so overrides must be as fast @@ -58,19 +50,14 @@ public: class Observer { public: - virtual ~Observer() {} - - /// Called when a map is registered for a first time. - virtual void OnMapRegistered(string const & file) {} + virtual ~Observer() = default; - /// Called when an update for a map is downloaded. - virtual void OnMapUpdateIsReady(string const & file) {} + /// Called when a map is registered for a first time and can be + /// used. + virtual void OnMapRegistered(platform::LocalCountryFile const & localFile) {} - /// Called when an update for a map is applied. - virtual void OnMapUpdated(string const & file) {} - - /// Called when a map is deleted. - virtual void OnMapDeleted(string const & file) {} + /// Called when a map is deregistered and can not be used. + virtual void OnMapDeregistered(platform::LocalCountryFile const & localFile) {} }; Index(); @@ -78,37 +65,21 @@ public: /// Registers a new map. /// - /// \return A pair of an MwmLock and a flag. MwmLock is locked iff the - /// map with fileName was created or already exists. Flag - /// is set when the map was registered for a first - /// time. Thus, there are three main cases: - /// - /// * the map already exists - returns active lock and unset flag - /// * the map was already registered - returns active lock and set flag - /// * the map can't be registered - returns inactive lock and unset flag - WARN_UNUSED_RESULT pair<MwmLock, bool> RegisterMap(string const & fileName); - - /// Replaces a map file corresponding to fileName with a new one, when - /// it's possible - no clients of the map file. Otherwise, update - /// will be delayed. - /// - /// \return * the map file have been updated - returns active lock and - /// UPDATE_STATUS_OK - /// * update is delayed because the map is busy - returns active lock and - /// UPDATE_STATUS_UPDATE_DELAYED - /// * the file isn't suitable for update - returns inactive lock and - /// UPDATE_STATUS_BAD_FILE - WARN_UNUSED_RESULT pair<MwmLock, UpdateStatus> UpdateMap(string const & fileName); - - /// Deletes a map both from the file system and internal tables, also, - /// deletes all files related to the map. If the map was successfully - /// deleted, notifies observers. + /// \return A pair of an MwmLock and a flag. There are three cases: + /// * the map is newer than the newset registered - returns + /// active lock and set flag + /// * the map is older than the newset registered - returns inactive lock and + /// unset flag. + /// * the version of the map equals to the version of the newest registered - + /// returns active lock and unset flag. + WARN_UNUSED_RESULT pair<MwmLock, bool> RegisterMap(platform::LocalCountryFile const & localFile); + + /// Deregisters a map from internal records. /// - /// \param fileName A fileName denoting the map to be deleted, may - /// be a full path or a short path relative to - /// executable's directories. - //// \return True if the map was successfully deleted. - bool DeleteMap(string const & fileName); + /// \param countryFile A countryFile denoting a map to be deregistered. + /// \return True if the map was successfully deregistered. If map is locked + /// now, returns false. + bool DeregisterMap(platform::CountryFile const & countryFile); bool AddObserver(Observer & observer); @@ -280,7 +251,7 @@ public: FeaturesLoaderGuard(Index const & parent, MwmId id); inline MwmSet::MwmId GetId() const { return m_lock.GetId(); } - string GetFileName() const; + string GetCountryFileName() const; bool IsWorld() const; void GetFeature(uint32_t offset, FeatureType & ft); diff --git a/indexer/indexer_tests/index_builder_test.cpp b/indexer/indexer_tests/index_builder_test.cpp index 7efeb27fb6..1ebfe9c068 100644 --- a/indexer/indexer_tests/index_builder_test.cpp +++ b/indexer/indexer_tests/index_builder_test.cpp @@ -56,7 +56,7 @@ UNIT_TEST(BuildIndexTest) { // Check that index actually works. Index index; - UNUSED_VALUE(index.Register(fileName)); + UNUSED_VALUE(index.Register(platform::LocalCountryFile::MakeForTesting("build_index_test"))); // Make sure that index is actually parsed. NoopFunctor fn; diff --git a/indexer/indexer_tests/index_test.cpp b/indexer/indexer_tests/index_test.cpp index dfea961b07..1e24aa5365 100644 --- a/indexer/indexer_tests/index_test.cpp +++ b/indexer/indexer_tests/index_test.cpp @@ -6,6 +6,8 @@ #include "coding/file_name_utils.hpp" #include "coding/internal/file_data.hpp" +#include "platform/country_file.hpp" +#include "platform/local_country_file.hpp" #include "platform/platform.hpp" #include "base/logging.hpp" @@ -16,6 +18,11 @@ #include "std/bind.hpp" #include "std/string.hpp" +using platform::CountryFile; +using platform::LocalCountryFile; + +namespace +{ void CheckedDeleteFile(string const & file) { if (Platform::IsFileExistsByFullPath(file)) @@ -25,57 +32,60 @@ void CheckedDeleteFile(string const & file) class Observer : public Index::Observer { public: - Observer(string const & file) - : m_file(file), - m_map_registered_calls(0), - m_map_update_is_ready_calls(0), - m_map_updated_calls(0), - m_map_deleted_calls(0) + Observer() : m_numRegisteredCalls(0), m_numDeregisteredCalls(0) {} + + ~Observer() { CheckExpectations(); } + + void ExpectRegisteredMap(platform::LocalCountryFile const & localFile) { + m_expectedRegisteredMaps.push_back(localFile); } - // Index::Observer overrides: - void OnMapRegistered(string const & file) override + void ExpectDeregisteredMap(platform::LocalCountryFile const & localFile) { - CHECK_EQUAL(m_file, file, ()); - ++m_map_registered_calls; + m_expectedDeregisteredMaps.push_back(localFile); } - void OnMapUpdateIsReady(string const & file) override + void CheckExpectations() { - CHECK_EQUAL(m_file, file, ()); - ++m_map_update_is_ready_calls; + CHECK_EQUAL(m_numRegisteredCalls, m_expectedRegisteredMaps.size(), ()); + CHECK_EQUAL(m_numDeregisteredCalls, m_expectedDeregisteredMaps.size(), ()); } - void OnMapUpdated(string const & file) override + // Index::Observer overrides: + void OnMapRegistered(platform::LocalCountryFile const & localFile) override { - CHECK_EQUAL(m_file, file, ()); - ++m_map_updated_calls; + CHECK_LESS(m_numRegisteredCalls, m_expectedRegisteredMaps.size(), + ("Unexpected OnMapRegistered() call (", m_numRegisteredCalls, "): ", localFile)); + CHECK_EQUAL(m_expectedRegisteredMaps[m_numRegisteredCalls], localFile, (m_numRegisteredCalls)); + ++m_numRegisteredCalls; } - void OnMapDeleted(string const & file) override + void OnMapDeregistered(platform::LocalCountryFile const & localFile) override { - CHECK_EQUAL(m_file, file, ()); - ++m_map_deleted_calls; + CHECK_LESS(m_numDeregisteredCalls, m_expectedDeregisteredMaps.size(), + ("Unexpected OnMapDeregistered() call (", m_numDeregisteredCalls, "): ", localFile)); + CHECK_EQUAL(m_expectedDeregisteredMaps[m_numDeregisteredCalls], localFile, + (m_numDeregisteredCalls)); + ++m_numDeregisteredCalls; } - int map_registered_calls() const { return m_map_registered_calls; } - int map_update_is_ready_calls() const { return m_map_update_is_ready_calls; } - int map_updated_calls() const { return m_map_updated_calls; } - int map_deleted_calls() const { return m_map_deleted_calls; } + inline size_t MapRegisteredCalls() const { return m_numRegisteredCalls; } + inline size_t MapDeregisteredCalls() const { return m_numDeregisteredCalls; } private: - string const m_file; - int m_map_registered_calls; - int m_map_update_is_ready_calls; - int m_map_updated_calls; - int m_map_deleted_calls; + size_t m_numRegisteredCalls; + size_t m_numDeregisteredCalls; + + vector<LocalCountryFile> m_expectedRegisteredMaps; + vector<LocalCountryFile> m_expectedDeregisteredMaps; }; +} // namespace UNIT_TEST(Index_Parse) { Index index; - UNUSED_VALUE(index.RegisterMap("minsk-pass" DATA_FILE_EXTENSION)); + UNUSED_VALUE(index.RegisterMap(platform::LocalCountryFile::MakeForTesting("minsk-pass"))); // Make sure that index is actually parsed. NoopFunctor fn; @@ -84,65 +94,68 @@ UNIT_TEST(Index_Parse) UNIT_TEST(Index_MwmStatusNotifications) { - string const resourcesDir = GetPlatform().ResourcesDir(); - string const sourceMapName = "minsk-pass" DATA_FILE_EXTENSION; - string const sourceMapPath = my::JoinFoldersToPath(resourcesDir, sourceMapName); - string const testMapName = "minsk-pass-copy" DATA_FILE_EXTENSION; - string const testMapPath = my::JoinFoldersToPath(resourcesDir, testMapName); - string const testMapUpdatePath = testMapPath + READY_FILE_EXTENSION; + Platform & platform = GetPlatform(); + string const mapsDir = GetPlatform().WritableDir(); + CountryFile const countryFile("minsk-pass"); - TEST(my::CopyFileX(sourceMapPath, testMapPath), ()); - MY_SCOPE_GUARD(testMapGuard, bind(&CheckedDeleteFile, testMapPath)); + // These two classes point to the same file, but will be considered + // by Index as distinct files because versions are artificially set + // to different numbers. + LocalCountryFile const localFileV1(mapsDir, countryFile, 1 /* version */); + LocalCountryFile const localFileV2(mapsDir, countryFile, 2 /* version */); Index index; - Observer observer(testMapName); + Observer observer; index.AddObserver(observer); - TEST_EQUAL(0, observer.map_registered_calls(), ()); + TEST_EQUAL(0, observer.MapRegisteredCalls(), ()); + + MwmSet::MwmId localFileV1Id; // Checks that observers are triggered after map registration. { - pair<MwmSet::MwmLock, bool> const p = index.RegisterMap(testMapName); + observer.ExpectRegisteredMap(localFileV1); + pair<MwmSet::MwmLock, bool> const p = index.RegisterMap(localFileV1); TEST(p.first.IsLocked(), ()); TEST(p.second, ()); - TEST_EQUAL(1, observer.map_registered_calls(), ()); + observer.CheckExpectations(); + localFileV1Id = p.first.GetId(); } - // Checks that map can't registered twice and observers aren't - // triggered. + // Checks that map can't registered twice. { - pair<MwmSet::MwmLock, bool> const p = index.RegisterMap(testMapName); + pair<MwmSet::MwmLock, bool> const p = index.RegisterMap(localFileV1); TEST(p.first.IsLocked(), ()); TEST(!p.second, ()); - TEST_EQUAL(1, observer.map_registered_calls(), ()); + observer.CheckExpectations(); + TEST_EQUAL(localFileV1Id, p.first.GetId(), ()); } - TEST(my::CopyFileX(testMapPath, testMapUpdatePath), ()); - MY_SCOPE_GUARD(testMapUpdateGuard, bind(&CheckedDeleteFile, testMapUpdatePath)); - - // Checks that observers are notified when map is deleted. + // Checks that observers are notified when map is updated. + MwmSet::MwmId localFileV2Id; { - TEST_EQUAL(0, observer.map_update_is_ready_calls(), ()); - TEST_EQUAL(0, observer.map_updated_calls(), ()); - pair<MwmSet::MwmLock, Index::UpdateStatus> const p = index.UpdateMap(testMapName); + observer.ExpectRegisteredMap(localFileV2); + observer.ExpectDeregisteredMap(localFileV1); + pair<MwmSet::MwmLock, bool> const p = index.RegisterMap(localFileV2); TEST(p.first.IsLocked(), ()); - TEST_EQUAL(Index::UPDATE_STATUS_OK, p.second, ()); - TEST_EQUAL(1, observer.map_update_is_ready_calls(), ()); - TEST_EQUAL(1, observer.map_updated_calls(), ()); + TEST(p.second, ()); + observer.CheckExpectations(); + localFileV2Id = p.first.GetId(); + TEST_NOT_EQUAL(localFileV1Id, localFileV2Id, ()); } - // Tries to delete map in presence of active lock. Map should be - // marked "to be removed" but can't be deleted. + // Tries to deregister a map in presence of an active lock. Map + // should be marked "to be removed" but can't be deregistered. After + // leaving the inner block the map should be deregistered. { - MwmSet::MwmLock const lock = index.GetMwmLockByFileName(testMapName); + MwmSet::MwmLock const lock = index.GetMwmLockByCountryFile(countryFile); TEST(lock.IsLocked(), ()); - TEST(!index.DeleteMap(testMapName), ()); - TEST_EQUAL(0, observer.map_deleted_calls(), ()); - } - - // Checks that observers are notified when all locks are destroyed. - TEST_EQUAL(1, observer.map_deleted_calls(), ()); + TEST(!index.DeregisterMap(countryFile), ()); + observer.CheckExpectations(); + observer.ExpectDeregisteredMap(localFileV2); + } + observer.CheckExpectations(); index.RemoveObserver(observer); } diff --git a/indexer/indexer_tests/mwm_set_test.cpp b/indexer/indexer_tests/mwm_set_test.cpp index 27fd148f6b..cc62d5d44e 100644 --- a/indexer/indexer_tests/mwm_set_test.cpp +++ b/indexer/indexer_tests/mwm_set_test.cpp @@ -7,6 +7,9 @@ #include "std/initializer_list.hpp" #include "std/unordered_map.hpp" +using platform::CountryFile; +using platform::LocalCountryFile; + namespace { class MwmValue : public MwmSet::MwmValueBase @@ -16,16 +19,17 @@ class MwmValue : public MwmSet::MwmValueBase class TestMwmSet : public MwmSet { protected: - bool GetVersion(string const & path, MwmInfo & info) const override + // MwmSet overrides: + bool GetVersion(LocalCountryFile const & localFile, MwmInfo & info) const override { - int const n = path[0] - '0'; + int const n = localFile.GetCountryFile().GetNameWithoutExt()[0] - '0'; info.m_maxScale = n; info.m_limitRect = m2::RectD(0, 0, 1, 1); info.m_version.format = version::lastFormat; return true; } - TMwmValueBasePtr CreateValue(string const &) const override + TMwmValueBasePtr CreateValue(LocalCountryFile const &) const override { return TMwmValueBasePtr(new MwmValue()); } @@ -34,23 +38,22 @@ public: ~TestMwmSet() { Cleanup(); } }; -void GetMwmsInfo(MwmSet const & mwmSet, - unordered_map<MwmSet::TMwmFileName, shared_ptr<MwmInfo>> & mwmsInfo) +void GetMwmsInfo(MwmSet const & mwmSet, unordered_map<string, shared_ptr<MwmInfo>> & mwmsInfo) { vector<shared_ptr<MwmInfo>> mwmsInfoList; mwmSet.GetMwmsInfo(mwmsInfoList); mwmsInfo.clear(); for (shared_ptr<MwmInfo> const & info : mwmsInfoList) - mwmsInfo[info->GetFileName()] = info; + mwmsInfo[info->GetCountryName()] = info; } -void TestFilesPresence(unordered_map<MwmSet::TMwmFileName, shared_ptr<MwmInfo>> const & mwmsInfo, - initializer_list<MwmSet::TMwmFileName> const & expectedNames) +void TestFilesPresence(unordered_map<string, shared_ptr<MwmInfo>> const & mwmsInfo, + initializer_list<string> const & expectedNames) { TEST_EQUAL(expectedNames.size(), mwmsInfo.size(), ()); - for (MwmSet::TMwmFileName const & fileName : expectedNames) - TEST_EQUAL(1, mwmsInfo.count(fileName), (fileName)); + for (string const & countryFileName : expectedNames) + TEST_EQUAL(1, mwmsInfo.count(countryFileName), (countryFileName)); } } // namespace @@ -58,12 +61,12 @@ void TestFilesPresence(unordered_map<MwmSet::TMwmFileName, shared_ptr<MwmInfo>> UNIT_TEST(MwmSetSmokeTest) { TestMwmSet mwmSet; - unordered_map<MwmSet::TMwmFileName, shared_ptr<MwmInfo>> mwmsInfo; + unordered_map<string, shared_ptr<MwmInfo>> mwmsInfo; - UNUSED_VALUE(mwmSet.Register("0")); - UNUSED_VALUE(mwmSet.Register("1")); - UNUSED_VALUE(mwmSet.Register("2")); - mwmSet.Deregister("1"); + UNUSED_VALUE(mwmSet.Register(LocalCountryFile::MakeForTesting("0"))); + UNUSED_VALUE(mwmSet.Register(LocalCountryFile::MakeForTesting("1"))); + UNUSED_VALUE(mwmSet.Register(LocalCountryFile::MakeForTesting("2"))); + mwmSet.Deregister(CountryFile("1")); GetMwmsInfo(mwmSet, mwmsInfo); TestFilesPresence(mwmsInfo, {"0", "2"}); @@ -72,13 +75,13 @@ UNIT_TEST(MwmSetSmokeTest) TEST_EQUAL(mwmsInfo["0"]->m_maxScale, 0, ()); TEST(mwmsInfo["2"]->IsUpToDate(), ()); { - MwmSet::MwmLock const lock0 = mwmSet.GetMwmLockByFileName("0"); - MwmSet::MwmLock const lock1 = mwmSet.GetMwmLockByFileName("1"); + MwmSet::MwmLock const lock0 = mwmSet.GetMwmLockByCountryFile(CountryFile("0")); + MwmSet::MwmLock const lock1 = mwmSet.GetMwmLockByCountryFile(CountryFile("1")); TEST(lock0.IsLocked(), ()); TEST(!lock1.IsLocked(), ()); } - UNUSED_VALUE(mwmSet.Register("3")); + UNUSED_VALUE(mwmSet.Register(LocalCountryFile::MakeForTesting("3"))); GetMwmsInfo(mwmSet, mwmsInfo); TestFilesPresence(mwmsInfo, {"0", "2", "3"}); @@ -91,10 +94,10 @@ UNIT_TEST(MwmSetSmokeTest) TEST_EQUAL(mwmsInfo["3"]->m_maxScale, 3, ()); { - MwmSet::MwmLock const lock1 = mwmSet.GetMwmLockByFileName("1"); + MwmSet::MwmLock const lock1 = mwmSet.GetMwmLockByCountryFile(CountryFile("1")); TEST(!lock1.IsLocked(), ()); - mwmSet.Deregister("3"); - UNUSED_VALUE(mwmSet.Register("4")); + mwmSet.Deregister(CountryFile("3")); + UNUSED_VALUE(mwmSet.Register(LocalCountryFile::MakeForTesting("4"))); } GetMwmsInfo(mwmSet, mwmsInfo); @@ -107,7 +110,7 @@ UNIT_TEST(MwmSetSmokeTest) TEST(mwmsInfo["4"]->IsUpToDate(), ()); TEST_EQUAL(mwmsInfo["4"]->m_maxScale, 4, ()); - UNUSED_VALUE(mwmSet.Register("5")); + UNUSED_VALUE(mwmSet.Register(LocalCountryFile::MakeForTesting("5"))); GetMwmsInfo(mwmSet, mwmsInfo); TestFilesPresence(mwmsInfo, {"0", "2", "4", "5"}); @@ -126,17 +129,17 @@ UNIT_TEST(MwmSetSmokeTest) UNIT_TEST(MwmSetIdTest) { TestMwmSet mwmSet; - TEST(mwmSet.Register("3").second, ()); + TEST(mwmSet.Register(LocalCountryFile::MakeForTesting("3")).second, ()); - MwmSet::MwmId const id0 = mwmSet.GetMwmLockByFileName("3").GetId(); - MwmSet::MwmId const id1 = mwmSet.GetMwmLockByFileName("3").GetId(); + MwmSet::MwmId const id0 = mwmSet.GetMwmLockByCountryFile(CountryFile("3")).GetId(); + MwmSet::MwmId const id1 = mwmSet.GetMwmLockByCountryFile(CountryFile("3")).GetId(); TEST(id0.IsAlive(), ()); TEST(id1.IsAlive(), ()); TEST_EQUAL(id0.GetInfo().get(), id1.GetInfo().get(), ()); - TEST_EQUAL(MwmInfo::STATUS_UP_TO_DATE, id0.GetInfo()->GetStatus(), ()); + TEST_EQUAL(MwmInfo::STATUS_REGISTERED, id0.GetInfo()->GetStatus(), ()); - TEST(mwmSet.Deregister("3"), ()); + TEST(mwmSet.Deregister(CountryFile("3")), ()); // Test that both id's are sour now. TEST(!id0.IsAlive(), ()); @@ -151,14 +154,15 @@ UNIT_TEST(MwmSetLockAndIdTest) MwmSet::MwmId id; { - pair<MwmSet::MwmLock, bool> const lockFlag = mwmSet.Register("4"); + pair<MwmSet::MwmLock, bool> const lockFlag = + mwmSet.Register(LocalCountryFile::MakeForTesting("4")); MwmSet::MwmLock const & lock = lockFlag.first; bool const success = lockFlag.second; TEST(lock.IsLocked(), ()); TEST(success, ("Can't register test mwm 4")); - TEST_EQUAL(MwmInfo::STATUS_UP_TO_DATE, lock.GetInfo()->GetStatus(), ()); + TEST_EQUAL(MwmInfo::STATUS_REGISTERED, lock.GetInfo()->GetStatus(), ()); - TEST(!mwmSet.Deregister("4"), ()); // It's not possible to remove mwm 4 right now. + TEST(!mwmSet.Deregister(CountryFile("4")), ()); // It's not possible to remove mwm 4 right now. TEST(lock.IsLocked(), ()); TEST_EQUAL(MwmInfo::STATUS_MARKED_TO_DEREGISTER, lock.GetInfo()->GetStatus(), ()); id = lock.GetId(); @@ -172,7 +176,7 @@ UNIT_TEST(MwmSetLockAndIdTest) // It is not possible to lock mwm 4 because it is already deleted, // and it is not possible to get to it's info from mwmSet. - MwmSet::MwmLock lock = mwmSet.GetMwmLockByFileName("4"); + MwmSet::MwmLock lock = mwmSet.GetMwmLockByCountryFile(CountryFile("4")); TEST(!lock.IsLocked(), ()); TEST(!lock.GetId().IsAlive(), ()); TEST(!lock.GetId().GetInfo().get(), ()); diff --git a/indexer/mwm_set.cpp b/indexer/mwm_set.cpp index c9005078ce..a360a4181b 100644 --- a/indexer/mwm_set.cpp +++ b/indexer/mwm_set.cpp @@ -9,6 +9,9 @@ #include "std/algorithm.hpp" +using platform::CountryFile; +using platform::LocalCountryFile; + MwmInfo::MwmInfo() : m_minScale(0), m_maxScale(0), m_status(STATUS_DEREGISTERED), m_lockCount(0) {} MwmInfo::MwmTypeT MwmInfo::GetType() const @@ -74,57 +77,65 @@ MwmSet::~MwmSet() void MwmSet::Cleanup() { lock_guard<mutex> lock(m_lock); - ClearCacheImpl(m_cache.begin(), m_cache.end()); - -#ifdef DEBUG - for (auto const & p : m_info) - { - MwmInfo const & info = *p.second; - if (info.IsUpToDate()) - { - ASSERT_EQUAL(info.m_lockCount, 0, (info.m_fileName)); - ASSERT(!info.m_fileName.empty(), ()); - } - } -#endif } -MwmSet::MwmId MwmSet::GetMwmIdByFileNameImpl(TMwmFileName const & name) const +MwmSet::MwmId MwmSet::GetMwmIdByCountryFileImpl(CountryFile const & countryFile) const { + string const name = countryFile.GetNameWithoutExt(); ASSERT(!name.empty(), ()); auto const it = m_info.find(name); - if (it == m_info.cend()) + if (it == m_info.cend() || it->second.empty()) return MwmId(); - return MwmId(it->second); + return MwmId(it->second.back()); } -pair<MwmSet::MwmLock, bool> MwmSet::Register(TMwmFileName const & fileName) +pair<MwmSet::MwmLock, bool> MwmSet::Register(LocalCountryFile const & localFile) { lock_guard<mutex> lock(m_lock); - MwmId const id = GetMwmIdByFileNameImpl(fileName); + CountryFile const & countryFile = localFile.GetCountryFile(); + string const name = countryFile.GetNameWithoutExt(); + + MwmId const id = GetMwmIdByCountryFileImpl(countryFile); if (!id.IsAlive()) - return RegisterImpl(fileName); + return RegisterImpl(localFile); + shared_ptr<MwmInfo> info = id.GetInfo(); - if (info->IsRegistered()) - LOG(LWARNING, ("Trying to add already registered mwm:", fileName)); - else - info->SetStatus(MwmInfo::STATUS_UP_TO_DATE); - return make_pair(GetLock(id), false); + + // Deregister old mwm for the country. + if (info->GetVersion() < localFile.GetVersion()) + { + DeregisterImpl(id); + return RegisterImpl(localFile); + } + + // Update the status of the mwm with the same version. + if (info->GetVersion() == localFile.GetVersion()) + { + LOG(LWARNING, ("Trying to add already registered mwm:", name)); + info->SetStatus(MwmInfo::STATUS_REGISTERED); + return make_pair(GetLock(id), false); + } + + LOG(LWARNING, ("Trying to add too old (", localFile.GetVersion(), ") mwm (", name, + "), current version:", info->GetVersion())); + return make_pair(MwmLock(), false); } -pair<MwmSet::MwmLock, bool> MwmSet::RegisterImpl(TMwmFileName const & fileName) +pair<MwmSet::MwmLock, bool> MwmSet::RegisterImpl(LocalCountryFile const & localFile) { shared_ptr<MwmInfo> info(new MwmInfo()); // This function can throw an exception for a bad mwm file. - if (!GetVersion(fileName, *info)) + if (!GetVersion(localFile, *info)) return make_pair(MwmLock(), false); - info->SetStatus(MwmInfo::STATUS_UP_TO_DATE); - info->m_fileName = fileName; - m_info[fileName] = info; + info->SetStatus(MwmInfo::STATUS_REGISTERED); + info->m_file = localFile; + string const name = localFile.GetCountryFile().GetNameWithoutExt(); + vector<shared_ptr<MwmInfo>> & infos = m_info[name]; + infos.push_back(info); return make_pair(GetLock(MwmId(info)), true); } @@ -133,26 +144,29 @@ bool MwmSet::DeregisterImpl(MwmId const & id) if (!id.IsAlive()) return false; shared_ptr<MwmInfo> const & info = id.GetInfo(); + string const name = info->GetCountryName(); + if (info->m_lockCount == 0) { info->SetStatus(MwmInfo::STATUS_DEREGISTERED); - m_info.erase(info->m_fileName); - OnMwmDeleted(info); + vector<shared_ptr<MwmInfo>> & infos = m_info[name]; + infos.erase(remove(infos.begin(), infos.end(), info), infos.end()); + OnMwmDeregistered(info->GetLocalFile()); return true; } info->SetStatus(MwmInfo::STATUS_MARKED_TO_DEREGISTER); return false; } -bool MwmSet::Deregister(TMwmFileName const & fileName) +bool MwmSet::Deregister(CountryFile const & countryFile) { lock_guard<mutex> lock(m_lock); - return DeregisterImpl(fileName); + return DeregisterImpl(countryFile); } -bool MwmSet::DeregisterImpl(TMwmFileName const & fileName) +bool MwmSet::DeregisterImpl(CountryFile const & countryFile) { - MwmId const id = GetMwmIdByFileNameImpl(fileName); + MwmId const id = GetMwmIdByCountryFileImpl(countryFile); if (!id.IsAlive()) return false; bool const deregistered = DeregisterImpl(id); @@ -164,21 +178,24 @@ void MwmSet::DeregisterAll() { lock_guard<mutex> lock(m_lock); - for (auto it = m_info.begin(); it != m_info.end();) + for (auto const & p : m_info) { - auto cur = it++; - DeregisterImpl(MwmId(cur->second)); + // Vector of shared pointers is copied here because an original + // vector will be modified by the body of the cycle. + vector<shared_ptr<MwmInfo>> infos = p.second; + for (shared_ptr<MwmInfo> info : infos) + DeregisterImpl(MwmId(info)); } // Do not call ClearCache - it's under mutex lock. ClearCacheImpl(m_cache.begin(), m_cache.end()); } -bool MwmSet::IsLoaded(TMwmFileName const & fileName) const +bool MwmSet::IsLoaded(CountryFile const & countryFile) const { lock_guard<mutex> lock(m_lock); - MwmId const id = GetMwmIdByFileNameImpl(fileName + DATA_FILE_EXTENSION); + MwmId const id = GetMwmIdByCountryFileImpl(countryFile); return id.IsAlive() && id.GetInfo()->IsRegistered(); } @@ -188,7 +205,10 @@ void MwmSet::GetMwmsInfo(vector<shared_ptr<MwmInfo>> & info) const info.clear(); info.reserve(m_info.size()); for (auto const & p : m_info) - info.push_back(p.second); + { + if (!p.second.empty()) + info.push_back(p.second.back()); + } } MwmSet::TMwmValueBasePtr MwmSet::LockValue(MwmId const & id) @@ -216,7 +236,7 @@ MwmSet::TMwmValueBasePtr MwmSet::LockValueImpl(MwmId const & id) return result; } } - return CreateValue(info->m_fileName); + return CreateValue(info->GetLocalFile()); } void MwmSet::UnlockValue(MwmId const & id, TMwmValueBasePtr p) @@ -235,20 +255,8 @@ void MwmSet::UnlockValueImpl(MwmId const & id, TMwmValueBasePtr p) shared_ptr<MwmInfo> const & info = id.GetInfo(); CHECK_GREATER(info->m_lockCount, 0, ()); --info->m_lockCount; - if (info->m_lockCount == 0) - { - switch (info->GetStatus()) - { - case MwmInfo::STATUS_MARKED_TO_DEREGISTER: - CHECK(DeregisterImpl(id), ()); - break; - case MwmInfo::STATUS_PENDING_UPDATE: - OnMwmReadyForUpdate(info); - break; - default: - break; - } - } + if (info->m_lockCount == 0 && info->GetStatus() == MwmInfo::STATUS_MARKED_TO_DEREGISTER) + CHECK(DeregisterImpl(id), ()); if (info->IsUpToDate()) { @@ -267,20 +275,20 @@ void MwmSet::ClearCache() ClearCacheImpl(m_cache.begin(), m_cache.end()); } -MwmSet::MwmId MwmSet::GetMwmIdByFileName(TMwmFileName const & fileName) const +MwmSet::MwmId MwmSet::GetMwmIdByCountryFile(CountryFile const & countryFile) const { lock_guard<mutex> lock(m_lock); - MwmId const id = GetMwmIdByFileNameImpl(fileName); - ASSERT(id.IsAlive(), ("Can't get an mwm's (", fileName, ") identifier.")); + MwmId const id = GetMwmIdByCountryFileImpl(countryFile); + ASSERT(id.IsAlive(), ("Can't get an mwm's (", countryFile.GetNameWithoutExt(), ") identifier.")); return id; } -MwmSet::MwmLock MwmSet::GetMwmLockByFileName(TMwmFileName const & fileName) +MwmSet::MwmLock MwmSet::GetMwmLockByCountryFile(CountryFile const & countryFile) { lock_guard<mutex> lock(m_lock); - MwmId const id = GetMwmIdByFileNameImpl(fileName); + MwmId const id = GetMwmIdByCountryFileImpl(countryFile); TMwmValueBasePtr value(nullptr); if (id.IsAlive()) value = LockValueImpl(id); diff --git a/indexer/mwm_set.hpp b/indexer/mwm_set.hpp index 25682c16b0..9365e2b81e 100644 --- a/indexer/mwm_set.hpp +++ b/indexer/mwm_set.hpp @@ -2,6 +2,9 @@ #include "indexer/mwm_version.hpp" +#include "platform/country_file.hpp" +#include "platform/local_country_file.hpp" + #include "geometry/rect2d.hpp" #include "base/macros.hpp" @@ -31,10 +34,9 @@ public: enum Status { - STATUS_UP_TO_DATE, ///< Mwm is registered and up-to-date + STATUS_REGISTERED, ///< Mwm is registered and up to date. STATUS_MARKED_TO_DEREGISTER, ///< Mwm is marked to be deregistered as soon as possible STATUS_DEREGISTERED, ///< Mwm is deregistered - STATUS_PENDING_UPDATE ///< Mwm is registered but there're a pending update to it }; MwmInfo(); @@ -46,31 +48,32 @@ public: inline Status GetStatus() const { return m_status; } + inline bool IsUpToDate() const { return IsRegistered(); } + inline bool IsRegistered() const { - return m_status == STATUS_UP_TO_DATE || m_status == STATUS_PENDING_UPDATE; + return m_status == STATUS_REGISTERED; } - inline bool IsUpToDate() const { return m_status == STATUS_UP_TO_DATE; } + inline platform::LocalCountryFile const & GetLocalFile() const { return m_file; } + + inline string GetCountryName() const { return m_file.GetCountryFile().GetNameWithoutExt(); } - inline string const & GetFileName() const { return m_fileName; } + inline int64_t GetVersion() const { return m_file.GetVersion(); } MwmTypeT GetType() const; private: inline void SetStatus(Status status) { m_status = status; } - string m_fileName; ///< Path to the mwm file. - Status m_status; ///< Current country status. - uint8_t m_lockCount; ///< Number of locks. + platform::LocalCountryFile m_file; ///< Path to the mwm file. + Status m_status; ///< Current country status. + uint8_t m_lockCount; ///< Number of locks. }; class MwmSet { public: - using TMwmFileName = string; - using TMwmInfoTable = map<TMwmFileName, shared_ptr<MwmInfo>>; - struct MwmId { public: @@ -94,9 +97,9 @@ public: friend ostream & operator<<(ostream & os, MwmId const & id) { if (id.m_info.get()) - os << "MwmId[" << id.m_info->GetFileName() << "]"; + os << "MwmId [" << id.m_info->GetCountryName() << "]"; else - os << "MwmId[invalid]"; + os << "MwmId [invalid]"; return os; } @@ -152,41 +155,43 @@ public: /// Registers a new map. /// - /// \return A pair of an MwmLock and a flag. MwmLock is locked iff the - /// map with fileName was created or already exists. Flag - /// is set when the map was registered for a first - /// time. Thus, there are three main cases: + /// \return A pair of an MwmLock and a flag. There are three cases: + /// * the map is newer than the newset registered - returns + /// active lock and set flag + /// * the map is older than the newset registered - returns inactive lock and + /// unset flag. + /// * the version of the map equals to the version of the newest registered - + /// returns active lock and unset flag. /// - /// * the map already exists - returns active lock and unset flag - /// * the map was already registered - returns active lock and set flag - /// * the map can't be registered - returns inactive lock and unset flag - //@{ + /// *NOTE* When a new version for the same country is registered, + /// all previous versions will be automatically deregistered. protected: - WARN_UNUSED_RESULT pair<MwmLock, bool> RegisterImpl(TMwmFileName const & fileName); + WARN_UNUSED_RESULT pair<MwmLock, bool> RegisterImpl(platform::LocalCountryFile const & localFile); public: - WARN_UNUSED_RESULT pair<MwmLock, bool> Register(TMwmFileName const & fileName); + WARN_UNUSED_RESULT pair<MwmLock, bool> Register(platform::LocalCountryFile const & localFile); //@} /// @name Remove mwm. //@{ protected: - /// Deregisters a map from the set when it's possible. Note that an - /// underlying file is not deleted. + /// Deregisters a map from internal records. /// - /// @return true when the map was deregistered. + /// \param countryFile A countryFile denoting a map to be deregistered. + /// \return True if the map was successfully deregistered. If map is locked + /// now, returns false. //@{ bool DeregisterImpl(MwmId const & id); - bool DeregisterImpl(TMwmFileName const & ofileName); + bool DeregisterImpl(platform::CountryFile const & countryFile); //@} public: - bool Deregister(TMwmFileName const & fileName); + bool Deregister(platform::CountryFile const & countryFile); void DeregisterAll(); //@} - /// @param[in] file File name without extension. - bool IsLoaded(TMwmFileName const & fileName) const; + /// Returns true when country is registered and can be used. + bool IsLoaded(platform::CountryFile const & countryFile) const; /// Get ids of all mwms. Some of them may be with not active status. /// In that case, LockValue returns NULL. @@ -194,14 +199,14 @@ public: void ClearCache(); - MwmId GetMwmIdByFileName(TMwmFileName const & fileName) const; + MwmId GetMwmIdByCountryFile(platform::CountryFile const & countryFile) const; - MwmLock GetMwmLockByFileName(TMwmFileName const & fileName); + MwmLock GetMwmLockByCountryFile(platform::CountryFile const & countryFile); protected: /// @return True when file format version was successfully read to MwmInfo. - virtual bool GetVersion(TMwmFileName const & fileName, MwmInfo & info) const = 0; - virtual TMwmValueBasePtr CreateValue(string const & name) const = 0; + virtual bool GetVersion(platform::LocalCountryFile const & localFile, MwmInfo & info) const = 0; + virtual TMwmValueBasePtr CreateValue(platform::LocalCountryFile const & localFile) const = 0; void Cleanup(); @@ -226,7 +231,7 @@ protected: /// Find mwm with a given name. /// @precondition This function is always called under mutex m_lock. - MwmId GetMwmIdByFileNameImpl(TMwmFileName const & fileName) const; + MwmId GetMwmIdByCountryFileImpl(platform::CountryFile const & countryFile) const; /// @precondition This function is always called under mutex m_lock. WARN_UNUSED_RESULT inline MwmLock GetLock(MwmId const & id) @@ -236,12 +241,9 @@ protected: // This method is called under m_lock when mwm is removed from a // registry. - virtual void OnMwmDeleted(shared_ptr<MwmInfo> const & info) {} - - // This method is called under m_lock when mwm is ready for update. - virtual void OnMwmReadyForUpdate(shared_ptr<MwmInfo> const & info) {} + virtual void OnMwmDeregistered(platform::LocalCountryFile const & localFile) {} - TMwmInfoTable m_info; + map<string, vector<shared_ptr<MwmInfo>>> m_info; mutable mutex m_lock; }; diff --git a/integration_tests/osrm_test_tools.cpp b/integration_tests/osrm_test_tools.cpp index 90f9dbeaa9..72acd2e3d6 100644 --- a/integration_tests/osrm_test_tools.cpp +++ b/integration_tests/osrm_test_tools.cpp @@ -11,6 +11,8 @@ #include "map/feature_vec_model.hpp" +#include "platform/local_country_file.hpp" +#include "platform/local_country_file_utils.hpp" #include "platform/platform.hpp" #include "platform/preferred_languages.hpp" @@ -20,6 +22,7 @@ using namespace routing; +using platform::LocalCountryFile; namespace { @@ -34,16 +37,18 @@ namespace namespace integration { - shared_ptr<model::FeaturesFetcher> CreateFeaturesFetcher(vector<string> const & mapNames) +shared_ptr<model::FeaturesFetcher> CreateFeaturesFetcher( + vector<LocalCountryFile> const & localFiles) { size_t const maxOpenFileNumber = 1024; ChangeMaxNumberOfOpenFiles(maxOpenFileNumber); shared_ptr<model::FeaturesFetcher> featuresFetcher(new model::FeaturesFetcher); featuresFetcher->InitClassificator(); - for (auto const mapName : mapNames) + for (LocalCountryFile const & localFile : localFiles) { - pair<MwmSet::MwmLock, bool> result = featuresFetcher->RegisterMap(mapName); + LOG(LINFO, ("Registering mwm:", localFile.GetPath(TMapOptions::EMap))); + pair<MwmSet::MwmLock, bool> result = featuresFetcher->RegisterMap(localFile); if (!result.second) { ASSERT(false, ()); @@ -82,21 +87,27 @@ namespace integration ASSERT(featuresFetcher, ()); ASSERT(searchEngine, ()); - shared_ptr<OsrmRouter> osrmRouter(new OsrmRouter(&featuresFetcher->GetIndex(), - [searchEngine](m2::PointD const & pt) - { - return searchEngine->GetCountryFile(pt); - })); + shared_ptr<OsrmRouter> osrmRouter(new OsrmRouter( + &featuresFetcher->GetIndex(), [searchEngine](m2::PointD const & pt) + { + return searchEngine->GetCountryFile(pt); + }, + [](string const & countryFileName) + { + return make_shared<LocalCountryFile>(LocalCountryFile::MakeForTesting(countryFileName)); + })); return osrmRouter; } class OsrmRouterComponents { public: - OsrmRouterComponents(vector<string> const & mapNames) - : m_featuresFetcher(CreateFeaturesFetcher(mapNames)), - m_searchEngine(CreateSearchEngine(m_featuresFetcher)), - m_osrmRouter(CreateOsrmRouter(m_featuresFetcher, m_searchEngine)) {} + OsrmRouterComponents(vector<LocalCountryFile> const & localFiles) + : m_featuresFetcher(CreateFeaturesFetcher(localFiles)), + m_searchEngine(CreateSearchEngine(m_featuresFetcher)), + m_osrmRouter(CreateOsrmRouter(m_featuresFetcher, m_searchEngine)) + { + } OsrmRouter * GetOsrmRouter() const { return m_osrmRouter.get(); } search::Engine * GetSearchEngine() const { return m_searchEngine.get(); } @@ -106,28 +117,17 @@ namespace integration shared_ptr<OsrmRouter> m_osrmRouter; }; - void GetMapNames(vector<string> & maps) - { - Platform const & pl = GetPlatform(); - - pl.GetFilesByExt(pl.ResourcesDir(), DATA_FILE_EXTENSION, maps); - pl.GetFilesByExt(pl.WritableDir(), DATA_FILE_EXTENSION, maps); - - sort(maps.begin(), maps.end()); - maps.erase(unique(maps.begin(), maps.end()), maps.end()); - } - - shared_ptr<OsrmRouterComponents> LoadMaps(vector<string> const & mapNames) + shared_ptr<OsrmRouterComponents> LoadMaps(vector<LocalCountryFile> const & localFiles) { - return shared_ptr<OsrmRouterComponents>(new OsrmRouterComponents(mapNames)); + return shared_ptr<OsrmRouterComponents>(new OsrmRouterComponents(localFiles)); } shared_ptr<OsrmRouterComponents> LoadAllMaps() { - vector<string> maps; - GetMapNames(maps); - ASSERT(!maps.empty(), ()); - return LoadMaps(maps); + vector<LocalCountryFile> localFiles; + platform::FindAllLocalMaps(localFiles); + ASSERT(!localFiles.empty(), ()); + return LoadMaps(localFiles); } OsrmRouterComponents & GetAllMaps() diff --git a/integration_tests/osrm_test_tools.hpp b/integration_tests/osrm_test_tools.hpp index 8bc5608e7a..b6d23146d7 100644 --- a/integration_tests/osrm_test_tools.hpp +++ b/integration_tests/osrm_test_tools.hpp @@ -8,6 +8,8 @@ #include "routing/osrm_router.hpp" +#include "platform/local_country_file.hpp" + /* * These tests are developed to simplify routing integration tests writing. * You can use the interface bellow however you want but there are some hints. @@ -46,7 +48,7 @@ namespace integration OsrmRouterComponents & routerComponents); OsrmRouterComponents & GetAllMaps(); - shared_ptr<OsrmRouterComponents> LoadMaps(vector<string> const & mapNames); + shared_ptr<OsrmRouterComponents> LoadMaps(vector<platform::LocalCountryFile> const & localFiles); TRouteResult CalculateRoute(OsrmRouterComponents const & routerComponents, m2::PointD const & startPoint, m2::PointD const & startDirection, m2::PointD const & finalPoint); diff --git a/iphone/Maps/Classes/CustomAlert/DownloadTransitMapsAlert/MWMDownloadTransitMapAlert.mm b/iphone/Maps/Classes/CustomAlert/DownloadTransitMapsAlert/MWMDownloadTransitMapAlert.mm index 567183be8d..c1ff8c6a9c 100644 --- a/iphone/Maps/Classes/CustomAlert/DownloadTransitMapsAlert/MWMDownloadTransitMapAlert.mm +++ b/iphone/Maps/Classes/CustomAlert/DownloadTransitMapsAlert/MWMDownloadTransitMapAlert.mm @@ -100,4 +100,4 @@ extern UIColor * const kActiveDownloaderViewColor; self.downloadButton.minY = self.notNowButton.frame.origin.y; } -@end
\ No newline at end of file +@end diff --git a/map/active_maps_layout.cpp b/map/active_maps_layout.cpp index 8dc200ded3..090cef36b5 100644 --- a/map/active_maps_layout.cpp +++ b/map/active_maps_layout.cpp @@ -36,14 +36,14 @@ ActiveMapsLayout::~ActiveMapsLayout() #endif } -void ActiveMapsLayout::Init(vector<string> const & maps) +void ActiveMapsLayout::Init(vector<platform::CountryFile> const & files) { Clear(); Storage & storage = GetStorage(); - for (auto const & file : maps) + for (auto const & file : files) { - vector<TIndex> arr = storage.FindAllIndexesByFile(Storage::MapWithoutExt(file)); + vector<TIndex> arr = storage.FindAllIndexesByFile(file.GetNameWithoutExt()); if (!arr.empty()) { TStatus status; @@ -53,7 +53,9 @@ void ActiveMapsLayout::Init(vector<string> const & maps) m_items.push_back({ arr, status, options, options }); } else + { LOG(LWARNING, ("Can't find map index for", file)); + } } auto const comparatorFn = [&storage] (Item const & lhs, Item const & rhs) @@ -237,7 +239,7 @@ LocalAndRemoteSizeT const ActiveMapsLayout::GetRemoteCountrySizes(TGroup const & LocalAndRemoteSizeT const ActiveMapsLayout::GetRemoteCountrySizes(TIndex const & index) const { - CountryFile const & c = GetStorage().CountryByIndex(index).GetFile(); + platform::CountryFile const & c = GetStorage().CountryByIndex(index).GetFile(); size_t const mapSize = c.GetRemoteSize(TMapOptions::EMap); return { mapSize, c.GetRemoteSize(TMapOptions::ECarRouting) }; } @@ -265,7 +267,7 @@ void ActiveMapsLayout::DownloadMap(TIndex const & index, TMapOptions const & opt else { Storage const & s = GetStorage(); - vector<TIndex> arr = s.FindAllIndexesByFile(s.CountryFileNameWithoutExt(index)); + vector<TIndex> arr = s.FindAllIndexesByFile(s.GetCountryFile(index).GetNameWithoutExt()); int position = InsertInGroup(TGroup::ENewMap, { arr, TStatus::ENotDownloaded, validOptions, validOptions }); NotifyInsertion(TGroup::ENewMap, position); } diff --git a/map/active_maps_layout.hpp b/map/active_maps_layout.hpp index 27c1e101e4..e4e7cbb87d 100644 --- a/map/active_maps_layout.hpp +++ b/map/active_maps_layout.hpp @@ -1,7 +1,9 @@ #pragma once -#include "storage/storage_defines.hpp" #include "storage/index.hpp" +#include "storage/storage_defines.hpp" + +#include "platform/country_file.hpp" #include "platform/country_defines.hpp" @@ -95,7 +97,7 @@ private: Storage const & GetStorage() const; Storage & GetStorage(); - void Init(vector<string> const & maps); + void Init(vector<platform::CountryFile> const & files); void Clear(); void ShowMap(TIndex const & index); diff --git a/map/benchmark_engine.cpp b/map/benchmark_engine.cpp index 3c99a0b9f9..3e2a6bcffe 100644 --- a/map/benchmark_engine.cpp +++ b/map/benchmark_engine.cpp @@ -133,8 +133,12 @@ void BenchmarkEngine::PrepareMaps() // add only maps needed for benchmarks MapsCollector collector; ForEachBenchmarkRecord(collector); - for_each(collector.m_maps.begin(), collector.m_maps.end(), - bind(&Framework::RegisterMap, m_framework, _1)); + string const directory = GetPlatform().WritableDir(); + for (string const & map : collector.m_maps) + { + LOG(LINFO, ("Looking for:", map)); + m_framework->RegisterMap(platform::LocalCountryFile::MakeForTesting(map)); + } } BenchmarkEngine::BenchmarkEngine(Framework * fw) diff --git a/map/benchmark_tool/features_loading.cpp b/map/benchmark_tool/features_loading.cpp index c9fd84ab7d..a316188916 100644 --- a/map/benchmark_tool/features_loading.cpp +++ b/map/benchmark_tool/features_loading.cpp @@ -9,6 +9,8 @@ #include "platform/platform.hpp" +#include "coding/file_name_utils.hpp" + #include "base/macros.hpp" #include "base/timer.hpp" @@ -97,8 +99,20 @@ namespace void RunFeaturesLoadingBenchmark(string const & file, pair<int, int> scaleR, AllResult & res) { + string baseName = file; + my::GetNameFromFullPath(baseName); + + // Check that file is relative to maps dir. + ASSERT_EQUAL(file, baseName, ()); + + string countryFileName = baseName; + my::GetNameWithoutExt(countryFileName); + + platform::LocalCountryFile localFile = + platform::LocalCountryFile::MakeForTesting(countryFileName); + feature::DataHeader header; - LoadMapHeader(GetPlatform().GetReader(file), header); + LoadMapHeader(GetPlatform().GetCountryReader(localFile, TMapOptions::EMap), header); pair<int, int> const r = header.GetScaleRange(); if (r.first > scaleR.first) @@ -110,7 +124,7 @@ void RunFeaturesLoadingBenchmark(string const & file, pair<int, int> scaleR, All return; model::FeaturesFetcher src; - UNUSED_VALUE(src.RegisterMap(file)); + UNUSED_VALUE(src.RegisterMap(platform::LocalCountryFile::MakeForTesting(countryFileName))); RunBenchmark(src, header.GetBounds(), scaleR, res); } diff --git a/map/country_tree.cpp b/map/country_tree.cpp index e0c53bccd2..4d62070703 100644 --- a/map/country_tree.cpp +++ b/map/country_tree.cpp @@ -65,7 +65,7 @@ CountryTree & CountryTree::operator=(CountryTree const & other) return *this; } -void CountryTree::Init(vector<string> const & maps) +void CountryTree::Init(vector<platform::CountryFile> const & maps) { ASSERT(IsValid(), ()); m_layout->Init(maps); diff --git a/map/country_tree.hpp b/map/country_tree.hpp index ab8e88838a..2a28dce7a3 100644 --- a/map/country_tree.hpp +++ b/map/country_tree.hpp @@ -5,6 +5,8 @@ #include "storage/index.hpp" #include "storage/storage_defines.hpp" +#include "platform/country_file.hpp" + #include "base/buffer_vector.hpp" #include "std/string.hpp" @@ -36,7 +38,7 @@ public: CountryTree & operator=(CountryTree const & other); /// @param[in] Sorted vector of current .mwm files. - void Init(vector<string> const & maps); + void Init(vector<platform::CountryFile> const & maps); void Clear(); ActiveMapsLayout & GetActiveMapLayout(); diff --git a/map/feature_vec_model.cpp b/map/feature_vec_model.cpp index ec7b701f25..e1ba82c165 100644 --- a/map/feature_vec_model.cpp +++ b/map/feature_vec_model.cpp @@ -13,9 +13,14 @@ #include "std/bind.hpp" +using platform::CountryFile; +using platform::LocalCountryFile; namespace model { +FeaturesFetcher::FeaturesFetcher() { m_multiIndex.AddObserver(*this); } + +FeaturesFetcher::~FeaturesFetcher() { m_multiIndex.RemoveObserver(*this); } // While reading any files (classificator or mwm), there are 2 types of possible exceptions: // Reader::Exception, FileAbsentException. @@ -33,71 +38,47 @@ void FeaturesFetcher::InitClassificator() } } -pair<MwmSet::MwmLock, bool> FeaturesFetcher::RegisterMap(string const & file) +pair<MwmSet::MwmLock, bool> FeaturesFetcher::RegisterMap(LocalCountryFile const & localFile) { + string const countryFileName = localFile.GetCountryFile().GetNameWithoutExt(); try { - pair<MwmSet::MwmLock, bool> p = m_multiIndex.RegisterMap(file); - if (!p.second) + pair<MwmSet::MwmLock, bool> result = m_multiIndex.RegisterMap(localFile); + if (!result.second) { - LOG(LWARNING, - ("Can't add map", file, "Probably it's already added or has newer data version.")); - return p; + LOG(LWARNING, ("Can't add map", countryFileName, + "Probably it's already added or has newer data version.")); + return result; } - MwmSet::MwmLock & lock = p.first; + MwmSet::MwmLock & lock = result.first; ASSERT(lock.IsLocked(), ("Mwm lock invariant violation.")); m_rect.Add(lock.GetInfo()->m_limitRect); - return p; + return result; } catch (RootException const & e) { - LOG(LERROR, ("IO error while adding ", file, " map. ", e.what())); + LOG(LERROR, ("IO error while adding ", countryFileName, " map. ", e.what())); return make_pair(MwmSet::MwmLock(), false); } } -void FeaturesFetcher::DeregisterMap(string const & file) { m_multiIndex.Deregister(file); } - -void FeaturesFetcher::DeregisterAllMaps() { m_multiIndex.DeregisterAll(); } - -bool FeaturesFetcher::DeleteMap(string const & file) -{ - return m_multiIndex.DeleteMap(file); -} - -pair<MwmSet::MwmLock, Index::UpdateStatus> FeaturesFetcher::UpdateMap(string const & file) +bool FeaturesFetcher::DeregisterMap(CountryFile const & countryFile) { - return m_multiIndex.UpdateMap(file); + return m_multiIndex.Deregister(countryFile); } -//void FeaturesFetcher::Clean() -//{ -// m_rect.MakeEmpty(); -// // TODO: m_multiIndex.Clear(); - is it needed? -//} +void FeaturesFetcher::DeregisterAllMaps() { m_multiIndex.DeregisterAll(); } void FeaturesFetcher::ClearCaches() { m_multiIndex.ClearCache(); } -/* -bool FeaturesFetcher::IsLoaded(m2::PointD const & pt) const +void FeaturesFetcher::OnMapDeregistered(platform::LocalCountryFile const & localFile) { - vector<MwmInfo> info; - m_multiIndex.GetMwmInfo(info); - - for (size_t i = 0; i < info.size(); ++i) - if (info[i].IsExist() && - info[i].GetType() == MwmInfo::COUNTRY && - info[i].m_limitRect.IsPointInside(pt)) - { - return true; - } - - return false; + if (m_onMapDeregistered) + m_onMapDeregistered(localFile); } -*/ m2::RectD FeaturesFetcher::GetWorldRect() const { diff --git a/map/feature_vec_model.hpp b/map/feature_vec_model.hpp index b671ba0721..39ddabd690 100644 --- a/map/feature_vec_model.hpp +++ b/map/feature_vec_model.hpp @@ -15,7 +15,7 @@ namespace model { //#define USE_BUFFER_READER - class FeaturesFetcher +class FeaturesFetcher : public Index::Observer { public: #ifdef USE_BUFFER_READER @@ -24,58 +24,56 @@ namespace model typedef ModelReaderPtr ReaderT; #endif + typedef function<void(platform::LocalCountryFile const &)> TMapDeregisteredCallback; + private: m2::RectD m_rect; Index m_multiIndex; + TMapDeregisteredCallback m_onMapDeregistered; + public: + FeaturesFetcher(); + + virtual ~FeaturesFetcher(); + void InitClassificator(); + inline void SetOnMapDeregisteredCallback(TMapDeregisteredCallback const & callback) + { + m_onMapDeregistered = callback; + } + /// Registers a new map. /// - /// \return A pair of an MwmLock and a flag. MwmLock is locked iff the - /// map with fileName was created or already exists. Flag - /// is set when the map was registered for a first - /// time. Thus, there are three main cases: - /// - /// * the map already exists - returns active lock and unset flag - /// * the map was already registered - returns active lock and set flag - /// * the map can't be registered - returns inactive lock and unset flag - WARN_UNUSED_RESULT pair<MwmSet::MwmLock, bool> RegisterMap(string const & file); + /// \return A pair of an MwmLock and a flag. There are three cases: + /// * the map is newer than the newset registered - returns + /// active lock and set flag + /// * the map is older than the newset registered - returns inactive lock and + /// unset flag. + /// * the version of the map equals to the version of the newest registered - + /// returns active lock and unset flag. + WARN_UNUSED_RESULT pair<MwmSet::MwmLock, bool> RegisterMap( + platform::LocalCountryFile const & localFile); /// Deregisters a map denoted by file from internal records. - void DeregisterMap(string const & file); + bool DeregisterMap(platform::CountryFile const & countryFile); /// Deregisters all registered maps. void DeregisterAllMaps(); - /// Deletes all files related to map denoted by file. - /// - /// \return True if a map was successfully deleted. - bool DeleteMap(string const & file); - - /// Replaces a map file corresponding to fileName with a new one, when - /// it's possible - no clients of the map file. Otherwise, update - /// will be delayed. - /// - /// \return * the map file have been updated - returns active lock and - /// UPDATE_STATUS_OK - /// * update is delayed because the map is busy - returns active lock and - /// UPDATE_STATUS_UPDATE_DELAYED - /// * the file isn't suitable for update - returns inactive lock and - /// UPDATE_STATUS_BAD_FILE - WARN_UNUSED_RESULT pair<MwmSet::MwmLock, Index::UpdateStatus> UpdateMap(string const & file); - //@} - //void Clean(); void ClearCaches(); - inline bool IsLoaded(string const & fName) const + inline bool IsLoaded(string const & countryFileName) const { - return m_multiIndex.IsLoaded(fName); + return m_multiIndex.IsLoaded(platform::CountryFile(countryFileName)); } + // Index::Observer overrides: + void OnMapDeregistered(platform::LocalCountryFile const & localFile) override; + //bool IsLoaded(m2::PointD const & pt) const; /// @name Features enumeration. diff --git a/map/framework.cpp b/map/framework.cpp index 9265215902..9bbc86ad94 100644 --- a/map/framework.cpp +++ b/map/framework.cpp @@ -44,10 +44,11 @@ #include "gui/controller.hpp" +#include "platform/local_country_file_utils.hpp" #include "platform/measurement_utils.hpp" -#include "platform/settings.hpp" -#include "platform/preferred_languages.hpp" #include "platform/platform.hpp" +#include "platform/preferred_languages.hpp" +#include "platform/settings.hpp" #include "coding/internal/file_data.hpp" #include "coding/zip_reader.hpp" @@ -81,6 +82,8 @@ using namespace storage; using namespace routing; using namespace location; +using platform::CountryFile; +using platform::LocalCountryFile; #ifdef FIXED_LOCATION Framework::FixedPosition::FixedPosition() @@ -95,38 +98,20 @@ namespace static const int BM_TOUCH_PIXEL_INCREASE = 20; } -pair<MwmSet::MwmLock, bool> Framework::RegisterMap(string const & file) +pair<MwmSet::MwmLock, bool> Framework::RegisterMap(LocalCountryFile const & localFile) { - LOG(LINFO, ("Loading map:", file)); - - pair<MwmSet::MwmLock, bool> p = m_model.RegisterMap(file); - if (!p.second) - return p; - MwmSet::MwmLock const & lock = p.first; - ASSERT(lock.IsLocked(), ()); - - shared_ptr<MwmInfo> info = lock.GetInfo(); - ASSERT(info, ()); - - if (info->m_version.format == version::v1) - { - // Now we do force delete of old (April 2011) maps. - LOG(LINFO, ("Deleting old map:", file)); - - DeregisterMap(file); - VERIFY(my::DeleteFileX(GetPlatform().WritablePathForFile(file)), ()); - return make_pair(MwmSet::MwmLock(), false); - } - - return p; + string const countryFileName = localFile.GetCountryFile().GetNameWithoutExt(); + LOG(LINFO, ("Loading map:", countryFileName)); + return m_model.RegisterMap(localFile); } -void Framework::DeregisterMap(string const & file) { m_model.DeregisterMap(file); } - -void Framework::OnLocationError(TLocationError /*error*/) +void Framework::DeregisterMap(CountryFile const & countryFile) { + m_model.DeregisterMap(countryFile); } +void Framework::OnLocationError(TLocationError /*error*/) {} + void Framework::OnLocationUpdate(GpsInfo const & info) { #ifdef FIXED_LOCATION @@ -198,32 +183,6 @@ m2::PointD Framework::GetWidgetSize(InformationDisplay::WidgetType widget) const return m_informationDisplay.GetWidgetSize(widget); } -void Framework::GetMaps(vector<string> & maps) const -{ - Platform & pl = GetPlatform(); - - pl.GetFilesByExt(pl.ResourcesDir(), DATA_FILE_EXTENSION, maps); - pl.GetFilesByExt(pl.WritableDir(), DATA_FILE_EXTENSION, maps); - - // Remove duplicate maps if they're both present in Resources and in Writable dirs. - sort(maps.begin(), maps.end()); - maps.erase(unique(maps.begin(), maps.end()), maps.end()); - -#ifdef OMIM_OS_ANDROID - // On Android World and WorldCoasts can be stored in alternative /Android/obb/ path. - char const * arrCheck[] = { WORLD_FILE_NAME DATA_FILE_EXTENSION, - WORLD_COASTS_FILE_NAME DATA_FILE_EXTENSION }; - - for (size_t i = 0; i < ARRAY_SIZE(arrCheck); ++i) - { - auto const it = lower_bound(maps.begin(), maps.end(), arrCheck[i]); - if (it == maps.end() || *it != arrCheck[i]) - maps.insert(it, arrCheck[i]); - } -#endif -} - - Framework::Framework() : m_navigator(m_scales), m_animator(this), @@ -280,6 +239,7 @@ Framework::Framework() #endif m_model.InitClassificator(); + m_model.SetOnMapDeregisteredCallback(bind(&Framework::OnMapDeregistered, this, _1)); LOG(LDEBUG, ("Classificator initialized")); // To avoid possible races - init search engine once in constructor. @@ -292,7 +252,7 @@ Framework::Framework() LOG(LDEBUG, ("Maps initialized")); // Init storage with needed callback. - m_storage.Init(bind(&Framework::UpdateAfterDownload, this, _1, _2)); + m_storage.Init(bind(&Framework::UpdateAfterDownload, this, _1)); LOG(LDEBUG, ("Storage initialized")); #ifdef USE_PEDESTRIAN_ROUTER @@ -308,6 +268,7 @@ Framework::Framework() Framework::~Framework() { delete m_benchmarkEngine; + m_model.SetOnMapDeregisteredCallback(nullptr); } void Framework::DrawSingleFrame(m2::PointD const & center, int zoomModifier, @@ -395,44 +356,32 @@ double Framework::GetVisualScale() const return m_scales.GetVisualScale(); } -void Framework::DeleteCountry(TIndex const & index, TMapOptions opt) +void Framework::DeleteCountry(storage::TIndex const & index, TMapOptions opt) { - if (HasOptions(opt, TMapOptions::EMap)) - opt = TMapOptions::EMapWithCarRouting; - - if (!m_storage.DeleteFromDownloader(index)) + switch (opt) { - CountryFile const & file = m_storage.CountryByIndex(index).GetFile(); - - if (HasOptions(opt, TMapOptions::EMap)) + case TMapOptions::ENothing: + return; + case TMapOptions::EMap: // fall through + case TMapOptions::EMapWithCarRouting: { - if (m_model.DeleteMap(file.GetFileWithExt(TMapOptions::EMap))) - InvalidateRect(GetCountryBounds(file.GetFileWithoutExt()), true); + CountryFile const & countryFile = m_storage.GetCountryFile(index); + // m_model will notify us when latest map file will be deleted via + // OnMapDeregistered call. + if (m_model.DeregisterMap(countryFile)) + { + InvalidateRect(GetCountryBounds(countryFile.GetNameWithoutExt()), true /* doForceUpdate */); + } } - - if (HasOptions(opt, TMapOptions::ECarRouting)) - m_routingSession.DeleteIndexFile(file.GetFileWithExt(TMapOptions::ECarRouting)); - - m_storage.NotifyStatusChanged(index); - - DeleteCountryIndexes(m_storage.CountryFileNameWithoutExt(index)); + m_routingSession.Reset(); + return; + case TMapOptions::ECarRouting: + m_routingSession.Reset(); + m_storage.DeleteCountry(index, opt); + return; } } -void Framework::DeleteCountryIndexes(string const & mwmName) -{ - m_routingSession.Reset(); - - Platform::FilesList files; - Platform const & pl = GetPlatform(); - string const path = pl.WritablePathForCountryIndexes(mwmName); - - /// @todo We need correct regexp for any file (not including "." and ".."). - pl.GetFilesByRegExp(path, mwmName + "\\..*", files); - for (auto const & file : files) - (void) my::DeleteFileX(path + file); -} - void Framework::DownloadCountry(TIndex const & index, TMapOptions opt) { m_storage.DownloadCountry(index, opt); @@ -459,7 +408,8 @@ m2::RectD Framework::GetCountryBounds(string const & file) const m2::RectD Framework::GetCountryBounds(TIndex const & index) const { - return GetCountryBounds(m_storage.CountryFileNameWithoutExt(index)); + CountryFile const & file = m_storage.GetCountryFile(index); + return GetCountryBounds(file.GetNameWithoutExt()); } void Framework::ShowCountry(TIndex const & index) @@ -469,72 +419,55 @@ void Framework::ShowCountry(TIndex const & index) ShowRectEx(GetCountryBounds(index)); } -void Framework::UpdateAfterDownload(string const & fileName, TMapOptions opt) +void Framework::UpdateAfterDownload(LocalCountryFile const & localFile) { - if (HasOptions(opt, TMapOptions::EMap)) - { - // Delete old (splitted) map files, if any. - char const * arr[] = { "Japan", "Brazil" }; - for (size_t i = 0; i < ARRAY_SIZE(arr); ++i) - if (fileName.find(arr[i]) == 0) - { - if (m_model.DeleteMap(string(arr[i]) + DATA_FILE_EXTENSION)) - Invalidate(true); - - // Routing index doesn't exist for this map files. - } - - // Add downloaded map. - pair<MwmSet::MwmLock, Index::UpdateStatus> const p = m_model.UpdateMap(fileName); - if (p.second == Index::UPDATE_STATUS_OK) - { - MwmSet::MwmLock const & lock = p.first; - ASSERT(lock.IsLocked(), ()); - InvalidateRect(lock.GetInfo()->m_limitRect, true); - } - - GetSearchEngine()->ClearViewportsCache(); - } + m_routingSession.Reset(); - // Replace routing file. - if (HasOptions(opt, TMapOptions::ECarRouting)) - { - string routingName = fileName + ROUTING_FILE_EXTENSION; - m_routingSession.DeleteIndexFile(routingName); + if (!HasOptions(localFile.GetFiles(), TMapOptions::EMap)) + return; - routingName = GetPlatform().WritableDir() + routingName; - VERIFY(my::RenameFileX(routingName + READY_FILE_EXTENSION, routingName), ()); - } + // Add downloaded map. + pair<MwmSet::MwmLock, bool> const result = m_model.RegisterMap(localFile); + MwmSet::MwmLock const & lock = result.first; + if (lock.IsLocked()) + InvalidateRect(lock.GetInfo()->m_limitRect, true /* doForceUpdate */); + GetSearchEngine()->ClearViewportsCache(); +} - string countryName(fileName); - my::GetNameWithoutExt(countryName); - DeleteCountryIndexes(countryName); +void Framework::OnMapDeregistered(platform::LocalCountryFile const & localFile) +{ + m_storage.DeleteCustomCountryVersion(localFile); } void Framework::RegisterAllMaps() { - ASSERT(!Storage().IsDownloadInProgress(), - ("Registering maps while map downloading leads to removing downloading maps from ActiveMapsListener::m_items.")); - //ASSERT(m_model.IsEmpty(), ()); + ASSERT(!m_storage.IsDownloadInProgress(), + ("Registering maps while map downloading leads to removing downloading maps from " + "ActiveMapsListener::m_items.")); + + platform::CleanupMapsDirectory(); + m_storage.RegisterAllLocalMaps(); - int minVersion = numeric_limits<int>::max(); + int minFormat = numeric_limits<int>::max(); + vector<CountryFile> maps; + m_storage.GetLocalMaps(maps); - vector<string> maps; - GetMaps(maps); - for_each(maps.begin(), maps.end(), [&](string const & file) + for (CountryFile const & countryFile : maps) { - pair<MwmSet::MwmLock, bool> const p = RegisterMap(file); - if (p.second) - { - MwmSet::MwmLock const & lock = p.first; - ASSERT(lock.IsLocked(), ()); - minVersion = min(minVersion, static_cast<int>(lock.GetInfo()->m_version.format)); - } - }); + shared_ptr<LocalCountryFile> localFile = m_storage.GetLatestLocalFile(countryFile); + if (!localFile.get()) + continue; + pair<MwmSet::MwmLock, bool> const p = RegisterMap(*localFile); + if (!p.second) + continue; + MwmSet::MwmLock const & lock = p.first; + ASSERT(lock.IsLocked(), ()); + minFormat = min(minFormat, static_cast<int>(lock.GetInfo()->m_version.format)); + } m_countryTree.Init(maps); - GetSearchEngine()->SupportOldFormat(minVersion < version::v3); + GetSearchEngine()->SupportOldFormat(minFormat < version::v3); } void Framework::DeregisterAllMaps() @@ -2212,22 +2145,27 @@ void Framework::SetRouter(RouterType type) alohalytics::LogEvent("Routing_CalculatingRoute", statistics); }; + auto countryFileGetter = [this](m2::PointD const & p) -> string + { + // TODO (@gorshenin): fix search engine to return CountryFile + // instances instead of plain strings. + return GetSearchEngine()->GetCountryFile(p); + }; + auto localFileGetter = [this](string const & countryFile) -> shared_ptr<LocalCountryFile> + { + return m_storage.GetLatestLocalFile(CountryFile(countryFile)); + }; + unique_ptr<IRouter> router; if (type == RouterType::Pedestrian) { - auto const countryFileFn = [this](m2::PointD const & pt) - { - return GetSearchEngine()->GetCountryFile(pt) + DATA_FILE_EXTENSION; - }; - router = CreatePedestrianAStarBidirectionalRouter(m_model.GetIndex(), countryFileFn, routingVisualizerFn); + router = CreatePedestrianAStarBidirectionalRouter(m_model.GetIndex(), countryFileGetter, + routingVisualizerFn); } else { - auto const countryFileFn = [this](m2::PointD const & pt) - { - return GetSearchEngine()->GetCountryFile(pt); - }; - router.reset(new OsrmRouter(&m_model.GetIndex(), countryFileFn, routingVisualizerFn)); + router.reset(new OsrmRouter(&m_model.GetIndex(), countryFileGetter, localFileGetter, + routingVisualizerFn)); } m_routingSession.SetRouter(move(router), routingStatisticsFn); diff --git a/map/framework.hpp b/map/framework.hpp index d0e6a723b2..89f0541bda 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -135,8 +135,10 @@ protected: static const int TOUCH_PIXEL_RADIUS = 20; /// This function is called by m_storage to notify that country downloading is finished. - /// @param[in] file Country file name (without extensions). - void UpdateAfterDownload(string const & file, TMapOptions opt); + void UpdateAfterDownload(platform::LocalCountryFile const & localFile); + + /// This function is called by m_model when the map file is deregistered. + void OnMapDeregistered(platform::LocalCountryFile const & localFile); //my::Timer m_timer; inline double ElapsedSeconds() const @@ -155,10 +157,7 @@ protected: void ClearAllCaches(); - void DeregisterMap(string const & file); - - /// Deletes user calculated indexes on country updates - void DeleteCountryIndexes(string const & mwmName); + void DeregisterMap(platform::CountryFile const & countryFile); public: Framework(); @@ -189,11 +188,6 @@ public: void ReleaseSingleFrameRenderer(); bool IsSingleFrameRendererInited() const; - /// @name Process storage connecting/disconnecting. - //@{ - /// @param[out] maps File names without path. - void GetMaps(vector<string> & maps) const; - /// Registers all local map files in internal indexes. void RegisterAllMaps(); @@ -204,7 +198,7 @@ public: /// /// @return True and inner mwm data version from header in version /// or false in case of errors. - pair<MwmSet::MwmLock, bool> RegisterMap(string const & file); + pair<MwmSet::MwmLock, bool> RegisterMap(platform::LocalCountryFile const & localFile); //@} /// Deletes all disk files corresponding to country. diff --git a/map/map_tests/bookmarks_test.cpp b/map/map_tests/bookmarks_test.cpp index 8edd6d69f6..51cc6e854b 100644 --- a/map/map_tests/bookmarks_test.cpp +++ b/map/map_tests/bookmarks_test.cpp @@ -400,7 +400,7 @@ UNIT_TEST(Bookmarks_AddressInfo) // Maps added in constructor (we need minsk-pass.mwm only) Framework fm; fm.DeregisterAllMaps(); - fm.RegisterMap("minsk-pass.mwm"); + fm.RegisterMap(platform::LocalCountryFile::MakeForTesting("minsk-pass")); fm.OnSize(800, 600); // assume that developers have English or Russian system language :) diff --git a/map/mwm_tests/multithread_mwm_test.cpp b/map/mwm_tests/multithread_mwm_test.cpp index 22529e5752..974cb436a4 100644 --- a/map/mwm_tests/multithread_mwm_test.cpp +++ b/map/mwm_tests/multithread_mwm_test.cpp @@ -63,7 +63,7 @@ namespace SourceT src; src.InitClassificator(); - UNUSED_VALUE(src.RegisterMap(file + DATA_FILE_EXTENSION)); + UNUSED_VALUE(src.RegisterMap(platform::LocalCountryFile::MakeForTesting(file))); // Check that country rect is valid and not infinity. m2::RectD const r = src.GetWorldRect(); diff --git a/map/mwm_tests/mwm_foreach_test.cpp b/map/mwm_tests/mwm_foreach_test.cpp index 6036885f89..ce23dc7eea 100644 --- a/map/mwm_tests/mwm_foreach_test.cpp +++ b/map/mwm_tests/mwm_foreach_test.cpp @@ -247,17 +247,18 @@ public: } }; -void RunTest(string const & file) +void RunTest(string const & countryFileName) { model::FeaturesFetcher src1; src1.InitClassificator(); - UNUSED_VALUE(src1.RegisterMap(file)); + platform::LocalCountryFile localFile(platform::LocalCountryFile::MakeForTesting(countryFileName)); + UNUSED_VALUE(src1.RegisterMap(localFile)); vector<m2::RectD> rects; rects.push_back(src1.GetWorldRect()); - ModelReaderPtr reader = GetPlatform().GetReader(file); + ModelReaderPtr reader = GetPlatform().GetCountryReader(localFile, TMapOptions::EMap); while (!rects.empty()) { @@ -301,15 +302,10 @@ void RunTest(string const & file) } } -void RunTestForChoice(string const & fName) -{ - RunTest(fName + DATA_FILE_EXTENSION); -} - } UNIT_TEST(ForEach_QueryResults) { - RunTestForChoice("minsk-pass"); + RunTest("minsk-pass"); //RunTestForChoice("london-center"); } diff --git a/map/mwm_tests/mwm_index_test.cpp b/map/mwm_tests/mwm_index_test.cpp index 5f3b3e509e..700d1ddf12 100644 --- a/map/mwm_tests/mwm_index_test.cpp +++ b/map/mwm_tests/mwm_index_test.cpp @@ -36,10 +36,11 @@ public: } }; -bool RunTest(string const & fileName, int lowS, int highS) +bool RunTest(string const & countryFileName, int lowS, int highS) { model::FeaturesFetcher src; - pair<MwmSet::MwmLock, bool> const p = src.RegisterMap(fileName); + pair<MwmSet::MwmLock, bool> const p = + src.RegisterMap(platform::LocalCountryFile::MakeForTesting(countryFileName)); if (!p.second) return false; MwmSet::MwmLock const & lock = p.first; @@ -66,8 +67,8 @@ UNIT_TEST(ForEachFeatureID_Test) classificator::Load(); /// @todo Uncomment World* checking after next map data update. - //TEST(RunTest("World.mwm", 0, scales::GetUpperWorldScale()), ()); - //TEST(RunTest("WorldCoasts.mwm", 0, scales::GetUpperWorldScale()), ()); - //TEST(RunTest("Belarus.mwm", scales::GetUpperWorldScale() + 1, scales::GetUpperStyleScale()), ()); - TEST(RunTest("minsk-pass.mwm", scales::GetUpperWorldScale() + 1, scales::GetUpperStyleScale()), ()); + // TEST(RunTest("World", 0, scales::GetUpperWorldScale()), ()); + // TEST(RunTest("WorldCoasts.mwm", 0, scales::GetUpperWorldScale()), ()); + // TEST(RunTest("Belarus", scales::GetUpperWorldScale() + 1, scales::GetUpperStyleScale()), ()); + TEST(RunTest("minsk-pass", scales::GetUpperWorldScale() + 1, scales::GetUpperStyleScale()), ()); } diff --git a/map/routing_session.cpp b/map/routing_session.cpp index 64aa286261..431f13ee00 100644 --- a/map/routing_session.cpp +++ b/map/routing_session.cpp @@ -8,12 +8,10 @@ #include "coding/internal/file_data.hpp" - using namespace location; namespace routing { - static int const ON_ROUTE_MISSED_COUNT = 5; RoutingSession::RoutingSession() @@ -46,7 +44,7 @@ void RoutingSession::RebuildRoute(m2::PointD const & startPoint, TReadyCallbackF DoReadyCallback(*this, callback, m_routeSessionMutex)); } -void RoutingSession::DoReadyCallback::operator() (Route & route, IRouter::ResultCode e) +void RoutingSession::DoReadyCallback::operator()(Route & route, IRouter::ResultCode e) { threads::MutexGuard guard(m_routeSessionMutexInner); UNUSED_VALUE(guard); @@ -67,6 +65,8 @@ void RoutingSession::Reset() threads::MutexGuard guard(m_routeSessionMutex); UNUSED_VALUE(guard); Route(string()).Swap(m_route); + + m_router->ClearState(); } RoutingSession::State RoutingSession::OnLocationPositionChanged(m2::PointD const & position, @@ -196,14 +196,8 @@ void RoutingSession::SetRouter(unique_ptr<IRouter> && router, TRoutingStatistics m_router.reset(new AsyncRouter(move(router), routingStatisticsFn)); } -void RoutingSession::DeleteIndexFile(string const & fileName) -{ - Reset(); - m_router->ClearState(); - (void) my::DeleteFileX(GetPlatform().WritablePathForFile(fileName)); -} - -void RoutingSession::MatchLocationToRoute(location::GpsInfo & location, location::RouteMatchingInfo & routeMatchingInfo) const +void RoutingSession::MatchLocationToRoute(location::GpsInfo & location, + location::RouteMatchingInfo & routeMatchingInfo) const { if (m_state != State::OnRoute) return; @@ -212,5 +206,4 @@ void RoutingSession::MatchLocationToRoute(location::GpsInfo & location, location UNUSED_VALUE(guard); m_route.MatchLocationToRoute(location, routeMatchingInfo); } - } diff --git a/map/routing_session.hpp b/map/routing_session.hpp index fd1abdb910..98a7606ae3 100644 --- a/map/routing_session.hpp +++ b/map/routing_session.hpp @@ -13,24 +13,25 @@ #include "base/mutex.hpp" - -namespace location { class RouteMatchingInfo;} +namespace location +{ +class RouteMatchingInfo; +} namespace routing { - class RoutingSession { public: enum State { RoutingNotActive, - RouteBuilding, // we requested a route and wait when it will be builded - RouteNotReady, // routing was not build - RouteNotStarted, // route is builded but the user isn't on it - OnRoute, // user follows the route - RouteNeedRebuild, // user left the route - RouteFinished // destination point is reached but the session isn't closed + RouteBuilding, // we requested a route and wait when it will be builded + RouteNotReady, // routing was not build + RouteNotStarted, // route is builded but the user isn't on it + OnRoute, // user follows the route + RouteNeedRebuild, // user left the route + RouteFinished // destination point is reached but the session isn't closed }; /* @@ -47,7 +48,7 @@ public: typedef function<void(map<string, string> const &)> TRoutingStatisticsCallback; - typedef function<void (Route const &, IRouter::ResultCode)> TReadyCallbackFn; + typedef function<void(Route const &, IRouter::ResultCode)> TReadyCallbackFn; RoutingSession(); @@ -55,7 +56,8 @@ public: TRoutingStatisticsCallback const & routingStatisticsFn); /// @param[in] startPoint and endPoint in mercator - void BuildRoute(m2::PointD const & startPoint, m2::PointD const & endPoint, TReadyCallbackFn const & callback); + void BuildRoute(m2::PointD const & startPoint, m2::PointD const & endPoint, + TReadyCallbackFn const & callback); void RebuildRoute(m2::PointD const & startPoint, TReadyCallbackFn const & callback); m2::PointD GetEndPoint() const { return m_endPoint; } @@ -68,8 +70,8 @@ public: State OnLocationPositionChanged(m2::PointD const & position, location::GpsInfo const & info); void GetRouteFollowingInfo(location::FollowingInfo & info) const; - void DeleteIndexFile(string const & fileName); - void MatchLocationToRoute(location::GpsInfo & location, location::RouteMatchingInfo & routeMatchingInfo) const; + void MatchLocationToRoute(location::GpsInfo & location, + location::RouteMatchingInfo & routeMatchingInfo) const; // TODO (Dragunov) Make activation of the pedestrian routing void ActivateAdditionalFeatures() {} @@ -81,12 +83,13 @@ private: TReadyCallbackFn m_callback; threads::Mutex & m_routeSessionMutexInner; - DoReadyCallback(RoutingSession & rs, TReadyCallbackFn const & cb, threads::Mutex & routeSessionMutex) - : m_rs(rs), m_callback(cb), m_routeSessionMutexInner(routeSessionMutex) + DoReadyCallback(RoutingSession & rs, TReadyCallbackFn const & cb, + threads::Mutex & routeSessionMutex) + : m_rs(rs), m_callback(cb), m_routeSessionMutexInner(routeSessionMutex) { } - void operator() (Route & route, IRouter::ResultCode e); + void operator()(Route & route, IRouter::ResultCode e); }; void AssignRoute(Route & route); @@ -103,5 +106,4 @@ private: int m_moveAwayCounter; m2::PointD m_lastGoodPosition; }; - } diff --git a/pedestrian_routing_benchmarks/pedestrian_routing_benchmarks.cpp b/pedestrian_routing_benchmarks/pedestrian_routing_benchmarks.cpp index 3cffe31eaa..572388e540 100644 --- a/pedestrian_routing_benchmarks/pedestrian_routing_benchmarks.cpp +++ b/pedestrian_routing_benchmarks/pedestrian_routing_benchmarks.cpp @@ -17,11 +17,13 @@ #include "std/utility.hpp" #include "std/vector.hpp" +using platform::CountryFile; +using platform::LocalCountryFile; + namespace { string const MAP_NAME = "UK_England"; -string const MAP_FILE = MAP_NAME + DATA_FILE_EXTENSION; // Since for test purposes we compare routes lengths to check algorithms consistency, // we should use simplified pedestrian model, where all available edges have max speed @@ -128,8 +130,13 @@ void TestTwoPointsOnFeature(m2::PointD const & startPos, m2::PointD const & fina classificator::Load(); Index index; - UNUSED_VALUE(index.RegisterMap(MAP_FILE)); - TEST(index.IsLoaded(MAP_NAME), ()); + + CountryFile countryFile(MAP_NAME); + LocalCountryFile localFile = LocalCountryFile::MakeForTesting(MAP_NAME); + UNUSED_VALUE(index.RegisterMap(localFile)); + TEST(index.IsLoaded(countryFile), ()); + MwmSet::MwmId const id = index.GetMwmIdByCountryFile(countryFile); + TEST(id.IsAlive(), ()); vector<pair<routing::Edge, m2::PointD>> startEdges; GetNearestPedestrianEdges(index, startPos, startEdges); @@ -149,11 +156,13 @@ void TestTwoPoints(m2::PointD const & startPos, m2::PointD const & finalPos) { classificator::Load(); - Index index; - UNUSED_VALUE(index.RegisterMap(MAP_FILE)); - TEST(index.IsLoaded(MAP_NAME), ()); + CountryFile countryFile(MAP_NAME); + LocalCountryFile localFile = LocalCountryFile::MakeForTesting(MAP_NAME); - MwmSet::MwmId const id = index.GetMwmIdByFileName(MAP_FILE); + Index index; + UNUSED_VALUE(index.RegisterMap(localFile)); + TEST(index.IsLoaded(countryFile), ()); + MwmSet::MwmId const id = index.GetMwmIdByCountryFile(countryFile); TEST(id.IsAlive(), ()); TestRouters(index, startPos, finalPos); diff --git a/platform/country_defines.cpp b/platform/country_defines.cpp index 5a3ab9e8f5..927f883709 100644 --- a/platform/country_defines.cpp +++ b/platform/country_defines.cpp @@ -2,14 +2,25 @@ #include "base/assert.hpp" -bool HasOptions(TMapOptions options, TMapOptions bits) +bool HasOptions(TMapOptions mask, TMapOptions options) { - return (static_cast<uint8_t>(options) & static_cast<uint8_t>(bits)) == static_cast<uint8_t>(bits); + return (static_cast<uint8_t>(mask) & static_cast<uint8_t>(options)) == + static_cast<uint8_t>(options); } -TMapOptions SetOptions(TMapOptions options, TMapOptions bits) +TMapOptions SetOptions(TMapOptions mask, TMapOptions options) { - return static_cast<TMapOptions>(static_cast<uint8_t>(options) | static_cast<uint8_t>(bits)); + return static_cast<TMapOptions>(static_cast<uint8_t>(mask) | static_cast<uint8_t>(options)); +} + +TMapOptions UnsetOptions(TMapOptions mask, TMapOptions options) +{ + return static_cast<TMapOptions>(static_cast<uint8_t>(mask) & ~static_cast<uint8_t>(options)); +} + +TMapOptions LeastSignificantOption(TMapOptions mask) +{ + return static_cast<TMapOptions>(static_cast<uint8_t>(mask) & -static_cast<uint8_t>(mask)); } string DebugPrint(TMapOptions options) @@ -24,8 +35,5 @@ string DebugPrint(TMapOptions options) return "CarRouting"; case TMapOptions::EMapWithCarRouting: return "MapWithCarRouting"; - default: - ASSERT(false, ("Unknown TMapOptions (", static_cast<uint8_t>(options), ")")); - return string(); } } diff --git a/platform/country_defines.hpp b/platform/country_defines.hpp index 11e6f97842..5c010cbd13 100644 --- a/platform/country_defines.hpp +++ b/platform/country_defines.hpp @@ -10,8 +10,12 @@ enum class TMapOptions : uint8_t EMapWithCarRouting = 0x3 }; -bool HasOptions(TMapOptions options, TMapOptions bits); +bool HasOptions(TMapOptions mask, TMapOptions options); -TMapOptions SetOptions(TMapOptions options, TMapOptions bits); +TMapOptions SetOptions(TMapOptions mask, TMapOptions options); + +TMapOptions UnsetOptions(TMapOptions mask, TMapOptions options); + +TMapOptions LeastSignificantOption(TMapOptions mask); string DebugPrint(TMapOptions options); diff --git a/platform/country_file.cpp b/platform/country_file.cpp index 8fffb2a52d..b7f4210e8b 100644 --- a/platform/country_file.cpp +++ b/platform/country_file.cpp @@ -22,8 +22,8 @@ string CountryFile::GetNameWithExt(TMapOptions file) const return m_name + DATA_FILE_EXTENSION + ROUTING_FILE_EXTENSION; default: ASSERT(false, ("Can't get name for:", file)); + return string(); } - return string(); } void CountryFile::SetRemoteSizes(uint32_t mapSize, uint32_t routingSize) diff --git a/platform/local_country_file.cpp b/platform/local_country_file.cpp index b730395ad5..4cf20444ce 100644 --- a/platform/local_country_file.cpp +++ b/platform/local_country_file.cpp @@ -34,20 +34,26 @@ void LocalCountryFile::SyncWithDisk() Platform & platform = GetPlatform(); - if (platform.GetFileSizeByName(GetPath(TMapOptions::EMap), m_mapSize)) + if (platform.GetFileSizeByFullPath(GetPath(TMapOptions::EMap), m_mapSize)) m_files = SetOptions(m_files, TMapOptions::EMap); string const routingPath = GetPath(TMapOptions::ECarRouting); - if (platform.GetFileSizeByName(routingPath, m_routingSize)) + if (platform.GetFileSizeByFullPath(routingPath, m_routingSize)) m_files = SetOptions(m_files, TMapOptions::ECarRouting); } -void LocalCountryFile::DeleteFromDisk() +void LocalCountryFile::DeleteFromDisk(TMapOptions files) const { for (TMapOptions file : {TMapOptions::EMap, TMapOptions::ECarRouting}) { - if (OnDisk(file)) - VERIFY(my::DeleteFileX(GetPath(file)), (file, "from", *this, "wasn't deleted from disk.")); + if (OnDisk(file) && HasOptions(files, file)) + { + if (!my::DeleteFileX(GetPath(file))) + { + LOG(LERROR, (file, "from", *this, "wasn't deleted from disk.")); + ASSERT(false, ()); + } + } } } diff --git a/platform/local_country_file.hpp b/platform/local_country_file.hpp index c73975f772..1a2afa0180 100644 --- a/platform/local_country_file.hpp +++ b/platform/local_country_file.hpp @@ -25,8 +25,9 @@ public: // their sizes etc. with disk. void SyncWithDisk(); - // Removes known country files from disk. - void DeleteFromDisk(); + // Removes specified files from disk if they're known for LocalCountryFile, i.e. + // were found by previous SyncWithDisk() call. + void DeleteFromDisk(TMapOptions files) const; // Returns path to a file. Return value may be empty until // SyncWithDisk() is called. @@ -47,6 +48,7 @@ public: return (static_cast<unsigned>(m_files) & static_cast<unsigned>(filesMask)) == static_cast<unsigned>(filesMask); } + inline string const & GetDirectory() const { return m_directory; } inline int64_t GetVersion() const { return m_version; } inline CountryFile const & GetCountryFile() const { return m_countryFile; } diff --git a/platform/local_country_file_utils.cpp b/platform/local_country_file_utils.cpp index 87e76142d3..4c2ca4993a 100644 --- a/platform/local_country_file_utils.cpp +++ b/platform/local_country_file_utils.cpp @@ -1,12 +1,16 @@ #include "platform/local_country_file_utils.hpp" #include "platform/platform.hpp" + #include "coding/file_name_utils.hpp" #include "coding/file_writer.hpp" + #include "base/string_utils.hpp" #include "base/logging.hpp" + #include "std/algorithm.hpp" #include "std/cctype.hpp" +#include "std/sstream.hpp" namespace platform { @@ -26,10 +30,11 @@ void CleanupMapsDirectory() // Remove partially downloaded maps. { Platform::FilesList files; - string const regexp = "\\" DATA_FILE_EXTENSION "\\.(downloading2?$|resume2?$)"; + // .(downloading|resume|ready)[0-9]?$ + string const regexp = "\\.(downloading|resume|ready)[0-9]?$"; platform.GetFilesByRegExp(mapsDir, regexp, files); for (string const & file : files) - FileWriter::DeleteFileX(file); + FileWriter::DeleteFileX(my::JoinFoldersToPath(mapsDir, file)); } // Find and remove Brazil and Japan maps. @@ -41,7 +46,7 @@ void CleanupMapsDirectory() if (countryFile.GetNameWithoutExt() == "Japan" || countryFile.GetNameWithoutExt() == "Brazil") { localFile.SyncWithDisk(); - localFile.DeleteFromDisk(); + localFile.DeleteFromDisk(TMapOptions::EMapWithCarRouting); } } @@ -104,9 +109,10 @@ void FindAllLocalMaps(vector<LocalCountryFile> & localFiles) { int64_t version; if (ParseVersion(subdir, version)) - FindAllLocalMapsInDirectory(my::JoinFoldersToPath(directory, subdir), version, allFiles); + FindAllLocalMapsInDirectory(my::JoinFoldersToPath(directory, subdir), version, localFiles); } } + #if defined(OMIM_OS_ANDROID) // On Android World and WorldCoasts can be stored in alternative /Android/obb/ path. for (string const & file : {WORLD_FILE_NAME, WORLD_COASTS_FILE_NAME}) @@ -135,7 +141,6 @@ void FindAllLocalMaps(vector<LocalCountryFile> & localFiles) } } #endif // defined(OMIM_OS_ANDROID) - localFiles.insert(localFiles.end(), allFiles.begin(), allFiles.end()); } @@ -143,6 +148,7 @@ bool ParseVersion(string const & s, int64_t & version) { if (s.empty() || s.size() > kMaxTimestampLength) return false; + int64_t v = 0; for (char const c : s) { @@ -162,22 +168,28 @@ shared_ptr<LocalCountryFile> PreparePlaceForCountryFiles(CountryFile const & cou return make_shared<LocalCountryFile>(platform.WritableDir(), countryFile, version); string const directory = my::JoinFoldersToPath(platform.WritableDir(), strings::to_string(version)); - switch (platform.MkDir(directory)) + Platform::EError ret = platform.MkDir(directory); + switch (ret) { case Platform::ERR_OK: return make_shared<LocalCountryFile>(directory, countryFile, version); case Platform::ERR_FILE_ALREADY_EXISTS: { Platform::EFileType type; - if (Platform::GetFileType(directory, type) != Platform::ERR_OK || - type != Platform::FILE_TYPE_DIRECTORY) + if (Platform::GetFileType(directory, type) != Platform::ERR_OK) { + LOG(LERROR, ("Can't determine file type for:", directory)); + return shared_ptr<LocalCountryFile>(); + } + if (type != Platform::FILE_TYPE_DIRECTORY) + { + LOG(LERROR, (directory, "exists, but not a directory:", type)); return shared_ptr<LocalCountryFile>(); } return make_shared<LocalCountryFile>(directory, countryFile, version); } default: return shared_ptr<LocalCountryFile>(); - }; + } } } // namespace platform diff --git a/platform/platform.cpp b/platform/platform.cpp index a5b0f720a5..d7b04686cf 100644 --- a/platform/platform.cpp +++ b/platform/platform.cpp @@ -1,5 +1,7 @@ #include "platform/platform.hpp" +#include "platform/local_country_file.hpp" + #include "coding/sha2.hpp" #include "coding/base64.hpp" #include "coding/file_name_utils.hpp" @@ -147,3 +149,10 @@ string Platform::GetIndexFileName(string const & mwmName, string const & extensi { return GetPlatform().WritablePathForCountryIndexes(mwmName) + mwmName + extension; } + +ModelReader * Platform::GetCountryReader(platform::LocalCountryFile const & file, + TMapOptions options) const +{ + LOG(LINFO, ("Getting country reader for:", file, options)); + return GetReader(file.GetPath(options), "f"); +} diff --git a/platform/platform.hpp b/platform/platform.hpp index 91f86740d7..7effb78d90 100644 --- a/platform/platform.hpp +++ b/platform/platform.hpp @@ -1,5 +1,7 @@ #pragma once +#include "platform/country_defines.hpp" + #include "coding/reader.hpp" #include "base/exception.hpp" @@ -12,10 +14,14 @@ #include "defines.hpp" - DECLARE_EXCEPTION(FileAbsentException, RootException); DECLARE_EXCEPTION(NotImplementedException, RootException); +namespace platform +{ +class LocalCountryFile; +} + class Platform { public: @@ -109,6 +115,9 @@ public: /// @return full path to file in the settings directory string SettingsPathForFile(string const & file) const { return SettingsDir() + file; } + ModelReader * GetCountryReader(platform::LocalCountryFile const & file, + TMapOptions options) const; + /// @return reader for file decriptor. /// @throws FileAbsentException /// @param[in] file name or full path which we want to read, don't forget to free memory or wrap it to ReaderPtr diff --git a/platform/platform_android.cpp b/platform/platform_android.cpp index 92e6fde61c..225547015e 100644 --- a/platform/platform_android.cpp +++ b/platform/platform_android.cpp @@ -20,8 +20,15 @@ Platform::Platform() namespace { - -enum SourceT { EXTERNAL_RESOURCE, RESOURCE, WRITABLE_PATH, SETTINGS_PATH, FULL_PATH }; +enum SourceT +{ + EXTERNAL_RESOURCE, + RESOURCE, + WRITABLE_PATH, + SETTINGS_PATH, + FULL_PATH, + SOURCE_COUNT +}; bool IsResource(string const & file, string const & ext) { @@ -40,7 +47,8 @@ bool IsResource(string const & file, string const & ext) return true; } -size_t GetSearchSources(string const & file, string const & searchScope, SourceT (&arr)[4]) +size_t GetSearchSources(string const & file, string const & searchScope, + SourceT (&arr)[SOURCE_COUNT]) { size_t ret = 0; @@ -88,7 +96,7 @@ ModelReader * Platform::GetReader(string const & file, string const & searchScop uint32_t const logPageSize = (ext == DATA_FILE_EXTENSION) ? READER_CHUNK_LOG_SIZE : 10; uint32_t const logPageCount = (ext == DATA_FILE_EXTENSION) ? READER_CHUNK_LOG_COUNT : 4; - SourceT sources[4]; + SourceT sources[SOURCE_COUNT]; size_t n = 0; if (searchScope.empty()) @@ -172,6 +180,7 @@ ModelReader * Platform::GetReader(string const & file, string const & searchScop } } + LOG(LERROR, ("Can't get reader for:", file)); MYTHROW(FileAbsentException, ("File not found", file)); return 0; } @@ -238,8 +247,9 @@ bool Platform::GetFileSizeByName(string const & fileName, uint64_t & size) const size = ReaderPtr<Reader>(GetReader(fileName)).Size(); return true; } - catch (RootException const &) + catch (RootException const & ex) { + LOG(LWARNING, ("Can't get file size for:", fileName)); return false; } } @@ -249,7 +259,7 @@ Platform::EError Platform::MkDir(string const & dirName) const if (mkdir(dirName.c_str(), 0755)) { LOG(LWARNING, ("Can't create directory: ", dirName)); - return Platform::ERR_UNKNOWN; + return ErrnoToError(); } return Platform::ERR_OK; } diff --git a/platform/platform_ios.mm b/platform/platform_ios.mm index 5fd1310059..cb14de0171 100644 --- a/platform/platform_ios.mm +++ b/platform/platform_ios.mm @@ -37,7 +37,7 @@ Platform::Platform() Platform::EError Platform::MkDir(string const & dirName) const { if (::mkdir(dirName.c_str(), 0755)) - return Platform::ERR_UNKNOWN; + return ErrnoToError(); return Platform::ERR_OK; } diff --git a/platform/platform_tests/local_country_file_tests.cpp b/platform/platform_tests/local_country_file_tests.cpp index 5594a4e63f..12aace8803 100644 --- a/platform/platform_tests/local_country_file_tests.cpp +++ b/platform/platform_tests/local_country_file_tests.cpp @@ -7,6 +7,7 @@ #include "coding/file_name_utils.hpp" #include "coding/file_writer.hpp" +#include "coding/internal/file_data.hpp" #include "base/scope_guard.hpp" @@ -26,24 +27,30 @@ bool Contains(vector<T> const & v, T const & t) return find(v.begin(), v.end(), t) != v.end(); } +// Scoped test directory in a writable dir. class ScopedTestDir { public: - ScopedTestDir(string const & path) : m_path(path), m_reset(false) + /// Creates test dir in a writable directory. + /// @param path Path for a testing directory, should be relative to writable-dir. + ScopedTestDir(string const & relativePath) + : m_fullPath(my::JoinFoldersToPath(GetPlatform().WritableDir(), relativePath)), + m_relativePath(relativePath), + m_reset(false) { Platform & platform = GetPlatform(); - Platform::EError ret = platform.MkDir(m_path); + Platform::EError ret = platform.MkDir(GetFullPath()); switch (ret) { case Platform::ERR_OK: break; case Platform::ERR_FILE_ALREADY_EXISTS: Platform::EFileType type; - TEST_EQUAL(Platform::ERR_OK, Platform::GetFileType(m_path, type), ()); + TEST_EQUAL(Platform::ERR_OK, Platform::GetFileType(GetFullPath(), type), ()); TEST_EQUAL(Platform::FILE_TYPE_DIRECTORY, type, ()); break; default: - CHECK(false, ("Can't create directory:", m_path, "error:", ret)); + CHECK(false, ("Can't create directory:", GetFullPath(), "error:", ret)); break; } } @@ -53,43 +60,99 @@ public: if (m_reset) return; - Platform::EError ret = Platform::RmDir(m_path); + Platform::EError ret = Platform::RmDir(GetFullPath()); switch (ret) { case Platform::ERR_OK: break; case Platform::ERR_FILE_DOES_NOT_EXIST: - LOG(LWARNING, (m_path, "was deleted before destruction of ScopedTestDir.")); + LOG(LWARNING, (GetFullPath(), "was deleted before destruction of ScopedTestDir.")); break; case Platform::ERR_DIRECTORY_NOT_EMPTY: - LOG(LWARNING, ("There are files in", m_path)); + LOG(LWARNING, ("There are files in", GetFullPath())); break; default: - LOG(LWARNING, ("Platform::RmDir() error:", ret)); + LOG(LWARNING, ("Platform::RmDir() error for", GetFullPath(), ":", ret)); break; } } inline void Reset() { m_reset = true; } - inline string const GetPath() const { return m_path; } + inline string const & GetFullPath() const { return m_fullPath; } + + inline string const & GetRelativePath() const { return m_relativePath; } + + bool Exists() const { return GetPlatform().IsFileExistsByFullPath(GetFullPath()); } private: - string const m_path; + string const m_fullPath; + string const m_relativePath; bool m_reset; DISALLOW_COPY_AND_MOVE(ScopedTestDir); }; -void CreateTestFile(string const & testFile, string const & contents) +class ScopedTestFile { +public: + ScopedTestFile(string const & relativePath, string const & contents) + : m_fullPath(my::JoinFoldersToPath(GetPlatform().WritableDir(), relativePath)), m_reset(false) + { + { + FileWriter writer(GetFullPath()); + writer.Write(contents.data(), contents.size()); + } + TEST(Exists(), ("Can't create test file", GetFullPath())); + } + + ScopedTestFile(ScopedTestDir const & dir, CountryFile const & countryFile, TMapOptions file, + string const & contents) + : ScopedTestFile( + my::JoinFoldersToPath(dir.GetRelativePath(), countryFile.GetNameWithExt(file)), + contents) { - FileWriter writer(testFile); - writer.Write(contents.data(), contents.size()); } - TEST(Platform::IsFileExistsByFullPath(testFile), ("Can't create test file", testFile)); + + ~ScopedTestFile() + { + if (m_reset) + return; + if (!Exists()) + { + LOG(LWARNING, ("File", GetFullPath(), "was deleted before dtor of ScopedTestFile.")); + return; + } + if (!my::DeleteFileX(GetFullPath())) + LOG(LWARNING, ("Can't remove test file:", GetFullPath())); + } + + inline string const & GetFullPath() const { return m_fullPath; } + + inline void Reset() { m_reset = true; } + + bool Exists() const { return GetPlatform().IsFileExistsByFullPath(GetFullPath()); } + +private: + string const m_fullPath; + bool m_reset; + + DISALLOW_COPY_AND_MOVE(ScopedTestFile); +}; + +string DebugPrint(ScopedTestDir const & dir) +{ + ostringstream os; + os << "ScopedTestDir [" << dir.GetFullPath() << "]"; + return os.str(); } +string DebugPrint(ScopedTestFile const & file) +{ + ostringstream os; + os << "ScopedTestFile [" << file.GetFullPath() << "]"; + return os.str(); +} } // namespace // Checks that all unsigned numbers less than 10 ^ 18 can be parsed as @@ -160,17 +223,12 @@ UNIT_TEST(LocalCountryFile_DiskFiles) CountryFile countryFile("TestCountry"); countryFile.SetRemoteSizes(1 /* mapSize */, 2 /* routingSize */); - string const testMapFile = - my::JoinFoldersToPath(platform.WritableDir(), countryFile.GetNameWithExt(TMapOptions::EMap)); - string const testRoutingFile = my::JoinFoldersToPath( - platform.WritableDir(), countryFile.GetNameWithExt(TMapOptions::ECarRouting)); - LocalCountryFile localFile(platform.WritableDir(), countryFile, 0 /* version */); TEST(!localFile.OnDisk(TMapOptions::EMap), ()); TEST(!localFile.OnDisk(TMapOptions::ECarRouting), ()); TEST(!localFile.OnDisk(TMapOptions::EMapWithCarRouting), ()); - CreateTestFile(testMapFile, "map"); + ScopedTestFile testMapFile(countryFile.GetNameWithExt(TMapOptions::EMap), "map"); localFile.SyncWithDisk(); TEST(localFile.OnDisk(TMapOptions::EMap), ()); @@ -178,7 +236,7 @@ UNIT_TEST(LocalCountryFile_DiskFiles) TEST(!localFile.OnDisk(TMapOptions::EMapWithCarRouting), ()); TEST_EQUAL(3, localFile.GetSize(TMapOptions::EMap), ()); - CreateTestFile(testRoutingFile, "routing"); + ScopedTestFile testRoutingFile(countryFile.GetNameWithExt(TMapOptions::ECarRouting), "routing"); localFile.SyncWithDisk(); TEST(localFile.OnDisk(TMapOptions::EMap), ()); @@ -188,14 +246,15 @@ UNIT_TEST(LocalCountryFile_DiskFiles) TEST_EQUAL(7, localFile.GetSize(TMapOptions::ECarRouting), ()); TEST_EQUAL(10, localFile.GetSize(TMapOptions::EMapWithCarRouting), ()); - localFile.DeleteFromDisk(); - TEST(!platform.IsFileExistsByFullPath(testMapFile), - ("Map file", testMapFile, "wasn't deleted by LocalCountryFile.")); - TEST(!platform.IsFileExistsByFullPath(testRoutingFile), - ("Routing file", testRoutingFile, "wasn't deleted by LocalCountryFile.")); + localFile.DeleteFromDisk(TMapOptions::EMapWithCarRouting); + TEST(!testMapFile.Exists(), (testMapFile, "wasn't deleted by LocalCountryFile.")); + testMapFile.Reset(); + + TEST(!testRoutingFile.Exists(), (testRoutingFile, "wasn't deleted by LocalCountryFile.")); + testRoutingFile.Reset(); } -UNIT_TEST(LocalCountryFile_DirectoryCleanup) +UNIT_TEST(LocalCountryFile_CleanupMapFiles) { Platform & platform = GetPlatform(); string const mapsDir = platform.WritableDir(); @@ -204,19 +263,19 @@ UNIT_TEST(LocalCountryFile_DirectoryCleanup) CountryFile brazilFile("Brazil"); CountryFile irelandFile("Ireland"); - ScopedTestDir testDir1(my::JoinFoldersToPath(mapsDir, "1")); - LocalCountryFile japanLocalFile(testDir1.GetPath(), japanFile, 1 /* version */); - CreateTestFile(japanLocalFile.GetPath(TMapOptions::EMap), "Japan"); - - ScopedTestDir testDir2(my::JoinFoldersToPath(mapsDir, "2")); - LocalCountryFile brazilLocalFile(testDir2.GetPath(), brazilFile, 2 /* version */); - CreateTestFile(brazilLocalFile.GetPath(TMapOptions::EMap), "Brazil"); + ScopedTestDir testDir1("1"); + LocalCountryFile japanLocalFile(testDir1.GetFullPath(), japanFile, 1 /* version */); + ScopedTestFile japanMapFile(testDir1, japanFile, TMapOptions::EMap, "Japan"); - ScopedTestDir testDir3(my::JoinFoldersToPath(mapsDir, "3")); + ScopedTestDir testDir2("2"); + LocalCountryFile brazilLocalFile(testDir2.GetFullPath(), brazilFile, 2 /* version */); + ScopedTestFile brazilMapFile(testDir2, brazilFile, TMapOptions::EMap, "Brazil"); + LocalCountryFile irelandLocalFile(testDir2.GetFullPath(), irelandFile, 2 /* version */); + ScopedTestFile irelandMapFile(testDir2, irelandFile, TMapOptions::EMap, "Ireland"); - LocalCountryFile irelandLocalFile(testDir2.GetPath(), irelandFile, 2 /* version */); - CreateTestFile(irelandLocalFile.GetPath(TMapOptions::EMap), "Ireland"); + ScopedTestDir testDir3("3"); + // Check that FindAllLocalMaps() vector<LocalCountryFile> localFiles; FindAllLocalMaps(localFiles); TEST(Contains(localFiles, japanLocalFile), (japanLocalFile)); @@ -227,20 +286,47 @@ UNIT_TEST(LocalCountryFile_DirectoryCleanup) japanLocalFile.SyncWithDisk(); TEST_EQUAL(TMapOptions::ENothing, japanLocalFile.GetFiles(), ()); - TEST(!Platform::IsFileExistsByFullPath(testDir1.GetPath()), ("Empty directory wasn't removed.")); + TEST(!testDir1.Exists(), ("Empty directory", testDir1, "wasn't removed.")); testDir1.Reset(); + TEST(!japanMapFile.Exists(), (japanMapFile)); + japanMapFile.Reset(); brazilLocalFile.SyncWithDisk(); TEST_EQUAL(TMapOptions::ENothing, brazilLocalFile.GetFiles(), ()); + TEST(!brazilMapFile.Exists(), (brazilMapFile)); + brazilMapFile.Reset(); irelandLocalFile.SyncWithDisk(); TEST_EQUAL(TMapOptions::EMap, irelandLocalFile.GetFiles(), ()); - irelandLocalFile.DeleteFromDisk(); + irelandLocalFile.DeleteFromDisk(TMapOptions::EMap); + TEST(!irelandMapFile.Exists(), (irelandMapFile)); + irelandMapFile.Reset(); - TEST(!Platform::IsFileExistsByFullPath(testDir3.GetPath()), ("Empty directory wasn't removed.")); + TEST(!testDir3.Exists(), ("Empty directory", testDir3, "wasn't removed.")); testDir3.Reset(); } +UNIT_TEST(LocalCountryFile_CleanupPartiallyDownloadedFiles) +{ + ScopedTestFile toBeDeleted[] = {{"Ireland.mwm.ready", "Ireland"}, + {"Netherlands.mwm.routing.downloading2", "Netherlands"}, + {"Germany.mwm.ready3", "Germany"}, + {"UK_England.mwm.resume4", "UK"}}; + ScopedTestFile toBeKept[] = { + {"Italy.mwm", "Italy"}, {"Spain.mwm", "Spain map"}, {"Spain.mwm.routing", "Spain routing"}}; + + CleanupMapsDirectory(); + + for (ScopedTestFile & file : toBeDeleted) + { + TEST(!file.Exists(), (file)); + file.Reset(); + } + + for (ScopedTestFile & file : toBeKept) + TEST(file.Exists(), (file)); +} + // Creates test-dir and following files: // * test-dir/Ireland.mwm // * test-dir/Netherlands.mwm @@ -253,37 +339,24 @@ UNIT_TEST(LocalCountryFile_DirectoryLookup) CountryFile const irelandFile("Ireland"); CountryFile const netherlandsFile("Netherlands"); - Platform & platform = GetPlatform(); - - ScopedTestDir testDir(my::JoinFoldersToPath(platform.WritableDir(), "test-dir")); - - string const testIrelandMapFile = - my::JoinFoldersToPath(testDir.GetPath(), irelandFile.GetNameWithExt(TMapOptions::EMap)); - CreateTestFile(testIrelandMapFile, "Ireland-map"); - MY_SCOPE_GUARD(removeTestIrelandMapFile, bind(&FileWriter::DeleteFileX, testIrelandMapFile)); + ScopedTestDir testDir("test-dir"); - string const testNetherlandsMapFile = - my::JoinFoldersToPath(testDir.GetPath(), netherlandsFile.GetNameWithExt(TMapOptions::EMap)); - CreateTestFile(testNetherlandsMapFile, "Netherlands-map"); - MY_SCOPE_GUARD(removeTestNetherlandsMapFile, - bind(&FileWriter::DeleteFileX, testNetherlandsMapFile)); - - string const testNetherlandsRoutingFile = my::JoinFoldersToPath( - testDir.GetPath(), netherlandsFile.GetNameWithExt(TMapOptions::ECarRouting)); - CreateTestFile(testNetherlandsRoutingFile, "Netherlands-routing"); - MY_SCOPE_GUARD(removeTestNetherlandsRoutingFile, - bind(&FileWriter::DeleteFileX, testNetherlandsRoutingFile)); + ScopedTestFile testIrelandMapFile(testDir, irelandFile, TMapOptions::EMap, "Ireland-map"); + ScopedTestFile testNetherlandsMapFile(testDir, netherlandsFile, TMapOptions::EMap, + "Netherlands-map"); + ScopedTestFile testNetherlandsRoutingFile(testDir, netherlandsFile, TMapOptions::ECarRouting, + "Netherlands-routing"); vector<LocalCountryFile> localFiles; - FindAllLocalMapsInDirectory(testDir.GetPath(), 150309, localFiles); + FindAllLocalMapsInDirectory(testDir.GetFullPath(), 150309, localFiles); sort(localFiles.begin(), localFiles.end()); for (LocalCountryFile & localFile : localFiles) localFile.SyncWithDisk(); - LocalCountryFile expectedIrelandFile(testDir.GetPath(), irelandFile, 150309); + LocalCountryFile expectedIrelandFile(testDir.GetFullPath(), irelandFile, 150309); expectedIrelandFile.m_files = TMapOptions::EMap; - LocalCountryFile expectedNetherlandsFile(testDir.GetPath(), netherlandsFile, 150309); + LocalCountryFile expectedNetherlandsFile(testDir.GetFullPath(), netherlandsFile, 150309); expectedNetherlandsFile.m_files = TMapOptions::EMapWithCarRouting; vector<LocalCountryFile> expectedLocalFiles = {expectedIrelandFile, expectedNetherlandsFile}; @@ -301,12 +374,8 @@ UNIT_TEST(LocalCountryFile_AllLocalFilesLookup) Platform & platform = GetPlatform(); - ScopedTestDir testDir(my::JoinFoldersToPath(platform.WritableDir(), "010101")); - - string const testItalyMapFile = - my::JoinFoldersToPath(testDir.GetPath(), italyFile.GetNameWithExt(TMapOptions::EMap)); - CreateTestFile(testItalyMapFile, "Italy-map"); - MY_SCOPE_GUARD(remoteTestItalyMapFile, bind(&FileWriter::DeleteFileX, testItalyMapFile)); + ScopedTestDir testDir("010101"); + ScopedTestFile testItalyMapFile(testDir, italyFile, TMapOptions::EMap, "Italy-map"); vector<LocalCountryFile> localFiles; FindAllLocalMaps(localFiles); @@ -320,7 +389,7 @@ UNIT_TEST(LocalCountryFile_AllLocalFilesLookup) CountryFile(WORLD_COASTS_FILE_NAME), 0 /* version */); TEST_EQUAL(1, localFilesSet.count(expectedWorldCoastsFile), ()); - LocalCountryFile expectedItalyFile(testDir.GetPath(), italyFile, 10101); + LocalCountryFile expectedItalyFile(testDir.GetFullPath(), italyFile, 10101); TEST_EQUAL(1, localFilesSet.count(expectedItalyFile), ()); } @@ -335,17 +404,17 @@ UNIT_TEST(LocalCountryFile_PreparePlaceForCountryFiles) TEST(italyLocalFile.get(), ()); TEST_EQUAL(expectedItalyFile, *italyLocalFile, ()); - ScopedTestDir directoryForV1(my::JoinFoldersToPath(platform.WritableDir(), "1")); + ScopedTestDir directoryForV1("1"); CountryFile germanyFile("Germany"); - LocalCountryFile expectedGermanyFile(directoryForV1.GetPath(), germanyFile, 1 /* version */); + LocalCountryFile expectedGermanyFile(directoryForV1.GetFullPath(), germanyFile, 1 /* version */); shared_ptr<LocalCountryFile> germanyLocalFile = PreparePlaceForCountryFiles(germanyFile, 1 /* version */); TEST(germanyLocalFile.get(), ()); TEST_EQUAL(expectedGermanyFile, *germanyLocalFile, ()); CountryFile franceFile("France"); - LocalCountryFile expectedFranceFile(directoryForV1.GetPath(), franceFile, 1 /* version */); + LocalCountryFile expectedFranceFile(directoryForV1.GetFullPath(), franceFile, 1 /* version */); shared_ptr<LocalCountryFile> franceLocalFile = PreparePlaceForCountryFiles(franceFile, 1 /* version */); TEST(franceLocalFile.get(), ()); diff --git a/routing/osrm_router.cpp b/routing/osrm_router.cpp index 1fb3840e50..4f1f9dea72 100644 --- a/routing/osrm_router.cpp +++ b/routing/osrm_router.cpp @@ -4,6 +4,7 @@ #include "turns_generator.hpp" #include "vehicle_model.hpp" +#include "platform/country_file.hpp" #include "platform/platform.hpp" #include "geometry/angles.hpp" @@ -349,9 +350,12 @@ public: }; } // namespace -OsrmRouter::OsrmRouter(Index const * index, TCountryFileFn const & fn, - TRoutingVisualizerFn routingVisualization) - : m_pIndex(index), m_indexManager(fn, index), m_routingVisualization(routingVisualization) +OsrmRouter::OsrmRouter(Index const * index, TCountryFileFn const & countryFileFn, + TCountryLocalFileFn const & countryLocalFileFn, + RoutingVisualizerFn routingVisualization) + : m_pIndex(index), + m_indexManager(countryFileFn, countryLocalFileFn, index), + m_routingVisualization(routingVisualization) { } diff --git a/routing/osrm_router.hpp b/routing/osrm_router.hpp index 3eac5e9483..e10a4a756b 100644 --- a/routing/osrm_router.hpp +++ b/routing/osrm_router.hpp @@ -28,18 +28,17 @@ namespace routing struct RoutePathCross; using TCheckedPath = vector<RoutePathCross>; -typedef OsrmDataFacade<QueryEdge::EdgeData> TDataFacade; - /// All edges available for start route while routing typedef vector<FeatureGraphNode> TFeatureGraphNodeVec; - class OsrmRouter : public IRouter { public: typedef vector<double> GeomTurnCandidateT; - OsrmRouter(Index const * index, TCountryFileFn const & fn, TRoutingVisualizerFn routingVisualization = nullptr); + OsrmRouter(Index const * index, TCountryFileFn const & countryFileFn, + TCountryLocalFileFn const & countryLocalFileFn, + RoutingVisualizerFn routingVisualization = nullptr); virtual string GetName() const; diff --git a/routing/road_graph_router.cpp b/routing/road_graph_router.cpp index 7224e73d34..3f1a1b67cc 100644 --- a/routing/road_graph_router.cpp +++ b/routing/road_graph_router.cpp @@ -92,7 +92,8 @@ IRouter::ResultCode RoadGraphRouter::CalculateRoute(m2::PointD const & startPoin if (m_countryFileFn(startPoint) != mwmName) return PointsInDifferentMWM; - MwmSet::MwmLock const mwmLock = const_cast<Index&>(m_index).GetMwmLockByFileName(mwmName); + platform::CountryFile countryFile(mwmName); + MwmSet::MwmLock const mwmLock = const_cast<Index &>(m_index).GetMwmLockByCountryFile(countryFile); if (!mwmLock.IsLocked()) return RouteFileNotExist; diff --git a/routing/routing_mapping.cpp b/routing/routing_mapping.cpp index 1d2001c578..c760cf1921 100644 --- a/routing/routing_mapping.cpp +++ b/routing/routing_mapping.cpp @@ -10,34 +10,49 @@ #include "indexer/mwm_version.hpp" +#include "platform/country_file.hpp" +#include "platform/local_country_file.hpp" #include "platform/platform.hpp" +using platform::CountryFile; +using platform::LocalCountryFile; + namespace routing { -RoutingMapping::RoutingMapping(string const & fName, Index const * pIndex) +RoutingMapping::RoutingMapping(CountryFile const & countryFile) : m_mapCounter(0), m_facadeCounter(0), m_crossContextLoaded(0), - m_baseName(fName), + m_countryFileName(countryFile.GetNameWithoutExt()), + m_isValid(false), + m_error(IRouter::ResultCode::RouteFileNotExist) +{ +} + +RoutingMapping::RoutingMapping(LocalCountryFile const & localFile, Index const * pIndex) + : m_mapCounter(0), + m_facadeCounter(0), + m_crossContextLoaded(0), + m_countryFileName(localFile.GetCountryFile().GetNameWithoutExt()), m_isValid(true), m_error(IRouter::ResultCode::NoError) { Platform & pl = GetPlatform(); - string const mwmName = m_baseName + DATA_FILE_EXTENSION; - string const fPath = pl.WritablePathForFile(mwmName + ROUTING_FILE_EXTENSION); - if (!pl.IsFileExistsByFullPath(fPath)) + if (!HasOptions(localFile.GetFiles(), TMapOptions::EMapWithCarRouting)) { m_isValid = false; m_error = IRouter::ResultCode::RouteFileNotExist; return; } + string const routingFilePath = localFile.GetPath(TMapOptions::ECarRouting); // Open new container and check that mwm and routing have equal timestamp. - LOG(LDEBUG, ("Load routing index for file:", fPath)); - m_container.Open(fPath); + LOG(LDEBUG, ("Load routing index for file:", routingFilePath)); + m_container.Open(routingFilePath); { FileReader r1 = m_container.GetReader(VERSION_FILE_TAG); ReaderSrc src1(r1); - ModelReaderPtr r2 = FilesContainerR(pl.GetReader(mwmName)).GetReader(VERSION_FILE_TAG); + ModelReaderPtr r2 = FilesContainerR(pl.GetCountryReader(localFile, TMapOptions::EMap)) + .GetReader(VERSION_FILE_TAG); ReaderSrc src2(r2.GetPtr()); version::MwmVersion version1; @@ -55,7 +70,7 @@ RoutingMapping::RoutingMapping(string const & fName, Index const * pIndex) } } - m_mwmId = pIndex->GetMwmIdByFileName(mwmName); + m_mwmId = pIndex->GetMwmIdByCountryFile(localFile.GetCountryFile()); } RoutingMapping::~RoutingMapping() @@ -113,22 +128,33 @@ void RoutingMapping::FreeCrossContext() m_crossContext = CrossRoutingContextReader(); } +// static +shared_ptr<RoutingMapping> RoutingMapping::MakeInvalid(platform::CountryFile const & countryFile) +{ + return shared_ptr<RoutingMapping>(new RoutingMapping(countryFile)); +} + TRoutingMappingPtr RoutingIndexManager::GetMappingByPoint(m2::PointD const & point) { - return GetMappingByName(m_countryFn(point)); + return GetMappingByName(m_countryFileFn(point)); } TRoutingMappingPtr RoutingIndexManager::GetMappingByName(string const & mapName) { + shared_ptr<platform::LocalCountryFile> localFile = m_countryLocalFileFn(mapName); + // Return invalid mapping when file does not exist. + if (!localFile.get()) + return RoutingMapping::MakeInvalid(platform::CountryFile(mapName)); + // Check if we have already loaded this file. auto mapIter = m_mapping.find(mapName); if (mapIter != m_mapping.end()) return mapIter->second; // Or load and check file. - TRoutingMappingPtr new_mapping = make_shared<RoutingMapping>(mapName, m_index); - m_mapping.insert(make_pair(mapName, new_mapping)); - return new_mapping; + TRoutingMappingPtr newMapping = make_shared<RoutingMapping>(*localFile, m_index); + m_mapping.insert(make_pair(mapName, newMapping)); + return newMapping; } } // namespace routing diff --git a/routing/routing_mapping.h b/routing/routing_mapping.h index e791599f30..41d0b0da50 100644 --- a/routing/routing_mapping.h +++ b/routing/routing_mapping.h @@ -11,10 +11,17 @@ #include "std/algorithm.hpp" #include "std/unordered_map.hpp" +namespace platform +{ +class CountryFile; +class LocalCountryFile; +} + namespace routing { using TDataFacade = OsrmDataFacade<QueryEdge::EdgeData>; using TCountryFileFn = function<string(m2::PointD const &)>; +using TCountryLocalFileFn = function<shared_ptr<platform::LocalCountryFile>(string const &)>; /// Datamapping and facade for single MWM and MWM.routing file struct RoutingMapping @@ -24,7 +31,7 @@ struct RoutingMapping CrossRoutingContextReader m_crossContext; ///@param fName: mwm file path - RoutingMapping(string const & fName, Index const * pIndex); + RoutingMapping(platform::LocalCountryFile const & localFile, Index const * pIndex); ~RoutingMapping(); @@ -44,15 +51,21 @@ struct RoutingMapping IRouter::ResultCode GetError() const {return m_error;} - string const & GetName() const { return m_baseName; } + string const & GetName() const { return m_countryFileName; } Index::MwmId const & GetMwmId() const { return m_mwmId; } + // static + static shared_ptr<RoutingMapping> MakeInvalid(platform::CountryFile const & countryFile); + private: + // Ctor for invalid mappings. + RoutingMapping(platform::CountryFile const & countryFile); + size_t m_mapCounter; size_t m_facadeCounter; bool m_crossContextLoaded; - string m_baseName; + string m_countryFileName; FilesMappingContainer m_container; Index::MwmId m_mwmId; bool m_isValid; @@ -86,8 +99,9 @@ public: class RoutingIndexManager { public: - RoutingIndexManager(TCountryFileFn const & fn, Index const * index) - : m_countryFn(fn), m_index(index) + RoutingIndexManager(TCountryFileFn const & countryFileFn, + TCountryLocalFileFn const & countryLocalFileFn, Index const * index) + : m_countryFileFn(countryFileFn), m_countryLocalFileFn(countryLocalFileFn), m_index(index) { ASSERT(index, ()); } @@ -105,9 +119,10 @@ public: void Clear() { m_mapping.clear(); } private: - TCountryFileFn m_countryFn; - Index const * m_index; + TCountryFileFn m_countryFileFn; + TCountryLocalFileFn m_countryLocalFileFn; unordered_map<string, TRoutingMappingPtr> m_mapping; + Index const * m_index; }; } // namespace routing diff --git a/search/search_query.cpp b/search/search_query.cpp index 0a5eab18d3..12741b473b 100644 --- a/search/search_query.cpp +++ b/search/search_query.cpp @@ -600,7 +600,7 @@ namespace impl if (m_pFV->IsWorld()) country.clear(); else - country = m_pFV->GetFileName(); + country = m_pFV->GetCountryFileName(); } public: @@ -1659,11 +1659,14 @@ void Query::SearchAddress(Results & res) { MwmSet::MwmId id(info); Index::MwmLock const mwmLock(const_cast<Index &>(*m_pIndex), id); - string fileName; if (mwmLock.IsLocked()) - fileName = mwmLock.GetValue<MwmValue>()->GetFileName(); - if (m_pInfoGetter->IsBelongToRegion(fileName, region.m_ids)) - SearchInMWM(mwmLock, params); + { + platform::CountryFile const & countryFile = + mwmLock.GetValue<MwmValue>()->GetCountryFile(); + string const countryFileName = countryFile.GetNameWithoutExt(); + if (m_pInfoGetter->IsBelongToRegion(countryFileName, region.m_ids)) + SearchInMWM(mwmLock, params); + } } } } @@ -2108,9 +2111,8 @@ void Query::SearchInMWM(Index::MwmLock const & mwmLock, Params const & params, TrieRootPrefix(*pLangRoot, edge), filter, categoriesHolder, emitter); - LOG(LDEBUG, ("Country", pMwm->GetFileName(), - "Lang", StringUtf8Multilang::GetLangByCode(lang), - "Matched", emitter.GetCount())); + LOG(LDEBUG, ("Country", pMwm->GetCountryFile().GetNameWithoutExt(), "Lang", + StringUtf8Multilang::GetLangByCode(lang), "Matched", emitter.GetCount())); emitter.Reset(); } @@ -2193,8 +2195,11 @@ void Query::SearchAdditional(Results & res, size_t resCount) for (shared_ptr<MwmInfo> const & info : mwmsInfo) { Index::MwmLock const mwmLock(const_cast<Index &>(*m_pIndex), MwmSet::MwmId(info)); - if (mwmLock.IsLocked() && mwmLock.GetValue<MwmValue>()->GetFileName() == fileName) + if (mwmLock.IsLocked() && + mwmLock.GetValue<MwmValue>()->GetCountryFile().GetNameWithoutExt() == fileName) + { SearchInMWM(mwmLock, params); + } } FlushResults(res, true, resCount); diff --git a/search/search_tests/house_detector_tests.cpp b/search/search_tests/house_detector_tests.cpp index 1f03cb824d..eae291fb9a 100644 --- a/search/search_tests/house_detector_tests.cpp +++ b/search/search_tests/house_detector_tests.cpp @@ -17,6 +17,7 @@ #include "std/iostream.hpp" #include "std/fstream.hpp" +using platform::LocalCountryFile; class StreetIDsByName { @@ -183,7 +184,8 @@ UNIT_TEST(HS_StreetsMerge) classificator::Load(); Index index; - pair<MwmSet::MwmLock, bool> const p = index.Register("minsk-pass.mwm"); + pair<MwmSet::MwmLock, bool> const p = + index.Register(LocalCountryFile::MakeForTesting("minsk-pass")); TEST(p.first.IsLocked(), ()); TEST(p.second, ()); @@ -272,7 +274,8 @@ UNIT_TEST(HS_FindHouseSmoke) classificator::Load(); Index index; - pair<MwmSet::MwmLock, bool> const p = index.Register("minsk-pass.mwm"); + pair<MwmSet::MwmLock, bool> const p = + index.Register(LocalCountryFile::MakeForTesting("minsk-pass")); TEST(p.first.IsLocked(), ()); TEST(p.second, ()); @@ -345,10 +348,7 @@ struct Address string m_house; double m_lat, m_lon; - bool operator<(Address const & rhs) const - { - return (m_streetKey < rhs.m_streetKey); - } + bool operator<(Address const & rhs) const { return (m_streetKey < rhs.m_streetKey); } }; void swap(Address & a1, Address & a2) @@ -375,7 +375,7 @@ UNIT_TEST(HS_MWMSearch) } Index index; - pair<MwmSet::MwmLock, bool> const p = index.Register(country + ".mwm"); + pair<MwmSet::MwmLock, bool> const p = index.Register(LocalCountryFile::MakeForTesting(country)); if (!p.second) { LOG(LWARNING, ("MWM file not found")); diff --git a/search/search_tests/locality_finder_test.cpp b/search/search_tests/locality_finder_test.cpp index 30182f7267..62efda0f5e 100644 --- a/search/search_tests/locality_finder_test.cpp +++ b/search/search_tests/locality_finder_test.cpp @@ -5,6 +5,10 @@ #include "search/locality_finder.hpp" +#include "platform/country_file.hpp" +#include "platform/local_country_file.hpp" +#include "platform/platform.hpp" + namespace { @@ -34,7 +38,8 @@ void doTests2(search::LocalityFinder & finder, vector<m2::PointD> const & input, UNIT_TEST(LocalityFinder) { Index index; - pair<MwmSet::MwmLock, bool> const p = index.Register("World.mwm"); + pair<MwmSet::MwmLock, bool> const p = + index.Register(platform::LocalCountryFile::MakeForTesting("World")); TEST(p.second, ()); MwmSet::MwmLock const & lock = p.first; TEST(lock.IsLocked(), ()); diff --git a/std/algorithm.hpp b/std/algorithm.hpp index 426296336b..913e1c0482 100644 --- a/std/algorithm.hpp +++ b/std/algorithm.hpp @@ -6,6 +6,7 @@ #include <algorithm> +using std::all_of; using std::binary_search; using std::equal; using std::find; diff --git a/storage/country.cpp b/storage/country.cpp index 3e39eb674a..d2be152219 100644 --- a/storage/country.cpp +++ b/storage/country.cpp @@ -6,96 +6,19 @@ #include "3party/jansson/myjansson.hpp" +using platform::CountryFile; namespace storage { - -string CountryFile::GetFileWithExt(TMapOptions opt) const -{ - switch (opt) - { - case TMapOptions::EMap: return m_fileName + DATA_FILE_EXTENSION; - case TMapOptions::ECarRouting: return m_fileName + DATA_FILE_EXTENSION + ROUTING_FILE_EXTENSION; - - case TMapOptions::EMapWithCarRouting: - default: - ASSERT(false, ()); - } - return string(); -} - -uint32_t CountryFile::GetFileSize(TMapOptions opt) const +uint64_t Country::Size(TMapOptions opt) const { uint64_t size = 0; - if (GetPlatform().GetFileSizeByName(GetFileWithExt(opt), size)) - { - uint32_t const ret = static_cast<uint32_t>(size); - ASSERT_EQUAL ( ret, size, () ); - return ret; - } - else - return 0; -} - -uint32_t CountryFile::GetRemoteSize(TMapOptions opt) const -{ - switch (opt) - { - case TMapOptions::ENothing: - return 0; - case TMapOptions::EMap: - return m_mapSize; - case TMapOptions::ECarRouting: - return m_routingSize; - case TMapOptions::EMapWithCarRouting: - return m_mapSize + m_routingSize; - } -} - -/* -class CountryBoundsCalculator -{ - m2::RectD & m_bounds; - Platform & m_platform; - -public: - CountryBoundsCalculator(m2::RectD & bounds) - : m_bounds(bounds), m_platform(GetPlatform()) - { - } - - void operator()(CountryFile const & file) - { - feature::DataHeader h; - LoadMapHeader(m_platform.GetReader(file.GetFileWithExt()), h); - m_bounds.Add(h.GetBounds()); - } -}; - -m2::RectD Country::Bounds() const -{ - m2::RectD bounds; - std::for_each(m_files.begin(), m_files.end(), CountryBoundsCalculator(bounds)); - return bounds; -} -*/ - -LocalAndRemoteSizeT Country::Size(TMapOptions opt) const -{ - uint64_t localSize = 0, remoteSize = 0; - for (CountryFile const & f : m_files) - { - localSize += f.GetFileSize(opt); - remoteSize += f.GetRemoteSize(opt); - } - return LocalAndRemoteSizeT(localSize, remoteSize); -} - -void Country::AddFile(CountryFile const & file) -{ - m_files.push_back(file); + for (CountryFile const & file : m_files) + size += file.GetRemoteSize(opt); + return size; } +void Country::AddFile(CountryFile const & file) { m_files.push_back(file); } //////////////////////////////////////////////////////////////////////// @@ -118,10 +41,9 @@ void LoadGroupImpl(int depth, json_t * group, ToDo & toDo) char const * flag = json_string_value(json_object_get(j, "c")); toDo(name, file, flag ? flag : "", - // We expect what mwm and routing files should be less 2Gb + // We expect what mwm and routing files should be less 2Gb static_cast<uint32_t>(json_integer_value(json_object_get(j, "s"))), - static_cast<uint32_t>(json_integer_value(json_object_get(j, "rs"))), - depth); + static_cast<uint32_t>(json_integer_value(json_object_get(j, "rs"))), depth); json_t * children = json_object_get(j, "g"); if (children) @@ -130,120 +52,133 @@ void LoadGroupImpl(int depth, json_t * group, ToDo & toDo) } template <class ToDo> -int64_t LoadCountriesImpl(string const & jsonBuffer, ToDo & toDo) +bool LoadCountriesImpl(string const & jsonBuffer, ToDo & toDo) { - int64_t version = -1; - try { my::Json root(jsonBuffer.c_str()); - version = json_integer_value(json_object_get(root.get(), "v")); json_t * children = json_object_get(root.get(), "g"); if (!children) MYTHROW(my::Json::Exception, ("Root country doesn't have any groups")); LoadGroupImpl(0, children, toDo); + return true; } - catch (my::Json::Exception const & ex) + catch (my::Json::Exception const & e) { - LOG(LERROR, (ex.Msg())); - return -1; + LOG(LERROR, (e.Msg())); + return false; } - - return version; } namespace { - class DoStoreCountries - { - CountriesContainerT & m_cont; - public: - DoStoreCountries(CountriesContainerT & cont) : m_cont(cont) {} +class DoStoreCountries +{ + CountriesContainerT & m_cont; + +public: + DoStoreCountries(CountriesContainerT & cont) : m_cont(cont) {} - void operator() (string const & name, string const & file, string const & flag, - uint32_t mapSize, uint32_t routingSize, int depth) + void operator()(string const & name, string const & file, string const & flag, uint32_t mapSize, + uint32_t routingSize, int depth) + { + Country country(name, flag); + if (mapSize) { - Country country(name, flag); - if (mapSize) - country.AddFile(CountryFile(file, mapSize, routingSize)); - m_cont.AddAtDepth(depth, country); + CountryFile countryFile(file); + countryFile.SetRemoteSizes(mapSize, routingSize); + country.AddFile(countryFile); } - }; + m_cont.AddAtDepth(depth, country); + } +}; - class DoStoreFile2Info - { - map<string, CountryInfo> & m_file2info; - string m_lastFlag; +class DoStoreFile2Info +{ + map<string, CountryInfo> & m_file2info; + string m_lastFlag; - public: - DoStoreFile2Info(map<string, CountryInfo> & file2info) : m_file2info(file2info) {} +public: + DoStoreFile2Info(map<string, CountryInfo> & file2info) : m_file2info(file2info) {} - void operator() (string name, string file, string const & flag, - uint32_t mapSize, uint32_t, int) + void operator()(string name, string file, string const & flag, uint32_t mapSize, uint32_t, int) + { + if (!flag.empty()) + m_lastFlag = flag; + + if (mapSize) { - if (!flag.empty()) - m_lastFlag = flag; + CountryInfo info; - if (mapSize) + // if 'file' is empty - it's equal to 'name' + if (!file.empty()) { - CountryInfo info; + // make compound name: country_region + size_t const i = file.find_first_of('_'); + if (i != string::npos) + name = file.substr(0, i) + '_' + name; + + // fill 'name' only when it differs with 'file' + if (name != file) + info.m_name.swap(name); + } + else + file.swap(name); - // if 'file' is empty - it's equal to 'name' - if (!file.empty()) - { - // make compound name: country_region - size_t const i = file.find_first_of('_'); - if (i != string::npos) - name = file.substr(0, i) + '_' + name; + // Do not use 'name' here! It was swapped! - // fill 'name' only when it differs with 'file' - if (name != file) - info.m_name.swap(name); - } - else - file.swap(name); + ASSERT(!m_lastFlag.empty(), ()); + info.m_flag = m_lastFlag; - // Do not use 'name' here! It was swapped! + m_file2info[file] = info; + } + } +}; - ASSERT ( !m_lastFlag.empty(), () ); - info.m_flag = m_lastFlag; +class DoStoreCode2File +{ + multimap<string, string> & m_code2file; - m_file2info[file] = info; - } - } - }; +public: + DoStoreCode2File(multimap<string, string> & code2file) : m_code2file(code2file) {} - class DoStoreCode2File + void operator()(string const &, string const & file, string const & flag, uint32_t, uint32_t, int) { - multimap<string, string> & m_code2file; - public: - DoStoreCode2File(multimap<string, string> & code2file) : m_code2file(code2file) {} - - void operator() (string const &, string const & file, string const & flag, - uint32_t, uint32_t, int) - { - m_code2file.insert(make_pair(flag, file)); - } - }; + m_code2file.insert(make_pair(flag, file)); + } +}; } int64_t LoadCountries(string const & jsonBuffer, CountriesContainerT & countries) { countries.Clear(); - DoStoreCountries doStore(countries); - return LoadCountriesImpl(jsonBuffer, doStore); + + int64_t version = -1; + try + { + my::Json root(jsonBuffer.c_str()); + version = json_integer_value(json_object_get(root.get(), "v")); + DoStoreCountries doStore(countries); + if (!LoadCountriesImpl(jsonBuffer, doStore)) + return -1; + } + catch (my::Json::Exception const & e) + { + LOG(LERROR, (e.Msg())); + } + return version; } void LoadCountryFile2CountryInfo(string const & jsonBuffer, map<string, CountryInfo> & id2info) { - ASSERT ( id2info.empty(), () ); + ASSERT(id2info.empty(), ()); DoStoreFile2Info doStore(id2info); LoadCountriesImpl(jsonBuffer, doStore); } void LoadCountryCode2File(string const & jsonBuffer, multimap<string, string> & code2file) { - ASSERT ( code2file.empty(), () ); + ASSERT(code2file.empty(), ()); DoStoreCode2File doStore(code2file); LoadCountriesImpl(jsonBuffer, doStore); } @@ -273,11 +208,12 @@ void SaveImpl(T const & v, json_t * jParent) if (countriesCount > 0) { CountryFile const & file = v[i].Value().GetFile(); - string const & strFile = file.GetFileWithoutExt(); + string const & strFile = file.GetNameWithoutExt(); if (strFile != strName) json_object_set_new(jCountry.get(), "f", json_string(strFile.c_str())); json_object_set_new(jCountry.get(), "s", json_integer(file.GetRemoteSize(TMapOptions::EMap))); - json_object_set_new(jCountry.get(), "rs", json_integer(file.GetRemoteSize(TMapOptions::ECarRouting))); + json_object_set_new(jCountry.get(), "rs", + json_integer(file.GetRemoteSize(TMapOptions::ECarRouting))); } if (v[i].SiblingsCount()) @@ -304,4 +240,4 @@ bool SaveCountries(int64_t version, CountriesContainerT const & countries, strin return true; } -} // namespace storage +} // namespace storage diff --git a/storage/country.hpp b/storage/country.hpp index 0140722634..a51997be9d 100644 --- a/storage/country.hpp +++ b/storage/country.hpp @@ -1,8 +1,10 @@ #pragma once -#include "storage/storage_defines.hpp" -#include "storage/simple_tree.hpp" #include "storage/country_decl.hpp" +#include "storage/simple_tree.hpp" +#include "storage/storage_defines.hpp" + +#include "platform/local_country_file.hpp" #include "platform/country_defines.hpp" @@ -15,91 +17,56 @@ #include "std/string.hpp" #include "std/vector.hpp" - -namespace update { class SizeUpdater; } +namespace update +{ +class SizeUpdater; +} namespace storage { - /// Information about each file for a country - class CountryFile +/// Serves as a proxy between GUI and downloaded files +class Country +{ + friend class update::SizeUpdater; + /// Name in the country node tree + string m_name; + /// Flag to display + string m_flag; + /// stores squares with world pieces which are part of the country + buffer_vector<platform::CountryFile, 1> m_files; + +public: + Country() {} + Country(string const & name, string const & flag = "") : m_name(name), m_flag(flag) {} + + bool operator<(Country const & other) const { return Name() < other.Name(); } + + void AddFile(platform::CountryFile const & file); + + size_t GetFilesCount() const { return m_files.size(); } + + /// This function valid for current logic - one file for one country (region). + /// If the logic will be changed, replace GetFile with ForEachFile. + platform::CountryFile const & GetFile() const { - string m_fileName; /// Same as id of country\region. - uint32_t m_mapSize, m_routingSize; + ASSERT_EQUAL(m_files.size(), 1, (m_name)); + return m_files.front(); + } - public: - CountryFile() : m_mapSize(0), m_routingSize(0) {} - CountryFile(string const & fName, uint32_t mapSize, uint32_t routingSize) - : m_fileName(fName), m_mapSize(mapSize), m_routingSize(routingSize) - { - } + string const & Name() const { return m_name; } + string const & Flag() const { return m_flag; } - void AssignSizes(uint32_t mapSize, uint32_t routingSize) - { - m_mapSize = mapSize; - m_routingSize = routingSize; - } + uint64_t Size(TMapOptions opt) const; +}; - string GetFileWithExt(TMapOptions opt) const; - string const & GetFileWithoutExt() const { return m_fileName; } +typedef SimpleTree<Country> CountriesContainerT; - uint32_t GetFileSize(TMapOptions opt) const; - uint32_t GetRemoteSize(TMapOptions opt) const; - }; +/// @return version of country file or -1 if error was encountered +int64_t LoadCountries(string const & jsonBuffer, CountriesContainerT & countries); - typedef buffer_vector<CountryFile, 1> FilesContainerT; +void LoadCountryFile2CountryInfo(string const & jsonBuffer, map<string, CountryInfo> & id2info); - /// Serves as a proxy between GUI and downloaded files - class Country - { - friend class update::SizeUpdater; - /// Name in the country node tree - string m_name; - /// Flag to display - string m_flag; - /// stores squares with world pieces which are part of the country - FilesContainerT m_files; - - public: - Country() {} - Country(string const & name, string const & flag = "") - : m_name(name), m_flag(flag) {} - - bool operator<(Country const & other) const { return Name() < other.Name(); } - - void AddFile(CountryFile const & file); - - size_t GetFilesCount() const { return m_files.size(); } - - /* - template <class ToDo> void ForEachFile(ToDo toDo) const - { - for (FilesContainerT::const_iterator i = m_files.begin(); i != m_files.end(); ++i) - toDo(i->GetFileWithExt()); - } - */ - - /// This function valid for current logic - one file for one country (region). - /// If the logic will be changed, replace GetFile with ForEachFile. - CountryFile const & GetFile() const - { - ASSERT_EQUAL ( m_files.size(), 1, (m_name) ); - return m_files.front(); - } - - string const & Name() const { return m_name; } - string const & Flag() const { return m_flag; } - - /// @return bounds for downloaded parts of the country or empty rect - //m2::RectD Bounds() const; - LocalAndRemoteSizeT Size(TMapOptions opt) const; - }; - - typedef SimpleTree<Country> CountriesContainerT; - - /// @return version of country file or -1 if error was encountered - int64_t LoadCountries(string const & jsonBuffer, CountriesContainerT & countries); - void LoadCountryFile2CountryInfo(string const & jsonBuffer, map<string, CountryInfo> & id2info); - void LoadCountryCode2File(string const & jsonBuffer, multimap<string, string> & code2file); - - bool SaveCountries(int64_t version, CountriesContainerT const & countries, string & jsonBuffer); -} +void LoadCountryCode2File(string const & jsonBuffer, multimap<string, string> & code2file); + +bool SaveCountries(int64_t version, CountriesContainerT const & countries, string & jsonBuffer); +} // namespace storage diff --git a/storage/map_files_downloader.hpp b/storage/map_files_downloader.hpp index ce0cf2fc1f..ad9224b426 100644 --- a/storage/map_files_downloader.hpp +++ b/storage/map_files_downloader.hpp @@ -2,6 +2,7 @@ #include "std/function.hpp" #include "std/string.hpp" + #include "std/utility.hpp" #include "std/vector.hpp" diff --git a/storage/queued_country.cpp b/storage/queued_country.cpp new file mode 100644 index 0000000000..9034370e77 --- /dev/null +++ b/storage/queued_country.cpp @@ -0,0 +1,52 @@ +#include "storage/queued_country.hpp" + +#include "base/assert.hpp" + +namespace storage +{ +QueuedCountry::QueuedCountry(TIndex const & index, TMapOptions opt) + : m_index(index), m_init(opt), m_left(opt), m_current(TMapOptions::ENothing) +{ + ASSERT(GetIndex().IsValid(), ("Only valid countries may be downloaded.")); + ASSERT(m_left != TMapOptions::ENothing, ("Empty file set was requested for downloading.")); + SwitchToNextFile(); +} + +void QueuedCountry::AddOptions(TMapOptions opt) +{ + for (TMapOptions file : {TMapOptions::EMap, TMapOptions::ECarRouting}) + { + if (HasOptions(opt, file) && !HasOptions(m_init, file)) + { + m_init = SetOptions(m_init, file); + m_left = SetOptions(m_left, file); + } + } +} + +void QueuedCountry::RemoveOptions(TMapOptions opt) +{ + for (TMapOptions file : {TMapOptions::EMap, TMapOptions::ECarRouting}) + { + if (HasOptions(opt, file) && HasOptions(m_init, file)) + { + m_init = UnsetOptions(m_init, file); + m_left = UnsetOptions(m_left, file); + } + } + if (HasOptions(opt, m_current)) + m_current = LeastSignificantOption(m_left); +} + +bool QueuedCountry::SwitchToNextFile() +{ + // static_casts are needed here because TMapOptions values are + // actually enum flags (see 3party/enum_flags.hpp) and bitwise + // operators are overloaded for them. + ASSERT(HasOptions(m_left, m_current), + ("Current file (", m_current, ") is not specified in left files (", m_left, ").")); + m_left = UnsetOptions(m_left, m_current); + m_current = LeastSignificantOption(m_left); + return m_current != TMapOptions::ENothing; +} +} // namespace storage diff --git a/storage/queued_country.hpp b/storage/queued_country.hpp new file mode 100644 index 0000000000..de0f070273 --- /dev/null +++ b/storage/queued_country.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "storage/index.hpp" +#include "platform/country_defines.hpp" + +namespace storage +{ +/// Country queued for downloading. +class QueuedCountry +{ +public: + QueuedCountry(TIndex const & index, TMapOptions opt); + + void AddOptions(TMapOptions opt); + void RemoveOptions(TMapOptions opt); + bool SwitchToNextFile(); + + inline TIndex const & GetIndex() const { return m_index; } + inline TMapOptions GetInitOptions() const { return m_init; } + inline TMapOptions GetCurrentFile() const { return m_current; } + inline TMapOptions GetDownloadedFiles() const { return UnsetOptions(m_init, m_left); } + + inline bool operator==(TIndex const & index) const { return m_index == index; } + +private: + TIndex m_index; + TMapOptions m_init; + TMapOptions m_left; + TMapOptions m_current; +}; +} // namespace storage diff --git a/storage/storage.cpp b/storage/storage.cpp index bad306c873..8994f873cd 100644 --- a/storage/storage.cpp +++ b/storage/storage.cpp @@ -4,17 +4,22 @@ #include "defines.hpp" +#include "platform/local_country_file_utils.hpp" #include "platform/platform.hpp" #include "platform/servers_list.hpp" #include "platform/settings.hpp" -#include "coding/file_writer.hpp" -#include "coding/file_reader.hpp" #include "coding/file_container.hpp" -#include "coding/url_encode.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/url_encode.hpp" + +#include "platform/local_country_file_utils.hpp" #include "base/logging.hpp" +#include "base/scope_guard.hpp" #include "base/string_utils.hpp" #include "std/algorithm.hpp" @@ -25,688 +30,843 @@ #include "3party/Alohalytics/src/alohalytics.h" using namespace downloader; +using namespace platform; namespace storage { - /* - static string ErrorString(DownloadResultT res) - { - switch (res) - { - case EHttpDownloadCantCreateFile: - return "File can't be created. Probably, you have no disk space available or " - "using read-only file system."; - case EHttpDownloadFailed: - return "Download failed due to missing or poor connection. " - "Please, try again later."; - case EHttpDownloadFileIsLocked: - return "Download can't be finished because file is locked. " - "Please, try again after restarting application."; - case EHttpDownloadFileNotFound: - return "Requested file is absent on the server."; - case EHttpDownloadNoConnectionAvailable: - return "No network connection is available."; - case EHttpDownloadOk: - return "Download finished successfully."; - } - return "Unknown error"; - } - */ +namespace +{ +template <typename T> +void RemoveIf(vector<T> & v, function<bool(T const & t)> const & p) +{ + v.erase(remove_if(v.begin(), v.end(), p), v.end()); +} - Storage::QueuedCountry::QueuedCountry(Storage const & storage, TIndex const & index, - TMapOptions opt) - : m_index(index), m_init(opt), m_left(opt) +uint64_t GetLocalSize(shared_ptr<LocalCountryFile> file, TMapOptions opt) +{ + if (file.get() == nullptr) + return 0; + uint64_t size = 0; + for (TMapOptions bit : {TMapOptions::EMap, TMapOptions::ECarRouting}) { - m_pFile = &(storage.CountryByIndex(index).GetFile()); - - // Don't queue files with 0-size on server (empty or absent). - // Downloader has lots of assertions about it. If car routing was - // requested for downloading, try to download it first. - if (HasOptions(m_init, TMapOptions::ECarRouting) && - m_pFile->GetRemoteSize(TMapOptions::ECarRouting) > 0) - { - m_current = TMapOptions::ECarRouting; - } - else - { - m_init = m_current = m_left = TMapOptions::EMap; - } + if (HasOptions(opt, bit)) + size += file->GetSize(bit); } + return size; +} - void Storage::QueuedCountry::AddOptions(TMapOptions opt) +uint64_t GetRemoteSize(CountryFile const & file, TMapOptions opt) +{ + uint64_t size = 0; + for (TMapOptions bit : {TMapOptions::EMap, TMapOptions::ECarRouting}) { - TMapOptions const arr[] = { TMapOptions::EMap, TMapOptions::ECarRouting }; - for (size_t i = 0; i < ARRAY_SIZE(arr); ++i) - { - if (HasOptions(opt, arr[i]) && !HasOptions(m_init, arr[i])) - { - m_init = SetOptions(m_init, arr[i]); - m_left = SetOptions(m_left, arr[i]); - } - } + if (HasOptions(opt, bit)) + size += file.GetRemoteSize(bit); } + return size; +} - bool Storage::QueuedCountry::MoveNextFile() - { - if (m_current == m_left) - return false; +// TODO (@gorshenin): directory where country indexes are stored +// should be abstracted out to LocalCountryIndexes. +void DeleteCountryIndexes(CountryFile const & file) +{ + Platform::FilesList files; + Platform const & platform = GetPlatform(); + string const name = file.GetNameWithoutExt(); + string const path = platform.WritablePathForCountryIndexes(name); + + /// @todo We need correct regexp for any file (not including "." and ".."). + platform.GetFilesByRegExp(path, name + "\\..*", files); + for (auto const & file : files) + my::DeleteFileX(path + file); +} - /// if m_current != m_left than we download map and routing - /// routing already downloaded and we need to download map - ASSERT(m_current == TMapOptions::ECarRouting, ()); - m_left = m_current = TMapOptions::EMap; - return true; - } +class EqualFileName +{ + string const & m_name; - bool Storage::QueuedCountry::Correct(TStatus currentStatus) +public: + explicit EqualFileName(string const & name) : m_name(name) {} + bool operator()(SimpleTree<Country> const & node) const { - ASSERT(currentStatus != TStatus::EDownloadFailed, ()); - ASSERT(currentStatus != TStatus::EOutOfMemFailed, ()); - ASSERT(currentStatus != TStatus::EInQueue, ()); - ASSERT(currentStatus != TStatus::EDownloading, ()); - ASSERT(currentStatus != TStatus::EUnknown, ()); - - if (m_init == TMapOptions::EMap && currentStatus == TStatus::EOnDisk) + Country const & c = node.Value(); + if (c.GetFilesCount() > 0) + return (c.GetFile().GetNameWithoutExt() == m_name); + else return false; + } +}; +} // namespace -#ifdef DEBUG - if (currentStatus == TStatus::EOnDiskOutOfDate && m_init == TMapOptions::EMap) - ASSERT(m_pFile->GetFileSize(TMapOptions::ECarRouting) == 0, ()); -#endif +Storage::Storage() : m_downloader(new HttpMapFilesDownloader()), m_currentSlotId(0) +{ + LoadCountriesFile(false /* forceReload */); +} - if (HasOptions(m_init, TMapOptions::ECarRouting)) - { - ASSERT(HasOptions(m_init, TMapOptions::EMap), ()); +void Storage::Init(TUpdateAfterDownload const & updateFn) { m_updateAfterDownload = updateFn; } - if (currentStatus == TStatus::EOnDisk) - { - if (m_pFile->GetFileSize(TMapOptions::ECarRouting) == 0) - m_init = m_left = m_current = TMapOptions::ECarRouting; - else - return false; - } - } +void Storage::RegisterAllLocalMaps() +{ + m_localFiles.clear(); + m_localFilesForFakeCountries.clear(); - return true; - } + vector<LocalCountryFile> localFiles; + FindAllLocalMaps(localFiles); - uint64_t Storage::QueuedCountry::GetDownloadSize() const + auto compareByCountryAndVersion = [](LocalCountryFile const & lhs, LocalCountryFile const & rhs) { - return m_pFile->GetRemoteSize(m_current); - } + if (lhs.GetCountryFile() != rhs.GetCountryFile()) + return lhs.GetCountryFile() < rhs.GetCountryFile(); + return lhs.GetVersion() > rhs.GetVersion(); + }; - LocalAndRemoteSizeT Storage::QueuedCountry::GetFullSize() const + auto equalByCountry = [](LocalCountryFile const & lhs, LocalCountryFile const & rhs) { - LocalAndRemoteSizeT res(0, 0); - TMapOptions const arr[] = { TMapOptions::EMap, TMapOptions::ECarRouting }; - for (size_t i = 0; i < ARRAY_SIZE(arr); ++i) - { - if (HasOptions(m_init, arr[i])) - { - res.first += m_pFile->GetFileSize(arr[i]); - res.second += m_pFile->GetRemoteSize(arr[i]); - } - } + return lhs.GetCountryFile() == rhs.GetCountryFile(); + }; - return res; - } + sort(localFiles.begin(), localFiles.end(), compareByCountryAndVersion); - size_t Storage::QueuedCountry::GetFullRemoteSize() const + auto i = localFiles.begin(); + while (i != localFiles.end()) { - size_t res = 0; - TMapOptions const arr[] = { TMapOptions::EMap, TMapOptions::ECarRouting }; - for (size_t i = 0; i < ARRAY_SIZE(arr); ++i) + auto j = i + 1; + while (j != localFiles.end() && equalByCountry(*i, *j)) { - if (HasOptions(m_init, arr[i])) - res += m_pFile->GetRemoteSize(arr[i]); + LocalCountryFile & localFile = *j; + LOG(LINFO, ("Removing obsolete", localFile)); + localFile.SyncWithDisk(); + localFile.DeleteFromDisk(TMapOptions::EMapWithCarRouting); + ++j; } - return res; + LocalCountryFile const & localFile = *i; + string const name = localFile.GetCountryFile().GetNameWithoutExt(); + TIndex index = FindIndexByFile(name); + if (index.IsValid()) + RegisterCountryFiles(index, localFile.GetDirectory(), localFile.GetVersion()); + else + RegisterFakeCountryFiles(localFile); + LOG(LINFO, ("Found file:", name, "in directory:", localFile.GetDirectory())); + + i = j; } +} - string Storage::QueuedCountry::GetFileName() const +void Storage::GetLocalMaps(vector<CountryFile> & maps) +{ + for (auto const & p : m_localFiles) { - return m_pFile->GetFileWithExt(m_current); + TIndex const & index = p.first; + maps.push_back(GetLatestLocalFile(index)->GetCountryFile()); } + for (auto const & p : m_localFilesForFakeCountries) + maps.push_back(p.second->GetCountryFile()); +} - string Storage::QueuedCountry::GetMapFileName() const - { - return m_pFile->GetFileWithExt(TMapOptions::EMap); +CountriesContainerT const & NodeFromIndex(CountriesContainerT const & root, TIndex const & index) +{ + // complex logic to avoid [] out_of_bounds exceptions + if (index.m_group == TIndex::INVALID || index.m_group >= static_cast<int>(root.SiblingsCount())) + return root; + else + { + if (index.m_country == TIndex::INVALID || + index.m_country >= static_cast<int>(root[index.m_group].SiblingsCount())) + return root[index.m_group]; + if (index.m_region == TIndex::INVALID || + index.m_region >= static_cast<int>(root[index.m_group][index.m_country].SiblingsCount())) + return root[index.m_group][index.m_country]; + return root[index.m_group][index.m_country][index.m_region]; } +} - Storage::Storage() : m_downloader(new HttpMapFilesDownloader()), m_currentSlotId(0) - { - LoadCountriesFile(false); +Country const & Storage::CountryByIndex(TIndex const & index) const +{ + return NodeFromIndex(m_countries, index).Value(); +} - if (Settings::IsFirstLaunchForDate(121031)) - { - Platform & pl = GetPlatform(); - string const dir = pl.WritableDir(); +void Storage::GetGroupAndCountry(TIndex const & index, string & group, string & country) const +{ + string fName = CountryByIndex(index).GetFile().GetNameWithoutExt(); + CountryInfo::FileName2FullName(fName); + CountryInfo::FullName2GroupAndMap(fName, group, country); +} - // Delete all: .mwm.downloading; .mwm.downloading2; .mwm.resume; .mwm.resume2 - string const regexp = "\\" DATA_FILE_EXTENSION "\\.(downloading2?$|resume2?$)"; +size_t Storage::CountriesCount(TIndex const & index) const +{ + return NodeFromIndex(m_countries, index).SiblingsCount(); +} - Platform::FilesList files; - pl.GetFilesByRegExp(dir, regexp, files); +string const & Storage::CountryName(TIndex const & index) const +{ + return NodeFromIndex(m_countries, index).Value().Name(); +} - for (size_t j = 0; j < files.size(); ++j) - FileWriter::DeleteFileX(dir + files[j]); - } - } +string const & Storage::CountryFlag(TIndex const & index) const +{ + return NodeFromIndex(m_countries, index).Value().Flag(); +} - void Storage::Init(TUpdateAfterDownload const & updateFn) +LocalAndRemoteSizeT Storage::CountrySizeInBytes(TIndex const & index, TMapOptions opt) const +{ + QueuedCountry const * queuedCountry = FindCountryInQueue(index); + shared_ptr<LocalCountryFile> localFile = GetLatestLocalFile(index); + CountryFile const & countryFile = GetCountryFile(index); + if (queuedCountry == nullptr) { - m_updateAfterDownload = updateFn; + return LocalAndRemoteSizeT(GetLocalSize(localFile, opt), GetRemoteSize(countryFile, opt)); } - CountriesContainerT const & NodeFromIndex(CountriesContainerT const & root, TIndex const & index) + LocalAndRemoteSizeT sizes(0, GetRemoteSize(countryFile, opt)); + if (!m_downloader->IsIdle() && IsCountryFirstInQueue(index)) { - // complex logic to avoid [] out_of_bounds exceptions - if (index.m_group == TIndex::INVALID || index.m_group >= static_cast<int>(root.SiblingsCount())) - return root; - else - { - if (index.m_country == TIndex::INVALID || index.m_country >= static_cast<int>(root[index.m_group].SiblingsCount())) - return root[index.m_group]; - if (index.m_region == TIndex::INVALID || index.m_region >= static_cast<int>(root[index.m_group][index.m_country].SiblingsCount())) - return root[index.m_group][index.m_country]; - return root[index.m_group][index.m_country][index.m_region]; - } + sizes.first = m_downloader->GetDownloadingProgress().first + + GetRemoteSize(countryFile, queuedCountry->GetDownloadedFiles()); } + return sizes; +} - Country const & Storage::CountryByIndex(TIndex const & index) const - { - return NodeFromIndex(m_countries, index).Value(); - } +CountryFile const & Storage::GetCountryFile(TIndex const & index) const +{ + return CountryByIndex(index).GetFile(); +} - void Storage::GetGroupAndCountry(TIndex const & index, string & group, string & country) const +shared_ptr<LocalCountryFile> Storage::GetLatestLocalFile(CountryFile const & countryFile) const +{ + TIndex const index = FindIndexByFile(countryFile.GetNameWithoutExt()); { - string fName = CountryByIndex(index).GetFile().GetFileWithoutExt(); - CountryInfo::FileName2FullName(fName); - CountryInfo::FullName2GroupAndMap(fName, group, country); + shared_ptr<LocalCountryFile> localFile = GetLatestLocalFile(index); + if (localFile.get()) + return localFile; } - - size_t Storage::CountriesCount(TIndex const & index) const { - return NodeFromIndex(m_countries, index).SiblingsCount(); + auto const it = m_localFilesForFakeCountries.find(countryFile); + if (it != m_localFilesForFakeCountries.end()) + return it->second; } + return shared_ptr<LocalCountryFile>(); +} - string const & Storage::CountryName(TIndex const & index) const +shared_ptr<LocalCountryFile> Storage::GetLatestLocalFile(TIndex const & index) const +{ + auto const it = m_localFiles.find(index); + if (it == m_localFiles.end() || it->second.empty()) + return shared_ptr<LocalCountryFile>(); + list<shared_ptr<LocalCountryFile>> const & files = it->second; + shared_ptr<LocalCountryFile> latest = files.front(); + for (shared_ptr<LocalCountryFile> const & file : files) { - return NodeFromIndex(m_countries, index).Value().Name(); + if (file->GetVersion() > latest->GetVersion()) + latest = file; } + return latest; +} - string const & Storage::CountryFlag(TIndex const & index) const +TStatus Storage::CountryStatus(TIndex const & index) const +{ + // first, check if we already downloading this country or have in in the queue + if (IsCountryInQueue(index)) { - return NodeFromIndex(m_countries, index).Value().Flag(); + if (IsCountryFirstInQueue(index)) + return TStatus::EDownloading; + else + return TStatus::EInQueue; } - string Storage::CountryFileName(TIndex const & index, TMapOptions opt) const - { - return QueuedCountry(*this, index, opt).GetFileName(); - } + // second, check if this country has failed while downloading + if (m_failedCountries.count(index) > 0) + return TStatus::EDownloadFailed; + + return TStatus::EUnknown; +} + +TStatus Storage::CountryStatusEx(TIndex const & index) const +{ + return CountryStatusFull(index, CountryStatus(index)); +} + +void Storage::CountryStatusEx(TIndex const & index, TStatus & status, TMapOptions & options) const +{ + status = CountryStatusEx(index); - string const & Storage::CountryFileNameWithoutExt(TIndex const & index) const + if (status == TStatus::EOnDisk || status == TStatus::EOnDiskOutOfDate) { - return CountryByIndex(index).GetFile().GetFileWithoutExt(); + options = TMapOptions::EMap; + + shared_ptr<LocalCountryFile> localFile = GetLatestLocalFile(index); + ASSERT(localFile.get(), ("Invariant violation: local file out of sync with disk.")); + if (localFile->OnDisk(TMapOptions::ECarRouting)) + options = SetOptions(options, TMapOptions::ECarRouting); } +} - string Storage::MapWithoutExt(string mapFile) +void Storage::DownloadCountry(TIndex const & index, TMapOptions opt) +{ + opt = NormalizeDownloadFileSet(index, opt); + if (opt == TMapOptions::ENothing) + return; + + if (QueuedCountry * queuedCountry = FindCountryInQueue(index)) { - string::size_type const pos = mapFile.rfind(DATA_FILE_EXTENSION); - if (pos != string::npos) - mapFile.resize(pos); - return std::move(mapFile); + queuedCountry->AddOptions(opt); + return; } - LocalAndRemoteSizeT Storage::CountrySizeInBytes(TIndex const & index, TMapOptions opt) const - { - LocalAndRemoteSizeT sizes(0, 0); - QueuedCountry cnt(*this, index, opt); - auto const found = find(m_queue.begin(), m_queue.end(), index); - if (found != m_queue.end()) - { - sizes.second = cnt.GetFullRemoteSize(); - if (!m_downloader->IsIdle() && m_queue.front().GetIndex() == index) - sizes.first = m_downloader->GetDownloadingProgress().first + m_countryProgress.first; - } - else - sizes = cnt.GetFullSize(); + m_failedCountries.erase(index); + m_queue.push_back(QueuedCountry(index, opt)); + if (m_queue.size() == 1) + DownloadNextCountryFromQueue(); + else + NotifyStatusChanged(index); +} - return sizes; - } +void Storage::DeleteCountry(TIndex const & index, TMapOptions opt) +{ + opt = NormalizeDeleteFileSet(opt); + DeleteCountryFiles(index, opt); + DeleteCountryFilesFromDownloader(index, opt); + KickDownloaderAfterDeletionOfCountryFiles(index); + NotifyStatusChanged(index); +} + +void Storage::DeleteCustomCountryVersion(LocalCountryFile const & localFile) +{ + CountryFile const countryFile = localFile.GetCountryFile(); + localFile.DeleteFromDisk(TMapOptions::EMapWithCarRouting); - TStatus Storage::CountryStatus(TIndex const & index) const { - // first, check if we already downloading this country or have in in the queue - auto const found = find(m_queue.begin(), m_queue.end(), index); - if (found != m_queue.end()) + auto it = m_localFilesForFakeCountries.find(countryFile); + if (it != m_localFilesForFakeCountries.end()) { - if (found == m_queue.begin()) - return TStatus::EDownloading; - else - return TStatus::EInQueue; + m_localFilesForFakeCountries.erase(it); + return; } - - // second, check if this country has failed while downloading - if (m_failedCountries.count(index) > 0) - return TStatus::EDownloadFailed; - - return TStatus::EUnknown; } - TStatus Storage::CountryStatusEx(TIndex const & index) const + TIndex const index = FindIndexByFile(countryFile.GetNameWithoutExt()); + if (!index.IsValid()) { - return CountryStatusFull(index, CountryStatus(index)); + LOG(LERROR, ("Removed files for an unknown country:", localFile)); + return; } - void Storage::CountryStatusEx(TIndex const & index, TStatus & status, TMapOptions & options) const - { - status = CountryStatusEx(index); - - if (status == TStatus::EOnDisk || status == TStatus::EOnDiskOutOfDate) - { - options = TMapOptions::EMap; + MY_SCOPE_GUARD(notifyStatusChanged, bind(&Storage::NotifyStatusChanged, this, index)); - string const fName = QueuedCountry(*this, index, TMapOptions::ECarRouting).GetFileName(); - Platform const & pl = GetPlatform(); - if (pl.IsFileExistsByFullPath(pl.WritablePathForFile(fName))) - options = SetOptions(options, TMapOptions::ECarRouting); - } + // If file version equals to current data version, delete from downloader all pending requests for + // the country. + if (localFile.GetVersion() == GetCurrentDataVersion()) + DeleteCountryFilesFromDownloader(index, TMapOptions::EMapWithCarRouting); + auto countryFilesIt = m_localFiles.find(index); + if (countryFilesIt == m_localFiles.end()) + { + LOG(LERROR, ("Deleted files of an unregistered country:", localFile)); + return; } - void Storage::DownloadCountry(TIndex const & index, TMapOptions opt) + auto equalsToLocalFile = [&localFile](shared_ptr<LocalCountryFile> const & rhs) { - ASSERT(opt == TMapOptions::EMap || opt == TMapOptions::EMapWithCarRouting, ()); - // check if we already downloading this country - auto const found = find(m_queue.begin(), m_queue.end(), index); - if (found != m_queue.end()) - { - found->AddOptions(opt); - return; - } + return localFile == *rhs; + }; + countryFilesIt->second.remove_if(equalsToLocalFile); +} - // remove it from failed list - m_failedCountries.erase(index); +void Storage::NotifyStatusChanged(TIndex const & index) +{ + for (CountryObservers const & observer : m_observers) + observer.m_changeCountryFn(index); +} - // add it into the queue - QueuedCountry cnt(*this, index, opt); - if (!cnt.Correct(CountryStatusWithoutFailed(index))) - return; - m_queue.push_back(cnt); +void Storage::DownloadNextCountryFromQueue() +{ + if (m_queue.empty()) + return; - // and start download if necessary - if (m_queue.size() == 1) - { - DownloadNextCountryFromQueue(); - } - else - { - // notify about "In Queue" status - NotifyStatusChanged(index); - } - } + QueuedCountry & queuedCountry = m_queue.front(); + DownloadNextFile(queuedCountry); - void Storage::NotifyStatusChanged(TIndex const & index) - { - for (CountryObservers const & o : m_observers) - o.m_changeCountryFn(index); - } + // New status for the country, "Downloading" + NotifyStatusChanged(queuedCountry.GetIndex()); +} - void Storage::DownloadNextCountryFromQueue() - { - if (!m_queue.empty()) - { - QueuedCountry & cnt = m_queue.front(); +void Storage::DownloadNextFile(QueuedCountry const & country) +{ + CountryFile const & countryFile = GetCountryFile(country.GetIndex()); - m_countryProgress.first = 0; - m_countryProgress.second = cnt.GetFullRemoteSize(); + // send Country name for statistics + m_downloader->GetServersList(countryFile.GetNameWithoutExt(), + bind(&Storage::OnServerListDownloaded, this, _1)); +} - DownloadNextFile(cnt); +bool Storage::DeleteFromDownloader(TIndex const & index) +{ + if (!DeleteCountryFilesFromDownloader(index, TMapOptions::EMapWithCarRouting)) + return false; + KickDownloaderAfterDeletionOfCountryFiles(index); + NotifyStatusChanged(index); + return true; +} - // new status for country, "Downloading" - NotifyStatusChanged(cnt.GetIndex()); - } - } +bool Storage::IsDownloadInProgress() const { return !m_queue.empty(); } - void Storage::DownloadNextFile(QueuedCountry const & cnt) - { - // send Country name for statistics - m_downloader->GetServersList(cnt.GetFileName(), - bind(&Storage::OnServerListDownloaded, this, _1)); - } +void Storage::LoadCountriesFile(bool forceReload) +{ + if (forceReload) + m_countries.Clear(); - /* - m2::RectD Storage::CountryBounds(TIndex const & index) const + if (m_countries.SiblingsCount() == 0) { - Country const & country = CountryByIndex(index); - return country.Bounds(); + string json; + ReaderPtr<Reader>(GetPlatform().GetReader(COUNTRIES_FILE)).ReadAsString(json); + m_currentVersion = LoadCountries(json, m_countries); + if (m_currentVersion < 0) + LOG(LERROR, ("Can't load countries file", COUNTRIES_FILE)); } - */ +} - bool Storage::DeleteFromDownloader(TIndex const & index) - { - // check if we already downloading this country - auto const found = find(m_queue.begin(), m_queue.end(), index); - if (found != m_queue.end()) - { - if (found == m_queue.begin()) - { - // stop download - m_downloader->Reset(); - // remove from the queue - m_queue.erase(found); - // start another download if the queue is not empty - DownloadNextCountryFromQueue(); - } - else - { - // remove from the queue - m_queue.erase(found); - } +int Storage::Subscribe(TChangeCountryFunction const & change, TProgressFunction const & progress) +{ + CountryObservers obs; - NotifyStatusChanged(index); - return true; - } + obs.m_changeCountryFn = change; + obs.m_progressFn = progress; + obs.m_slotId = ++m_currentSlotId; - return false; - } + m_observers.push_back(obs); - bool Storage::IsDownloadInProgress() const - { - return !m_queue.empty(); - } + return obs.m_slotId; +} - void Storage::LoadCountriesFile(bool forceReload) +void Storage::Unsubscribe(int slotId) +{ + for (auto i = m_observers.begin(); i != m_observers.end(); ++i) { - if (forceReload) - m_countries.Clear(); - - if (m_countries.SiblingsCount() == 0) + if (i->m_slotId == slotId) { - string json; - ReaderPtr<Reader>(GetPlatform().GetReader(COUNTRIES_FILE)).ReadAsString(json); - m_currentVersion = LoadCountries(json, m_countries); - if (m_currentVersion < 0) - LOG(LERROR, ("Can't load countries file", COUNTRIES_FILE)); + m_observers.erase(i); + return; } } +} - int Storage::Subscribe(TChangeCountryFunction const & change, - TProgressFunction const & progress) - { - CountryObservers obs; +void Storage::OnMapFileDownloadFinished(bool success, + MapFilesDownloader::TProgress const & progress) +{ + if (m_queue.empty()) + return; - obs.m_changeCountryFn = change; - obs.m_progressFn = progress; - obs.m_slotId = ++m_currentSlotId; + QueuedCountry & queuedCountry = m_queue.front(); + TIndex const index = queuedCountry.GetIndex(); - m_observers.push_back(obs); + Platform & platform = GetPlatform(); + string const path = GetFileDownloadPath(index, queuedCountry.GetCurrentFile()); - return obs.m_slotId; - } + success = success && platform.IsFileExistsByFullPath(path) && + RegisterDownloadedFile(path, progress.first /* size */, GetCurrentDataVersion()); - void Storage::Unsubscribe(int slotId) + if (success && queuedCountry.SwitchToNextFile()) { - for (auto i = m_observers.begin(); i != m_observers.end(); ++i) - { - if (i->m_slotId == slotId) - { - m_observers.erase(i); - return; - } - } + DownloadNextFile(queuedCountry); + return; } - void Storage::OnMapDownloadFinished(bool success, MapFilesDownloader::TProgress const & progress) { - if (m_queue.empty()) + string optionsName; + switch (queuedCountry.GetInitOptions()) { - ASSERT ( false, ("queue can't be empty") ); - return; - } - - QueuedCountry & cnt = m_queue.front(); - TIndex const index = cnt.GetIndex(); - - bool const downloadHasFailed = !success; - { - string optionName; - switch (cnt.GetInitOptions()) - { - case TMapOptions::ENothing: optionName = "Nothing"; break; - case TMapOptions::EMap: optionName = "Map"; break; - case TMapOptions::ECarRouting: optionName = "CarRouting"; break; - case TMapOptions::EMapWithCarRouting: optionName = "MapWithCarRouting"; break; - } - alohalytics::LogEvent("$OnMapDownloadFinished", - alohalytics::TStringMap({{"name", cnt.GetMapFileName()}, - {"status", downloadHasFailed ? "failed" : "ok"}, - {"version", strings::to_string(GetCurrentDataVersion())}, - {"option", optionName}})); + case TMapOptions::ENothing: + optionsName = "Nothing"; + break; + case TMapOptions::EMap: + optionsName = "Map"; + break; + case TMapOptions::ECarRouting: + optionsName = "CarRouting"; + break; + case TMapOptions::EMapWithCarRouting: + optionsName = "MapWithCarRouting"; + break; } + alohalytics::LogEvent( + "$OnMapDownloadFinished", + alohalytics::TStringMap({{"name", GetCountryFile(index).GetNameWithoutExt()}, + {"status", success ? "ok" : "failed"}, + {"version", strings::to_string(GetCurrentDataVersion())}, + {"option", optionsName}})); + } + if (success) + { + shared_ptr<LocalCountryFile> localFile = GetLocalFile(index, GetCurrentDataVersion()); + ASSERT(localFile.get(), ()); + OnMapDownloadFinished(localFile); + } + else + { + OnMapDownloadFailed(); + } - if (downloadHasFailed) - { - // add to failed countries set - m_failedCountries.insert(index); - } - else - { - ASSERT_EQUAL(progress.first, progress.second, ()); - ASSERT_EQUAL(progress.first, cnt.GetDownloadSize(), ()); + m_queue.pop_front(); - m_countryProgress.first += progress.first; - if (cnt.MoveNextFile()) - { - DownloadNextFile(cnt); - return; - } + NotifyStatusChanged(index); - // notify framework that downloading is done - m_updateAfterDownload(cnt.GetMapFileName(), cnt.GetInitOptions()); - } + m_downloader->Reset(); + DownloadNextCountryFromQueue(); +} - m_queue.pop_front(); +void Storage::ReportProgress(TIndex const & idx, pair<int64_t, int64_t> const & p) +{ + for (CountryObservers const & o : m_observers) + o.m_progressFn(idx, p); +} - NotifyStatusChanged(index); +void Storage::OnServerListDownloaded(vector<string> const & urls) +{ + // Queue can be empty because countries were deleted from queue. + if (m_queue.empty()) + return; + + QueuedCountry const & queuedCountry = m_queue.front(); + TIndex const & index = queuedCountry.GetIndex(); + TMapOptions const file = queuedCountry.GetCurrentFile(); + + vector<string> fileUrls; + fileUrls.reserve(urls.size()); + for (string const & url : urls) + fileUrls.push_back(GetFileDownloadUrl(url, index, file)); + + string const filePath = GetFileDownloadPath(index, file); + m_downloader->DownloadMapFile(fileUrls, filePath, GetDownloadSize(queuedCountry), + bind(&Storage::OnMapFileDownloadFinished, this, _1, _2), + bind(&Storage::OnMapFileDownloadProgress, this, _1)); +} - m_downloader->Reset(); - DownloadNextCountryFromQueue(); - } +void Storage::OnMapFileDownloadProgress(MapFilesDownloader::TProgress const & progress) +{ + // Queue can be empty because countries were deleted from queue. + if (m_queue.empty()) + return; - void Storage::ReportProgress(TIndex const & idx, pair<int64_t, int64_t> const & p) + if (!m_observers.empty()) { - for (CountryObservers const & o : m_observers) - o.m_progressFn(idx, p); + QueuedCountry & queuedCountry = m_queue.front(); + CountryFile const & countryFile = GetCountryFile(queuedCountry.GetIndex()); + MapFilesDownloader::TProgress p = progress; + p.first += GetRemoteSize(countryFile, queuedCountry.GetDownloadedFiles()); + p.second = GetRemoteSize(countryFile, queuedCountry.GetInitOptions()); + + ReportProgress(m_queue.front().GetIndex(), p); } +} - void Storage::OnMapDownloadProgress(MapFilesDownloader::TProgress const & progress) - { - if (m_queue.empty()) - { - ASSERT ( false, ("queue can't be empty") ); - return; - } +bool Storage::RegisterDownloadedFile(string const & path, uint64_t size, int64_t version) +{ + QueuedCountry & queuedCountry = m_queue.front(); + TIndex const & index = queuedCountry.GetIndex(); + uint64_t const expectedSize = GetDownloadSize(queuedCountry); - if (!m_observers.empty()) - { - MapFilesDownloader::TProgress p = progress; - p.first += m_countryProgress.first; - p.second = m_countryProgress.second; + ASSERT_EQUAL(size, expectedSize, ("Downloaded file size mismatch:", size, + "bytes were downloaded,", expectedSize, "bytes expected.")); - ReportProgress(m_queue.front().GetIndex(), p); - } + CountryFile const countryFile = GetCountryFile(index); + shared_ptr<LocalCountryFile> localFile = GetLocalFile(index, version); + if (localFile.get() == nullptr) + localFile = PreparePlaceForCountryFiles(countryFile, version); + if (localFile.get() == nullptr) + { + LOG(LERROR, ("Local file data structure can't prepared for downloaded file(", path, ").")); + return false; } + if (!my::RenameFileX(path, localFile->GetPath(queuedCountry.GetCurrentFile()))) + return false; + RegisterCountryFiles(localFile); + return true; +} - void Storage::OnServerListDownloaded(vector<string> const & urls) - { - if (m_queue.empty()) - { - ASSERT ( false, ("queue can't be empty") ); - return; - } +void Storage::OnMapDownloadFinished(shared_ptr<LocalCountryFile> localFile) +{ + DeleteCountryIndexes(localFile->GetCountryFile()); - QueuedCountry const & cnt = m_queue.front(); + // Notify framework that all requested files for the country were downloaded. + m_updateAfterDownload(*localFile); +} - vector<string> fileUrls(urls.size()); - // append actual version and file name - string const fileName = cnt.GetFileName(); - for (size_t i = 0; i < urls.size(); ++i) - fileUrls[i] = GetFileDownloadUrl(urls[i], fileName); +void Storage::OnMapDownloadFailed() +{ + TIndex const & index = m_queue.front().GetIndex(); - string const filePath = GetPlatform().WritablePathForFile(fileName + READY_FILE_EXTENSION); - m_downloader->DownloadMapFile(fileUrls, filePath, cnt.GetDownloadSize(), - bind(&Storage::OnMapDownloadFinished, this, _1, _2), - bind(&Storage::OnMapDownloadProgress, this, _1)); - } + // Add country to the failed countries set. + m_failedCountries.insert(index); + m_downloader->Reset(); + DownloadNextCountryFromQueue(); +} - string Storage::GetFileDownloadUrl(string const & baseUrl, string const & fName) const - { - return baseUrl + OMIM_OS_NAME "/" + strings::to_string(m_currentVersion) + "/" + UrlEncode(fName); - } +string Storage::GetFileDownloadUrl(string const & baseUrl, TIndex const & index, + TMapOptions file) const +{ + CountryFile const & countryFile = GetCountryFile(index); + return baseUrl + OMIM_OS_NAME "/" + strings::to_string(GetCurrentDataVersion()) + "/" + + UrlEncode(countryFile.GetNameWithExt(file)); +} - namespace - { - class EqualFileName - { - string const & m_name; - public: - explicit EqualFileName(string const & name) : m_name(name) {} - bool operator()(SimpleTree<Country> const & node) const - { - Country const & c = node.Value(); - if (c.GetFilesCount() > 0) - return (c.GetFile().GetFileWithoutExt() == m_name); - else - return false; - } - }; - } +TIndex Storage::FindIndexByFile(string const & name) const +{ + EqualFileName fn(name); - TIndex Storage::FindIndexByFile(string const & name) const + for (size_t i = 0; i < m_countries.SiblingsCount(); ++i) { - EqualFileName fn(name); + if (fn(m_countries[i])) + return TIndex(static_cast<int>(i)); - for (size_t i = 0; i < m_countries.SiblingsCount(); ++i) + for (size_t j = 0; j < m_countries[i].SiblingsCount(); ++j) { - if (fn(m_countries[i])) - return TIndex(static_cast<int>(i)); + if (fn(m_countries[i][j])) + return TIndex(static_cast<int>(i), static_cast<int>(j)); - for (size_t j = 0; j < m_countries[i].SiblingsCount(); ++j) + for (size_t k = 0; k < m_countries[i][j].SiblingsCount(); ++k) { - if (fn(m_countries[i][j])) - return TIndex(static_cast<int>(i), static_cast<int>(j)); - - for (size_t k = 0; k < m_countries[i][j].SiblingsCount(); ++k) - { - if (fn(m_countries[i][j][k])) - return TIndex(static_cast<int>(i), static_cast<int>(j), static_cast<int>(k)); - } + if (fn(m_countries[i][j][k])) + return TIndex(static_cast<int>(i), static_cast<int>(j), static_cast<int>(k)); } } - - return TIndex(); } - vector<TIndex> Storage::FindAllIndexesByFile(string const & name) const + return TIndex(); +} + +vector<TIndex> Storage::FindAllIndexesByFile(string const & name) const +{ + EqualFileName fn(name); + vector<TIndex> res; + + for (size_t i = 0; i < m_countries.SiblingsCount(); ++i) { - EqualFileName fn(name); - vector<TIndex> res; + if (fn(m_countries[i])) + res.emplace_back(static_cast<int>(i)); - for (size_t i = 0; i < m_countries.SiblingsCount(); ++i) + for (size_t j = 0; j < m_countries[i].SiblingsCount(); ++j) { - if (fn(m_countries[i])) - res.emplace_back(static_cast<int>(i)); + if (fn(m_countries[i][j])) + res.emplace_back(static_cast<int>(i), static_cast<int>(j)); - for (size_t j = 0; j < m_countries[i].SiblingsCount(); ++j) + for (size_t k = 0; k < m_countries[i][j].SiblingsCount(); ++k) { - if (fn(m_countries[i][j])) - res.emplace_back(static_cast<int>(i), static_cast<int>(j)); - - for (size_t k = 0; k < m_countries[i][j].SiblingsCount(); ++k) - { - if (fn(m_countries[i][j][k])) - res.emplace_back(static_cast<int>(i), static_cast<int>(j), static_cast<int>(k)); - } + if (fn(m_countries[i][j][k])) + res.emplace_back(static_cast<int>(i), static_cast<int>(j), static_cast<int>(k)); } } - - return res; } - void Storage::GetOutdatedCountries(vector<Country const *> & res) const + return res; +} + +void Storage::GetOutdatedCountries(vector<Country const *> & countries) const +{ + for (auto const & p : m_localFiles) { - Platform & pl = GetPlatform(); - Platform::FilesList fList; - pl.GetFilesByExt(pl.WritableDir(), DATA_FILE_EXTENSION, fList); + TIndex const & index = p.first; + string const name = GetCountryFile(index).GetNameWithoutExt(); + shared_ptr<LocalCountryFile> const & file = GetLatestLocalFile(index); + if (file.get() && file->GetVersion() != GetCurrentDataVersion() && + name != WORLD_COASTS_FILE_NAME && name != WORLD_FILE_NAME) + { + countries.push_back(&CountryByIndex(index)); + } + } +} - for_each(fList.begin(), fList.end(), bind(&my::GetNameWithoutExt, _1)); +TStatus Storage::CountryStatusWithoutFailed(TIndex const & index) const +{ + // First, check if we already downloading this country or have in in the queue. + if (!IsCountryInQueue(index)) + return CountryStatusFull(index, TStatus::EUnknown); + return IsCountryFirstInQueue(index) ? TStatus::EDownloading : TStatus::EInQueue; +} - fList.erase(remove_if(fList.begin(), fList.end(), [] (string const & t) - { - return (t == WORLD_COASTS_FILE_NAME) || (t == WORLD_FILE_NAME); - }), fList.end()); +TStatus Storage::CountryStatusFull(TIndex const & index, TStatus const status) const +{ + if (status != TStatus::EUnknown) + return status; - fList.erase(remove_if(fList.begin(), fList.end(), [this] (string const & file) - { - return (CountryStatusEx(FindIndexByFile(file)) != TStatus::EOnDiskOutOfDate); - }), fList.end()); + shared_ptr<LocalCountryFile> localFile = GetLatestLocalFile(index); + if (localFile.get() == nullptr || !localFile->OnDisk(TMapOptions::EMap)) + return TStatus::ENotDownloaded; - for (size_t i = 0; i < fList.size(); ++i) - res.push_back(&CountryByIndex(FindIndexByFile(fList[i]))); - } + CountryFile const & countryFile = GetCountryFile(index); + if (GetRemoteSize(countryFile, TMapOptions::EMap) == 0) + return TStatus::EUnknown; - void Storage::SetDownloaderForTesting(unique_ptr<MapFilesDownloader> && downloader) - { - m_downloader = move(downloader); - } + if (localFile->GetVersion() != GetCurrentDataVersion()) + return TStatus::EOnDiskOutOfDate; + return TStatus::EOnDisk; +} - TStatus Storage::CountryStatusWithoutFailed(TIndex const & index) const +TMapOptions Storage::NormalizeDownloadFileSet(TIndex const & index, TMapOptions opt) const +{ + // Car routing files are useless without map files. + if (HasOptions(opt, TMapOptions::ECarRouting)) + opt = SetOptions(opt, TMapOptions::EMap); + + shared_ptr<LocalCountryFile> localCountryFile = GetLatestLocalFile(index); + for (TMapOptions file : {TMapOptions::EMap, TMapOptions::ECarRouting}) { - // first, check if we already downloading this country or have in in the queue - auto const found = find(m_queue.begin(), m_queue.end(), index); - if (found != m_queue.end()) + // Check whether requested files are on disk and up-to-date. + if (HasOptions(opt, file) && localCountryFile.get() && localCountryFile->OnDisk(file) && + localCountryFile->GetVersion() == GetCurrentDataVersion()) { - if (found == m_queue.begin()) - return TStatus::EDownloading; - else - return TStatus::EInQueue; + opt = UnsetOptions(opt, file); } - - return CountryStatusFull(index, TStatus::EUnknown); } + return opt; +} + +TMapOptions Storage::NormalizeDeleteFileSet(TMapOptions opt) const +{ + // Car routing files are useless without map files. + if (HasOptions(opt, TMapOptions::EMap)) + opt = SetOptions(opt, TMapOptions::ECarRouting); + return opt; +} + +QueuedCountry * Storage::FindCountryInQueue(TIndex const & index) +{ + auto it = find(m_queue.begin(), m_queue.end(), index); + return it == m_queue.end() ? nullptr : &*it; +} + +QueuedCountry const * Storage::FindCountryInQueue(TIndex const & index) const +{ + auto it = find(m_queue.begin(), m_queue.end(), index); + return it == m_queue.end() ? nullptr : &*it; +} + +bool Storage::IsCountryInQueue(TIndex const & index) const +{ + return FindCountryInQueue(index) != nullptr; +} + +bool Storage::IsCountryFirstInQueue(TIndex const & index) const +{ + return !m_queue.empty() && m_queue.front().GetIndex() == index; +} - TStatus Storage::CountryStatusFull(TIndex const & index, TStatus const status) const +void Storage::SetDownloaderForTesting(unique_ptr<MapFilesDownloader> && downloader) +{ + m_downloader = move(downloader); +} + +shared_ptr<LocalCountryFile> Storage::GetLocalFile(TIndex const & index, int64_t version) const +{ + auto const it = m_localFiles.find(index); + if (it == m_localFiles.end() || it->second.empty()) + return shared_ptr<LocalCountryFile>(); + list<shared_ptr<LocalCountryFile>> const & files = it->second; + for (shared_ptr<LocalCountryFile> const & file : files) { - if (status != TStatus::EUnknown) - return status; + if (file->GetVersion() == version) + return file; + } + return shared_ptr<LocalCountryFile>(); +} - TStatus res = status; - Country const & c = CountryByIndex(index); - LocalAndRemoteSizeT const size = c.Size(TMapOptions::EMap); +void Storage::RegisterCountryFiles(shared_ptr<LocalCountryFile> localFile) +{ + ASSERT(localFile.get(), ()); + localFile->SyncWithDisk(); + + TIndex const index = FindIndexByFile(localFile->GetCountryFile().GetNameWithoutExt()); + shared_ptr<LocalCountryFile> existingFile = GetLocalFile(index, localFile->GetVersion()); + if (existingFile.get() != nullptr) + ASSERT_EQUAL(localFile.get(), existingFile.get(), ()); + else + m_localFiles[index].push_front(localFile); +} - if (size.first == 0) - return TStatus::ENotDownloaded; +void Storage::RegisterCountryFiles(TIndex const & index, string const & directory, int64_t version) +{ + shared_ptr<LocalCountryFile> localFile = GetLocalFile(index, version); + if (localFile.get() != nullptr) + return; - if (size.second == 0) - return TStatus::EUnknown; + CountryFile const & countryFile = GetCountryFile(index); + localFile = make_shared<LocalCountryFile>(directory, countryFile, version); + RegisterCountryFiles(localFile); +} - res = TStatus::EOnDisk; - if (size.first != size.second) - { - /// @todo Do better version check, not just size comparison. +void Storage::RegisterFakeCountryFiles(platform::LocalCountryFile const & localFile) +{ + shared_ptr<LocalCountryFile> fakeCountryLocalFile = make_shared<LocalCountryFile>(localFile); + fakeCountryLocalFile->SyncWithDisk(); + m_localFilesForFakeCountries[fakeCountryLocalFile->GetCountryFile()] = fakeCountryLocalFile; +} - // Additional check for .ready file. - // Use EOnDisk status if it's good, or EOnDiskOutOfDate otherwise. - Platform const & pl = GetPlatform(); - string const fName = c.GetFile().GetFileWithExt(TMapOptions::EMap) + READY_FILE_EXTENSION; +void Storage::DeleteCountryFiles(TIndex const & index, TMapOptions opt) +{ + auto const it = m_localFiles.find(index); + if (it == m_localFiles.end()) + return; - uint64_t sz = 0; - if (!pl.GetFileSizeByFullPath(pl.WritablePathForFile(fName), sz) || sz != size.second) - res = TStatus::EOnDiskOutOfDate; - } + // TODO (@gorshenin): map-only indexes should not be touched when + // routing indexes are removed. + if (!it->second.empty()) + DeleteCountryIndexes(it->second.front()->GetCountryFile()); - return res; + list<shared_ptr<platform::LocalCountryFile>> & localFiles = it->second; + for (shared_ptr<LocalCountryFile> & localFile : localFiles) + { + localFile->DeleteFromDisk(opt); + localFile->SyncWithDisk(); + if (localFile->GetFiles() == TMapOptions::ENothing) + localFile.reset(); + } + auto isNull = [](shared_ptr<LocalCountryFile> const & localFile) + { + return !localFile.get(); + }; + localFiles.remove_if(isNull); + if (localFiles.empty()) + m_localFiles.erase(index); +} + +bool Storage::DeleteCountryFilesFromDownloader(TIndex const & index, TMapOptions opt) +{ + QueuedCountry * queuedCountry = FindCountryInQueue(index); + if (!queuedCountry) + return false; + if (IsCountryFirstInQueue(index)) + { + // Abrupt downloading of the current file if it should be removed. + if (HasOptions(opt, queuedCountry->GetCurrentFile())) + m_downloader->Reset(); } + queuedCountry->RemoveOptions(opt); + + // Remove country from the queue if there's nothing to download. + if (queuedCountry->GetInitOptions() == TMapOptions::ENothing) + m_queue.erase(find(m_queue.begin(), m_queue.end(), index)); + return true; +} + +void Storage::KickDownloaderAfterDeletionOfCountryFiles(TIndex const & index) +{ + // Do nothing when there're no counties to download or when downloader is busy. + if (m_queue.empty() || !m_downloader->IsIdle()) + return; + if (IsCountryFirstInQueue(index)) + DownloadNextFile(m_queue.front()); + else + DownloadNextCountryFromQueue(); +} + +uint64_t Storage::GetDownloadSize(QueuedCountry const & queuedCountry) const +{ + CountryFile const & file = GetCountryFile(queuedCountry.GetIndex()); + return GetRemoteSize(file, queuedCountry.GetCurrentFile()); +} + +string Storage::GetFileDownloadPath(TIndex const & index, TMapOptions file) const +{ + Platform & platform = GetPlatform(); + CountryFile const & countryFile = GetCountryFile(index); + return platform.WritablePathForFile(countryFile.GetNameWithExt(file) + READY_FILE_EXTENSION); } +} // namespace storage diff --git a/storage/storage.hpp b/storage/storage.hpp index efdc07f4c6..c080986061 100644 --- a/storage/storage.hpp +++ b/storage/storage.hpp @@ -3,168 +3,230 @@ #include "storage/country.hpp" #include "storage/index.hpp" #include "storage/map_files_downloader.hpp" +#include "storage/queued_country.hpp" +#include "storage/storage_defines.hpp" #include "storage/storage_defines.hpp" -#include "std/vector.hpp" +#include "std/function.hpp" #include "std/list.hpp" -#include "std/string.hpp" #include "std/set.hpp" -#include "std/function.hpp" +#include "std/shared_ptr.hpp" +#include "std/string.hpp" #include "std/unique_ptr.hpp" - +#include "std/vector.hpp" namespace storage { - /// Can be used to store local maps and/or maps available for download - class Storage +/// Can be used to store local maps and/or maps available for download +class Storage +{ + /// We support only one simultaneous request at the moment + unique_ptr<MapFilesDownloader> m_downloader; + + /// stores timestamp for update checks + int64_t m_currentVersion; + + CountriesContainerT m_countries; + + typedef list<QueuedCountry> TQueue; + + /// @todo. It appeared that our application uses m_queue from + /// different threads without any synchronization. To reproduce it + /// just download a map "from the map" on Android. (CountryStatus is + /// called from a different thread.) It's necessary to check if we + /// can call all the methods from a single thread using + /// RunOnUIThread. If not, at least use a syncronization object. + TQueue m_queue; + + /// stores countries which download has failed recently + typedef set<TIndex> TCountriesSet; + TCountriesSet m_failedCountries; + + map<TIndex, list<shared_ptr<platform::LocalCountryFile>>> m_localFiles; + map<platform::CountryFile, shared_ptr<platform::LocalCountryFile>> m_localFilesForFakeCountries; + + /// used to correctly calculate total country download progress with more than 1 file + /// <current, total> + MapFilesDownloader::TProgress m_countryProgress; + + /// @name Communicate with GUI + //@{ + typedef function<void(TIndex const &)> TChangeCountryFunction; + typedef function<void(TIndex const &, LocalAndRemoteSizeT const &)> TProgressFunction; + + int m_currentSlotId; + + struct CountryObservers { - /// We support only one simultaneous request at the moment - unique_ptr<MapFilesDownloader> m_downloader; + TChangeCountryFunction m_changeCountryFn; + TProgressFunction m_progressFn; + int m_slotId; + }; - /// stores timestamp for update checks - int64_t m_currentVersion; + typedef list<CountryObservers> ObserversContT; + ObserversContT m_observers; + //@} - CountriesContainerT m_countries; + typedef function<void(platform::LocalCountryFile const &)> TUpdateAfterDownload; - /// store queue for downloading - class QueuedCountry - { - TIndex m_index; - CountryFile const * m_pFile; - TMapOptions m_init, m_left, m_current; + // This function is called each time all files requested for a + // country were successfully downloaded. + TUpdateAfterDownload m_updateAfterDownload; - public: - QueuedCountry(Storage const & storage, TIndex const & index, TMapOptions opt); + void DownloadNextCountryFromQueue(); - void AddOptions(TMapOptions opt); - bool MoveNextFile(); - bool Correct(TStatus currentStatus); + void LoadCountriesFile(bool forceReload); - TIndex const & GetIndex() const { return m_index; } - TMapOptions GetInitOptions() const { return m_init; } - - bool operator== (TIndex const & index) const { return (m_index == index); } - - uint64_t GetDownloadSize() const; - LocalAndRemoteSizeT GetFullSize() const; - size_t GetFullRemoteSize() const; - string GetFileName() const; - string GetMapFileName() const; - }; + void ReportProgress(TIndex const & index, pair<int64_t, int64_t> const & p); - typedef list<QueuedCountry> TQueue; - /// @todo. It appeared that our application uses m_queue from different threads - /// without any synchronization. To reproduce it just download a map "from the map" - /// on Android. (CountryStatus is called from a different thread.) - /// It's necessary to check if we can call all the methods from a single thread using RunOnUIThread. - /// If not, at least use a syncronization object. - TQueue m_queue; + /// Called on the main thread by MapFilesDownloader when list of + /// suitable servers is received. + void OnServerListDownloaded(vector<string> const & urls); - /// stores countries which download has failed recently - typedef set<TIndex> TCountriesSet; - TCountriesSet m_failedCountries; + /// Called on the main thread by MapFilesDownloader when + /// downloading of a map file succeeds/fails. + void OnMapFileDownloadFinished(bool success, MapFilesDownloader::TProgress const & progress); - /// used to correctly calculate total country download progress with more than 1 file - /// <current, total> - MapFilesDownloader::TProgress m_countryProgress; + /// Periodically called on the main thread by MapFilesDownloader + /// during the downloading process. + void OnMapFileDownloadProgress(MapFilesDownloader::TProgress const & progress); - /// @name Communicate with GUI - //@{ - typedef function<void (TIndex const &)> TChangeCountryFunction; - typedef function<void (TIndex const &, LocalAndRemoteSizeT const &)> TProgressFunction; + bool RegisterDownloadedFile(string const & path, uint64_t size, int64_t version); + void OnMapDownloadFinished(shared_ptr<platform::LocalCountryFile> localFile); + void OnMapDownloadFailed(); - int m_currentSlotId; + /// Initiates downloading of the next file from the queue. + void DownloadNextFile(QueuedCountry const & country); - struct CountryObservers - { - TChangeCountryFunction m_changeCountryFn; - TProgressFunction m_progressFn; - int m_slotId; - }; +public: + Storage(); - typedef list<CountryObservers> ObserversContT; - ObserversContT m_observers; - //@} + void Init(TUpdateAfterDownload const & updateFn); - /// @name Communicate with Framework - //@{ - typedef function<void (string const &, TMapOptions)> TUpdateAfterDownload; - TUpdateAfterDownload m_updateAfterDownload; - //@} + // Finds and registers all map files in maps directory. In the case + // of several versions of the same map keeps only the latest one, others + // are deleted from disk. + // *NOTE* storage will forget all already known local maps. + void RegisterAllLocalMaps(); - void DownloadNextCountryFromQueue(); + // Returns list of all local maps, including fake countries. + void GetLocalMaps(vector<platform::CountryFile> & maps); - void LoadCountriesFile(bool forceReload); + /// @return unique identifier that should be used with Unsubscribe function + int Subscribe(TChangeCountryFunction const & change, TProgressFunction const & progress); + void Unsubscribe(int slotId); - void ReportProgress(TIndex const & index, pair<int64_t, int64_t> const & p); + Country const & CountryByIndex(TIndex const & index) const; + TIndex FindIndexByFile(string const & name) const; + /// @todo Temporary function to gel all associated indexes for the country file name. + /// Will be removed in future after refactoring. + vector<TIndex> FindAllIndexesByFile(string const & name) const; + void GetGroupAndCountry(TIndex const & index, string & group, string & country) const; - /// Called on the main thread by MapFilesDownloader when list of - /// suitable servers is received. - void OnServerListDownloaded(vector<string> const & urls); + size_t CountriesCount(TIndex const & index) const; + string const & CountryName(TIndex const & index) const; + string const & CountryFlag(TIndex const & index) const; - /// Called on the main thread by MapFilesDownloader when - /// downloading of a map file succeeds/fails. - void OnMapDownloadFinished(bool success, MapFilesDownloader::TProgress const & progress); + LocalAndRemoteSizeT CountrySizeInBytes(TIndex const & index, TMapOptions opt) const; + platform::CountryFile const & GetCountryFile(TIndex const & index) const; + shared_ptr<platform::LocalCountryFile> GetLatestLocalFile( + platform::CountryFile const & countryFile) const; + shared_ptr<platform::LocalCountryFile> GetLatestLocalFile(TIndex const & index) const; - /// Periodically called on the main thread by MapFilesDownloader - /// during the downloading process. - void OnMapDownloadProgress(MapFilesDownloader::TProgress const & progress); + /// Fast version, doesn't check if country is out of date + TStatus CountryStatus(TIndex const & index) const; + /// Slow version, but checks if country is out of date + TStatus CountryStatusEx(TIndex const & index) const; + void CountryStatusEx(TIndex const & index, TStatus & status, TMapOptions & options) const; - /// Initiates downloading of the next file from the queue. - void DownloadNextFile(QueuedCountry const & cnt); + /// Puts country denoted by index into the downloader's queue. + /// During downloading process notifies observers about downloading + /// progress and status changes. + void DownloadCountry(TIndex const & index, TMapOptions opt); - public: - Storage(); + /// Removes country files (for all versions) from the device. + /// Notifies observers about country status change. + void DeleteCountry(TIndex const & index, TMapOptions opt); - void Init(TUpdateAfterDownload const & updateFn); + /// Removes country files of a particular version from the device. + /// Notifies observers about country status change. + void DeleteCustomCountryVersion(platform::LocalCountryFile const & localFile); - /// @return unique identifier that should be used with Unsubscribe function - int Subscribe(TChangeCountryFunction const & change, - TProgressFunction const & progress); - void Unsubscribe(int slotId); + /// \return True iff country denoted by index was successfully + /// deleted from the downloader's queue. + bool DeleteFromDownloader(TIndex const & index); + bool IsDownloadInProgress() const; - Country const & CountryByIndex(TIndex const & index) const; - TIndex FindIndexByFile(string const & name) const; - /// @todo Temporary function to gel all associated indexes for the country file name. - /// Will be removed in future after refactoring. - vector<TIndex> FindAllIndexesByFile(string const & name) const; - void GetGroupAndCountry(TIndex const & index, string & group, string & country) const; + void NotifyStatusChanged(TIndex const & index); - size_t CountriesCount(TIndex const & index) const; - string const & CountryName(TIndex const & index) const; - string const & CountryFlag(TIndex const & index) const; + string GetFileDownloadUrl(string const & baseUrl, TIndex const & index, TMapOptions file) const; - string CountryFileName(TIndex const & index, TMapOptions opt) const; - string const & CountryFileNameWithoutExt(TIndex const & index) const; - /// Removes map file extension. - static string MapWithoutExt(string mapFile); - LocalAndRemoteSizeT CountrySizeInBytes(TIndex const & index, TMapOptions opt) const; + /// @param[out] res Populated with oudated countries. + void GetOutdatedCountries(vector<Country const *> & countries) const; - /// Fast version, doesn't check if country is out of date - TStatus CountryStatus(TIndex const & index) const; - /// Slow version, but checks if country is out of date - TStatus CountryStatusEx(TIndex const & index) const; - void CountryStatusEx(TIndex const & index, TStatus & status, TMapOptions & options) const; + inline int64_t GetCurrentDataVersion() const { return m_currentVersion; } - //m2::RectD CountryBounds(TIndex const & index) const; + void SetDownloaderForTesting(unique_ptr<MapFilesDownloader> && downloader); - void DownloadCountry(TIndex const & index, TMapOptions opt); - bool DeleteFromDownloader(TIndex const & index); - bool IsDownloadInProgress() const; +private: + TStatus CountryStatusWithoutFailed(TIndex const & index) const; + TStatus CountryStatusFull(TIndex const & index, TStatus const status) const; - void NotifyStatusChanged(TIndex const & index); + // Modifies file set of requested files - always adds a map file + // when routing file is requested for downloading, but drops all + // already downloaded and up-to-date files. + TMapOptions NormalizeDownloadFileSet(TIndex const & index, TMapOptions opt) const; - string GetFileDownloadUrl(string const & baseUrl, string const & fName) const; + // Modifies file set of file to deletion - always adds (marks for + // removal) a routing file when map file is marked for deletion. + TMapOptions NormalizeDeleteFileSet(TMapOptions opt) const; - /// @param[out] res Populated with oudated countries. - void GetOutdatedCountries(vector<Country const *> & res) const; + // Returns a pointer to a country in the downloader's queue. + QueuedCountry * FindCountryInQueue(TIndex const & index); - int64_t GetCurrentDataVersion() const { return m_currentVersion; } + // Returns a pointer to a country in the downloader's queue. + QueuedCountry const * FindCountryInQueue(TIndex const & index) const; - void SetDownloaderForTesting(unique_ptr<MapFilesDownloader> && downloader); + // Returns true when country is in the downloader's queue. + bool IsCountryInQueue(TIndex const & index) const; - private: - TStatus CountryStatusWithoutFailed(TIndex const & index) const; - TStatus CountryStatusFull(TIndex const & index, TStatus const status) const; - }; -} + // Returns true when country is first in the downloader's queue. + bool IsCountryFirstInQueue(TIndex const & index) const; + + // Returns local country files of a particular version, or wrapped + // nullptr if there're no country files corresponding to the + // version. + shared_ptr<platform::LocalCountryFile> GetLocalFile(TIndex const & index, int64_t version) const; + + // Tries to register disk files for a real (listed in countries.txt) + // country. If map files of the same version were already + // registered, does nothing. + void RegisterCountryFiles(shared_ptr<platform::LocalCountryFile> localFile); + + // Registers disk files for a country. This method must be used only + // for real (listed in counties.txt) countries. + void RegisterCountryFiles(TIndex const & index, string const & directory, int64_t version); + + // Registers disk files for a country. This method must be used only + // for custom (made by user) map files. + void RegisterFakeCountryFiles(platform::LocalCountryFile const & localFile); + + // Removes disk files for all versions of a country. + void DeleteCountryFiles(TIndex const & index, TMapOptions opt); + + // Removes country files from downloader. + bool DeleteCountryFilesFromDownloader(TIndex const & index, TMapOptions opt); + + // Resumes possibly cancelled downloading of countries after + // deletion of country files. + void KickDownloaderAfterDeletionOfCountryFiles(TIndex const & index); + + // Returns download size of the currently downloading file for the + // queued country. + uint64_t GetDownloadSize(QueuedCountry const & queuedCountry) const; + + // Returns a path to a place on disk downloader can use for + // downloaded files. + string GetFileDownloadPath(TIndex const & index, TMapOptions file) const; +}; +} // storage diff --git a/storage/storage.pro b/storage/storage.pro index ba4d116a3e..5cd9da9f8a 100644 --- a/storage/storage.pro +++ b/storage/storage.pro @@ -18,6 +18,7 @@ HEADERS += \ http_map_files_downloader.hpp \ index.hpp \ map_files_downloader.hpp \ + queued_country.hpp \ simple_tree.hpp \ storage.hpp \ storage_defines.hpp \ @@ -28,5 +29,6 @@ SOURCES += \ country_info.cpp \ http_map_files_downloader.cpp \ index.cpp \ + queued_country.cpp \ storage.cpp \ storage_defines.cpp \ diff --git a/storage/storage_defines.hpp b/storage/storage_defines.hpp index 5aa9df921b..901ecc10d0 100644 --- a/storage/storage_defines.hpp +++ b/storage/storage_defines.hpp @@ -22,4 +22,4 @@ namespace storage string DebugPrint(TStatus status); typedef pair<uint64_t, uint64_t> LocalAndRemoteSizeT; -} + } // namespace storage diff --git a/storage/storage_tests/fake_map_files_downloader.hpp b/storage/storage_tests/fake_map_files_downloader.hpp index ae544db595..cb69bcb2fa 100644 --- a/storage/storage_tests/fake_map_files_downloader.hpp +++ b/storage/storage_tests/fake_map_files_downloader.hpp @@ -18,6 +18,7 @@ class FakeMapFilesDownloader : public MapFilesDownloader { public: FakeMapFilesDownloader(TaskRunner & taskRunner); + virtual ~FakeMapFilesDownloader(); // MapFilesDownloader overrides: diff --git a/storage/storage_tests/queued_country_tests.cpp b/storage/storage_tests/queued_country_tests.cpp new file mode 100644 index 0000000000..01634a6bcf --- /dev/null +++ b/storage/storage_tests/queued_country_tests.cpp @@ -0,0 +1,106 @@ +#include "testing/testing.hpp" + +#include "storage/queued_country.hpp" +#include "storage/storage.hpp" + +namespace storage +{ +UNIT_TEST(QueuedCountry_AddOptions) +{ + Storage storage; + TIndex const index = storage.FindIndexByFile("USA_Georgia"); + QueuedCountry country(index, TMapOptions::ECarRouting); + + TEST_EQUAL(index, country.GetIndex(), ()); + TEST_EQUAL(TMapOptions::ECarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::ECarRouting, country.GetCurrentFile(), ()); + + country.AddOptions(TMapOptions::EMap); + TEST_EQUAL(TMapOptions::EMapWithCarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::ECarRouting, country.GetCurrentFile(), ()); + + TEST(country.SwitchToNextFile(), ()); + TEST_EQUAL(TMapOptions::EMapWithCarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::EMap, country.GetCurrentFile(), ()); + + TEST(!country.SwitchToNextFile(), ()); + TEST_EQUAL(TMapOptions::EMapWithCarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::ENothing, country.GetCurrentFile(), ()); +} + +UNIT_TEST(QueuedCountry_RemoveOptions) +{ + Storage storage; + TIndex const index = storage.FindIndexByFile("USA_Georgia"); + + { + QueuedCountry country(index, TMapOptions::EMapWithCarRouting); + TEST_EQUAL(TMapOptions::EMapWithCarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::EMap, country.GetCurrentFile(), ()); + TEST_EQUAL(TMapOptions::ENothing, country.GetDownloadedFiles(), ()); + + country.RemoveOptions(TMapOptions::EMap); + TEST_EQUAL(TMapOptions::ECarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::ECarRouting, country.GetCurrentFile(), ()); + TEST_EQUAL(TMapOptions::ENothing, country.GetDownloadedFiles(), ()); + + country.RemoveOptions(TMapOptions::ECarRouting); + TEST_EQUAL(TMapOptions::ENothing, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::ENothing, country.GetCurrentFile(), ()); + TEST_EQUAL(TMapOptions::ENothing, country.GetDownloadedFiles(), ()); + } + + { + QueuedCountry country(index, TMapOptions::EMapWithCarRouting); + TEST_EQUAL(TMapOptions::EMapWithCarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::EMap, country.GetCurrentFile(), ()); + TEST_EQUAL(TMapOptions::ENothing, country.GetDownloadedFiles(), ()); + + country.SwitchToNextFile(); + TEST_EQUAL(TMapOptions::EMapWithCarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::ECarRouting, country.GetCurrentFile(), ()); + TEST_EQUAL(TMapOptions::EMap, country.GetDownloadedFiles(), ()); + + country.RemoveOptions(TMapOptions::ECarRouting); + TEST_EQUAL(TMapOptions::EMap, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::ENothing, country.GetCurrentFile(), ()); + TEST_EQUAL(TMapOptions::EMap, country.GetDownloadedFiles(), ()); + } + + { + QueuedCountry country(index, TMapOptions::EMapWithCarRouting); + TEST_EQUAL(TMapOptions::EMapWithCarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::EMap, country.GetCurrentFile(), ()); + TEST_EQUAL(TMapOptions::ENothing, country.GetDownloadedFiles(), ()); + + country.SwitchToNextFile(); + TEST_EQUAL(TMapOptions::EMapWithCarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::ECarRouting, country.GetCurrentFile(), ()); + TEST_EQUAL(TMapOptions::EMap, country.GetDownloadedFiles(), ()); + + country.RemoveOptions(TMapOptions::EMap); + TEST_EQUAL(TMapOptions::ECarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::ECarRouting, country.GetCurrentFile(), ()); + TEST_EQUAL(TMapOptions::ENothing, country.GetDownloadedFiles(), ()); + + country.SwitchToNextFile(); + TEST_EQUAL(TMapOptions::ECarRouting, country.GetInitOptions(), ()); + TEST_EQUAL(TMapOptions::ENothing, country.GetCurrentFile(), ()); + TEST_EQUAL(TMapOptions::ECarRouting, country.GetDownloadedFiles(), ()); + } +} + +UNIT_TEST(QueuedCountry_Bits) +{ + Storage storage; + TIndex const index = storage.FindIndexByFile("USA_Georgia"); + QueuedCountry country(index, TMapOptions::EMapWithCarRouting); + TEST_EQUAL(TMapOptions::ENothing, country.GetDownloadedFiles(), ()); + + TEST(country.SwitchToNextFile(), ()); + TEST_EQUAL(TMapOptions::EMap, country.GetDownloadedFiles(), ()); + + TEST(!country.SwitchToNextFile(), ()); + TEST_EQUAL(TMapOptions::EMapWithCarRouting, country.GetDownloadedFiles(), ()); +} +} // namespace storage diff --git a/storage/storage_tests/storage_tests.cpp b/storage/storage_tests/storage_tests.cpp index 60d795d4c7..55eefbf7d9 100644 --- a/storage/storage_tests/storage_tests.cpp +++ b/storage/storage_tests/storage_tests.cpp @@ -5,6 +5,9 @@ #include "storage/storage_tests/fake_map_files_downloader.hpp" #include "storage/storage_tests/task_runner.hpp" +#include "platform/country_file.hpp" +#include "platform/local_country_file.hpp" +#include "platform/local_country_file_utils.hpp" #include "platform/platform.hpp" #include "coding/file_name_utils.hpp" @@ -14,67 +17,73 @@ #include "defines.hpp" #include "base/scope_guard.hpp" +#include "base/string_utils.hpp" #include "std/bind.hpp" +#include "std/map.hpp" #include "std/unique_ptr.hpp" +#include "std/vector.hpp" +using namespace platform; using namespace storage; namespace { +// This class checks steps Storage::DownloadMap() performs to download a map. class CountryDownloaderChecker { public: - CountryDownloaderChecker(Storage & storage, string const & countryFileName, TMapOptions files) + CountryDownloaderChecker(Storage & storage, TIndex const & index, TMapOptions files, + vector<TStatus> const & transitionList) : m_storage(storage), - m_index(m_storage.FindIndexByFile(countryFileName)), + m_index(index), + m_countryFile(storage.GetCountryFile(m_index)), m_files(files), - m_lastStatus(TStatus::ENotDownloaded), m_bytesDownloaded(0), m_totalBytesToDownload(0), - m_slot(0) + m_slot(0), + m_currStatus(0), + m_transitionList(transitionList) { m_slot = m_storage.Subscribe( bind(&CountryDownloaderChecker::OnCountryStatusChanged, this, _1), bind(&CountryDownloaderChecker::OnCountryDownloadingProgress, this, _1, _2)); - CHECK(m_index.IsValid(), ()); + CHECK(m_index.IsValid(), (m_countryFile)); + CHECK(!m_transitionList.empty(), (m_countryFile)); } void StartDownload() { - CHECK_EQUAL(m_lastStatus, m_storage.CountryStatusEx(m_index), ()); + CHECK_EQUAL(0, m_currStatus, (m_countryFile)); + CHECK_LESS(m_currStatus, m_transitionList.size(), (m_countryFile)); + CHECK_EQUAL(m_transitionList[m_currStatus], m_storage.CountryStatusEx(m_index), + (m_countryFile)); m_storage.DownloadCountry(m_index, m_files); } virtual ~CountryDownloaderChecker() { + CHECK_EQUAL(m_currStatus + 1, m_transitionList.size(), (m_countryFile)); m_storage.Unsubscribe(m_slot); - - CHECK_EQUAL(TStatus::EOnDisk, m_lastStatus, ()); - CHECK_EQUAL(m_bytesDownloaded, m_totalBytesToDownload, ()); - - LocalAndRemoteSizeT localAndRemoteSize = m_storage.CountrySizeInBytes(m_index, m_files); - CHECK_EQUAL(m_bytesDownloaded, localAndRemoteSize.first, ()); - CHECK_EQUAL(m_totalBytesToDownload, localAndRemoteSize.second, ()); } -protected: - virtual void CheckStatusTransition(TStatus oldStatus, TStatus newStatus) const = 0; - private: void OnCountryStatusChanged(TIndex const & index) { if (index != m_index) return; - TStatus status = m_storage.CountryStatusEx(m_index); - CheckStatusTransition(m_lastStatus, status); - if (status == TStatus::EDownloading) + TStatus const nextStatus = m_storage.CountryStatusEx(m_index); + LOG(LINFO, (m_countryFile, "status transition: from", m_transitionList[m_currStatus], "to", + nextStatus)); + CHECK_LESS(m_currStatus + 1, m_transitionList.size(), (m_countryFile)); + CHECK_EQUAL(nextStatus, m_transitionList[m_currStatus + 1], (m_countryFile)); + ++m_currStatus; + if (m_transitionList[m_currStatus] == TStatus::EDownloading) { LocalAndRemoteSizeT localAndRemoteSize = m_storage.CountrySizeInBytes(m_index, m_files); m_totalBytesToDownload = localAndRemoteSize.second; } - m_lastStatus = status; } void OnCountryDownloadingProgress(TIndex const & index, LocalAndRemoteSizeT const & progress) @@ -82,185 +91,410 @@ private: if (index != m_index) return; - CHECK_GREATER(progress.first, m_bytesDownloaded, ()); + LOG(LINFO, (m_countryFile, "downloading progress:", progress)); + + CHECK_GREATER(progress.first, m_bytesDownloaded, (m_countryFile)); m_bytesDownloaded = progress.first; - CHECK_LESS_OR_EQUAL(m_bytesDownloaded, m_totalBytesToDownload, ()); + CHECK_LESS_OR_EQUAL(m_bytesDownloaded, m_totalBytesToDownload, (m_countryFile)); LocalAndRemoteSizeT localAndRemoteSize = m_storage.CountrySizeInBytes(m_index, m_files); - CHECK_EQUAL(m_totalBytesToDownload, localAndRemoteSize.second, ()); + CHECK_EQUAL(m_totalBytesToDownload, localAndRemoteSize.second, (m_countryFile)); } Storage & m_storage; TIndex const m_index; - + CountryFile const m_countryFile; TMapOptions const m_files; - TStatus m_lastStatus; int64_t m_bytesDownloaded; int64_t m_totalBytesToDownload; int m_slot; + + size_t m_currStatus; + vector<TStatus> m_transitionList; }; // Checks following state transitions: // NotDownloaded -> Downloading -> OnDisk. -class AbsentCountryDownloaderChecker : public CountryDownloaderChecker +unique_ptr<CountryDownloaderChecker> AbsentCountryDownloaderChecker(Storage & storage, + TIndex const & index, + TMapOptions files) { -public: - AbsentCountryDownloaderChecker(Storage & storage, string const & countryFileName, - TMapOptions files) - : CountryDownloaderChecker(storage, countryFileName, files) - { - } + return make_unique<CountryDownloaderChecker>( + storage, index, files, + vector<TStatus>{TStatus::ENotDownloaded, TStatus::EDownloading, TStatus::EOnDisk}); +} + +// Checks following state transitions: +// OnDisk -> Downloading -> OnDisk. +unique_ptr<CountryDownloaderChecker> PresentCountryDownloaderChecker(Storage & storage, + TIndex const & index, + TMapOptions files) +{ + return make_unique<CountryDownloaderChecker>( + storage, index, files, + vector<TStatus>{TStatus::EOnDisk, TStatus::EDownloading, TStatus::EOnDisk}); +} + +// Checks following state transitions: +// NotDownloaded -> InQueue -> Downloading -> OnDisk. +unique_ptr<CountryDownloaderChecker> QueuedCountryDownloaderChecker(Storage & storage, + TIndex const & index, + TMapOptions files) +{ + return make_unique<CountryDownloaderChecker>( + storage, index, files, vector<TStatus>{TStatus::ENotDownloaded, TStatus::EInQueue, + TStatus::EDownloading, TStatus::EOnDisk}); +} - ~AbsentCountryDownloaderChecker() override = default; +// Checks following state transitions: +// NotDownloaded -> Downloading -> NotDownloaded. +unique_ptr<CountryDownloaderChecker> CancelledCountryDownloaderChecker(Storage & storage, + TIndex const & index, + TMapOptions files) +{ + return make_unique<CountryDownloaderChecker>( + storage, index, files, + vector<TStatus>{TStatus::ENotDownloaded, TStatus::EDownloading, TStatus::ENotDownloaded}); +} -protected: - void CheckStatusTransition(TStatus oldStatus, TStatus newStatus) const override +void OnCountryDownloaded(LocalCountryFile const & localFile) +{ + LOG(LINFO, ("OnCountryDownloaded:", localFile)); +} + +shared_ptr<LocalCountryFile> CreateDummyMapFile(CountryFile const & countryFile, int64_t version, + size_t size) +{ + shared_ptr<LocalCountryFile> localFile = + platform::PreparePlaceForCountryFiles(countryFile, version); + CHECK(localFile.get(), ("Can't prepare place for", countryFile, "(version ", version, ")")); { - switch (newStatus) - { - case TStatus::EDownloading: - CHECK_EQUAL(oldStatus, TStatus::ENotDownloaded, - ("It's only possible to move from NotDownloaded to Downloading.")); - break; - case TStatus::EOnDisk: - CHECK_EQUAL(oldStatus, TStatus::EDownloading, - ("It's only possible to move from Downloading to OnDisk.")); - break; - default: - CHECK(false, ("Unknown state change: from", oldStatus, "to", newStatus)); - } + string const zeroes(size, '\0'); + FileWriter writer(localFile->GetPath(TMapOptions::EMap)); + writer.Write(zeroes.data(), zeroes.size()); } -}; + localFile->SyncWithDisk(); + CHECK_EQUAL(TMapOptions::EMap, localFile->GetFiles(), ()); + CHECK_EQUAL(size, localFile->GetSize(TMapOptions::EMap), ()); + return localFile; +} -// Checks following state transitions: -// NotDownloaded -> InQueue -> Downloading -> OnDisk. -class QueuedCountryDownloaderChecker : public CountryDownloaderChecker +class CountryStatusChecker { public: - QueuedCountryDownloaderChecker(Storage & storage, string const & countryFileName, - TMapOptions files) - : CountryDownloaderChecker(storage, countryFileName, files) + CountryStatusChecker(Storage & storage, TIndex const & index, TStatus status) + : m_storage(storage), m_index(index), m_status(status), m_triggered(false) { + m_slot = m_storage.Subscribe( + bind(&CountryStatusChecker::OnCountryStatusChanged, this, _1), + bind(&CountryStatusChecker::OnCountryDownloadingProgress, this, _1, _2)); } - ~QueuedCountryDownloaderChecker() override = default; + ~CountryStatusChecker() + { + TEST(m_triggered, ("Status checker wasn't triggered.")); + m_storage.Unsubscribe(m_slot); + } -protected: - void CheckStatusTransition(TStatus oldStatus, TStatus newStatus) const override +private: + void OnCountryStatusChanged(TIndex const & index) { - switch (newStatus) - { - case TStatus::EInQueue: - CHECK_EQUAL(oldStatus, TStatus::ENotDownloaded, - ("It's only possible to move from NotDownloaded to InQueue.")); - break; - case TStatus::EDownloading: - CHECK_EQUAL(oldStatus, TStatus::EInQueue, - ("It's only possible to move from InQueue to Downloading.")); - break; - case TStatus::EOnDisk: - CHECK_EQUAL(oldStatus, TStatus::EDownloading, - ("It's only possible to move from Downloading to OnDisk.")); - break; - default: - CHECK(false, ("Unknown state change: from", oldStatus, "to", newStatus)); - } + if (index != m_index) + return; + TEST(!m_triggered, ("Status checker can be triggered only once.")); + TStatus status = m_storage.CountryStatusEx(m_index); + TEST_EQUAL(m_status, status, ()); + m_triggered = true; } + + void OnCountryDownloadingProgress(TIndex const & /* index */, + LocalAndRemoteSizeT const & /* progress */) + { + TEST(false, ("Unexpected country downloading progress.")); + } + + Storage & m_storage; + TIndex const & m_index; + TStatus m_status; + bool m_triggered; + int m_slot; }; -// Removes country's files that can be created during and after downloading. -void CleanupCountryFiles(string const & countryFileName) +void InitStorage(Storage & storage, TaskRunner & runner) { - Platform & platform = GetPlatform(); + storage.Init(&OnCountryDownloaded); + storage.RegisterAllLocalMaps(); + storage.SetDownloaderForTesting(make_unique<FakeMapFilesDownloader>(runner)); +} +} // namespace - string const localMapFile = - my::JoinFoldersToPath(platform.WritableDir(), countryFileName + DATA_FILE_EXTENSION); - my::DeleteFileX(localMapFile); - my::DeleteFileX(localMapFile + READY_FILE_EXTENSION); +UNIT_TEST(StorageTest_Smoke) +{ + Storage storage; - string const localRoutingFile = localMapFile + ROUTING_FILE_EXTENSION; - my::DeleteFileX(localRoutingFile); - my::DeleteFileX(localRoutingFile + READY_FILE_EXTENSION); + TIndex const usaGeorgiaIndex = storage.FindIndexByFile("USA_Georgia"); + TEST(usaGeorgiaIndex.IsValid(), ()); + CountryFile usaGeorgiaFile = storage.GetCountryFile(usaGeorgiaIndex); + TEST_EQUAL(usaGeorgiaFile.GetNameWithExt(TMapOptions::EMap), "USA_Georgia" DATA_FILE_EXTENSION, + ()); + + TIndex const georgiaIndex = storage.FindIndexByFile("Georgia"); + TEST(georgiaIndex.IsValid(), ()); + CountryFile georgiaFile = storage.GetCountryFile(georgiaIndex); + TEST_EQUAL(georgiaFile.GetNameWithExt(TMapOptions::ECarRouting), + "Georgia" DATA_FILE_EXTENSION ROUTING_FILE_EXTENSION, ()); + + TEST_NOT_EQUAL(usaGeorgiaIndex, georgiaIndex, ()); } -void OnCountryDownloaded(string const & mapFileName, TMapOptions files) +UNIT_TEST(StorageTest_SingleCountryDownloading) { - Platform & platform = GetPlatform(); + Storage storage; + TaskRunner runner; + InitStorage(storage, runner); - string const localMapFile = my::JoinFoldersToPath(platform.WritableDir(), mapFileName); - string const localRoutingFile = localMapFile + ROUTING_FILE_EXTENSION; + TIndex const azerbaijanIndex = storage.FindIndexByFile("Azerbaijan"); + TEST(azerbaijanIndex.IsValid(), ()); - if (HasOptions(files, TMapOptions::EMap)) - CHECK(my::RenameFileX(localMapFile + READY_FILE_EXTENSION, localMapFile), ()); - if (HasOptions(files, TMapOptions::ECarRouting)) - CHECK(my::RenameFileX(localRoutingFile + READY_FILE_EXTENSION, localRoutingFile), ()); + CountryFile azerbaijanFile = storage.GetCountryFile(azerbaijanIndex); + storage.DeleteCountry(azerbaijanIndex, TMapOptions::EMapWithCarRouting); + + { + MY_SCOPE_GUARD(cleanupCountryFiles, + bind(&Storage::DeleteCountry, &storage, azerbaijanIndex, TMapOptions::EMap)); + unique_ptr<CountryDownloaderChecker> checker = + AbsentCountryDownloaderChecker(storage, azerbaijanIndex, TMapOptions::EMapWithCarRouting); + checker->StartDownload(); + runner.Run(); + } + + { + MY_SCOPE_GUARD(cleanupCountryFiles, bind(&Storage::DeleteCountry, &storage, azerbaijanIndex, + TMapOptions::EMapWithCarRouting)); + unique_ptr<CountryDownloaderChecker> checker = + AbsentCountryDownloaderChecker(storage, azerbaijanIndex, TMapOptions::EMapWithCarRouting); + checker->StartDownload(); + runner.Run(); + } } -} // namespace +UNIT_TEST(StorageTest_TwoCountriesDownloading) +{ + Storage storage; + TaskRunner runner; + InitStorage(storage, runner); + + TIndex const uruguayIndex = storage.FindIndexByFile("Uruguay"); + TEST(uruguayIndex.IsValid(), ()); + storage.DeleteCountry(uruguayIndex, TMapOptions::EMap); + MY_SCOPE_GUARD(cleanupUruguayFiles, + bind(&Storage::DeleteCountry, &storage, uruguayIndex, TMapOptions::EMap)); + + TIndex const venezuelaIndex = storage.FindIndexByFile("Venezuela"); + TEST(venezuelaIndex.IsValid(), ()); + storage.DeleteCountry(venezuelaIndex, TMapOptions::EMapWithCarRouting); + MY_SCOPE_GUARD(cleanupVenezuelaFiles, bind(&Storage::DeleteCountry, &storage, venezuelaIndex, + TMapOptions::EMapWithCarRouting)); + + unique_ptr<CountryDownloaderChecker> uruguayChecker = + AbsentCountryDownloaderChecker(storage, uruguayIndex, TMapOptions::EMap); + unique_ptr<CountryDownloaderChecker> venezuelaChecker = + QueuedCountryDownloaderChecker(storage, venezuelaIndex, TMapOptions::EMapWithCarRouting); + uruguayChecker->StartDownload(); + venezuelaChecker->StartDownload(); + runner.Run(); +} -UNIT_TEST(StorageTest_Smoke) +UNIT_TEST(StorageTest_DeleteTwoVersionsOfTheSameCountry) { - Storage st; + Storage storage; + storage.Init(&OnCountryDownloaded); + storage.RegisterAllLocalMaps(); + + TIndex const index = storage.FindIndexByFile("Azerbaijan"); + TEST(index.IsValid(), ()); + CountryFile const countryFile = storage.GetCountryFile(index); + + storage.DeleteCountry(index, TMapOptions::EMapWithCarRouting); + shared_ptr<LocalCountryFile> latestLocalFile = storage.GetLatestLocalFile(index); + TEST(!latestLocalFile.get(), ("Country wasn't deleted from disk.")); + TEST_EQUAL(TStatus::ENotDownloaded, storage.CountryStatusEx(index), ()); + + shared_ptr<LocalCountryFile> localFileV1 = + CreateDummyMapFile(countryFile, 1 /* version */, 1024 /* size */); + storage.RegisterAllLocalMaps(); + latestLocalFile = storage.GetLatestLocalFile(index); + TEST(latestLocalFile.get(), ("Created map file wasn't found by storage.")); + TEST_EQUAL(latestLocalFile->GetVersion(), localFileV1->GetVersion(), ()); + TEST_EQUAL(TStatus::EOnDiskOutOfDate, storage.CountryStatusEx(index), ()); + + shared_ptr<LocalCountryFile> localFileV2 = + CreateDummyMapFile(countryFile, 2 /* version */, 2048 /* size */); + storage.RegisterAllLocalMaps(); + latestLocalFile = storage.GetLatestLocalFile(index); + TEST(latestLocalFile.get(), ("Created map file wasn't found by storage.")); + TEST_EQUAL(latestLocalFile->GetVersion(), localFileV2->GetVersion(), ()); + TEST_EQUAL(TStatus::EOnDiskOutOfDate, storage.CountryStatusEx(index), ()); + + storage.DeleteCountry(index, TMapOptions::EMap); + + localFileV1->SyncWithDisk(); + TEST_EQUAL(TMapOptions::ENothing, localFileV1->GetFiles(), ()); + + localFileV2->SyncWithDisk(); + TEST_EQUAL(TMapOptions::ENothing, localFileV2->GetFiles(), ()); + + TEST_EQUAL(TStatus::ENotDownloaded, storage.CountryStatusEx(index), ()); +} + +UNIT_TEST(StorageTest_DownloadCountryAndDeleteRoutingOnly) +{ + Storage storage; + TaskRunner runner; + InitStorage(storage, runner); - TIndex const i1 = st.FindIndexByFile("USA_Georgia"); - TEST(i1.IsValid(), ()); - TEST_EQUAL(st.CountryFileName(i1, TMapOptions::EMap), "USA_Georgia" DATA_FILE_EXTENSION, ()); + TIndex const index = storage.FindIndexByFile("Azerbaijan"); + TEST(index.IsValid(), ()); + storage.DeleteCountry(index, TMapOptions::EMapWithCarRouting); - TIndex const i2 = st.FindIndexByFile("Georgia"); - TEST(i2.IsValid(), ()); - TEST_EQUAL(st.CountryFileName(i2, TMapOptions::ECarRouting), - "Georgia" DATA_FILE_EXTENSION ROUTING_FILE_EXTENSION, ()); + { + unique_ptr<CountryDownloaderChecker> checker = + AbsentCountryDownloaderChecker(storage, index, TMapOptions::EMapWithCarRouting); + checker->StartDownload(); + runner.Run(); + } + + // Delete routing file only and check that latest local file wasn't changed. + shared_ptr<LocalCountryFile> localFileA = storage.GetLatestLocalFile(index); + TEST(localFileA.get(), ()); + TEST_EQUAL(TMapOptions::EMapWithCarRouting, localFileA->GetFiles(), ()); + + storage.DeleteCountry(index, TMapOptions::ECarRouting); + + shared_ptr<LocalCountryFile> localFileB = storage.GetLatestLocalFile(index); + TEST(localFileB.get(), ()); + TEST_EQUAL(localFileA.get(), localFileB.get(), (*localFileA, *localFileB)); + TEST_EQUAL(TMapOptions::EMap, localFileB->GetFiles(), ()); - TEST_NOT_EQUAL(i1, i2, ()); + storage.DeleteCountry(index, TMapOptions::EMap); + shared_ptr<LocalCountryFile> localFileC = storage.GetLatestLocalFile(index); + TEST(!localFileC.get(), (*localFileC)); } -UNIT_TEST(StorageTest_SingleCountryDownloading) +UNIT_TEST(StorageTest_DownloadMapAndRoutingSeparately) { - string const azerbaijanFileName = "Azerbaijan"; - CleanupCountryFiles(azerbaijanFileName); - Storage storage; - storage.Init(&OnCountryDownloaded); + TaskRunner runner; + InitStorage(storage, runner); - TaskRunner taskRunner; - storage.SetDownloaderForTesting(make_unique<FakeMapFilesDownloader>(taskRunner)); + TIndex const index = storage.FindIndexByFile("Azerbaijan"); + TEST(index.IsValid(), ()); + storage.DeleteCountry(index, TMapOptions::EMapWithCarRouting); + // Download map file only. { - MY_SCOPE_GUARD(cleanupCountryFiles, bind(&CleanupCountryFiles, azerbaijanFileName)); - AbsentCountryDownloaderChecker checker(storage, azerbaijanFileName, TMapOptions::EMap); - checker.StartDownload(); - taskRunner.Run(); + unique_ptr<CountryDownloaderChecker> checker = + AbsentCountryDownloaderChecker(storage, index, TMapOptions::EMap); + checker->StartDownload(); + runner.Run(); } + shared_ptr<LocalCountryFile> localFileA = storage.GetLatestLocalFile(index); + TEST(localFileA.get(), ()); + TEST_EQUAL(TMapOptions::EMap, localFileA->GetFiles(), ()); + + // Download routing file in addition to exising map file. { - MY_SCOPE_GUARD(cleanupCountryFiles, bind(&CleanupCountryFiles, azerbaijanFileName)); - AbsentCountryDownloaderChecker checker(storage, azerbaijanFileName, - TMapOptions::EMapWithCarRouting); - checker.StartDownload(); - taskRunner.Run(); + unique_ptr<CountryDownloaderChecker> checker = + PresentCountryDownloaderChecker(storage, index, TMapOptions::ECarRouting); + checker->StartDownload(); + runner.Run(); + } + + shared_ptr<LocalCountryFile> localFileB = storage.GetLatestLocalFile(index); + TEST(localFileB.get(), ()); + TEST_EQUAL(localFileA.get(), localFileB.get(), (*localFileA, *localFileB)); + TEST_EQUAL(TMapOptions::EMapWithCarRouting, localFileB->GetFiles(), ()); + + // Delete routing file and check status update. + { + CountryStatusChecker checker(storage, index, TStatus::EOnDisk); + storage.DeleteCountry(index, TMapOptions::ECarRouting); + } + shared_ptr<LocalCountryFile> localFileC = storage.GetLatestLocalFile(index); + TEST(localFileC.get(), ()); + TEST_EQUAL(localFileB.get(), localFileC.get(), (*localFileB, *localFileC)); + TEST_EQUAL(TMapOptions::EMap, localFileC->GetFiles(), ()); + + // Delete map file and check status update. + { + CountryStatusChecker checker(storage, index, TStatus::ENotDownloaded); + storage.DeleteCountry(index, TMapOptions::EMap); } } -UNIT_TEST(StorageTest_TwoCountriesDownloading) +UNIT_TEST(StorageTest_DeletePendingCountry) { - string const uruguayFileName = "Uruguay"; - string const venezuelaFileName = "Venezuela"; - CleanupCountryFiles(uruguayFileName); - MY_SCOPE_GUARD(cleanupUruguayFiles, bind(&CleanupCountryFiles, uruguayFileName)); + Storage storage; + TaskRunner runner; + InitStorage(storage, runner); + + TIndex const index = storage.FindIndexByFile("Azerbaijan"); + TEST(index.IsValid(), ()); + storage.DeleteCountry(index, TMapOptions::EMapWithCarRouting); - CleanupCountryFiles(venezuelaFileName); - MY_SCOPE_GUARD(cleanupVenezuelaFiles, bind(&CleanupCountryFiles, venezuelaFileName)); + { + unique_ptr<CountryDownloaderChecker> checker = + CancelledCountryDownloaderChecker(storage, index, TMapOptions::EMap); + checker->StartDownload(); + storage.DeleteCountry(index, TMapOptions::EMapWithCarRouting); + runner.Run(); + } +} +UNIT_TEST(StorageTest_DownloadTwoCountriesAndDelete) +{ Storage storage; - storage.Init(&OnCountryDownloaded); + TaskRunner runner; + InitStorage(storage, runner); + + TIndex const uruguayIndex = storage.FindIndexByFile("Uruguay"); + TEST(uruguayIndex.IsValid(), ()); + storage.DeleteCountry(uruguayIndex, TMapOptions::EMapWithCarRouting); + MY_SCOPE_GUARD(cleanupUruguayFiles, bind(&Storage::DeleteCountry, &storage, uruguayIndex, + TMapOptions::EMapWithCarRouting)); - TaskRunner taskRunner; - storage.SetDownloaderForTesting(make_unique<FakeMapFilesDownloader>(taskRunner)); + TIndex const venezuelaIndex = storage.FindIndexByFile("Venezuela"); + TEST(venezuelaIndex.IsValid(), ()); + storage.DeleteCountry(venezuelaIndex, TMapOptions::EMapWithCarRouting); + MY_SCOPE_GUARD(cleanupVenezuelaFiles, bind(&Storage::DeleteCountry, &storage, venezuelaIndex, + TMapOptions::EMapWithCarRouting)); + + { + // Map file will be deleted for Uruguay, thus, routing file should also be deleted. Therefore, + // Uruguay should pass through following states: NotDownloaded -> Downloading -> NotDownloaded. + unique_ptr<CountryDownloaderChecker> uruguayChecker = make_unique<CountryDownloaderChecker>( + storage, uruguayIndex, TMapOptions::EMapWithCarRouting, + vector<TStatus>{TStatus::ENotDownloaded, TStatus::EDownloading, TStatus::ENotDownloaded}); + // Only routing file will be deleted for Venezuela, thus, Venezuela should pass through + // following + // states: + // NotDownloaded -> InQueue (Venezuela is added after Uruguay) -> Downloading -> Downloading + // (second notification will be sent after deletion of a routing file) -> OnDisk. + unique_ptr<CountryDownloaderChecker> venezuelaChecker = make_unique<CountryDownloaderChecker>( + storage, venezuelaIndex, TMapOptions::EMapWithCarRouting, + vector<TStatus>{TStatus::ENotDownloaded, TStatus::EInQueue, TStatus::EDownloading, + TStatus::EDownloading, TStatus::EOnDisk}); + uruguayChecker->StartDownload(); + venezuelaChecker->StartDownload(); + storage.DeleteCountry(uruguayIndex, TMapOptions::EMap); + storage.DeleteCountry(venezuelaIndex, TMapOptions::ECarRouting); + runner.Run(); + } + shared_ptr<LocalCountryFile> uruguayFile = storage.GetLatestLocalFile(uruguayIndex); + TEST(!uruguayFile.get(), (*uruguayFile)); - AbsentCountryDownloaderChecker uruguayChecker(storage, uruguayFileName, TMapOptions::EMap); - QueuedCountryDownloaderChecker venezuelaChecker(storage, venezuelaFileName, - TMapOptions::EMapWithCarRouting); - uruguayChecker.StartDownload(); - venezuelaChecker.StartDownload(); - taskRunner.Run(); + shared_ptr<LocalCountryFile> venezuelaFile = storage.GetLatestLocalFile(venezuelaIndex); + TEST(venezuelaFile.get(), ()); + TEST_EQUAL(TMapOptions::EMap, venezuelaFile->GetFiles(), ()); } diff --git a/storage/storage_tests/storage_tests.pro b/storage/storage_tests/storage_tests.pro index 86879502d5..1f59397d0d 100644 --- a/storage/storage_tests/storage_tests.pro +++ b/storage/storage_tests/storage_tests.pro @@ -21,11 +21,11 @@ HEADERS += \ fake_map_files_downloader.hpp \ task_runner.hpp \ - SOURCES += \ ../../testing/testingmain.cpp \ country_info_test.cpp \ fake_map_files_downloader.cpp \ + queued_country_tests.cpp \ simple_tree_test.cpp \ storage_tests.cpp \ task_runner.cpp \ |